1. 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()
运行与验证
- 将RS485适配器A/B线分别连接传感器的A/B端子,确认传感器供电正常。
- 在设备管理器中确认串口号(Windows)或使用
ls /dev/ttyUSB*(Linux),修改代码中SERIAL_PORT。 - 确认传感器从站地址为1(若不同,修改
SLAVE_ID),波特率9600、数据位8、无校验、停止位1(代码默认)。 - 运行:
python modbus_temp_hum.py
通过条件:程序输出温度与湿度数值,且数值与传感器现场环境一致(如室温约25℃)。若输出“无响应”,依次检查接线极性、从站地址、波特率;若输出“CRC校验失败”,检查线路干扰或适配器是否正常。
关键说明
本案例使用功能码0x03读取保持寄存器,这是多数温湿度传感器的标准方式。若传感器使用输入寄存器(功能码0x04),只需将请求帧中功能码改为0x04,并同步修改parse_read_response中的功能码校验值。CRC校验采用Modbus标准多项式0xA001,低字节在前发送。代码中ser.read(9)按固定响应长度读取,适用于本案例的2寄存器场景;若需读取更多寄存器,应改为循环读取直至收满3 + 2*N + 2字节。