485和Modbus-RTU

单片机 MCU(Microcontroller Unit)输出 TTL 板子内部通信,不同超过1m,0~3.3V

RS485: 差分串口,上层常用 Modbus-RTU、厂家私有协议,信号线:A(DA)、B(DB),额外供电 VCC/GND ;

RS485:只是物理层的电气标准(怎么传电平信号)。

CAN/CANFD: 向下兼容 CAN2.0;单帧最大 64 字节,带宽提升 5~8 倍,最高 12Mbps;

485 + Modbus-RTU

URAT 串口 全双工 同时发送和接收

485 差分信号 同一时刻只能一个方向的传输 半双工

Modbus主控信息

Modbus是一主多从的协议,主控发出的数据里,必定含有如下信息:

  • 设备地址:你要访问从设备1,还是访问从设备2
  • 访问哪类寄存器,是读还是写,只访问1个寄存器,还是多个寄存器:这被称为功能码
  • 起始寄存器地址、寄存器数量:这在数据里定义
  • 为了保证数据传输的可靠,还附带有CRC检验码

Modbus RTU 数据包格式

以Modbus RTU协议为例,主控发出的数据包格式如下:

起始间隙 设备地址 功能代码 数据 CRC校验 停止间隙
≥3.5个字符 8位 8位 Nx8位 16位 ≥3.5个字符

功能代码有哪些?常用的功能码如下:

  • 读线圈状态(01)
  • 读离散输入状态(02)
  • 写单个线圈(05)、写多个线圈(15)
  • 读保持寄存器(03)
  • 读输入寄存器(04)
  • 写单个保存寄存器(06)、写多个保存寄存器(16)

功能码含义拆解

  • Read/Write
  • 哪一类寄存器
  • 单个或多个

Modbus数据包示例

数据的格式,由功能代码确定。

以"读线圈状态"为例,主控发出的请求格式如下:

字段 例(Hex) ASCII模式字符型 RTU模式8位(Hex)
帧头 ":"
从设备地址 0x03 "0", "3" 0x03
功能码 0x01 "0", "1" 0x01
起始地址(高位) 0x00 "0", "0" 0x00
起始地址(低位) 0x13 "1", "3" 0x13
寄存器数(高位) 0x00 "0", "0" 0x00
寄存器数(低位) 0x25 "2", "5" 0x25
差错校验 LRC(2字符) CRC(2字节)
帧尾 CR/LF
合计字节数 17 8

设备发出的响应报文格式如下:

字段 例(Hex) ASCII模式字符型 RTU模式8位(Hex)
帧头 ":"
从设备地址 0x03 "0", "3" 0x03
功能码 0x01 "0", "1" 0x01
数据域字节数 0x05 "0", "5" 0x05
数据1 0x53 "5", "3" 0x53
数据2 0x6B "6", "B" 0x6B
数据3 0x01 "0", "1" 0x01
数据4 0xF4 "F", "4" 0xF4
数据5 0x1B "1", "B" 0x1B
差错校验 LRC(2字符) CRC(2字节)
帧尾 CR/LF
合计字节数 21 10

数据长度换算示例

0X25 ⇒ 32+5=37

37bit

37bit/8=4.625byte

返回 传输 5byte

位地址示例

bit0 ⇒ 0x13

bit1 ⇒ 0x14

传感器点表(寄存器说明):

设备地址 寄存器地址 寄存器类别 用途 描述
03H 0000H DO 控制蜂鸣器1 1-响
0001H DO 控制蜂鸣器2 1-响
0002H DO 控制LED1 1-亮
0003H DO 控制LED2 1-亮
0004H DO 控制LED3 1-亮
0000H AI 读取温度 单位0.1摄氏度 16位有符号整数
0001H AI 读取湿度 单位0.1%RH 16位有符合整数

应用层调用代码

c 复制代码
do_write_bit(uartbus, 0x03, 0x0002, 1);
HAL_Delay(300);
do_write_bit(uartbus, 0x03, 0x0002, 0);
HAL_Delay(300);

if(ai_read_bit(uartbus, 0x03, 0x0000, &rx_val) == 0)
{
    Read_Temp(&rx_val, &temp);
    sprintf(buf, "temp : %.1f C ", temp);
    Draw_String(0, 0, buf, 0x0000ff00, 0);
}
HAL_Delay(300);

