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 Arduino Uno — Modbus RTU master on SoftwareSerial

Read Modbus RTU registers over RS485 from an Arduino Uno R3: MAX485 on D2/D3 with SoftwareSerial, DE+RE on D4, and a 5 V module that needs no divider.

Arduino C++Verified — core avr 1.8.8, ModbusMaster 2.0.1, 3 Nov 2026Compile-only

Community draft — report a mistake

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 R3Only the pins used are shown · 5 V logicMAX485 TTL to RS485 module5VGNDD2D3D4ROREDEDIVCCB → RS485 bus B / D− — B of every deviceA → RS485 bus A / D+ — A of every deviceGND
Also needed: 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
Arduino Uno R3MAX485 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)
5VVCCredThe MAX485 is a 5 V chip — same as the Uno, so no level shifting anywhere.
GNDGNDblack
D2ROyellowSoftwareSerial RX.
D3DIgreenSoftwareSerial TX.
D4DEpurpleDE and RE share one pin: HIGH = transmit, LOW = receive.
D4REpurple

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 readHoldingRegisters for readInputRegisters if 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-uno.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// 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

Serial Monitor · 9600Output
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 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.

↑↓ to move · Enter to open