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()
运行与验证
- 确认传感器RS485接线正确,从站地址、波特率与代码一致(默认1,9600)。
- 将
SERIAL_PORT改为实际串口号(Windows设备管理器查看,Linux用ls /dev/ttyUSB*)。 - 运行脚本:
python modbus_temp_humi.py。
通过判定标准:程序无异常退出,打印的温度值在室温合理范围(如15~35℃),湿度值在30%~70%RH范围(室内环境)。若打印CRC校验失败,检查波特率、从站地址和接线;若超时无响应,检查RS485的A/B是否接反、传感器是否上电。
常见问题
- 超时无响应:RS485是半双工,确认适配器自动收发切换正常;部分USB转485适配器需外部供电。
- CRC错误:确认传感器数据手册中寄存器地址和数量与代码一致;部分传感器地址从0开始,需按手册调整。
- 温度负值解析:本代码已处理有符号16位转换,若传感器使用偏移量表示负温,需按手册改用对应公式。