底层驱动实现代码

c 复制代码
/* 写单个DO */
int do_write_bit(struct Modbus *bus, int dev_addr, int reg_addr, int val)
{
    /* 构造包 */
    uint8_t tx_buf[8] = {0};
    uint8_t rx_buf[8] = {0};
    uint16_t crc_val = 0;

    tx_buf[0] = dev_addr;
    tx_buf[1] = 0x05;
    tx_buf[2] = reg_addr >> 8;
    tx_buf[3] = reg_addr;
    tx_buf[4] = val ? 0xFF : 0x00;
    tx_buf[5] = 0x00;

    /* 构造发送CRC */
    crc_val = CRC16(tx_buf, 6);
    tx_buf[6] = crc_val & 0xFF;
    tx_buf[7] = crc_val >> 8;

    /* 发送数据包 */
    bus->Send(bus, tx_buf, 8, 100);

    /* 接收数据包 */
    bus->Recv(bus, rx_buf, 8, 100);

    /* CRC校验 */
    crc_val = CRC16(tx_buf, 6);
    if(rx_buf[6] == (crc_val & 0xFF) && rx_buf[7] == (crc_val >> 8))
    {
        return 0;
    }
    return -1;
}
c 复制代码
/* 读单个Input Reg */
int ai_read_bit(struct Modbus *bus, int dev_addr, int reg_addr, int *val)
{
    /* 构造包 */
    uint8_t tx_buf[8] = {0};
    uint8_t rx_buf[7] = {0};
    uint16_t crc_val = 0;

    tx_buf[0] = dev_addr;
    tx_buf[1] = 0x04;
    tx_buf[2] = reg_addr >> 8;
    tx_buf[3] = reg_addr;
    tx_buf[4] = 0x00;
    tx_buf[5] = 0x01;

    /* 构造发送CRC */
    crc_val = CRC16(tx_buf, 6);
    tx_buf[6] = crc_val & 0xFF;
    tx_buf[7] = crc_val >> 8;

    /* 发送数据包 */
    bus->Send(bus, tx_buf, 8, 100);

    /* 接收数据包 */
    bus->Recv(bus, rx_buf, 7, 100);

    /* CRC校验 */
    crc_val = CRC16(rx_buf, 5);
    if(rx_buf[5] == (crc_val & 0xFF) && rx_buf[6] == (crc_val >> 8))
    {
        /* 提取数据 */
        *val = (rx_buf[3] << 8) | rx_buf[4];
        return 0;
    }
    return -1;
}

一、 RS485 总线参数:

总线接口:RS485

波特率(常规模式,单电机命令):

9600bps

19200bps

38400bps

57600bps

115200bps(默认)

230400bps

460800bps

1Mbps

2Mbps

4Mbps

波特率(广播模式,多电机命令):

1Mbps

2Mbps

4Mbps

数据位:8

奇偶校验:无

停止位:1

二、 单电机命令:

同一总线上共可以挂载多达 32 个(视总线负载情况而定)驱动,为了防止总线冲突,每个驱动需要设置不同的 ID,ID 编号为 1~32。

主控向总线发送单电机命令帧,对应 ID 的电机在收到命令后执行,并在一段时间后(0.25ms 内)向主控发送相同 ID 的回复帧。命令帧报文和回复帧报文格式如下:帧命令 + 帧数据(可选),具体描述如下表所示

数据描述 数据长度(byte) 说明
帧命令 帧头 1 帧头识别,0x3E
命令 1 CMD
ID 1 1~32,对应电机的 ID
数据长度 1 描述帧命令附带的数据长度,视不同命令而定
帧命令校验字节 1 CMD_SUM,帧命令所有字节校验和,保留低 8 位,高位抛弃
帧数据 数据 0~100 帧命令附带的数据
帧数据校验字节 0 或 1 DATA_SUM,帧数据所有字节校验和,保留低 8 位,高位抛弃

串口 TTL差分信号

RS232 全双工

485

● RS485电平:

  • 2线制(A、B),差分模式,A-B > 200mV时,总线状态表示逻辑"1";当A-B < -200mV时,总线状态表示逻辑"0";抗共模干扰能力强;
  • 最大通信距离1200m;
  • 半双工,一主多从通信。

