ESP32/Buses/34. RS485 and Modbus
Your chip
Your language
Buses · 34 of 81

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.

/esp32/rs485-and-modbus · arduino · S3

One pin, one bug

DE released, relative to the last bit
message truncated
You drop DE0.4 character times (0.42 ms)
One character at 9600
1 ms
Turnaround
too early
You stopped driving the line before the UART had finished sending. 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.

ESP32Transceiver
TXDI
RXRO
Any GPIODE and RE, tied together
3V3 and GNDVCC 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.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

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.

modbus_master.ino
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.

When it does not work

Replies are cut short, or the slave never answers

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.

The master reports a timeout and the slave looks dead

You held DE too long and were still driving the line when the slave answered. Two drivers on one pair means neither message survives.

It works with two devices and fails with eight

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.

Replies fail completely

Swap A and B. Chip makers and the Modbus standard letter the two wires opposite ways, and swapping is harmless.

Random errors on a long run in a factory

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.

Where this goes next

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.

Browse ESP32 on the forum →