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

任务目标

通过RS485接口,使用Modbus RTU协议读取温湿度传感器的原始寄存器值,并按照传感器数据手册将原始值解析为实际的温度(℃)和相对湿度(%RH)工程值。本案例以工业级RS485温湿度传感器(典型量程:温度-40~80℃,湿度0~100%RH)为对象,实现从串口帧收发到工程值输出的完整最小闭环。

环境与依赖

  • 硬件:USB转RS485适配器(如CH340+MAX485)、RS485温湿度传感器(从站地址1,波特率9600,8N1)
  • 软件:Python 3.8+,pyserial库(pip install pyserial)
  • 接线:适配器A/B分别接传感器A/B,共地

Modbus RTU 帧格式与寄存器映射

Modbus RTU请求帧:[从站地址][功能码][起始寄存器高][起始寄存器低][寄存器数量高][寄存器数量低][CRC低][CRC高]。传感器数据映射:温度寄存器地址0x0001(1个寄存器,有符号16位,分辨率0.1℃),湿度寄存器地址0x0002(1个寄存器,无符号16位,分辨率0.1%RH)。功能码0x03用于读保持寄存器。

关键完整代码

import serial
import struct
import time

# ---------- CRC16 (Modbus) ----------
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

# ---------- 构建读寄存器请求 ----------
def build_read_request(slave_id: int, start_reg: int, num_regs: int) -> bytes:
    pdu = struct.pack('>BHH', slave_id, 0x03, start_reg, num_regs)
    # 注意:PDU不含从站地址,CRC计算范围是从站地址+PDU
    crc = crc16_modbus(pdu)
    return pdu + struct.pack('<H', crc)

# ---------- 解析响应 ----------
def parse_read_response(resp: bytes, num_regs: int) -> list:
    if len(resp) < 5 + 2 * num_regs:
        raise ValueError(f'响应长度不足: {len(resp)} bytes')
    # 校验CRC
    recv_crc = struct.unpack('<H', resp[-2:])[0]
    calc_crc = crc16_modbus(resp[:-2])
    if recv_crc != calc_crc:
        raise ValueError(f'CRC校验失败: 接收0x{recv_crc:04X}, 计算0x{calc_crc:04X}')
    # 解析寄存器值(大端)
    regs = []
    for i in range(num_regs):
        offset = 3 + i * 2
        regs.append(struct.unpack('>H', resp[offset:offset+2])[0])
    return regs

# ---------- 主流程 ----------
SLAVE_ID = 0x01
SERIAL_PORT = 'COM3'   # Linux下如 '/dev/ttyUSB0'
BAUDRATE = 9600

ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=0.5)

try:
    # 读温度寄存器 (0x0001, 1个寄存器)
    temp_req = build_read_request(SLAVE_ID, 0x0001, 1)
    ser.write(temp_req)
    time.sleep(0.05)
    temp_resp = ser.read(7)  # 1从站+1功能+1字节数+2数据+2CRC
    temp_raw = parse_read_response(temp_resp, 1)[0]
    # 温度:有符号16位,分辨率0.1℃
    if temp_raw >= 0x8000:
        temp_raw -= 0x10000
    temperature = temp_raw * 0.1

    # 读湿度寄存器 (0x0002, 1个寄存器)
    humi_req = build_read_request(SLAVE_ID, 0x0002, 1)
    ser.write(humi_req)
    time.sleep(0.05)
    humi_resp = ser.read(7)
    humi_raw = parse_read_response(humi_resp, 1)[0]
    humidity = humi_raw * 0.1

    print(f'温度: {temperature:.1f} ℃')
    print(f'湿度: {humidity:.1f} %RH')

finally:
    ser.close()

运行与验证

  1. 确认传感器RS485接线正确,从站地址、波特率与代码一致(默认1,9600)。
  2. 将SERIAL_PORT改为实际串口号(Windows设备管理器查看,Linux用ls /dev/ttyUSB*)。
  3. 运行脚本:python modbus_temp_humi.py。

通过判定标准:程序无异常退出,打印的温度值在室温合理范围(如15~35℃),湿度值在30%~70%RH范围(室内环境)。若打印CRC校验失败,检查波特率、从站地址和接线;若超时无响应,检查RS485的A/B是否接反、传感器是否上电。

常见问题

  • 超时无响应:RS485是半双工,确认适配器自动收发切换正常;部分USB转485适配器需外部供电。
  • CRC错误:确认传感器数据手册中寄存器地址和数量与代码一致;部分传感器地址从0开始,需按手册调整。
  • 温度负值解析:本代码已处理有符号16位转换,若传感器使用偏移量表示负温,需按手册改用对应公式。