news 2026/9/4 7:04:14

嵌入式调试笔记<7>MODBUS协议详解与调试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式调试笔记<7>MODBUS协议详解与调试实战

嵌入式调试笔记

一、MAX13487芯片产生数据回环问题
二、Keil在线调试过程中遇到的问题
三、使用VScdoe开发STM32单片机
四、VANTPEK可调电源接负载电压被拉低
五、用CD74HC4067扩展16路ADC采集及两个调试坑
六、4档旋转开关省IO采集,与Modbus中float的拆分还原
七、MODBUS协议详解与调试实战

文章目录

  • 嵌入式调试笔记
  • 前言
  • 一、协议基础
    • 1.主从架构:
    • 2.三种传输模式:
    • 3.RTU帧结构与静默间隔:
    • 4.从机地址与广播规则:
  • 二、字节序(大小端)
    • 1.大端与小端:
    • 2.怎么区分:
    • 3.MODBUS 的字节序约定:
  • 三、功能码详解
    • 1.常用功能码总表:
    • 2.寄存器地址编号与报文偏移:
    • 3.功能码 01:读线圈
    • 4.功能码 02:读离散输入
    • 5.功能码 03:读保持寄存器
    • 6.功能码 04:读输入寄存器
    • 7.功能码 05:写单个线圈
    • 8.功能码 06:写单个寄存器
    • 9.功能码 0F:写多个线圈
    • 10.功能码 10:写多个寄存器
    • 11.异常响应:
  • 四、CRC-16/MODBUS 校验
    • 1.参数:
    • 2.计算步骤(逐位法):
    • 3.查表法代码(可直接用):
    • 4.验证数据:
  • 五、调试工具:Modbus Poll / Modbus Slave
    • 1.Modbus Poll(主机):
    • 2.Modbus Slave(从机):
    • 3.对测流程与常见错误排查:
  • 六、经验小结
  • 总结

前言

MODBUS 是工业现场用得最多的串行通信协议,做嵌入式离不开它。本文把协议要点和调试工具用法整理成一份速查手册,开发时直接翻这一篇。

一、协议基础

1.主从架构:

MODBUS 是一主多从的总线式协议:主机(Master)发起所有请求,从机(Slave)被动应答;从机之间不直接通信,也没有从机主动上报。

  • 主机:PC 上位机、PLC、或者 MCU;
  • 从机:传感器、仪表、驱动器等设备,每台一个地址;
  • 轮询机制:主机按地址逐个发请求,从机收到与自己地址匹配的帧才响应。

注意:广播帧(地址 0)所有从机都要执行,但从机不应答,所以广播只能用于写操作,读操作不能用广播。

2.三种传输模式:

模式载体数据格式校验特点
RTURS-232/485二进制CRC16帧紧凑、效率高,最常用,本文重点
ASCIIRS-232/485十六进制字符LRC帧长翻倍、可肉眼读,老设备才用
TCP以太网二进制无(TCP 自带)走 502 端口,带 MBAP 头,无 CRC

本文默认 RTU 模式。RTU 模式下波特率、校验位(常用 8N1)两边必须完全一致,否则必现帧错。

3.RTU帧结构与静默间隔:

RTU 帧结构(固定 4 段):

字段字节数说明
从机地址1目标从机地址 1~247,0 为广播
功能码1要执行的操作(见第三章)
数据域N随功能码而定,长度可变
CRC162校验值,低字节在前发送

静默间隔(帧定界靠时间,不是靠帧头帧尾):

  • 帧与帧之间:静默 ≥3.5 个字符时间(如 9600bps、8N1 约 4ms);
  • 帧内相邻字符间隔:≤1.5 个字符时间,超了从机就认为帧结束(且不完整,丢弃)。

这就是为什么 STM32 上做 RTU 从机必须用定时器量帧间隔——收到 3.5 字符静默才说明一帧结束,靠判断字节空闲是处理不了的。

4.从机地址与广播规则:

地址用途
1~247有效从机地址,同一条总线上不能重复
0广播:所有从机执行、不应答,只用于写
248~255保留,不要使用

RS-485 多从机共挂一条总线时,地址重复是最隐蔽的坑:两个从机都应答,主机收到的帧就乱了。

