LabWired The easy way to build hardware
An Arduino Uno, a MAX485 and two XY-MD02 Modbus sensors on one RS-485 pair, drawn in the LabWired playground.

Modbus RTU without a bus analyzer

Category: Engineering

A Modbus master with one sensor on the desk usually works. Problems show up with two or more devices on the bus: a reply gets cut off, a slave answers too early, two frames run together. Debugging that normally means an RS-485 adapter, a logic analyzer and reading hex. In LabWired you can now simulate the whole bus: the master, the MAX485 transceiver, the wire pair and two real sensors. It runs in the browser and in CI.

The setup

Arduino Uno wired to a MAX485: D1 to DI, D0 from RO, D2 to DE and RE. The A/B pair runs to two XY-MD02 sensors, with a 120 ohm resistor across the pair at each end.
The circuit, exported from the same playground diagram the simulator runs.

An Arduino Uno talks to a MAX485 on its UART. D2 drives DE and /RE together, the usual wiring. A and B go to two XY-MD02 sensors at addresses 1 and 2, with a 120 Ω resistor across the pair at each end. The XY-MD02 is an SHT20 temperature and humidity transmitter in a DIN-rail housing with a four-way screw terminal (B−, A+, GND, V+). The firmware is the ModbusMaster library (Apache-2.0) and a sketch of about sixty lines. Every half second it reads two input registers from each sensor, 0x0001 (temperature) and 0x0002 (humidity), and prints them.

The parts

MAX485. It passes bytes between the UART and the bus according to its pins. A byte from the Uno goes onto the wire only while DE is high. With DE high and /RE low, the Uno also receives its own frame back, as on real hardware. A reply from a slave reaches the Uno only while /RE is low. If a slave replies while DE is still high, or two slaves reply at once, the bytes collide and the run reports it.

XY-MD02. The sensor is described in a data file, using the register map from the manufacturer’s manual:

Every example frame in the manual is a test. The manual lists no exception codes, so the sensor answers with the standard Modbus ones.

Try it

Open the example in the playground and press Run. Serial prints one line per sensor and poll:

poll 1 s1 T=21.5 H=48.0
poll 1 s2 T=19.0 H=55.5
poll 2 s1 reg 0x0200 -> 0x2
poll 2 s2 write correction -> 0x0
poll 3 s2 T=19.5 H=55.5

Select slave 1 and drag its Temperature slider; the next poll shows the new value. In poll 2 the sketch also reads a register that doesn’t exist, and the sensor answers exception 02. It then writes a +0.5 °C correction to slave 2 (holding register 0x0103). The change shows up in poll 3.

The frames

The MAX485 logs every frame on the pair. The playground shows the Serial output; the frame log is in the CLI and in CI:

TimeFromBytes on the pairpymodbus decodes
0.583 msmaster01 04 00 01 00 02 20 0Bslave 1, read 2 input registers from 0x0001
4.270 msslave 101 04 04 00 D7 01 E0 4B A4215, 480 (21.5 °C, 48.0 %RH)
13.295 msmaster02 04 00 01 00 02 20 38slave 2, same read
16.982 msslave 202 04 04 00 BE 02 2B E9 DF190, 555
551.421 msmaster01 03 02 00 00 01 85 B2slave 1, read holding register 0x0200
555.109 msslave 101 83 02 C0 F1exception 02, illegal data address
559.591 msmaster02 06 01 03 00 05 B8 06slave 2, write 5 to 0x0103 (temperature correction)
563.279 msslave 202 06 01 03 00 05 B8 06write echoed back

Times are simulated time. The 3.7 ms between a request and its reply is the 3.5-character silence at 9600 baud.

The frames are checked by the RTU framer from pymodbus, which is independent of our code. It decodes every frame, CRC included, and checks addresses and values. The same test also builds requests with pymodbus and sends them on the bus. Three kinds of request get no reply. They are a wrong CRC, a frame split by a gap longer than 3.5 characters, and an address that no device has.

The example firmware is a committed image, so the playground, the CLI and CI run the same bytes.

Limits

Andrii Shylenko
Andrii Shylenko

Founder, LabWired.