自收发

ARM单片机串口硬件结构

  • 起始位:位于数据帧开头,占1位,始终为低电平,用于向接收设备表示发送端开始发送1帧数据;
  • 数据位:要传输的数据信息,一般为7/8位,可配置,通常由低位到高位依次传送;
  • 校验位:位于数据位之后,占1位,可配置,用于发送数据的校验,或传送多机串行通信的联络信息;
  • 停止位 :位于数据位末尾,占0.5~2位,可配置,始终为高电平,用于向接收端表示1帧数据已发送完毕。

空闲是高电平,所以加上下拉电阻。高阻。

120欧姆 终端电阻 多个设备

Modbus-RTU协议

Modbus协议介绍

  • Modbus是一种软件协议,支持RS232、RS485、TCP/IP等多种电气接口,
  • Modbus协议标准开放、公开发表且无版权要求;
  • Modbus协议消息帧格式简单、通俗易懂,便于厂商开发和集成。

Modbus报文

  • Modbus可以在各种介质上传输,它的传输报文类型支持三种,包括ASCII、RTU、TCP三种;使用串口传输时可以选择RTU或ASCII模式,当然常用的就是RTU模式。

Modbus-TCP 以太网

Modbus-RTU RS485

Modbus-RTU协议

  • Modbus报文是指主机发送给从机的一帧数据,其中包含着从机的地址,主机想执行的操作,校验码等内容,最大为256字节:
从机地址 功能码 数据 CRC
1字节 1字节 0到252字节 2字节CRC低CRC高
  • 从机地址: 每个从机都有唯一地址(机没有地址),占用一个字节,范围0-255,其中从机有效地址范围是1-247;
  • 功能码: 占用一个字节,功能码的意义是告诉从机这帧数据是干啥的,可以查询从机的数据,也可以修改从机的数据;
  • 数据: 根据功能码不同,对应不同内容,比如功能码是查询从机的数据,这里就包括要查询哪些数据和查询字节数等;
  • 校验: 在传输过程中数据坑会发生错误,CRC检验可以检测接收的数据是否正确。

Modbus功能码表

描述 功能码 (码) 功能码 (子码) 功能码 (十六进制)
比特访问 物理离散量输入 - 读输入离散量 02 02
内部比特或物理线圈 - 读线圈 01 01
内部比特或物理线圈 - 写单个线圈 05 05
内部比特或物理线圈 - 写多个线圈 15 0F
16比特访问 输入存储器 - 读输入寄存器 04 04
内部存储器或物理输出存储器 - 读多个寄存器 03 03
内部存储器或物理输出存储器 - 写单个寄存器 06 06
内部存储器或物理输出存储器 - 写多个寄存器 16 10
内部存储器或物理输出存储器 - 读/写多个寄存器 23 17
内部存储器或物理输出存储器 - 屏蔽写寄存器 22 16
文件记录访问 读文件记录 20 6 14
写文件记录 21 6 15
封装接口 读设备识别码 43 14 2B
  • 寄存器,可以理解为设备上的某一个控制项,比如传感器或者继电器,并不是单片机片上外设的硬件寄存器。

Modbus寄存器地址示例表

  • 寄存器,可以理解为设备上的某一个控制项,比如传感器或者继电器,对应的需要在软件中为每一个分配码值,在Mdobus中叫做寄存器地址,比如:
寄存器地址 字长 含义 初始值 单位 读写方式 备注
0x0000 1word PM2.5 0 ug/m³ R 0-999ug/m³
0x0001 1word 温度 0 C R 0-99℃(读取值是显示值的10倍)
0x0002 1word 湿度 0 %RH R 20-95%RH(读取值是显示值的10倍)
0x0003 1word CO₂ 0 ppm R 0~999900m
0x0004 1word TVOC 0 mg/m³ R 0-9.99mg/m(读取值是显示值的100倍)
0x0005 1word PM10 0 ug/m³ R 0-999ug/m³
0x0006 1word 开关 0 RW 0关机,1开机
0x0007 1word 模式 0 RW 0手动,1自动
0x0008 1word 风速 0 RW 1低速,2中速,3高速
0x0009 1word 风阀 0 RW 1开阀外循环新风0关阀内循环净化