二、字节序(大小端)

1.大端与小端:

  • 大端(Big-Endian):高字节在前。先发/先存的是高位字节;
  • 小端(Little-Endian):低字节在前。先发/先存的是低位字节。

2.怎么区分:

拿 16 位数0x1234举例:大端发12 34,小端发34 12。串口/网络协议里看到 16 位数据"高位在前",就是大端。

3.MODBUS 的字节序约定:

对象字节序说明
寄存器内 16 位数据大端0x1234在报文里是12 34
32 位数据(float/int32)两个字字序按 ABCD/CDAB 约定(见第 6 篇),字节再按大端
CRC16低字节在前CRC 计算值 0xD7B5,帧里发B5 D7

联调最容易栽在 32 位数据:float 拆两个寄存器,字序是 ABCD 还是 CDAB 两边不先说好,数值就是天文数字。用 12.5f(=0x41480000)当测试探针能快速定位(详见第 6 篇)。

三、功能码详解

1.常用功能码总表:

功能码名称读/写操作对象最大数量
01 (0x01)读线圈线圈(输出)2000 位
02 (0x02)读离散输入离散输入2000 位
03 (0x03)读保持寄存器保持寄存器(可读可写)125 个
04 (0x04)读输入寄存器输入寄存器(只读)125 个
05 (0x05)写单个线圈线圈1 位
06 (0x06)写单个寄存器保持寄存器1 个
0F (15)写多个线圈线圈1968 位
10 (16)写多个寄存器保持寄存器123 个

2.寄存器地址编号与报文偏移:

这是新手最容易懵的地方。报文里的偏移地址从 0x0000 起,而很多上位机/PLC 手册用"数据区 + 1 基编号":

数据区用户编号(1基)报文偏移说明
线圈000010x000001/05/0F 操作
离散输入100010x000002 操作
输入寄存器300010x000004 操作
保持寄存器400010x000003/06/10 操作

对应关系一句话:报文偏移 = 用户编号 - 1。例如保持寄存器40002→ 报文偏移0x0001。部分上位机工具直接填 0 基偏移,就无需换算;以设备手册为准,别想当然

3.功能码 01:读线圈

请求帧(主机→从机):

字段字节数说明示例值
从机地址1目标从机0x01
功能码10x010x01
起始线圈地址2偏移,高字节在前0x0013
线圈数量21~20000x0025
CRC162低字节在前0x0C 0x14

响应帧(从机→主机):

字段字节数说明示例值
从机地址10x01
功能码10x01
字节数1数量/8 向上取整0x05
线圈数据N每 8 个线圈打包 1 字节,位 0 对应起始线圈0xCD 0x6B 0xB2 0x0E 0x1B
CRC1620x44 0xEA

完整报文:

请求:01 01 00 13 00 25 0C 14 响应:01 01 05 CD 6B B2 0E 1B 44 EA

线圈按"位"打包:第一个数据字节的最低位(bit0)是起始线圈,不是最高位。解析时别倒序。
应用场景:回读继电器、接触器、固态继电器等输出点(DO)的真实状态——比如控制柜里各继电器是否真的吸合、阀门开到位/关到位反馈,上位机做"下发后回读确认"。

4.功能码 02:读离散输入

格式与 01 完全一致,只把功能码换成 0x02、操作对象换成只读的离散输入:

请求:01 02 00 C4 00 16 B8 39 响应:01 02 03 AC DB 35 22 88

应用场景:读取光电传感器、行程开关、按钮、门磁、限位开关这类开关量输入(DI)的状态,一个位代表一路输入。批量读 16 路传感器只要一条请求,按位解析即可。

5.功能码 03:读保持寄存器

请求帧:

字段字节数说明示例值
从机地址10x01
功能码10x03
起始地址2寄存器偏移,高字节在前0x006B
寄存器数量21~1250x0002
CRC1620xB5 0xD7
响应帧:
字段字节数说明示例值
------------------------------
从机地址10x01
功能码10x03
字节数1数量×20x04
寄存器数据N×2每寄存器 2 字节、大端0x000A 0x000B
CRC1620x9B 0xF6
完整报文:
请求:01 03 00 6B 00 02 B5 D7 响应:01 03 04 00 0A 00 0B 9B F6

