在工业自动化和物联网(IoT)的世界里,有一种协议就像是“普通话”一样普及。它诞生于 1979 年,比互联网还要早,但至今仍运行在数以亿计的设备上——从巨大的工厂 PLC 到小巧的温湿度传感器。
它就是 Modbus。
如果你是一名工程师、开发者,或者仅仅是对连接物理世界感兴趣的技术爱好者,理解 Modbus 都是一项必备技能。这篇博文将带你快速了解 Modbus 的核心概念,并教你如何使用 Python 这种现代语言来驾驭这个古老的协议。
第一部分:Modbus 的世界观
Modbus 之所以长盛不衰,秘诀在于它的简单性。在深入代码之前,我们需要理解它的两个核心概念。
1. 课堂里的对话:主从架构 (Master/Slave)
Modbus 网络就像一个纪律严明的课堂:
-
Master (主站/客户端): 就像老师。整个网络中只有一个(在标准串行网络中)。只有“老师”有权发起提问,比如“现在的温度是多少?”或“把那个阀门打开”。
-
Slave (从站/服务器): 就像学生。网络中可以有多个(最多 247 个)。“学生”从不主动说话,只有在被“老师”点名(通过唯一的 Slave ID)时,才会回答问题或执行命令。
记住: 如果你的电脑想去读一个传感器的数据,你的电脑就是 Master,传感器就是 Slave。
2. 数据的抽屉:寄存器模型
Modbus 设备到底是怎么存储数据的?你可以把它想象成一个有四个大抽屉的柜子。你想读什么样的数据,就去拉对应的抽屉。
| 抽屉名称 (数据类型) | 特点 | 数据单位 | 典型用途 |
| Coils (线圈) | 可读/可写 | 1 Bit (开/关) | 控制继电器、指示灯的开关 |
| Discrete Inputs (离散输入) | 只读 | 1 Bit (开/关) | 读取按钮是否按下、门磁状态 |
| Input Registers (输入寄存器) | 只读 | 16-bit 数值 | 读取温度传感器、模拟量数据 |
| Holding Registers (保持寄存器) | 可读/可写 | 16-bit 数值 | 最常用! 设定参数、读取关键数据 |
在实际应用中,保持寄存器 (Holding Registers) 是最常打交道的,因为它既能读又能写,很多设备厂家为了省事,把所有重要数据都塞在这个区域。
第二部分:Modbus 的两种“方言”
Modbus 协议本身定义了数据结构,但它需要搭载在物理媒介上传输。最常见的两种传输方式(方言)是:
-
Modbus RTU (经典版):
-
跑在 RS-485 或 RS-232 串口线上。
-
数据是紧凑的二进制格式。
-
关键: 通信双方的波特率(Baudrate)、数据位、校验位必须完全一致才能对话。
-
-
Modbus TCP (现代版):
-
跑在标准的 以太网(网线或 WiFi)上。
-
不需要复杂的校验(TCP/IP 协议栈帮你做了)。
-
关键: 通过 IP 地址和端口号(默认 502)连接。
-
第三部分:Python 实战 (使用 pymodbus)
理论听起来很枯燥,让我们动手吧!
在 Python 世界里,处理 Modbus 最流行、最强大的库是 pymodbus。它既能让你当 Master,也能让你当 Slave,并且同时支持 RTU 和 TCP。
环境准备
首先,安装必要的库。如果你要用 RTU(串口),还需要安装 pyserial。
Bash
pip install pymodbus pyserial
注意:以下示例基于 pymodbus v3.x 版本,这是目前推荐的同步客户端写法。
场景一:使用 Python 通过 Modbus TCP 读取数据
假设你有一个 Modbus TCP 设备(比如一个以太网温湿度变送器,或者你的电脑上运行的一个 Modbus Simulator 模拟器)。
-
设备 IP:
192.168.1.100 -
端口:
502 -
Slave ID:
1 -
目标:读取从地址
40001开始的 2 个保持寄存器的数据。
Python
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException
import logging
# 配置日志,方便调试(可选)
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.INFO)
# --- 配置连接参数 ---
SERVER_IP = '192.168.1.100' # 你的设备IP
SERVER_PORT = 502 # 默认Modbus TCP端口
SLAVE_ID = 1 # 目标从站ID
REGISTER_ADDRESS = 0 # 起始寄存器地址 (对应 40001)
COUNT = 2 # 要读取的寄存器数量
# 1. 创建客户端实例
client = ModbusTcpClient(SERVER_IP, port=SERVER_PORT)
try:
# 2. 尝试连接
print(f"正在连接到 {SERVER_IP}...")
connection = client.connect()
if not connection:
print("连接失败!请检查IP和端口。")
exit()
print("连接成功!")
# 3. 读取数据 (读取保持寄存器 read_holding_registers)
# 注意:address=0 通常对应文档中的 40001 寄存器
print(f"开始读取从站 {SLAVE_ID} 的寄存器 {REGISTER_ADDRESS}...")
result = client.read_holding_registers(address=REGISTER_ADDRESS, count=COUNT, slave=SLAVE_ID)
# 4. 处理结果
if result.isError():
# 处理 Modbus 协议层面的错误(如非法地址、从站忙等)
print(f"读取错误: {result}")
else:
# 成功获取数据,数据保存在 .registers 列表中
data = result.registers
print("-" * 20)
print(f"读取成功!原始数据 (List): {data}")
print(f"寄存器 40001 的值: {data[0]}")
print(f"寄存器 40002 的值: {data[1]}")
# 假设第一个寄存器是温度,且放大了10倍传输
temperature = data[0] / 10.0
print(f"换算后的温度: {temperature} °C")
print("-" * 20)
except ModbusException as e:
print(f"发生程序异常: {e}")
finally:
# 5. 断开连接,释放资源
client.close()
print("连接已关闭。")
场景二:使用 Python 通过 Modbus RTU (串口) 读取数据
假设你使用一个 USB 转 RS-485 转换器连接了一个传感器。
-
串口号:
COM3(Windows) 或/dev/ttyUSB0(Linux) -
波特率:
9600 -
Slave ID:
1
Python
from pymodbus.client import ModbusSerialClient
from pymodbus.exceptions import ModbusException
import time
# --- 配置串口参数 ---
# 这里的参数必须与你的设备说明书完全一致!
SERIAL_PORT = 'COM3' # Windows 示例,Linux可能是 '/dev/ttyUSB0'
BAUDRATE = 9600 # 波特率
PARITY = 'N' # 校验位: 'N'-无, 'E'-偶, 'O'-奇
STOPBITS = 1 # 停止位
BYTESIZE = 8 # 数据位
SLAVE_ID = 1
# 1. 创建 RTU 客户端实例
# 指定通过 rtu 模式,使用串口进行通信
client = ModbusSerialClient(
port=SERIAL_PORT,
baudrate=BAUDRATE,
parity=PARITY,
stopbits=STOPBITS,
bytesize=BYTESIZE,
timeout=1 # 设置超时时间
)
try:
# 2. 连接串口
print(f"正在打开串口 {SERIAL_PORT}...")
if not client.connect():
print(f"无法打开串口 {SERIAL_PORT},请检查占用或配置。")
exit()
print("串口已打开。")
# 简单的循环读取示例
for i in range(3):
print(f"\n--- 第 {i+1} 次读取 ---")
# 读取保持寄存器,地址 0,读取 1 个
result = client.read_holding_registers(address=0, count=1, slave=SLAVE_ID)
if result.isError():
print(f"读取失败: {result}")
else:
print(f"读取成功,寄存器值: {result.registers[0]}")
# RTU 通信建议在很多次请求间稍作延时
time.sleep(1)
except ModbusException as e:
print(f"发生异常: {e}")
finally:
client.close()
print("\n串口已关闭。")
总结与避坑指南
Modbus 虽然简单,但刚上手时总会遇到一些“玄学”问题。以下是新手最常踩的坑:
-
地址偏移之谜 (Offset by 1): 这是最令人困惑的。设备文档说数据在寄存器
40001,但在代码里你通常需要填写地址0。如果你读到的数据对不上,试着把代码里的地址 +1 或 -1 试试。 -
RTU 接线与参数: RS-485 的 A 和 B 线千万别接反了。如果通信不通,先检查波特率、校验位是否与设备说明书完全一致。
-
字节序 (Endianness) 陷阱: Modbus 标准传输的是 16 位数据。如果要传输 32 位浮点数(比如精确的温度值),需要用两个寄存器拼起来。这时,高低位的顺序(大端/小端)非常关键,顺序搞反了,读出来的数字会非常离谱。
掌握了 Modbus,你就掌握了打开工业数据大门的钥匙。希望这篇教程能帮你迈出第一步!
581

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



