1. Modbus RTU 温湿度传感器数据采集

任务目标

通过RS485接口,使用Modbus RTU协议读取温湿度传感器的保持寄存器值,并将原始寄存器数据解析为实际的温度和湿度工程单位。本案例以标准Modbus RTU从站设备为对象,实现主站读取功能。

环境与依赖

  • 硬件:RS485转USB适配器、支持Modbus RTU协议的温湿度传感器(从站地址1,波特率9600,8N1)
  • 软件:Python 3.8+,pyserial库(pip install pyserial)
  • 寄存器约定:温度保持寄存器地址0x0000(有符号16位,单位0.1℃),湿度保持寄存器地址0x0001(无符号16位,单位0.1%RH)

关键完整代码

import serial
import struct
import time

# Modbus RTU CRC16 计算
def crc16_modbus(data: bytes) -> int:
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

# 构建读保持寄存器请求帧(功能码0x03)
def build_read_request(slave_id: int, start_addr: int, quantity: int) -> bytes:
    frame = struct.pack('>BHH', slave_id, 0x03, start_addr, quantity)
    crc = crc16_modbus(frame)
    return frame + struct.pack('<H', crc)

# 解析响应帧,返回寄存器值列表
def parse_read_response(response: bytes, expected_quantity: int) -> list:
    if len(response) < 5 + expected_quantity * 2:
        raise ValueError('响应帧长度不足')
    # 校验CRC
    received_crc = struct.unpack('<H', response[-2:])[0]
    calc_crc = crc16_modbus(response[:-2])
    if received_crc != calc_crc:
        raise ValueError('CRC校验失败')
    byte_count = response[2]
    if byte_count != expected_quantity * 2:
        raise ValueError('字节数不匹配')
    values = []
    for i in range(expected_quantity):
        values.append(struct.unpack('>H', response[3 + i*2 : 5 + i*2])[0])
    return values

# 主读取函数:返回(温度℃, 湿度%RH)
def read_temp_humidity(port: str, slave_id: int = 1) -> tuple:
    ser = serial.Serial(port, baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1)
    try:
        # 从地址0x0000连续读2个寄存器
        request = build_read_request(slave_id, 0x0000, 2)
        ser.write(request)
        time.sleep(0.05)  # 等待从站响应
        response = ser.read(7 + 2)  # 从站地址+功能码+字节数+4数据字节+2CRC
        if len(response) == 0:
            raise ValueError('无响应')
        if response[1] & 0x80:
            raise ValueError(f'从站返回异常码: 0x{response[2]:02X}')
        regs = parse_read_response(response, 2)
        # 温度:有符号16位,单位0.1℃
        temp_raw = struct.unpack('>h', struct.pack('>H', regs[0]))[0]
        temperature = temp_raw / 10.0
        # 湿度:无符号16位,单位0.1%RH
        humidity = regs[1] / 10.0
        return temperature, humidity
    finally:
        ser.close()

if __name__ == '__main__':
    # 修改为实际串口设备名(Windows: COM3,Linux: /dev/ttyUSB0)
    temp, humi = read_temp_humidity('/dev/ttyUSB0', slave_id=1)
    print(f'温度: {temp:.1f} ℃')
    print(f'湿度: {humi:.1f} %RH')

运行与验证

  1. 将RS485适配器连接传感器,确认设备地址为1、波特率9600、数据格式8N1。
  2. 安装依赖:pip install pyserial
  3. 修改代码末尾的串口设备名为实际端口,运行:python modbus_temp_humi.py
  4. 通过条件:程序正常输出温度和湿度数值,且数值与传感器现场显示值一致(误差在传感器精度范围内)。若输出异常码或CRC错误,检查接线、从站地址和波特率设置。

常见问题

  • 无响应:检查RS485 A/B接线是否反接,确认串口设备名正确,传感器供电正常。
  • CRC校验失败:可能波特率或数据格式不匹配,或线路干扰导致数据帧损坏。
  • 温度值为负:寄存器按有符号数解析,若传感器输出补码格式的负温度,代码已通过struct.unpack('>h')正确处理。