含义:读从机 1 偏移 0x006B(=40001+107=40108)起的 2 个保持寄存器,得到 10 和 11。

应用场景:读取设备的参数与设定值(可读可写区)——温控器目标温度、变频器给定频率、报警阈值;不少设备也把运行累计值(总运行时间、总电量)放在这里。

6.功能码 04:读输入寄存器

格式与 03 相同,功能码换 0x04、对象换成只读输入寄存器:

请求:01 04 00 08 00 01 B0 08 响应:01 04 02 00 1E 39 38

应用场景:读取实时测量值(只读区)——温度、压力、流量、电压电流等连续量,仪表每秒更新的实测数据都在这里,和可读可写的保持寄存器分开,防止上位机误写测量值。

7.功能码 05:写单个线圈

请求帧:

字段字节数说明示例值
从机地址10x01
功能码10x05
线圈地址2偏移0x00AC
写入值20xFF00=ON,0x0000=OFF,其它值非法0xFF00
CRC1620x4C 0x1B

响应帧原样回显请求帧(从机收到什么回什么),这是 05/06 与其它功能码最大的区别:

请求:01 05 00 AC FF 00 4C 1B // 写线圈 ON 响应:01 05 00 AC FF 00 4C 1B // 原样回显

写线圈的值固定FF 00表示 ON,写01 00会被从机判为非法数据值(异常码 03)。这是常见低级错误。
应用场景单点启停控制——继电器开/关、电磁阀开/关、设备启停、故障复位(先写 OFF 再写 ON)。注意:写线圈不像读线圈能确认实际状态,关键回路建议再读回确认。

8.功能码 06:写单个寄存器

请求帧:

字段字节数说明示例值
从机地址10x01
功能码10x06
寄存器地址2偏移0x0001
寄存器值2大端0x0003
CRC1620x98 0x0B

响应同样原样回显:

请求:01 06 00 01 00 03 98 0B 响应:01 06 00 01 00 03 98 0B

应用场景修改单个参数——设定目标温度、给定电机转速、写入校零/标定系数。一次只改一个;要改多个参数用 10 功能码更高效。

9.功能码 0F:写多个线圈

请求帧:

字段字节数说明示例值
从机地址10x01
功能码10x0F
起始线圈地址2偏移0x0013
线圈数量21~19680x000A
字节数1数量/8 向上取整0x02
线圈数据N位打包,bit0=起始线圈0xCD 0x01
CRC1620x72 0xCB

响应帧(不含数据,只回起始地址和数量):

字段字节数说明示例值
从机地址10x01
功能码10x0F
起始线圈地址20x0013
线圈数量20x000A
CRC1620x24 0x09

完整报文:

请求:01 0F 00 13 00 0A 02 CD 01 72 CB 响应:01 0F 00 13 00 0A 24 09

应用场景批量控制输出——多路继电器组/阀组同时动作、多路报警输出一次性置位、LED/指示灯组整体刷新,一条帧搞定,比逐路发 05 快得多。

10.功能码 10:写多个寄存器

请求帧:

字段字节数说明示例值
从机地址10x01
功能码10x10
起始地址2偏移0x0001
寄存器数量21~1230x0002
字节数1数量×20x04
寄存器数据N×2大端0x000A 0x0102
CRC1620x92 0x30

响应帧(同 0F,只回地址和数量):

请求:01 10 00 01 00 02 04 00 0A 01 02 92 30 响应:01 10 00 01 00 02 10 08

写多个寄存器时,字节数字段必须等于 数量×2,填错从机直接回异常码 03。
应用场景批量下发参数——整组设定值一次性下载(如 PID 三个参数一条帧写完)、曲线/配方数据、标定表;比 06 逐条写帧数少,参数间不会出现"改了一半"的中间态。

11.异常响应:

从机收到请求但无法执行时,回异常帧,功能码最高位置 1(原功能码 + 0x80):

字段字节数说明示例值
从机地址10x01
功能码1原功能码0x80 → 0x83
异常码1见下表0x02
CRC1620xC0 0xF1
请求:01 03 00 6B 00 02 B5 D7 // 读保持寄存器 响应:01 83 02 C0 F1 // 异常:功能码0x83=03|0x80,异常码02=非法数据地址

