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 V1 | MAX485 TTL to RS485 module | Wire | Notes |
|---|---|---|---|
| — | A | — | To RS485 bus A / D+ — A of every device (not drawn) |
| — | B | — | To RS485 bus B / D− — B of every device (not drawn) |
| VIN | VCC | red | The MAX485 is a 5 V chip (4.75–5.25 V). 3V3 is out of spec. |
| GND | GND | black | |
| RX2 | RO | yellow | Via 1 kΩ / 2 kΩ divider — RO swings to 5 V. 1 kΩ from RO to RX2 (GPIO16), 2 kΩ from RX2 to GND. |
| TX2 | DI | green | GPIO17. The MAX485 reads 3.3 V as HIGH. |
| D4 | DE | purple | DE and RE share one GPIO: HIGH = transmit, LOW = receive. |
| D4 | RE | purple |
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 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);
}// 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
Modbus master: slave 1, 9600 baud, 8N1
Reg 0x0000 = 2318 Reg 0x0001 = 412
Timeout (0xE2) — check A/B, slave ID, baud rate and GNDModbus 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.