Modbus通信举例1:主机读取从机环境参数

  • 通信举例,主机读取从机环境参数(以PM2.5 值75、温度24、湿度值38、CO₂值900、TVOC 值1.25、PM10 值100为例):
0 1 2 3 4 5 6 7
设备地址 功能码 寄存器地址高字节 寄存器地址低字节 寄存器个数高字节 寄存器个数低字节 CRC低字节 CRC高字节
0x01 0x03 0x00 0x00 0x00 0x06 0xC5 0xCB

从机返回数据:

0 1 2 3 4 5 6 7 8
设备地址 功能码 应答字节数 数据内容1高字节 数据内容1低字节 数据内容2高字节 数据内容2低字节 数据内容3高字节 数据内容3低字节
0x01 0x03 0x0C 0x00 0x4B 0x00 0xF0 0x01 0x7C
9 10 11 12 13 14 15 16
数据内容4高字节 数据内容4低字节 数据内容5高字节 数据内容5低字节 数据内容6高字节 数据内容6低字节 CRC低字节 CRC高字节
0x03 0x84 0x00 0x7D 0x00 0x64 0xFA 0x44

Modbus通信举例2:控制从机开机

  • 通信举例,控制从机开机:
0 1 2 3 4 5 6 7
设备地址 功能码 寄存器地址高字节 寄存器地址低字节 数据内容高字节 数据内容低字节 CRC低字节 CRC高字节
0x01 0x06 0x00 0x06 0x00 0x01 0xA8 0x0B

从机返回数据:

0 1 2 3 4 5 6 7
设备地址 功能码 寄存器地址高字节 寄存器地址低字节 数据内容高字节 数据内容低字节 CRC低字节 CRC高字节
0x01 0x06 0x00 0x06 0x00 0x01 0xA8 0x0B

Modbus帧间隔 (3.5T)

  • 为了使软件更容易判断一帧数据包是否结束,Modbus规定两帧数据包之间至少有3.5 个字符时间的空闲间隔,叫做3.5T:
空闲 从机地址 功能码 数据 CRC 空闲
3.5字符 1字节 1字节 0到252字节 2字节CRC低CRC高 3.5字符
  • 当串口波特率大于19200时,那么3.5T固定为1750us;
  • 当串口波特率小于等于19200时,假如串口设置为:起始位1bit + 数据位8bit + 停止位1bit,
  • 1个字符传送所需时间,charTime=(10*1000/baudRate)ms;
  • 假设波特率为9600,charTime=11*1000/9600=1.15ms;
  • 3.5个字符需要的时间,3.5*charTime=4ms。

MODBUS异常码

  • MODBUS异常码:
代码 名称 含义
01 非法功能 从机不支持此功能码
02 非法地址 指定的地址在从机中不存在
03 非法数据值 指定的数据超过范围或者不允许使用
04 从机故障 从机处理响应的过程中,出现未知错误等

Modbus异常响应举例

  • 通信举例,控制从机开机,如果寄存器地址不存在:
0 1 2 3 4 5 6 7
设备地址 功能码 寄存器地址高字节 寄存器地址低字节 数据内容高字节 数据内容低字节 CRC低字节 CRC高字节
0x01 0x06 0x00 0xFF 0x00 0x01 0xA8 0x0B

在异常响应中,从机返回的功能码最高位置1,异常码0x02,表示非法地址:

0 1 2 3 4
设备地址 功能码 异常码 CRC低字节 CRC高字节
0x01 0x86 0x02 0xC3 0xA1

提问

一、 基础概念篇

Q1:请用一句话说明 RS485 和 Modbus-RTU 的关系?

  • 核心考点:分层思想。
  • 参考答案 :RS485 是物理层硬件标准 (定义电气信号、连线方式),而 Modbus-RTU 是应用层软件协议(定义数据格式、通信规则)。Modbus-RTU 可以运行在 RS485 或 RS232 上;RS485 总线上也可以跑其他私有协议。

Q2:RS485 相比 RS232 有什么优势?为什么工业现场更常用 RS485?

  • 核心考点:差分信号、传输距离、组网能力。
  • 参考答案 :
    1. 抗干扰强:RS485 采用差分信号(A、B线),共模干扰能相互抵消。
    2. 距离远:RS232 理论距离仅 15 米,RS485 可达 1200 米。
    3. 支持多机:RS232 只能点对点,RS485 支持"一主多从"(通常最多挂载 32 个节点,加中继器可扩展)。

