
嵌入式调试笔记一、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 Slave1.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~2470 为广播功能码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 3432 位数据float/int32两个字字序按 ABCD/CDAB 约定见第 6 篇字节再按大端CRC16低字节在前CRC 计算值 0xD7B5帧里发B5 D7联调最容易栽在 32 位数据float 拆两个寄存器字序是 ABCD 还是 CDAB 两边不先说好数值就是天文数字。用 12.5f0x41480000当测试探针能快速定位详见第 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~20000x0025CRC162低字节在前0x0C 0x14响应帧从机→主机字段字节数说明示例值从机地址10x01功能码10x01字节数1数量/8 向上取整0x05线圈数据N每 8 个线圈打包 1 字节位 0 对应起始线圈0xCD 0x6B 0xB2 0x0E 0x1BCRC1620x44 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~1250x0002CRC1620xB5 0xD7响应帧字段字节数说明示例值-----—-------------------------从机地址10x01功能码10x03字节数1数量×20x04寄存器数据N×2每寄存器 2 字节、大端0x000A 0x000BCRC1620x9B 0xF6完整报文请求01 03 00 6B 00 02 B5 D7 响应01 03 04 00 0A 00 0B 9B F6含义读从机 1 偏移 0x006B4000110740108起的 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写入值20xFF00ON0x0000OFF其它值非法0xFF00CRC1620x4C 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大端0x0003CRC1620x98 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 0x01CRC1620x72 0xCB响应帧不含数据只回起始地址和数量字段字节数说明示例值从机地址10x01功能码10x0F起始线圈地址20x0013线圈数量20x000ACRC1620x24 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 0x0102CRC1620x92 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见下表0x02CRC1620xC0 0xF1请求01 03 00 6B 00 02 B5 D7 // 读保持寄存器 响应01 83 02 C0 F1 // 异常功能码0x8303|0x80异常码02非法数据地址常见异常码异常码含义常见触发场景01非法功能码从机不支持该功能码02非法数据地址寄存器地址或数量超出从机范围03非法数据值数据内容不合法如 05 写值不是 FF00/000004从机设备故障从机内部错误无法处理06从机忙从机处理上一帧中稍后重试收到异常响应先看异常码比瞎猜快得多超范围多半是 02参数错多半是 03。四、CRC-16/MODBUS 校验1.参数CRC-16/MODBUS 是参数固定的 CRC与通用 CRC-16/CCITT 不是一回事别混用参数值宽度16 位多项式Poly0x8005计算用反射值 0xA001初值0xFFFF输入反转 / 输出反转都是refin/refout true输出异或值0x00002.计算步骤逐位法1 CRC 寄存器初始化为 0xFFFF2 取一字节数据与 CRC 低 8 位异或3 右移 1 位若移出的位为 1则与 0xA001 异或4 重复 8 次一个字节处理完5 处理完所有字节后低字节在前追加到帧尾。3.查表法代码可直接用// 上电初始化一次生成 256 项查表uint16_tcrc16_table[256];voidCRC_Table_Init(void){for(uint16_ti0;i256;i){uint16_tcrci;for(intb0;b8;b)crc(crc1)?(crc1)^0xA001:(crc1);crc16_table[i]crc;}}// 查表计算快适合从机每帧都要算的场景uint16_tModbus_CRC16(constuint8_t*data,uint16_tlen){uint16_tcrc0xFFFF;for(uint16_ti0;ilen;i)crc(crc8)^crc16_table[(crc^data[i])0xFF];returncrc;}// 使用帧尾先发低字节再发高字节uint16_tcrcModbus_CRC16(frame,len);tx[len]crc0xFF;// 低字节在前tx[len]crc8;// 高字节在后4.验证数据标准验证串123456789ASCII计算得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常用 8N1Parity / StopNone / 1两边一致从机带校验则改参数填写菜单Setup → Read/Write Definition填从机要请求的内容参数说明示例Slave ID从机地址1Function功能码03 Read Holding RegistersAddress寄存器偏移0 基0Quantity数量10Polling interval轮询周期 ms1000收发报文监视菜单Display → Communication勾选显示后能看到 HEX 原文逐字节和第三章的报文对很快能定位问题。2.Modbus Slave从机从机定义菜单Setup → Slave Definition设 Slave ID、功能码如 03、地址、数量界面里出现一个寄存器表双击单元格可以手动改寄存器值主机再读就能看到新值——这就是最方便的联调闭环。连接同样Connection → Connect选串口。Modbus Poll 和 Modbus Slave 分别占两个 COMUSB 转 485 模块各一个A/B 对接。没有真实从机时用 Modbus Slave 模拟一个先把主机流程跑通再换真机能省大量现场排查时间。3.对测流程与常见错误排查标准对测步骤1 Modbus Slave 设 Slave ID1、Function03、Address0、Quantity102 Modbus Poll 设 Slave ID1、Function03、Address0、Quantity103 两个工具都 Connect串口参数一致4 Slave 里改一个寄存器的值Poll 里应能看到同步变化。常见错误排查表现象可能原因排查方法Connection timed out超时地址错 / 接线错 / 从机没上电 / 波特率不一致查地址、A/B 接线、电源、串口参数异常码 02 非法数据地址寄存器地址或数量超出从机范围核对 Address Quantity异常码 03 非法数据值写入值不合法05 写线圈用 FF00/0000字节数别填错一直帧错 / CRC 错校验位或停止位不一致两端统一 8N1Poll 表格整列红色 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、工具用法都整理成表开发时直接照抄即可。如有表述错误、介绍模糊的地方欢迎交流指正。