A MAX485 module turns a UART into RS485, the two-wire bus that industrial Modbus devices use — energy meters, soil NPK sensors, VFDs and temperature controllers — over hundreds of metres of cable.
On the Uno the module is a 5 V part, so every wire goes straight across. Because the Uno’s one hardware UART is busy with USB, the sketch uses SoftwareSerial on D2 (RO) and D3 (DI), with D4 switching the module between transmit and receive. It is a Modbus RTU master built on ModbusMaster: it reads two registers from slave 1 every two seconds and prints them both as integers and as a float.
Wiring
| Arduino Uno R3 | 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) |
| 5V | VCC | red | The MAX485 is a 5 V chip — same as the Uno, so no level shifting anywhere. |
| GND | GND | black | |
| D2 | RO | yellow | SoftwareSerial RX. |
| D3 | DI | green | SoftwareSerial TX. |
| D4 | DE | purple | DE and RE share one pin: HIGH = transmit, LOW = receive. |
| D4 | RE | purple |
5 V (4.75–5.25 V) part, 5 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
- The Uno’s only hardware UART (D0/D1) is wired to USB, so the bus runs on SoftwareSerial (D2 RX, D3 TX). That suits RS485: the bus is half-duplex, and SoftwareSerial can’t send and receive at the same time anyway. Keep it at 9600 baud or below.
- SoftwareSerial only does 8N1. If your device uses even parity (8E1, common on some energy meters), change the device’s setting, or use a board with a spare hardware UART (Mega, ESP32).
- Slave ID, baud rate and the register map come from your device’s manual. Holding (0x03) and input (0x04) registers are different — swap
readHoldingRegistersforreadInputRegistersif the manual says “input”. - Many meters store a reading as a 32-bit float across two registers. The sketch also prints the pair as a float (high word first); if the number looks absurd, your device uses the other word order or plain integers.
- For a long bus: twisted pair for A/B, 120 Ω at both ends, and a third wire for GND. If every request times out, try swapping A and B — nothing is damaged by that.
Code
// MAX485 RS485 module on an Arduino Uno R3 as a Modbus-RTU master over SoftwareSerial.
// RO → D2 (soft RX), DI ← D3 (soft TX), DE + RE ← D4, VCC → 5V, GND → GND. A/B → the RS485 bus.
// The Uno's only hardware UART (D0/D1) is busy with USB, so Modbus runs on SoftwareSerial —
// fine at 9600 baud, because RS485 is half-duplex and SoftwareSerial can't send and receive at once anyway.
#include <SoftwareSerial.h>
#include <ModbusMaster.h>
const uint8_t RX_PIN = 2, TX_PIN = 3, DIR_PIN = 4;
const uint8_t SLAVE_ID = 1;
const uint16_t FIRST_REG = 0x0000;
SoftwareSerial rs485(RX_PIN, TX_PIN);
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(9600);
rs485.begin(9600); // SoftwareSerial is 8N1 only: for an 8E1 device, change the device or use a Mega
node.begin(SLAVE_ID, rs485);
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
Serial.println(F("Modbus master on D2/D3, slave 1, 9600 8N1"));
}
void loop() {
uint8_t r = node.readHoldingRegisters(FIRST_REG, 2);
if (r == node.ku8MBSuccess) {
uint16_t a = node.getResponseBuffer(0), b = node.getResponseBuffer(1);
Serial.print(F("Reg 0 = "));
Serial.print(a);
Serial.print(F(" Reg 1 = "));
Serial.print(b);
// Many energy meters store one value as a 32-bit float across two registers (high word first).
uint32_t raw = ((uint32_t)a << 16) | b;
float f;
memcpy(&f, &raw, 4);
Serial.print(F(" as float: "));
Serial.println(f, 2);
} else if (r == node.ku8MBResponseTimedOut) {
Serial.println(F("Timeout (0xE2) - check A/B, slave ID, baud rate and GND"));
} else {
Serial.print(F("Modbus error 0x"));
Serial.println(r, HEX);
}
delay(2000);
}// MAX485 RS485 module on an Arduino Uno R3 as a Modbus-RTU master over SoftwareSerial.
// RO → D2 (soft RX), DI ← D3 (soft TX), DE + RE ← D4, VCC → 5V, GND → GND. A/B → the RS485 bus.
// The Uno's only hardware UART (D0/D1) is busy with USB, so Modbus runs on SoftwareSerial —
// fine at 9600 baud, because RS485 is half-duplex and SoftwareSerial can't send and receive at once anyway.
#include <SoftwareSerial.h>
#include <ModbusMaster.h>
const uint8_t RX_PIN = 2, TX_PIN = 3, DIR_PIN = 4;
const uint8_t SLAVE_ID = 1;
const uint16_t FIRST_REG = 0x0000;
SoftwareSerial rs485(RX_PIN, TX_PIN);
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(9600);
rs485.begin(9600); // SoftwareSerial is 8N1 only: for an 8E1 device, change the device or use a Mega
node.begin(SLAVE_ID, rs485);
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
Serial.println(F("Modbus master on D2/D3, slave 1, 9600 8N1"));
}
void loop() {
uint8_t r = node.readHoldingRegisters(FIRST_REG, 2);
if (r == node.ku8MBSuccess) {
uint16_t a = node.getResponseBuffer(0), b = node.getResponseBuffer(1);
Serial.print(F("Reg 0 = "));
Serial.print(a);
Serial.print(F(" Reg 1 = "));
Serial.print(b);
// Many energy meters store one value as a 32-bit float across two registers (high word first).
uint32_t raw = ((uint32_t)a << 16) | b;
float f;
memcpy(&f, &raw, 4);
Serial.print(F(" as float: "));
Serial.println(f, 2);
} else if (r == node.ku8MBResponseTimedOut) {
Serial.println(F("Timeout (0xE2) - check A/B, slave ID, baud rate and GND"));
} else {
Serial.print(F("Modbus error 0x"));
Serial.println(r, HEX);
}
delay(2000);
}Library: ModbusMaster (Doc Walker) — compiled with ModbusMaster 2.0.1.
Expected serial output
Modbus master on D2/D3, slave 1, 9600 8N1
Reg 0 = 17254 Reg 1 = 26214 as float: 230.40
Timeout (0xE2) - check A/B, slave ID, baud rate and GNDModbus master on D2/D3, slave 1, 9600 8N1 Reg 0 = 17254 Reg 1 = 26214 as float: 230.40 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, parity is 8E1 (not possible with SoftwareSerial), the slave is unpowered, or there is no common GND.
Illegal data address (0x02)
The slave answered, so the wiring is fine — the register address is wrong. Register 40001 in a manual is address 0x0000 in code; check holding vs input registers.
Random CRC errors (0xE3)
Noise or a missing terminator on a long bus, or interrupts from other libraries upsetting SoftwareSerial. Keep the sketch simple while the bus is talking.