Q3:RS485 是半双工还是全双工?如何实现双向通信?

  • 参考答案 :RS485 通常是半双工(2线制),不能同时收发。需要主从轮询机制。在硬件上,MCU 需要一个 GPIO 控制收发器的 DE/RE 引脚,平时处于接收态,发送时拉高使能发送,发送完毕立刻切回接收。

二、 硬件与电气篇

Q4:为什么 RS485 总线两端需要接 120Ω 的终端电阻?

  • 核心考点:信号反射。
  • 参考答案 :为了进行阻抗匹配,吸收总线上的信号反射。如果不接,长距离或高波特率下会出现信号震荡、过冲,导致通信误码或失败。通常只需要在总线的最远两端各接一个 120Ω 电阻。

Q5:RS485 的 A 线和 B 线接反了会发生什么?

  • 参考答案:由于差分信号 A-B > 200mV 表示逻辑"1",A-B < -200mV 表示逻辑"0"。接反后,逻辑电平会反转,导致收发数据全错。如果用的是硬件串口,通常会表现为一直进入错误中断(如帧错误),无法正常接收数据。

Q6:RS485 硬件设计要注意什么?(防雷、防浪涌、共地)

  • 参考答案 :
    • 总线要使用双绞线(屏蔽层单点接地,防地环流)。
    • 工业现场必须加 TVS 管、防雷管、自恢复保险丝进行浪涌和静电保护。
    • 共地问题:如果两端设备不共地,可能烧毁收发器,建议使用隔离型 RS485 收发器(如带光耦或磁耦隔离的 ADM2483)。

三、 Modbus-RTU 协议细节篇

Q7:Modbus-RTU 的一帧数据格式是什么?

  • 参考答案 :从机地址(1Byte) + 功能码(1Byte) + 数据(N Bytes, 0~252) + CRC校验(2Bytes, 低字节在前)。最大总长度 256 字节。

Q8:Modbus 的 3.5T 间隔是什么?波特率大于 19200 时怎么算?

  • 参考答案 :为了区分两帧数据,Modbus-RTU 规定帧与帧之间至少有 3.5 个字符时间的空闲间隔。
    • 波特率 > 19200bps 时,3.5T 固定为 1750 微秒。
    • 波特率 ≤ 19200bps 时,按公式计算(如 9600bps,1个字符 11bit,3.5 * 11 * 1000 / 9600 ≈ 4ms)。

Q9:常用的 Modbus 功能码有哪些?(举例说明)

  • 参考答案 :
    • 0x03:读保持寄存器(最常用,读传感器数据、电机参数)。
    • 0x06:写单个保持寄存器(最常用,控制设备开关、设置参数)。
    • 0x01/0x05:读/写线圈(控制继电器、开关量)。
    • 0x10:写多个保持寄存器。

Q10:Modbus 通信如果出错,从机是怎么返回异常码的?

  • 参考答案 :从机返回的功能码最高位(MSB)置 1 (即原功能码 + 0x80),后跟一个字节的异常码。
    • 例如:主机发 0x03,从机返回 0x83 + 异常码。
    • 异常码 0x01:非法功能;0x02:非法地址(最常见);0x03:非法数据值。

Q11:Modbus 的 CRC 校验有什么特点?

  • 参考答案 :CRC-16 (多项式 0xA001),校验范围是从机地址到数据末尾。在报文中,CRC 低字节在前,高字节在后。

四、 测试与排障实战篇

Q12:如果一台设备 RS485 通信不上(超时),你的排查思路是什么?

  • 参考答案(分步) :
    1. 物理层:检查 A/B 线是否接反;是否共地;终端电阻是否匹配;供电是否正常。用示波器看 A/B 线是否有差分波形,波形是否畸变。
    2. 参数配置:主从设备的波特率(如 9600 或 115200)、数据位(8)、停止位(1)、校验位(无)是否完全一致;从机地址(ID)是否匹配。
    3. 软件层面:3.5T 间隔是否符合要求;如果是轮询,是否有总线冲突(多个从机同时回复);是否开启了大端/小端模式转换。
    4. 工具辅助:用 USB 转 485 配合 XCOM 或 Modbus Poll 抓包监听,看主机发了什么,从机回了什么。

