1. Modbus RTU 温湿度采集

Modbus RTU 温湿度采集 学习卡片

任务目标

通过RS485总线连接温湿度传感器(Modbus RTU从站),使用Python在主站侧发送读取请求,解析返回的寄存器数据,得到温度与湿度值并打印显示。本案例覆盖串口参数配置、Modbus RTU帧构造/发送/接收、CRC校验及数据解析的完整最小闭环。

环境与依赖

  • 硬件:USB转RS485适配器(主站)、温湿度传感器模块(从站,支持Modbus RTU,默认从站地址1)
  • 软件:Python 3.8+,pyserial库(pip install pyserial)
  • 传感器寄存器约定(本案例按常见协议):保持寄存器地址0x0000存温度(有符号16位,单位0.1℃),0x0001存湿度(无符号16位,单位0.1%RH)

完整实现代码

import serial
import struct
import time

# 串口与从站配置
SERIAL_PORT = 'COM3'   # Linux下如 '/dev/ttyUSB0'
BAUDRATE = 9600
SLAVE_ID = 0x01
REG_ADDR = 0x0000      # 起始寄存器地址
REG_COUNT = 0x0002     # 读取2个寄存器(温度+湿度)

def crc16_modbus(data: bytes) -> int:
    """计算Modbus RTU CRC16(多项式0x8005,初始值0xFFFF)"""
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

def build_read_holding_request(slave_id: int, reg_addr: int, reg_count: int) -> bytes:
    """构造读保持寄存器(功能码0x03)请求帧"""
    frame = struct.pack('>BBHH', slave_id, 0x03, reg_addr, reg_count)
    crc = crc16_modbus(frame)
    return frame + struct.pack('<H', crc)  # CRC低字节在前

def parse_read_response(resp: bytes) -> tuple:
    """解析响应帧,返回(温度℃, 湿度%RH)"""
    if len(resp) < 5:
        raise ValueError(f'响应帧过短: {len(resp)}字节')
    if resp[0] != SLAVE_ID or resp[1] != 0x03:
        raise ValueError(f'从站地址或功能码错误: {resp[0]:#x}, {resp[1]:#x}')
    byte_count = resp[2]
    if byte_count != REG_COUNT * 2:
        raise ValueError(f'字节数不匹配: {byte_count}')
    # 温度:有符号16位,单位0.1℃;湿度:无符号16位,单位0.1%RH
    temp_raw, hum_raw = struct.unpack('>hh', resp[3:7])
    # 校验CRC(最后2字节)
    recv_crc = struct.unpack('<H', resp[-2:])[0]
    calc_crc = crc16_modbus(resp[:-2])
    if recv_crc != calc_crc:
        raise ValueError(f'CRC校验失败: 接收{recv_crc:#x}, 计算{calc_crc:#x}')
    return temp_raw / 10.0, hum_raw / 10.0

def main():
    try:
        ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1)
    except serial.SerialException as e:
        print(f'串口打开失败: {e}')
        return
    req = build_read_holding_request(SLAVE_ID, REG_ADDR, REG_COUNT)
    try:
        ser.write(req)
        resp = ser.read(7 + 2)  # 从站地址+功能码+字节数+4数据+2CRC = 9字节
        if len(resp) == 0:
            print('无响应,请检查接线/从站地址/波特率')
            return
        temp, hum = parse_read_response(resp)
        print(f'温度: {temp:.1f} ℃')
        print(f'湿度: {hum:.1f} %RH')
    except (ValueError, struct.error) as e:
        print(f'解析失败: {e}')
    finally:
        ser.close()

if __name__ == '__main__':
    main()

运行与验证

  1. 将RS485适配器A/B线分别连接传感器的A/B端子,确认传感器供电正常。
  2. 在设备管理器中确认串口号(Windows)或使用ls /dev/ttyUSB*(Linux),修改代码中SERIAL_PORT。
  3. 确认传感器从站地址为1(若不同,修改SLAVE_ID),波特率9600、数据位8、无校验、停止位1(代码默认)。
  4. 运行:python modbus_temp_hum.py

通过条件:程序输出温度与湿度数值,且数值与传感器现场环境一致(如室温约25℃)。若输出“无响应”,依次检查接线极性、从站地址、波特率;若输出“CRC校验失败”,检查线路干扰或适配器是否正常。

关键说明

本案例使用功能码0x03读取保持寄存器,这是多数温湿度传感器的标准方式。若传感器使用输入寄存器(功能码0x04),只需将请求帧中功能码改为0x04,并同步修改parse_read_response中的功能码校验值。CRC校验采用Modbus标准多项式0xA001,低字节在前发送。代码中ser.read(9)按固定响应长度读取,适用于本案例的2寄存器场景;若需读取更多寄存器,应改为循环读取直至收满3 + 2*N + 2字节。