常见异常码:

异常码含义常见触发场景
01非法功能码从机不支持该功能码
02非法数据地址寄存器地址或数量超出从机范围
03非法数据值数据内容不合法(如 05 写值不是 FF00/0000)
04从机设备故障从机内部错误,无法处理
06从机忙从机处理上一帧中,稍后重试

收到异常响应先看异常码,比瞎猜快得多:超范围多半是 02,参数错多半是 03。

四、CRC-16/MODBUS 校验

1.参数:

CRC-16/MODBUS 是参数固定的 CRC,与通用 CRC-16/CCITT 不是一回事,别混用:

参数
宽度16 位
多项式(Poly)0x8005(计算用反射值 0xA001)
初值0xFFFF
输入反转 / 输出反转都是(refin/refout = true)
输出异或值0x0000

2.计算步骤(逐位法):

1> CRC 寄存器初始化为 0xFFFF;
2> 取一字节数据与 CRC 低 8 位异或;
3> 右移 1 位,若移出的位为 1,则与 0xA001 异或;
4> 重复 8 次(一个字节处理完);
5> 处理完所有字节后,低字节在前追加到帧尾。

3.查表法代码(可直接用):

// 上电初始化一次:生成 256 项查表uint16_tcrc16_table[256];voidCRC_Table_Init(void){for(uint16_ti=0;i<256;i++){uint16_tcrc=i;for(intb=0;b<8;b++)crc=(crc&1)?(crc>>1)^0xA001:(crc>>1);crc16_table[i]=crc;}}// 查表计算,快,适合从机每帧都要算的场景uint16_tModbus_CRC16(constuint8_t*data,uint16_tlen){uint16_tcrc=0xFFFF;for(uint16_ti=0;i<len;i++)crc=(crc>>8)^crc16_table[(crc^data[i])&0xFF];returncrc;}// 使用:帧尾先发低字节,再发高字节uint16_tcrc=Modbus_CRC16(frame,len);tx[len++]=crc&0xFF;// 低字节在前tx[len++]=crc>>8;// 高字节在后

4.验证数据:

  • 标准验证串"123456789"(ASCII)计算得0x4B37,这是 CRC-16/MODBUS 公认测试值,代码对不对用它一验便知;
  • 完整帧验证:01 03 00 6B 00 02的 CRC = 0xD7B5,帧尾发B5 D7,与第三章示例一致。

收到帧先算 CRC 再处理:CRC 不对直接丢弃整帧,这是从机防误动作的第一道防线。

五、调试工具:Modbus Poll / Modbus Slave

调试利器是 Witte Software 的两件套:Modbus Poll(当主机)+Modbus Slave(当从机),Windows 免费版够用。

1.Modbus Poll(主机):

连接配置:菜单Connection → Connect,选串口和参数,必须与从机一致:

参数常见值说明
COM Port实际串口号设备管理器里查
Baud Rate9600 / 115200与从机一致
Data Bits8常用 8N1
Parity / StopNone / 1两边一致,从机带校验则改

参数填写:菜单Setup → Read/Write Definition,填从机要请求的内容:

参数说明示例
Slave ID从机地址1
Function功能码03 Read Holding Registers
Address寄存器偏移(0 基)0
Quantity数量10
Polling interval轮询周期 ms1000

收发报文监视:菜单Display → Communication,勾选显示后能看到 HEX 原文,逐字节和第三章的报文对,很快能定位问题。

2.Modbus Slave(从机):

从机定义:菜单Setup → Slave Definition,设 Slave ID、功能码(如 03)、地址、数量,界面里出现一个寄存器表,双击单元格可以手动改寄存器值,主机再读就能看到新值——这就是最方便的联调闭环。

连接:同样Connection → Connect选串口。Modbus Poll 和 Modbus Slave 分别占两个 COM(USB 转 485 模块各一个,A/B 对接)。

没有真实从机时,用 Modbus Slave 模拟一个,先把主机流程跑通再换真机,能省大量现场排查时间。

3.对测流程与常见错误排查:

标准对测步骤:
1> Modbus Slave 设 Slave ID=1、Function=03、Address=0、Quantity=10;
2> Modbus Poll 设 Slave ID=1、Function=03、Address=0、Quantity=10;
3> 两个工具都 Connect,串口参数一致;
4> Slave 里改一个寄存器的值,Poll 里应能看到同步变化。
常见错误排查表:

现象可能原因排查方法
Connection timed out(超时)地址错 / 接线错 / 从机没上电 / 波特率不一致查地址、A/B 接线、电源、串口参数
异常码 02 非法数据地址寄存器地址或数量超出从机范围核对 Address + Quantity
异常码 03 非法数据值写入值不合法05 写线圈用 FF00/0000,字节数别填错
一直帧错 / CRC 错校验位或停止位不一致两端统一 8N1
Poll 表格整列红色 ERR见 Communication 窗口的具体报文看报文内容,别只看状态

六、经验小结

1> MODBUS 就是"地址 + 功能码 + 数据 + CRC"的框架,功能码决定数据域怎么拼怎么拆,一张表全搞定;
2>帧定界靠时间(3.5 字符静默)不靠帧头,STM32 从机必须用定时器量帧间隔——下一篇就是从 UART 空闲中断 + 定时器讲起的;
3> 字节序三件事:寄存器大端、32 位字序 ABCD/CDAB 先说好、CRC 低字节在前,联调前对齐能少吵半天;
4> 从机收到帧先过三道关:CRC 校验 → 地址匹配(含广播)→ 功能码分发,不合规就回异常码,这是从机代码的标准骨架;
5> 先 Modbus Poll + Slave 对测跑通协议,再接真机,排查效率翻倍。

总结

MODBUS 协议本身不复杂:主从架构 + 时间定界帧 + 大端数据 + 8 个常用功能码 + CRC16。难的是细节——偏移地址对应关系、位打包顺序、CRC 低字节在前、32 位字序,任何一个记错都会让你在联调现场怀疑人生。

本文把报文格式、CRC、工具用法都整理成表,开发时直接照抄即可。

如有表述错误、介绍模糊的地方,欢迎交流指正。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 7:03:34

Mediapipe实时健康监护系统:从算法检测到行为干预的完整闭环

简介&#xff1a;这是一份面向计算机及相关专业本科生的优质课程设计资源&#xff0c;聚焦于基于摄像头的实时健康监护场景&#xff0c;解决久坐办公或学习中的疲劳状态识别与不良坐姿提醒问题。资源包含9个文件&#xff08;4个核心Python模块、1个配置文件yml、1个依赖说明txt…

作者头像 李华
网站建设 2026/9/4 7:02:33

TM7711高精度滤波算法实战:从厨房秤到嵌入式信号处理

简介&#xff1a;本资源是一份面向嵌入式开发工程师与IoT设备开发者的技术实践代码包&#xff0c;聚焦TM7711高精度称重传感器在厨房秤场景下的0.1克级稳定读数实现问题。针对传感器原始数据易受噪声干扰、内码跳动大于5等典型工程痛点&#xff0c;提供经实际验证的C语言滤波算…

作者头像 李华
网站建设 2026/9/4 7:00:01

51单片机智能电饭锅控制逻辑设计与工程落地

简介&#xff1a;本资源是一套面向嵌入式初学者与电子类课程设计者的51单片机实践项目&#xff0c;聚焦智能电饭锅控制系统开发&#xff0c;覆盖硬件仿真、软件编程与闭环控制逻辑实现全过程。压缩包共45个文件&#xff0c;含5个C源程序&#xff08;主控逻辑与传感器驱动&#…

作者头像 李华
网站建设 2026/9/4 6:56:04

20秒快速分辨MOS管类型与引脚:基于寄生二极管的万用表实战法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 6:55:52

车载疲劳驾驶检测系统:端边云协同的实时多模态行为分析

简介&#xff1a;本资源是一份面向计算机视觉与智能交通方向初学者及课程设计者的深度学习实践报告&#xff0c;聚焦疲劳驾驶实时检测这一典型安全应用场景。报告完整呈现了从多模态特征感知、轻量化模型设计到系统集成预警的全流程技术方案&#xff0c;涵盖OpenCV/Dlib人脸关键…

作者头像 李华