Q13:如何对 Modbus-RTU 进行自动化测试或压力测试?

  • 参考答案 :
    • 工具 :用 Python 编写脚本(结合 pymodbus 库或直接 pyserial 发原始 Hex 帧)。
    • 测试内容 :
      • 功能测试:遍历所有功能码,测试读/写是否正常。
      • 边界测试:发送非法地址、非法数据值、错误 CRC,验证从机是否按标准返回异常码(0x83 + 0x02 等)。
      • 异常注入:拔掉通信线、注入干扰、模拟帧间隔不足(比如连续快速发两帧,中间不加 3.5T,测试从机容错处理)。
      • 压力测试:以最高支持波特率(如 115200)和最大挂载节点数(32个)循环读写,跑 24 小时甚至几天,统计丢包率、错误帧数。

Q14:RS485 和 CAN 总线有什么区别?在机器人关节模组中怎么选?

  • 核心考点:架构与可靠性。
  • 参考答案 :
    • 架构 :RS485 是一主多从 (主站轮询,从机被动应答);CAN 是多主架构(基于优先级的 CSMA/CR 仲裁,节点可主动发送)。
    • 协议层:RS485 只有物理层,靠上层软件(如 Modbus)处理冲突和重发;CAN 控制器硬件自带数据链路层(CRC校验、错误重发、报文过滤)。
    • 机器人场景 :如果是简单的传感器数据采集,RS485+Modbus 成本低;如果是多个关节电机的高实时性协同控制(如人形机器人),CANopen 或 CAN FD 更具优势,因为 CAN 的硬件级仲裁和错误处理保证了极高的实时性和可靠性。

一、 核心概念:Modbus-RTU 到底是什么?

  • 软件协议:Modbus 是应用层协议,而 RTU(Remote Terminal Unit)是它最常用的一种传输模式(另一种是 ASCII)。
  • 依赖物理层 :它通常运行在 RS485(最常用)或 RS232 硬件之上。
  • 通信模式 :一主多从(Master-Slave) 、半双工。主站发起请求,从站被动应答(不问不答)。从站之间绝对不能直接通信。

二、 物理层基础(RS485)

  • 接线:2线制(A、B),差分信号(A-B > 200mV 为逻辑1,A-B < -200mV 为逻辑0)。
  • 拓扑 :总线型手拉手连接,最远两端必须接 120Ω 终端电阻(防信号反射)。
  • 距离与节点:最大通信距离 1200m,标准挂载 32 个节点(加中继器可扩展)。
  • 抗干扰:采用双绞屏蔽线,屏蔽层单点接地。

三、 数据帧结构(核心知识点)

Modbus-RTU 的一帧数据由四部分组成,最大总长度 256 字节:

从机地址 功能码 数据 CRC 校验
1 Byte 1 Byte N Bytes (0~252) 2 Bytes (低在前, 高在后)
  • 从机地址:范围 1~247(0 是广播地址,248~255 保留)。
  • 功能码:告诉从机要干什么(见下文)。
  • 数据:具体参数(如寄存器地址、要读的个数、要写的值)。
  • CRC 校验:对"从机地址到数据末尾"进行 CRC-16 计算。

四、 帧间隔:3.5T(避免粘包)

为了让从机区分一帧是否结束,Modbus-RTU 规定两帧数据之间至少有 3.5 个字符时间的空闲。

  • 波特率 > 19200bps :3.5T 固定为 1750 微秒。
  • 波特率 ≤ 19200bps :按公式计算(如 9600bps,1个字符占 11bit,3.5 * 11 * 1000 / 9600 ≈ 4ms)。
  • 注:一帧数据内部,字符与字符之间的间隔不能超过 1.5 个字符时间,否则会被视为错误帧。

五、 四大常用功能码

功能码 (Hex) 名称 作用 典型应用
0x03 读保持寄存器 读取设备的参数或数据 读温度、速度、电流、PM2.5
0x06 写单个保持寄存器 修改设备的某个参数 开关机、设置目标速度、修改PID
0x01 读线圈 读取开关量状态 读取继电器状态、限位开关
0x05 写单个线圈 控制开关量输出 控制继电器吸合、电机使能

