3、传感器数据协议解析:Modbus RTU协议详解、CRC16校验算法实现、温湿度传感器(SHT30)数据读取
好,咱们进入第三章。这一章是硬骨头,也是整个系统的核心——你得让传感器开口说话。
我在做第一个农业项目时,最头疼的就是传感器数据读出来全是乱码。后来才发现,不是传感器坏了,是协议没搞对。说白了,传感器和单片机之间,得有一套双方都认可的「暗号」。这套暗号,在工业界就叫 Modbus RTU。
3.1 Modbus RTU 协议,到底是个啥?
Modbus 协议诞生于 1979 年,比我都大。但它至今还在用,为什么?因为它简单、可靠。
Modbus RTU 是 Modbus 的一种串行通信版本。它用两根线(A、B)传输数据,数据格式是二进制,效率很高。
一个完整的 Modbus RTU 数据帧长这样:
| 地址码 (1字节) | 功能码 (1字节) | 数据区 (N字节) | CRC16校验 (2字节) |
举个例子,你要读取地址为 0x01 的温湿度传感器:
- 地址码:0x01 —— 告诉总线上的设备,我在叫谁
- 功能码:0x03 —— 读取保持寄存器
- 数据区:起始地址 + 读取数量
- CRC16:校验数据是否传错
嗯,这里要注意:地址码不能重复。我在一个项目里,两个传感器都设成了 0x01,结果数据全混了。排查了整整一天。
核心要点:Modbus RTU 是主从协议。主机(你的单片机)发起请求,从机(传感器)响应。从机不会主动说话。
3.2 CRC16 校验算法,手写一个
CRC16 是 Modbus RTU 的「防伪标签」。数据在传输过程中,可能会受干扰,CRC 就是用来检查数据有没有被篡改的。
你想想看,如果传感器传回的温度是 25.3℃,但因为线路干扰变成了 85.6℃,你直接拿去用,大棚里的作物可就遭殃了。
CRC16 的计算原理不复杂,但手写起来有点绕。我直接给你一个 Python 实现,这是我项目里一直在用的:
def crc16_modbus(data: bytes) -> int:
"""
计算 Modbus RTU 的 CRC16 校验值
:param data: 待校验的字节数据
:return: 16位 CRC 值
"""
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
# 注意:Modbus 的 CRC 是低字节在前,高字节在后
return crc
这段代码里,0xA001 是多项式。你不需要背,直接复制用就行。
个人经验:我曾经在 CRC 的字节序上栽过跟头。Modbus 要求 CRC 低字节在前,高字节在后。如果你发现传感器不响应,先检查 CRC 的字节顺序对不对。
3.3 SHT30 温湿度传感器,怎么读?
SHT30 是 Sensirion 公司的产品,精度高、功耗低,农业气象站里很常见。它支持 I2C 和 Modbus 两种接口。咱们这里讲 Modbus 版本。
SHT30 的 Modbus 寄存器地址如下:
| 寄存器地址 | 内容 | 数据类型 |
|---|---|---|
| 0x0000 | 温度值 (原始) | 16位无符号整数 |
| 0x0001 | 湿度值 (原始) | 16位无符号整数 |
读取 SHT30 的完整流程:
- 主机发送请求帧:
01 03 00 00 00 02 C4 0B - 从机响应:
01 03 04 [温度高字节] [温度低字节] [湿度高字节] [湿度低字节] [CRC低] [CRC高] - 解析原始值,转换成实际温湿度
转换公式:
温度(℃) = -45 + 175 * (原始温度值 / 65535)
湿度(%RH) = 100 * (原始湿度值 / 65535)
举个例子,如果原始温度值是 0x1C4A(即 7242),那么:
温度 = -45 + 175 * (7242 / 65535) ≈ -45 + 19.34 ≈ -25.66℃
嗯,这个温度值明显不对。为什么会这样?
我遇到过这种情况。排查后发现,是传感器暴露在户外,冬天确实零下。但如果你在室内测试,温度应该在 20-30℃ 之间。如果读出来是负值,先检查传感器是不是真的在低温环境,还是接线有问题。
避坑指南:我曾经在读取 SHT30 时,发现湿度值一直显示 99.9%。后来发现是传感器被水汽包围了——因为我把传感器放在了加湿器旁边。所以,传感器安装位置很重要,别放在风口或水源附近。
3.4 完整代码示例:读取 SHT30 并解析
下面是一个完整的 Python 示例,通过串口读取 SHT30 的数据:
import serial
import struct
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 read_sht30(port: str, slave_addr: int = 0x01):
"""
读取 SHT30 温湿度传感器
:param port: 串口号,如 'COM3' 或 '/dev/ttyUSB0'
:param slave_addr: 从机地址,默认 0x01
"""
# 构建请求帧
request = bytes([slave_addr, 0x03, 0x00, 0x00, 0x00, 0x02])
crc = crc16_modbus(request)
request += bytes([crc & 0xFF, (crc >> 8) & 0xFF])
# 发送请求
ser = serial.Serial(port, 9600, timeout=1)
ser.write(request)
# 读取响应(7个字节)
response = ser.read(7)
ser.close()
if len(response) != 7:
print("响应长度错误,可能通信失败")
return None, None
# 解析数据
temp_raw = (response[3] << 8) | response[4]
hum_raw = (response[5] << 8) | response[6]
# 转换
temperature = -45.0 + 175.0 * temp_raw / 65535.0
humidity = 100.0 * hum_raw / 65535.0
return temperature, humidity
# 使用示例
temp, hum = read_sht30('COM3')
print(f"温度: {temp:.2f}℃, 湿度: {hum:.2f}%RH")
这段代码我用了很多次,基本是「拿来就用」。你只需要改一下串口号就行。
小技巧:如果读出来的数据偶尔跳变,可以在代码里加一个「多次读取取平均」的逻辑。比如连续读 5 次,去掉最大值和最小值,再取平均。这样能有效滤除干扰。
3.5 本章小结
这一章我们干了三件事:
- 搞懂了 Modbus RTU 的数据帧结构
- 手写了 CRC16 校验算法
- 用 Python 读取了 SHT30 温湿度传感器
说白了,传感器通信就是「发请求、收响应、做校验、算数值」这四步。你把这个流程走通了,换任何 Modbus 传感器都一样。
下一章,咱们要把这些数据存到数据库里,并搭建一个 Web 界面,让你在手机上就能看到大棚里的温湿度。敬请期待。