Part III · Chapter 13
Modbus RTU over RS-485
Now the bytes leave localhost and travel the wire that defines industrial Modbus. The good news: almost nothing you've learned changes. The new material is practical and physical: an adapter, a port name, a permission, and four serial settings that must match.
By the end of this chapter you can
- Wire a USB-to-RS-485 adapter to a device and find its port on Linux.
- Fix the "permission denied" problem with the
dialoutgroup. - Create a
ModbusSerialClientwith matching serial settings and read over RTU. - Build a two-adapter test rig, and work the silent-bus checklist in order.
The same pymodbus read and write calls work over serial. You create a serial client instead of a TCP one and tell it the port and serial settings, the very details Chapter 5 prepared you for. This is where Modbus stops being a desk exercise and becomes something you could wire into a real plant.
The adapter
A microcomputer has no RS-485 port of its own, so you add one with an inexpensive USB-to-RS-485 adapter: a small dongle that plugs into a USB socket and presents a pair of terminals, usually labeled A and B, for the bus. To the computer it looks like an ordinary serial port; on the bus side it speaks the differential RS-485 signaling from Chapter 5. With it, your board can join a real Modbus RTU network and talk to actual meters, drives, and controllers.
Wiring follows Chapter 5 exactly: adapter A to device A, B to device B, and join the grounds. Manufacturers sometimes disagree about which line is which, so if you get only silence, try swapping the two data wires. On a longer bus, remember termination resistors at each end; for a short bench connection between two nearby devices you can usually skip termination and still get a clean signal.
Finding the port
Plug the adapter into the Pi and Linux gives it a device name, almost always /dev/ttyUSB0 for the first adapter. Confirm it by listing USB serial devices, or by checking the kernel log after plugging it in:
ls /dev/ttyUSB*
dmesg | grep ttyUSB/dev/ttyUSB0The first command lists the USB serial ports present. The second shows the kernel messages that mention them, which is handy for telling two adapters apart: plug them in one at a time and watch which name each gets. Note the name (typically /dev/ttyUSB0); you'll pass it to pymodbus shortly. If nothing appears, the adapter isn't being recognized, and the cable or the adapter itself is the place to look.
Permission to use the port
One small obstacle trips up nearly everyone the first time. On Linux, serial ports are restricted to members of a group called dialout, and your user may not be in it. If your program fails with a permission error opening the port, add yourself to the group once, then log out and back in for the change to take effect:
sudo usermod -a -G dialout $USERIt's a one-time step, easy to forget and easy to fix. A "permission denied" on /dev/ttyUSB0 almost always means this group membership is missing, not that anything is wrong with your wiring or code.
The serial client
Here's the payoff for all of Part III: it's the Chapter 11 client with the connection swapped. Instead of ModbusTcpClient with an address, create a ModbusSerialClient with a port and Chapter 5's four serial settings: baud rate, parity, stop bits, and byte size:
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(
port="/dev/ttyUSB0",
baudrate=9600,
parity="N",
stopbits=1,
bytesize=8)
client.connect()Those four settings must match at both ends, just as Chapter 5 insisted: baudrate=9600, parity="N" (none), stopbits=1, and bytesize=8, which together spell the familiar "9600 8-N-1." Set them to whatever the device uses; a mismatch here is the leading cause of a silent serial bus. Notice there's no framer to choose: in pymodbus 3.7.4, ModbusSerialClient uses RTU framing by default, which is what you want.
Reading is unchanged
With the serial client connected, reading is identical to the TCP case with one addition. A serial bus is multidrop, so you must name which device with its unit address. In pymodbus that's the slave argument (older versions) or device_id (newer ones). A read of four holding registers from unit 1:
rr = client.read_holding_registers(
0, count=4, slave=1)
if not rr.isError():
print("Registers:", rr.registers)
client.close()With pymodbus 3.7.4, pinned in Chapter 10, slave=1 is correct. device_id= is the spelling from release 3.10 onward and won't work here. If a device ignores you, check this keyword before suspecting the wiring.
That's the whole difference between Ethernet and serial in your code: a different client object, the serial settings, and a unit address on each call. read_holding_registers, read_input_registers, write_register, and write_coil all behave exactly as they did over TCP. Everything from Chapters 11 and 12 carries straight over to the serial world.
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient(
"127.0.0.1", port=5020)
client.connect()
rr = client.read_holding_registers(
0, count=4)
if not rr.isError():
print("Registers:", rr.registers)
client.close()from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(
port="/dev/ttyUSB0", baudrate=9600,
parity="N", stopbits=1, bytesize=8)
client.connect()
rr = client.read_holding_registers(
0, count=4, slave=1)
if not rr.isError():
print("Registers:", rr.registers)
client.close()Testing without a field device
No industrial device on hand? Here are three ways to practice serial Modbus on your bench:
- A real but humble device: an inexpensive RS-485 temperature or energy sensor gives you genuine hardware to read.
- A two-adapter rig: plug two USB-to-RS-485 adapters into the same board, wire them A-to-A and B-to-B, run a serial server on one port and your client on the other, and let the board talk to itself over real RS-485.
- A software loopback, for testing framing alone, though the two-adapter rig is far more realistic.
The two-adapter rig is worth the small expense: it exercises the entire serial path (real ports, real differential signaling, real RTU framing and CRCs) in a setup you fully control. A serial server mirrors Chapter 12's TCP server. Build the same data blocks and contexts, then start it with StartSerialServer instead of StartTcpServer, giving it a port and baud rate:
from pymodbus.server import StartSerialServer
StartSerialServer(context=ctx,
port="/dev/ttyUSB1",
baudrate=9600)Point the serial server at one adapter (/dev/ttyUSB1) and your serial client at the other (/dev/ttyUSB0), match their settings, and you have a complete RTU conversation running over copper on your desk. Watch it work and you've closed the loop between Chapter 5's theory and a live serial bus.
When the bus stays silent
Serial brings back Chapter 3's silence outcome in full force, and its checklist is short and worth memorizing. If a serial read returns nothing, work down these in order: the port name; your permission (the dialout group); the serial settings, baud and parity first; the unit address; and the wiring, including an A/B swap. Almost every silent serial bus is one of these five. Chapter 9's diagnostic counters, especially a climbing CRC-error count, then tell an electrical problem apart from a configuration one.
Silent-bus checklist
-
Run
ls /dev/ttyUSB*and compare with theport=in your code. Unplug and replug while watchingdmesg | grep ttyUSBif you have two adapters. Nothing listed? Suspect the cable or the adapter. -
Run
groupsand look fordialout. If it's missing:sudo usermod -a -G dialout $USER, then log out and back in. A "permission denied" error points here. -
Baud and parity first, then stop bits and byte size. Compare
baudrate,parity,stopbits,bytesizewith the device's configuration (for example 9600 8-N-1). Every device on the bus must agree. -
Check the device's configured address against
slave=in your read call (correct for pymodbus 3.7.4). An address with no device behind it draws silence, not an exception. -
A to A, B to B, grounds joined. Labels disagree between manufacturers, so swap the two data wires and try again. On a long bus, check termination at each end.
You can now speak Modbus over both Ethernet and serial, reading and writing, with real or simulated devices. Before a full project, one thing remains: data that doesn't fit in a single 16-bit register (signed numbers, decimals, counts above 65535, and true floating-point readings). Decoding those correctly is the subject of Chapter 14.
Check your understanding
1. Opening /dev/ttyUSB0 fails with "permission denied." What's the usual fix?
Linux limits serial ports to the dialout group. The error is about access, not wiring or settings.
2. Moving a working TCP client to RS-485, what changes in the code?
The PDU is the same, so the read and write calls are the same. Only the transport details and the unit address change.
3. What does "9600 8-N-1" mean?
It's the shorthand for the four serial settings: baudrate=9600, bytesize=8, parity="N", stopbits=1.
4. A serial read is silent. You've confirmed the port name and your dialout membership. What do you check next?
The order is port name, permission, serial settings, unit address, wiring. A settings mismatch is the leading cause of a silent bus.