六、 通信过程与实战报文解析(结合您的图片案例)

案例1:读取环境参数(主机读,功能码 0x03)

  • 主机发送 :01 03 00 00 00 06 C5 CB
    • 01:目标从机地址为 1。
    • 03:功能码,读取保持寄存器。
    • 00 00:起始寄存器地址(PM2.5 的地址)。
    • 00 06:连续读取 6 个寄存器(PM2.5、温度、湿度、CO2、TVOC、PM10)。
    • C5 CB:CRC 校验(低字节 C5,高字节 CB)。
  • 从机返回 :01 03 0C 00 4B 00 F0 01 7C ...
    • 01:本机地址。
    • 03:功能码。
    • 0C:字节数(6个寄存器 × 2字节 = 12 = 0x0C)。
    • 00 4B:第1个数据(PM2.5),0x004B = 75。
    • 00 F0:第2个数据(温度),0x00F0 = 240,由于文档注明"读取值是显示值的10倍",所以实际温度为 24.0℃。

案例2:控制从机开机(主机写,功能码 0x06)

  • 主机发送 :01 06 00 06 00 01 A8 0B
    • 01:从机地址。
    • 06:写单个寄存器。
    • 00 06:寄存器地址(控制开关的地址)。
    • 00 01:写入的值(1 表示开机)。
    • A8 0B:CRC 校验。
  • 从机返回 :原样返回主机的报文 (即 01 06 00 06 00 01 A8 0B),表示写入成功。

七、 异常处理(排障关键)

如果主机发了错误指令,从机不会沉默,而是返回异常响应帧。

  • 规则 :功能码最高位置 1(即原功能码 + 0x80),后跟一个字节的异常码。
  • 异常码 :
    • 01:非法功能(从机不支持此功能码)。
    • 02:非法地址(地址不存在,如发了个 0x00FF 但设备只有 0x0000~0x0009)。
    • 03:非法数据值(写入的值超出范围)。
    • 04:从机故障(处理时出现未知错误)。
  • 示例 :主机发 01 06 00 FF 00 01 ...,从机返回 01 86 02 C3 A1(0x06 + 0x80 = 0x86,异常码 02 表示非法地址)。

八、 实战排障速查指南

Q:RS485 Modbus 通信不上,怎么排查?

  1. 看硬件:A/B 线是否接反?是否共地?终端电阻是否接入(长距离必须接)?供电是否正常?
  2. 看配置:波特率(9600/115200)、数据位(8)、停止位(1)、校验位(无)是否主从完全一致?从机地址是否匹配?
  3. 看软件:3.5T 帧间隔是否满足?是否出现多个从机同时回复导致总线冲突?
  4. 看工具:用 USB 转 485 工具配合 XCOM、Modbus Poll 或 ZCANPRO(如果是 CAN 转 485 网关)抓包,看波形,看谁先回的数据,抓到底是谁在捣乱。

Q:如何做 Modbus-RTU 的自动化测试(结合 Python)?

  • 使用 Python 的 pyserial 库打开串口,或者使用 pymodbus 库直接调用高层 API。
  • 功能测试:遍历所有支持的功能码和寄存器地址。
  • 边界/异常测试:发送非法地址、非法数据、错误的 CRC、破坏 3.5T 间隔(连续快速发两帧),看从机是否按标准返回异常码。
  • 压力测试:以最高波特率 115200 和最大节点数,长时间(24h+)循环读写,统计丢包率、CRC 错误率,验证总线稳定性。

CRC

在 Modbus-RTU 中,CRC(循环冗余校验)采用的是 CRC-16/MODBUS 标准。

基于多项式除法。

一、 CRC-16/MODBUS 的核心规则

在开始计算前,必须牢记 Modbus CRC 的 4 个死规定:

  1. 初始值 :0xFFFF(16位全1)。
  2. 多项式 :0xA001(这是 0x8005 反转后的结果,因为 Modbus 采用低位在前的传输方式)。
  3. 计算范围 :从机地址、功能码、数据 (即不包含 CRC 本身的所有字节)。
  4. 结果处理 :计算出的 16 位结果,低字节在前,高字节在后(小端模式)放入报文中。

二、 推导:以案例 1 为例

案例 1 主机发送的前 6 个字节 01 03 00 00 00 06 为例,来推导它是如何得出 C5 CB 的。

