Industrial Modbus RTU Slave Node over Galvanically Isolated RS-485
Build a noise-immune Modbus RTU industrial field node communicating across 1000m twisted-pair cable with CRC-16 error checking and PLC integration.
2. Prerequisites & Background Theory:
Differential transceivers, UART serial protocols, cyclic redundancy check (CRC-16), and PLC register maps.
Goal & Engineering Objectives
Design an industrial field node supporting standard Modbus RTU Function Codes 03 (Read Holding Registers) and 06 (Write Single Register) over differential RS-485 at 19200 baud.
Governing Mathematical Formulation & Physics
System Architecture & Block Flow Diagram
[PLC / SCADA Master] <--(RS-485 120Ω Shielded Twisted Pair)--> [ISO3082 Transceiver]
|
(Isolated TX/RX)
v
[STM32 / ESP32 MCU]
|
[Modbus Register Map]
|
[Analog 4-20mA Sensor]
Required Hardware Bill of Materials (BOM)
| Hardware Component | Qty | Engineering Specification |
|---|---|---|
| ISO3082 Galvanically Isolated RS-485 Transceiver | 1 | 2.5kVrms isolation, failsafe receiver, bus-fault protected |
| B0505S-1W Isolated DC-DC Converter | 1 | 5V to 5V isolated logic supply |
| Microcontroller (ESP32-S3 or ATmega328P) | 1 | Hardware UART, 3.3V or 5V logic |
| 120Ω 1/2W Metal Film Termination Resistor | 1 | Line impedance matching at bus end |
| B72520T0040M062 Transient Voltage Suppressors | 2 | Clamp inductive lightning and motor spikes |
Wiring, Pinout & Schematics Mapping
Firmware Implementation & Algorithms
// Modbus RTU CRC-16 Calculation & Frame Verification (ANSI C)
#include <stdint.h>
#include <stdbool.h>
uint16_t modbus_crc16(const uint8_t *buffer, uint16_t length) {
uint16_t crc = 0xFFFF;
for (uint16_t pos = 0; pos < length; pos++) {
crc ^= (uint16_t)buffer[pos];
for (int i = 8; i != 0; i--) {
if ((crc & 0x0001) != 0) {
crc >>= 1;
crc ^= 0xA001; // Reversed 0x8005 polynomial
} else {
crc >>= 1;
}
}
}
return crc;
}
bool verify_modbus_frame(const uint8_t *frame, uint16_t total_length) {
if (total_length < 4) return false;
uint16_t calculated = modbus_crc16(frame, total_length - 2);
uint16_t frame_crc = frame[total_length - 2] | (frame[total_length - 1] << 8);
return (calculated == frame_crc);
}