3、通信协议入门:Modbus RTU 协议详解、数据帧结构、CRC校验原理、实战读取传感器数据

好,咱们进入通信协议这一章。说实话,很多做物联网的朋友,一开始都被各种协议搞晕了。MQTT、HTTP、CoAP、Modbus……到底选哪个?

我个人习惯是:工业现场,先看Modbus。为什么?因为它太普及了。你随便拆开一个PLC、温控器、变频器,十有八九都支持Modbus。今天咱们就把它吃透。

3.1 Modbus RTU 是什么?

Modbus 是1979年由Modicon公司发明的。说白了,它就是一个“主从通信”的规矩。一个主机(比如你的网关)问,多个从机(比如传感器)答。

Modbus 有三种变体:

  • Modbus RTU:二进制传输,效率高,工业最常用。
  • Modbus ASCII:用文本传输,慢但可读性好。
  • Modbus TCP:跑在以太网上,适合远程。

咱们今天重点讲 Modbus RTU。它走的是串口(RS-232/RS-485),一根双绞线就能拉几百米,抗干扰还强。

核心要点: Modbus RTU 是工业物联网的“普通话”。你只要学会它,市面上80%的传感器都能搞定。

3.2 数据帧结构:主机到底在问什么?

Modbus RTU 的数据帧,结构非常固定。我刚开始学的时候,觉得它像一张“电报单”。每一帧包含四个部分:

字段 长度 说明
地址码 1字节 从机地址,范围1-247。0是广播地址。
功能码 1字节 告诉从机要做什么。比如03是读寄存器。
数据区 N字节 具体参数,比如起始地址、读取数量。
CRC校验 2字节 检查数据有没有传错。

举个例子。你想读取地址为1的传感器,它的温度值存在寄存器地址0x0000里。那么主机发出的请求帧就是:

01 03 00 00 00 01 84 0A

拆开看:

  • 01:从机地址
  • 03:功能码,读保持寄存器
  • 00 00:起始寄存器地址(高字节在前)
  • 00 01:读取1个寄存器
  • 84 0A:CRC校验码

嗯,这里要注意:Modbus RTU 用的是大端模式,高字节在前。我见过不少新手在这里栽跟头,把地址写反了,传感器死活没反应。

3.3 CRC校验原理:为什么必须算这个?

你想想看,工业现场环境多恶劣。电机一启动,电磁干扰就来了。串口线上传的数据,可能一个比特就翻车了。CRC就是用来揪出这种错误的。

CRC的全称是循环冗余校验。Modbus RTU 用的是 CRC-16,多项式是 0x8005。计算过程不复杂,但手算很麻烦。我一般直接用查表法。

我的经验: 别自己手写CRC算法,容易写错。直接用现成的查表代码,稳定可靠。我曾经在一个项目里手撸CRC,结果漏了一个移位,调试了整整两天……

下面是一个标准的CRC-16计算函数,C语言版:

unsigned int crc16_modbus(unsigned char *buf, int len) {
    unsigned int crc = 0xFFFF;
    for (int pos = 0; pos < len; pos++) {
        crc ^= (unsigned int)buf[pos];
        for (int i = 8; i != 0; i--) {
            if ((crc & 0x0001) != 0) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

注意:CRC校验码在帧里是低字节在前。比如计算结果是 0x0A84,发送时要先发 84,再发 0A。这一点和寄存器地址正好相反,别搞混了。

3.4 实战:读取传感器数据

光说不练假把式。咱们拿一个实际场景来走一遍。假设你有一个温湿度传感器,从机地址是 0x02,温度存在寄存器 0x0001,湿度存在 0x0002

第一步:构造请求帧

你想同时读取温度和湿度,也就是连续读2个寄存器。请求帧如下:

02 03 00 01 00 02 95 CB
  • 02:从机地址
  • 03:读保持寄存器
  • 00 01:从寄存器1开始
  • 00 02:读2个寄存器
  • 95 CB:CRC校验

第二步:解析响应帧

传感器正常回复的帧应该是:

02 03 04 01 2C 00 64 7A 1B

拆解一下:

  • 02:从机地址(回声)
  • 03:功能码(回声)
  • 04:数据字节数(2个寄存器 × 2字节 = 4字节)
  • 01 2C:温度值,十六进制,转十进制是300。如果精度是0.1℃,那温度就是30.0℃。
  • 00 64:湿度值,十进制100,即10.0%RH。
  • 7A 1B:CRC校验

避坑指南: 我曾经遇到一个传感器,它返回的温度值是补码形式。如果是负数,最高位是1。比如 FF 9C 代表 -100,也就是 -10.0℃。所以拿到数据后,一定要先看传感器手册,确认数据格式。

第三步:用Python验证

在实际项目中,我习惯先用Python快速验证通信是否正常。下面是一个简单的脚本:

import serial
import struct

# 打开串口
ser = serial.Serial('COM3', 9600, timeout=1)

# 构造请求帧:地址02,功能码03,起始地址0001,读取2个寄存器
request = bytes([0x02, 0x03, 0x00, 0x01, 0x00, 0x02])
# 计算CRC
crc = 0xFFFF
for b in request:
    crc ^= b
    for _ in range(8):
        if crc & 0x0001:
            crc = (crc >> 1) ^ 0xA001
        else:
            crc >>= 1
# CRC低字节在前
request += bytes([crc & 0xFF, (crc >> 8) & 0xFF])

# 发送
ser.write(request)
# 读取响应(至少等待8个字节)
response = ser.read(8)

if len(response) == 8:
    # 解析温度(第3-4字节)
    temp_raw = (response[3] << 8) | response[4]
    temp = temp_raw / 10.0
    # 解析湿度(第5-6字节)
    hum_raw = (response[5] << 8) | response[6]
    hum = hum_raw / 10.0
    print(f"温度: {temp}℃, 湿度: {hum}%")
else:
    print("响应异常,检查接线或地址")

ser.close()

这个脚本我用了很多年,基本没出过问题。你只要把 COM3 换成你的实际端口,把地址和寄存器号改一下,就能跑起来。

3.5 常见问题与调试技巧

最后,分享几个我在现场踩过的坑:

  • 没反应? 先检查接线。RS-485的A/B线别接反了。我见过有人把A接B,B接A,折腾半天。
  • 数据全是0xFF? 多半是波特率或校验位不匹配。Modbus RTU默认是9600,8,1,N(无校验)。
  • CRC校验失败? 用串口助手抓一下原始数据,看看是不是有多余的字节。有些传感器会在帧头加一个0x00,需要过滤掉。

总结一下: Modbus RTU 不难,核心就是“地址+功能码+数据+CRC”。你只要掌握了帧结构,再配合一个串口调试工具,任何传感器都能拿下。下一章咱们会讲怎么把Modbus数据通过MQTT传到云平台,敬请期待。