Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

MAX485 RS485 with ESP32 DevKit V1 — Modbus RTU master on Serial2

Read Modbus RTU registers over RS485 with a MAX485 module and an ESP32 DevKit V1: Serial2 on GPIO16/17, a divider on the 5 V RO line, one GPIO for DE+RE.

Arduino C++Verified — core esp32 3.3.12, ModbusMaster 2.0.1, 3 Nov 2026Compile-only

Community draft — report a mistake

A MAX485 module turns a UART into RS485, the two-wire differential bus used by industrial Modbus devices — energy meters, soil NPK sensors, VFDs and temperature controllers. RS485 runs over hundreds of metres of twisted pair, which is why farm and factory sensors use it.

On the ESP32 DevKit V1, use Serial2 on GPIO16/17 with the pins passed explicitly, one GPIO (D4) for the transmit/receive switch, and a 1 kΩ / 2 kΩ divider on RO because the MAX485 is a 5 V chip. The sketch is a Modbus RTU master built on ModbusMaster: it reads two holding registers from slave 1 every two seconds. Change the ID, baud and address to match your device.

Wiring

ESP32 DevKit V1Only the pins used are shown · 3.3 V logicMAX485 TTL to RS485 module1k│2kGNDVINTX2 · 17RX2 · 16D4ROREDEDIVCCB → RS485 bus B / D− — B of every deviceA → RS485 bus A / D+ — A of every deviceGND
Also needed: 1 kΩ and 2 kΩ resistors, 120 Ω resistor across A–B at each end of a long bus, A Modbus RTU slave: energy meter, soil NPK sensor, VFD, temperature controller….
Every connection
ESP32 DevKit V1MAX485 TTL to RS485 moduleWireNotes
—A—To RS485 bus A / D+ — A of every device (not drawn)
—B—To RS485 bus B / D− — B of every device (not drawn)
VINVCCredThe MAX485 is a 5 V chip (4.75–5.25 V). 3V3 is out of spec.
GNDGNDblack
RX2ROyellowVia 1 kΩ / 2 kΩ divider — RO swings to 5 V. 1 kΩ from RO to RX2 (GPIO16), 2 kΩ from RX2 to GND.
TX2DIgreenGPIO17. The MAX485 reads 3.3 V as HIGH.
D4DEpurpleDE and RE share one GPIO: HIGH = transmit, LOW = receive.
D4REpurple

5 V (4.75–5.25 V) part, 3.3 V board. The MAX485 is a 5 V chip and RO outputs 5 V. On ESP32 and Pico put a 1 kΩ / 2 kΩ divider on RO → board RX (DI, DE and RE accept 3.3 V as HIGH), or use a 3.3 V MAX3485 module instead.

Board quirks

  • Arduino-ESP32 3.x defaults Serial2 to RX 4 / TX 25, so the sketch names GPIO16 and GPIO17 (printed RX2 / TX2) in Serial2.begin().
  • D4 is free here because Serial2 was moved back to 16/17. Don’t put DE/RE on D2, D5, D12 or D15 — a strapping pin held by the transceiver can stop the ESP32 booting.
  • Slave ID, baud rate, parity and the register map come from your device’s manual. Many Indian energy meters use 9600 8N1 or 9600 8E1; soil NPK sensors often 4800 or 9600 8N1. Holding (0x03) and input (0x04) registers are different function codes — try both if one returns an error.
  • For a bus longer than a few metres: twisted pair for A/B, 120 Ω across A–B at both ends, and a third wire for GND. Many modules already carry a 120 Ω resistor across A–B — remove it if the module sits mid-bus.
  • If A and B are swapped nothing breaks — you just get timeouts. Swap them if every request times out.
  • A 3.3 V MAX3485 (or SP3485) module removes the divider and the 5 V supply. Wiring and code stay the same.

Code

max485-esp32.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// MAX485 RS485 module on an ESP32 DevKit V1 as a Modbus-RTU master (ModbusMaster).
// RO → 1 kΩ/2 kΩ divider → RX2 (GPIO16), DI ← TX2 (GPIO17), DE + RE → D4, VCC → VIN (5 V), GND → GND.
// Reads two holding registers from slave 1 every 2 s. Change ID, baud and register to your device's manual.
#include <ModbusMaster.h>

constexpr uint8_t RX_PIN = 16, TX_PIN = 17, DIR_PIN = 4;
constexpr uint8_t SLAVE_ID = 1;
constexpr uint32_t BAUD = 9600;
constexpr uint16_t FIRST_REG = 0x0000;

ModbusMaster node;

void preTransmission() { digitalWrite(DIR_PIN, HIGH); }  // DE high: drive the bus
void postTransmission() { digitalWrite(DIR_PIN, LOW); }  // RE low: listen for the reply

void setup() {
  pinMode(DIR_PIN, OUTPUT);
  digitalWrite(DIR_PIN, LOW);
  Serial.begin(115200);
  // Name the pins: arduino-esp32 3.x would otherwise put Serial2 on RX 4 / TX 25.
  Serial2.begin(BAUD, SERIAL_8N1, RX_PIN, TX_PIN);
  node.begin(SLAVE_ID, Serial2);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  Serial.printf("Modbus master: slave %u, %lu baud, 8N1\n", SLAVE_ID, (unsigned long)BAUD);
}

void loop() {
  uint8_t r = node.readHoldingRegisters(FIRST_REG, 2);
  if (r == node.ku8MBSuccess) {
    Serial.printf("Reg 0x%04X = %u   Reg 0x%04X = %u\n", FIRST_REG, node.getResponseBuffer(0), FIRST_REG + 1,
                  node.getResponseBuffer(1));
  } else if (r == node.ku8MBResponseTimedOut) {
    Serial.println("Timeout (0xE2) — check A/B, slave ID, baud rate and GND");
  } else {
    Serial.printf("Modbus error 0x%02X\n", r);
  }
  delay(2000);
}

Library: ModbusMaster (Doc Walker) — compiled with ModbusMaster 2.0.1.

Expected serial output

Serial Monitor · 115200Output
Modbus master: slave 1, 9600 baud, 8N1
Reg 0x0000 = 2318   Reg 0x0001 = 412
Timeout (0xE2) — check A/B, slave ID, baud rate and GND

Common errors

Every request times out (0xE2)

A/B swapped, wrong slave ID or baud rate, the slave unpowered, or no common GND. Check also that DE/RE switch: the module’s TX LED (if fitted) should flash.

Illegal data address (0x02)

The slave answered, so wiring is fine — the register address is wrong. Check whether the manual counts from 0 or 1 (register 40001 = address 0x0000), and holding vs input registers.

Garbled replies or CRC errors (0xE3)

Parity mismatch (8N1 vs 8E1), a missing terminator on a long bus, or noise. Use SERIAL_8E1 in Serial2.begin() if the manual says even parity.

↑↓ to move · Enter to open