Modbus 是 1979 年由 Modicon(现 Schneider Electric)开发的串行通信协议,现为工业自动化领域的 de facto 标准,用于 PLC、传感器、HMI 等设备间数据交换。它采用客户端-服务器(原 master-slave)模型:客户端发起请求,服务器响应。协议开放、简单,但无内置安全机制,依赖物理/网络隔离。
Modbus 关键特性
- 数据模型:四个表(每个最多 65536 项):
- 线圈(Coils):读/写,1 位(开关量)。
- 离散输入(Discrete Inputs):只读,1 位。
- 保持寄存器(Holding Registers):读/写,16 位字(配置值)。
- 输入寄存器(Input Registers):只读,16 位字(测量值)。
- 功能码(Function Codes):公共码(1-127):
- 0x01:读线圈。
- 0x02:读离散输入。
- 0x03:读多个保持寄存器。
- 0x04:读输入寄存器。
- 0x05:写单个线圈。
- 0x06:写单个保持寄存器。
- 0x0F:写多个线圈。
- 0x10:写多个保持寄存器。
- 其他:0x16(掩码写寄存器)、0x17(读/写多个寄存器)等。
- 异常响应:功能码 + 0x80 + 异常码(1:非法功能、2:非法地址等)。
- PDU vs ADU:
- PDU:功能码(1 字节)+ 数据(最大 252 字节)。
- ADU:PDU + 附加(地址、校验)。大端序编码。
- 变体:
- Modbus RTU:二进制,串行(RS-232/485),CRC-16 校验,静默间隔定帧。
- Modbus ASCII:ASCII 码,串行,LRC 校验,: 开始,CR/LF 结束(较少用)。
- Modbus TCP:TCP/IP(端口 502),MBAP 头(事务 ID + 协议 ID 0 + 长度 + 单元 ID),无校验。
- Modbus Plus:Schneider 专有,高速令牌环,非标准 Modbus。
- 帧示例:
- RTU 读寄存器:地址(1B) + 0x03 + 起始(2B) + 数量(2B) + CRC(2B)。
- TCP:事务ID(2B) + 0(2B) + 长度(2B) + 单元ID(1B) + PDU。
- 错误处理:超时、无响应、异常响应。
- 使用场景:工业控制、SCADA、能源管理、楼宇自动化。
- 规范:Modbus.org 提供免费下载(Application Protocol V1.1b3、Serial Line 等),无重大更新(2020 年术语改为 client-server)。
Python 使用详解(pymodbus 库)
pymodbus 是纯 Python Modbus 栈,支持同步/异步、TCP/RTU/ASCII/UDP。最新稳定版 ~3.6.x(开发版 4.0+)。
安装:
pip install pymodbus # TCP 等
pip install "pymodbus[serial]" # 加 RTU
TCP 客户端示例(读/写寄存器):
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('127.0.0.1', port=502) # 或 '192.168.1.100'
client.connect()
# 读保持寄存器 (FC 03)
rr = client.read_holding_registers(address=0, count=5, slave=1)
if not rr.isError():
print("寄存器值:", rr.registers) # [val1, val2, ...]
# 写单个寄存器 (FC 06)
wr = client.write_register(address=0, value=123, slave=1)
if not wr.isError():
print("写入成功")
# 读线圈 (FC 01)
rc = client.read_coils(address=0, count=10, slave=1)
print("线圈:", rc.bits)
client.close()
RTU 客户端示例(串口):
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(method='rtu', port='/dev/ttyUSB0', baudrate=9600, parity='N', bytesize=8, stopbits=1)
client.connect()
rr = client.read_holding_registers(0, 5, slave=1)
print(rr.registers)
client.close()
服务器示例(TCP,模拟设备):
from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [15]*100), # 离散输入
co=ModbusSequentialDataBlock(0, [False]*100), # 线圈
hr=ModbusSequentialDataBlock(0, [0]*100), # 保持寄存器
ir=ModbusSequentialDataBlock(0, [10]*100)) # 输入寄存器
context = ModbusServerContext(slaves=store, single=True)
StartTcpServer(context=context, address=("localhost", 502))
C 使用详解(libmodbus 库)
libmodbus 是高效 C 库,支持 TCP/RTU/ASCII,无依赖。最新版 ~3.1.x(活跃维护)。
安装:
git clone https://github.com/stephane/libmodbus.git
cd libmodbus
./autogen.sh # 或用 tarball
./configure
make
sudo make install
TCP 客户端示例(读保持寄存器):
#include <stdio.h>
#include <modbus.h>
int main() {
modbus_t *ctx;
uint16_t regs[10];
int rc;
ctx = modbus_new_tcp("127.0.0.1", 502); // IP 和端口
if (ctx == NULL) {
fprintf(stderr, "分配上下文失败\n");
return -1;
}
modbus_set_slave(ctx, 1); // 单元 ID/slave ID
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "连接失败: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
rc = modbus_read_registers(ctx, 0, 5, regs); // 地址 0,读 5 个
if (rc == -1) {
fprintf(stderr, "读取失败: %s\n", modbus_strerror(errno));
} else {
printf("读到 %d 个寄存器:\n", rc);
for (int i = 0; i < rc; i++) {
printf("regs[%d] = %u\n", i, regs[i]);
}
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
编译:gcc client.c -o client pkg-config --cflags --libs libmodbus``
RTU 客户端示例:
modbus_t *ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
modbus_set_slave(ctx, 1);
modbus_connect(ctx);
modbus_read_registers(ctx, 0, 5, regs);
// ... 同上
modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485); // 如需 RS485
服务器:用 modbus_mapping_new() 创建映射,modbus_tcp_listen() 监听,modbus_reply() 处理请求。

7万+

被折叠的 条评论
为什么被折叠?



