RS485 and Modbus
The same UART, sent as the difference between two wires instead of a voltage against ground. That one change buys up to about a kilometre of cable at slow speeds, in an electrically filthy building - and adds one pin that ruins most first attempts.
One pin, one bug
Serial.write() returns as soon as the bytes are in the FIFO, not when they are on the wire — that is the trap. Call Serial.flush() first, and every Modbus reply becomes a CRC error until you do.What the wiring looks like
The ESP32's pins are 3.3 V UART levels, not RS485: the physical layer needs a transceiver — MAX485, SP3485, or one of the automatic ones — between the UART and the twisted pair. The ESP-IDF UART driver also has an RS-485 half-duplex mode in which the UART drives DE for you and drops it after the last bit; I have not tested the Arduino call for it, so the manual flush sequence below is the portable way.
| ESP32 | Transceiver |
|---|---|
| TX | DI |
| RX | RO |
| Any GPIO | DE and RE, tied together |
| 3V3 and GND | VCC and GND |
Then A and B go to every device on the line, in parallel, with 120 Ω across the pair at each far end.
Modbus in one paragraph
Every device has an address, 1 to 247; address 0 is broadcast and gets no reply. The master sends address, function code, register number, count, and a CRC. The slave replies with the data and its own CRC. There is no discovery — you get the register map from the meter's manual — and two wires carry one talker at a time, which is why the direction pin matters so much.
Libraries do the framing and the CRC; the DE timing is still yours.
Modbus RTU's default is even parity, and 8N1 only works against a device set to
no parity with the spec's two stop bits, so the sketch uses SERIAL_8E1. Match
whatever the meter's manual says. The MicroPython sample leaves the UART at its
default parity, so set it to the meter's before relying on it.
The code
The whole discipline is in the send function - raise DE, write, flush, drop DE. flush is the line everyone leaves out, and it is why the last byte gets cut off.
const int DE = 4; // driver enable, often tied to RE as well
void sendFrame(const uint8_t *buf, size_t n) {
digitalWrite(DE, HIGH); // we are talking
Serial2.write(buf, n);
Serial2.flush(); // wait for the last bit to leave
digitalWrite(DE, LOW); // now listen
}
void setup() {
Serial.begin(115200);
pinMode(DE, OUTPUT);
digitalWrite(DE, LOW);
Serial2.begin(9600, SERIAL_8E1, 16, 17); // match the meter's manual
// read 1 holding register at 0x0000 from slave 1
uint8_t req[] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A };
sendFrame(req, sizeof(req));
}
void loop() {
while (Serial2.available()) Serial.printf("%02X ", Serial2.read());
}Serial.write returns as soon as the bytes are in the FIFO, not when they are on the wire. Without flush you drop the driver mid-character and the frame goes out cut short.
Same sequence. MicroPython's UART has a flush too, and the same rule applies - drop DE only after it returns.
from machine import UART, Pin
import time
de = Pin(4, Pin.OUT, value=0)
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16))
def send(frame):
de.value(1)
uart.write(frame)
uart.flush() # wait for the last bit
de.value(0)
send(bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A]))
time.sleep_ms(100)
print(uart.read())Some transceiver boards handle the direction automatically from the data line. If yours has no DE pin, it is one of those, and this whole problem disappears.
When it does not work
DE dropped before the last byte left the UART, so the frame is truncated. A truncated request fails the slave's CRC check and it says nothing. Call flush between write and dropping DE.
You held DE too long and were still driving the line when the slave answered. Two drivers on one pair means neither message survives.
Terminators go at the two ends only. At every node they load the driver down - eight of them are 15 Ω, and the driver is specified for 54 Ω. Too many devices on the bus, a long stub, or one device set to a different parity can do the same.
Swap A and B. Chip makers and the Modbus standard letter the two wires opposite ways, and swapping is harmless.
Reflections, a ground offset between the two ends, no common wire, or noise. The receiver works only while A and B are within -7 V to +12 V of its own ground, and a long run between separate supplies can leave that range.
That is every bus. Next: where the data lands once it is on the board, and why flash is not simply a disk.
Filesystem on flash →Edit this page — content/esp32/rs485-and-modbus.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.