准备工作:

  • 将每个字节看作 8 位二进制。
  • 准备一个 16 位的寄存器,初始值为 0xFFFF。

核心算法(逐字节异或与移位) :

每处理一个字节,执行以下步骤:

  1. 将 16 位寄存器的低 8 位与当前字节进行 异或(XOR) 运算。
  2. 将寄存器右移 1 位,最高位补 0。
  3. 如果移出的最低位是 1 ,则将寄存器与多项式 0xA001 进行 异或。
  4. 如果移出的最低位是 0,则不做异或。
  5. 重复步骤 2-4,共 8 次(处理完一个字节的 8 位)。
  6. 处理下一个字节,重复上述所有步骤,直到所有字节处理完毕。

(由于手动推导 6 个字节的 16 位运算非常冗长,这里展示简化逻辑)

  • 假设初始 0xFFFF,处理完 01 后,寄存器变为某个值。
  • 依次处理 03, 00, 00, 00, 06。
  • 最终得到的 16 位结果是:高字节为 0xCB,低字节为 0xC5。
  • 按照 Modbus 规定,低字节在前 ,所以报文末尾是 C5 CB。

补充:上面您写的从机返回数据 01 03 0C 00 4B 00 F0 01 7C ... 后面的 ... 就是 CRC,计算时要把前面的 01 03 0C 00 4B 00 F0 01 7C 全部算进去,得出 CRC 放在最后。


三、 实际工程中,CRC 怎么来的?

1. 软件代码自动计算(最常用)

实际开发中,开发者不会用手算,而是写一个 C 语言函数或 Python 脚本,让 MCU 自动计算。

c 复制代码
// 经典的 Modbus CRC16 C语言实现
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t length) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < length; i++) {
        crc ^= buffer[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc; // 返回值低字节在前,高字节在后
}

在发送数据前,调用这个函数,把算出的低字节和高字节分别拼接到报文末尾。

2. 查表法(空间换时间)

在资源紧张的 MCU 中,为了节省计算时间,会预先计算好 0~255 对应的 CRC 值存入数组,运行时直接查表,速度极快。这是工业级代码的标配。

3. 工具自动生成

在调试阶段,如果您用的是 XCOM 串口调试助手、Modbus Poll 或 SSCOM,这些工具都有自带的 CRC 计算功能 。您在发送框输入 01 03 00 00 00 06,点击"添加CRC校验",工具会自动帮您追加 C5 CB。

4. 从机校验(排障关键)

从机收到报文后,会把前面所有字节(不含收到的 CRC)重新算一遍 CRC。如果算出来的 CRC 与收到的 CRC 一致,说明数据没出错;如果不一致,从机直接丢弃,不回复(这就是为什么有时主站发送了,但从机没反应,可能是 CRC 错了)。


四、 总结

  1. 原理 :基于 CRC-16/MODBUS 标准,多项式是 0xA001,初始值是 0xFFFF。
  2. 算法:将报文每个字节与寄存器异或,然后循环右移 8 次,若最低位为 1 则异或多项式,最终得出 16 位结果。
  3. 工程实现 :实际开发不用手算,用 C语言循环算法 或 查表法 在 MCU 中自动生成,报文里低字节在前,高字节在后,接收方会重新计算比对,校验失败直接丢帧不回复。
相关推荐
东鸿电子Eastron1 个月前
深入解析智能电表通信架构:RS485 为何成为本地传输的主流方案
智能电视·rs485 总线·智能电表通信·modbus-rtu
北京盛世宏博2 个月前
Modbus TCP 与 Modbus RTU 区别,决定了温湿度传感器选型
以太网·传感器·区别·485·温湿度
【ql君】qlexcel9 个月前
Modbus通信协议从入门到精通
modbus·通讯协议·485·现场总线
Joshua.X2 年前
电脑总显示串口正在被占用处理方法
嵌入式硬件·串口·信息与通信·故障排查·232·485
津津有味道2 年前
Web浏览器485通讯读取RFID卡号js JavaScript
前端·javascript·浏览器·web·js·rfid·485
长沙红胖子Qt2 年前
硬件开发笔记(十七):RK3568底板电路串口、485、usb原理图详解
uart·usart·usb·485·rk3568原理图分析