1. Modbus RTU 从站数据采集

Modbus RTU 从站数据采集 学习卡片

任务目标

本案例实现一个基于 Python 的 Modbus RTU 主站程序,通过串口读取从站保持寄存器(Holding Register)数据,并将原始寄存器值按量程与偏移量解析为实际工程值(如温度、压力等)。程序使用 pymodbus 库完成协议帧封装与 CRC 校验,适用于工业现场对 PLC、仪表等从站设备的周期数据采集。

环境依赖

  • Python 3.8+
  • 安装依赖:pip install pymodbus==3.6.9 pyserial
  • 硬件准备:USB 转 RS485 适配器(或开发板自带串口),从站设备地址已知(本案例使用 0x01)

关键完整代码

from pymodbus.client import ModbusSerialClient
import time

# 从站配置
SLAVE_ID = 0x01
REGISTER_ADDR = 0x0000   # 起始寄存器地址
REGISTER_COUNT = 2       # 读取寄存器个数(32位浮点占用2个寄存器)
BAUDRATE = 9600
PORT = '/dev/ttyUSB0'    # Windows 下改为 'COM3' 等实际串口号

# 工程值换算参数(示例:量程 0~100℃,偏移 0)
SCALE_MAX = 100.0
SCALE_MIN = 0.0
RAW_MAX = 65535.0
RAW_MIN = 0.0

def raw_to_engineering(raw_value):
    """将16位无符号原始值线性映射为工程值"""
    if raw_value > RAW_MAX or raw_value < RAW_MIN:
        raise ValueError(f"原始值 {raw_value} 超出范围")
    ratio = (raw_value - RAW_MIN) / (RAW_MAX - RAW_MIN)
    return SCALE_MIN + ratio * (SCALE_MAX - SCALE_MIN)

def read_holding_registers(client):
    """读取保持寄存器并返回解析后的工程值列表"""
    try:
        result = client.read_holding_registers(
            address=REGISTER_ADDR,
            count=REGISTER_COUNT,
            slave=SLAVE_ID
        )
        if result.isError():
            raise RuntimeError(f"Modbus 读取错误: {result}")

        raw_values = result.registers
        # 将两个16位寄存器组合为32位整数(大端序)
        combined = (raw_values[0] << 16) | raw_values[1]
        # 转换为 IEEE 754 浮点数(若从站存储的是浮点数)
        import struct
        engineering_value = struct.unpack('>f', combined.to_bytes(4, 'big'))[0]
        return engineering_value
    except Exception as e:
        raise RuntimeError(f"读取失败: {e}")

def main():
    client = ModbusSerialClient(
        port=PORT,
        baudrate=BAUDRATE,
        parity='N',
        stopbits=1,
        bytesize=8,
        timeout=2
    )
    if not client.connect():
        raise ConnectionError(f"无法连接串口 {PORT}")

    print(f"开始采集从站 {SLAVE_ID} 保持寄存器数据...")
    try:
        for i in range(5):   # 连续采集5次
            value = read_holding_registers(client)
            print(f"第 {i+1} 次采集: {value:.2f} °C")
            time.sleep(1)
    finally:
        client.close()
        print("串口已关闭")

if __name__ == "__main__":
    main()

运行与验证

  1. 连接设备:将从站设备(如 Modbus 模拟器或真实仪表)的 A/B 线接入 RS485 适配器,确认串口号。
  2. 修改配置:根据实际从站地址、寄存器地址和串口号修改代码顶部配置常量。
  3. 执行程序:python modbus_rtu_collect.py
  4. 预期输出:若从站地址 0x01 的保持寄存器 0x0000 处存储 IEEE 754 浮点数 25.5,程序将连续打印 5 次 25.50 °C。

验证方法

  • 无硬件验证:使用 pymodbus 自带的 ModbusSerialClient 连接虚拟串口对(如 socat 创建的 PTY),配合 pymodbus.server 启动一个模拟从站,可验证程序逻辑正确性。
  • 判定标准:程序能连续读取并打印出与从站设定值一致的工程值,且无 CRC 校验错误或超时异常,即视为通过。
  • 常见问题:若出现 Modbus IOException,检查串口号、波特率、从站地址是否匹配;若解析值异常,确认从站数据字节序(大端/小端)与代码一致。