Modbus Protocol Basics
Learn Modbus protocol fundamentals - function codes, data models, addressing, master/slave architecture.
Modbus Protocol Basics
Modbus is a serial communication protocol from 1979. Still widely used because it's simple and reliable.
Master/Slave Architecture
[Master/Client] ---request---> [Slave/Server]
<--response---
- Master (Client): Initiates requests
- Slave (Server): Responds to requests
- One master can talk to 247 slaves
- Slaves have unique IDs (1-247)
Data Model
Modbus has 4 data types:
Type | Access | Size | Address Range | Common Use |
---|---|---|---|---|
Coils | Read/Write | 1 bit | 00001-09999 | Outputs, relays |
Discrete Inputs | Read only | 1 bit | 10001-19999 | Switches, sensors |
Input Registers | Read only | 16 bit | 30001-39999 | Measurements |
Holding Registers | Read/Write | 16 bit | 40001-49999 | Configuration |
PyModbus uses 0-based addressing!
- Coil 00001 = address 0
- Register 40001 = address 0
- Register 40100 = address 99
Function Codes
Main function codes you'll use:
# Reading
0x01 - Read Coils
0x02 - Read Discrete Inputs
0x03 - Read Holding Registers
0x04 - Read Input Registers
# Writing
0x05 - Write Single Coil
0x06 - Write Single Register
0x0F - Write Multiple Coils
0x10 - Write Multiple Registers
PyModbus Implementation
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.100')
# Function code 0x03 - Read Holding Registers
result = client.read_holding_registers(address=0, count=10, slave=1)
# Function code 0x01 - Read Coils
result = client.read_coils(address=0, count=8, slave=1)
# Function code 0x06 - Write Single Register
client.write_register(address=0, value=123, slave=1)
# Function code 0x05 - Write Single Coil
client.write_coil(address=0, value=True, slave=1)
Frame Formats
Modbus TCP Frame
[Transaction ID][Protocol ID][Length][Unit ID][Function Code][Data]
2 bytes 2 bytes 2 bytes 1 byte 1 byte n bytes
Modbus RTU Frame
[Slave Address][Function Code][Data][CRC]
1 byte 1 byte n bytes 2 bytes
Modbus ASCII Frame
[:][Slave Address][Function Code][Data][LRC][CR][LF]
1 2 chars 2 chars n chars 2 1 1
Exception Codes
When something goes wrong:
result = client.read_holding_registers(9999, 10)
if result.isError():
print(f"Exception code: {result.exception_code}")
Code | Meaning | Fix |
---|---|---|
01 | Illegal Function | Device doesn't support this function |
02 | Illegal Data Address | Address doesn't exist |
03 | Illegal Data Value | Value out of range |
04 | Slave Device Failure | Device error |
05 | Acknowledge | Request accepted, processing |
06 | Slave Device Busy | Try again later |
Address Mapping Example
Device manual says "Temperature at register 40101"?
# WRONG - This won't work
result = client.read_holding_registers(40101, 1)
# RIGHT - Subtract 40001
result = client.read_holding_registers(100, 1) # 40101 - 40001 = 100
Timing and Limits
Modbus TCP
- Default timeout: 3 seconds
- Max registers per read: 125
- Max coils per read: 2000
Modbus RTU
- Inter-frame delay: 3.5 character times
- Response timeout: depends on baud rate
- Max frame size: 256 bytes
# Set custom timeout
client = ModbusTcpClient('192.168.1.100', timeout=10)
# RTU timing depends on baudrate
client = ModbusSerialClient(
'/dev/ttyUSB0',
baudrate=9600, # Slower = longer delays
timeout=5
)
Endianness (Byte Order)
Different devices use different byte orders:
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian
# Read 2 registers for a 32-bit float
result = client.read_holding_registers(0, 2)
# Try different combinations if value looks wrong
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG, # or Endian.LITTLE
wordorder=Endian.BIG # or Endian.LITTLE
)
value = decoder.decode_32bit_float()
Practical Tips
-
Always check connections first
if not client.connect(): print("Can't connect") exit()
-
Always check for errors
result = client.read_holding_registers(0, 10) if result.isError(): print(f"Error: {result}") exit()
-
Close connections
try: # Your code pass finally: client.close()
-
Use context managers
with ModbusTcpClient('192.168.1.100') as client: result = client.read_holding_registers(0, 10)
Next Steps
- TCP Client - Connect over Ethernet
- RTU Client - Connect over serial
- Function Codes - Detailed function reference
How is this guide?