
做嵌入式这几年串口通信里的MODBUS协议是无论如何都绕不开的老伙计。从最开始的STM32单片机采集温湿度到后来调试伺服驱动器、变频器、仪表传感器几乎每次都要和MODBUS打交道。写过好几篇调试笔记今天这篇专门把MODBUS协议从头到尾捋一遍结合我调试过程中踩过的坑和总结的排障思路给嵌入式开发的朋友一个完整的参考。这篇笔记会包含协议原理、报文格式、CRC校验、实际调试工具的使用、RS485物理层的坑以及我整理的常见问题速查表。不管你是在单片机裸机上移植、在FreeRTOS里做任务调度还是用Linux工控板做上位机通信这套流程基本都是通用的。适合刚入行的嵌入式新手用来建立完整的协议认知也适合有经验的老手查漏补缺。1. 先搞明白MODBUS到底是什么1.1 一个请求-响应周期里到底发生了什么MODBUS是一个极其简单的主从式通信协议简单到什么程度呢它的核心思想就一句话主站发请求从站回响应一问一答绝不多话。总线上只能有一个主站其他设备都是从站每个从站有唯一的地址地址范围是1到247地址0用于广播。举个我调试时的实际例子。我手上的一个温湿度变送器从站地址是2要读取它的温度和湿度值保持寄存器地址分别是0x0001和0x0002。主站发送的RTU报文是这样的02 03 00 01 00 02 95 F702从站地址03功能码表示读保持寄存器00 01起始寄存器地址高字节在前00 02读取寄存器数量表示读2个寄存器也就是4个字节数据95 F7CRC16校验码低字节在前从站收到后回复的报文格式是02 03 04 01 2C 00 96 29 3B02从站地址03功能码原样返回04数据字节数2个寄存器就是4个字节01 2C第一个寄存器的值十六进制0x012C十进制30000 96第二个寄存器的值十进制15029 3BCRC校验码如果温度值是300乘以0.1的分辨率那就是30.0摄氏度湿度值150乘以0.1的分辨率就是15.0%RH。从这里就能看出来MODBUS本身不定义数据含义数据怎么解释完全看设备手册这在调试时特别容易出问题后面我会专门讲。1.2 四种数据对象与地址映射关系MODBUS协议定义了四种数据对象分别是线圈、离散输入、输入寄存器和保持寄存器。很多初学者会被这四种对象搞晕我用一个容易记的方式来说明线圈Coil可读可写按位操作。对应PLC里的DO数字量输出电磁阀、继电器、指示灯这类设备。离散输入Discrete Input只读按位操作。对应PLC里的DI数字量输入接近开关、限位开关、按钮这类设备。输入寄存器Input Register只读按16位字操作。对应模拟量输入比如电流、电压、温度采样值。保持寄存器Holding Register可读可写按16位字操作。对应模拟量输出或者可以被上位机修改的设定参数。为什么要这样设计因为MODBUS诞生于上世纪七十年代末当初就是为了PLC通信设计的。PLC的世界里数字量位和模拟量字是严格分开的输入和输出也是严格分开的所以协议里就有了四个独立的对象。今天调试的很多设备虽然没有PLC那么严格的区分但这个模型还是被延续了下来。地址映射关系也是调试时容易栽跟头的地方。设备的MODBUS地址从1开始编号而协议报文里的地址是从0开始编号的。一个设备手册上写着保持寄存器地址是40001那么报文里的起始地址就是0x0000写着保持寄存器地址是40003报文里就是0x0002。搞混这个映射关系读出来的数据永远是错位的。具体对应关系如下数据对象PLC地址区间协议报文地址读写属性操作单位线圈00001-099990x0000-0xFFFF读写位离散输入10001-199990x0000-0xFFFF只读位输入寄存器30001-399990x0000-0xFFFF只读16位字保持寄存器40001-499990x0000-0xFFFF读写16位字寄存器数量也会踩坑。MODBUS RTU协议规定一次读保持寄存器的最大数量是125个0x7D。如果请求超过这个数量从站会返回异常码03非法数据值。所以我习惯在代码里把这个上限写死防止上层逻辑传入非法参数。1.3 RTU、ASCII、TCP三种模式该选谁MODBUS有三种常见的传输模式RTU、ASCII和TCP。RTU和ASCII都跑在串口上TCP跑在以太网上。RTU是二进制传输效率高一帧报文最短只有8个字节是工业现场绝对的主流。ASCII模式把每个字节拆成两个ASCII字符发送报文长度翻倍效率低但好处是可以直接用文本方式查看出错调试相对直观现在很少见了我没在实际项目中用过。MODBUS TCP则是把RTU的报文直接塞进TCP的载荷里去掉地址和CRC这两个串口才需要的字段加上MBAP报文头。如果是在同一台Linux工控机上做进程间通信或者跨设备走以太网用TCP模式非常方便波特率、校验位这些串口参数完全不用考虑。我的建议是凡是走串口RS232/RS485的一律用MODBUS RTU这是兼容性最好的选择。凡是走以太网的直接用MODBUS TCP不用纠结RTU over TCP这种组合很多从站设备压根不支持。有些设备同时支持RTU和ASCLL配置参数时注意区分别把设备设成了ASCII模式而你的主机一直在发RTU这种无效通信查起来特别浪费时间。2. 调试要用的家伙什2.1 主站模拟工具调试MODBUS最怕的就是从站设备还没接上你就急着写代码。我的习惯是先把通信链路打通确认物理层没问题然后再调协议逻辑。这时候主站模拟工具就是必备的。我常用的工具是ModbusPoll它可以把PC模拟成一个MODBUS主站设置好串口号、波特率、数据位、停止位和校验方式然后按功能码循环发送请求。界面左侧是寄存器地址和数值右侧是收发报文原始内容格式一目了然。这个工具还带一个方便的功能可以设置轮询周期模拟周期性采集方便观察从站响应时间。VSPDVirtual Serial Port Driver也是个实用的工具可以在PC上虚拟出一对互联的串口COM3和COM4一端作为主机另一端作为从机在完全没有硬件的情况下就能验证代码逻辑。比如你把你的从站代码跑在开发板上串口接到真实USB转串口上再用VSPD虚拟一个串口对让ModbusPoll连接虚拟串口的一端另一端用虚拟串口软件转发到真实串口上这样在没有真实从站设备的情况下也能验证你的代码是否能正确响应主站的请求。2.2 从站模拟工具主站工具有了从站工具同样不能少。调试自己写的主站代码时需要有一个能模拟从站的小工具随时设置各寄存器的值验证读写的正确性。我用的比较多的是Modbus Slave它的界面和ModbusPoll是一家公司的风格很像。设置好串口参数和从站地址后可以在界面上直接维护各个寄存器的值改一个数主站那边立刻就能看到。另一种思路是用Node-RED这类带MODBUS节点的工具配合串口转发功能可以灵活构造异常响应、延迟响应等特殊场景适合做健壮性测试。用Node-RED的话需要自己搭一个流程从串口读取报文解析功能码然后构造响应帧回发虽然麻烦但自由度极高。2.3 RS485物理层那几个坑协议层聊完了但实际调试时约有一半的问题出在物理层。RS485用差分信号传输A、B两根线抗干扰能力强最远能传1200米这是它能成为工业现场标配的原因。但越是简单的东西就越容易出错。第一个坑是终端电阻。RS485总线两端必须各接一个120欧姆的终端电阻用来消除信号反射。很多新手不理解为什么通信不稳定、时好时坏还要看终端电阻。打个比方信号在线上传输到了线末端如果阻抗不匹配就像水流冲到墙上一部分反弹回来反弹的信号会和后面的信号叠加产生畸变。接上120欧姆的终端电阻信号到这直接被吸收掉不再反弹。实际调试时短距离几米以内不接终端电阻通常也能工作所以很多人就不重视但一旦距离拉长到几十米或者总线上挂的设备多了问题就会冒出来。第二个坑是共地问题。RS485虽然是差分信号A、B两线之间的电压差决定逻辑电平理论上不需要共地但实际使用时如果各个设备的地电位差太大超过芯片的共模输入范围就会导致通信异常。我处理过一个典型案例两端的设备分别用不同的开关电源供电电源之间没有共地结果通信时好时坏后来用万用表一量地之间的电位差居然有十几伏。处理方法很简单用一根线把两端的GND连起来问题立刻消失。这在调试中经常被忽略排查故障时建议优先检查。第三个坑是A/B线接反。RS485的A和B如果接反了设备处于空闲状态时总线上的电平是反的通信完全不通。你拿着示波器看波形会看到信号逻辑正好反相。很多设备在接线端子旁有A/B标记但标注方式不统一有的标D/D-有的标485/485-有的标P/N一定要先看手册确认定义。我在调试一台变频器时就被这个问题坑过它的说明书上把A标成DB标成D-模块上没有标注我按习惯接的反了折腾了半个小时才反应过来。3. 从零实现一个MODBUS RTU从站的核心逻辑3.1 CRC16校验现场最容易翻车的一步MODBUS RTU的报文尾部有2个字节的CRC校验码用于检测数据在传输过程中是否被干扰。这个校验算法是CRC16-MODBUS多项式是0x8005初始值是0xFFFF。和常见CRC16-CCITT用的多项式0x1021不同别搞混了。CRC校验的计算有很多种实现方式最常见的是查表法和按位计算法。按位计算的代码最直观适合理解原理但效率低。查表法把256个CRC值预先算好存成数组运行时查表速度飞快。我平时用到单片机上的代码直接用查表法反正表也就512个字节对Flash占用可以忽略。先说按位计算的核心逻辑初始化一个16位的寄存器为0xFFFF然后对每一个字节和寄存器低8位做异或再对得到的结果逐位判断如果最低位是1就右移一位并异或多项式0xA001即0x8005的反射多项式如果最低位是0就只右移一位。最后得到的寄存器值就是CRC。注意MODBUS的字节序是低字节在前所以发送时先发低8位再发高8位。我这里给一段实际验证过的查表法代码用C语言写的在STM32和Linux上都能直接跑static const uint16_t crc_table[256] { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, // ... 完整256项用工具预生成 }; uint16_t modbus_crc16(const uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; while (len--) { crc (crc 8) ^ crc_table[(crc ^ *data) 0xFF]; } return crc; }查表法的原理不复杂CRC计算本质上是对字节流做多项式除法每个新的输入字节和当前CRC值的高8位异或得到一个0到255的索引从表中查出对应的值再和CRC左移8位后的值异或。这个表可以用下面的代码预生成就不用把256个常数手敲进去了void generate_crc_table(uint16_t *table) { for (int i 0; i 256; i) { uint16_t crc i; for (int j 0; j 8; j) { if (crc 0x0001) crc (crc 1) ^ 0xA001; else crc 1; } table[i] crc; } }调试时最容易翻车的情况是接收方算出来的CRC和报文里的CRC对不上。这时候不要急着怀疑算法先确认两个点。第一报文里的CRC是不是低字节在前很多设备的SDK或上位机工具发的是高字节在前你得确认对端按哪种方式收发。第二校验范围是否包含CRC本身正确做法是校验除了最后2个CRC字节之外的所有字节不要把CRC本身算进去。3.2 报文收发的状态机设计在单片机上实现MODBUS从站最核心的是串口数据接收的状态管理。刚入门的同学很容易一上来就在串口中断里收一个字节处理一个字节收到0x03就当成功能码开始解析结果后续字节还没到处理逻辑就已经跑飞了。正确的做法是设计一个简单的状态机把数据完整收下来校验通过后再统一处理。我的状态机设计思路参考了Modbus从站实现的经典写法核心状态如下状态含义触发条件IDLE空闲等待帧起始等到从站地址匹配RECEIVING接收中持续有字节到达RECEIVE_COMPLETE一帧接收完毕超时未收到下一个字节PROCESSING处理中解析报文并构造响应SENDING发送响应中发送完成返回IDLE接收超时的判断是MODBUS RTU的关键因为RTU没有固定的帧结束标志只能靠空闲间隔来判定一帧是否结束。协议规定两个字节之间的间隔不能超过1.5个字符时间接收一帧的时间不能超过3.5个字符时间。以9600波特率、8位数据位、1位停止位为例1个字符大约是11位起始位1 数据8 校验1 停止13.5个字符时间大约是4毫秒。所以串口在收到一个字节后如果超过4毫秒没有新的字节到达就认为这一帧数据接收完毕。在实际工程中我用两种方式实现这个超时判断。一种是利用串口空闲中断IDLE interruptSTM32和很多国产MCU都支持空闲中断在串口配置上打开IDLE中断一帧数据接收完成后自动触发简单可靠。另一种是使用定时器每次收到字节就重置定时器定时器溢出中断标志帧结束。如果MCU没有空闲中断或者串口数量多、中断资源紧张就用定时器方案。这两条路我都走过实测下来空闲中断更省CPU但定时器方案移植性更好。接收完毕之后不要立刻处理数据先把整帧的CRC校验做了校验失败直接丢弃。帧处理完成后如果需要构造响应帧也要注意单工和半双工的区别。RS485是半双工发送和接收共用一对线所以发送前必须把485芯片的方向控制引脚拉高发送完毕再拉低恢复接收。很多新手在这里踩坑方向控制引脚拉高后没有延时就直接发数据结果第一个字节被切掉了一半。规范的做法是拉高方向引脚后延时至少1个字符的时间9600波特率下大约1.2毫秒再启动发送发送完成确认最后一个字节已经从移位寄存器里完全发出后再拉低方向引脚。3.3 从站响应帧的构造与异常码处理从站收到一帧合法请求后需要根据功能码来构造响应。我以常用的03读保持寄存器和06写单个寄存器为例拆解一下响应帧的构造过程。功能码03的请求帧格式是地址 03 起始地址高8位 起始地址低8位 寄存器数量高8位 寄存器数量低8位 CRC低8位 CRC高8位。从站处理时先判断请求的寄存器数量是否合法再判断起始地址加上寄存器数量是否越界。如果寄存器数量超过125返回异常码03。如果地址越界返回异常码02非法数据地址。正常响应帧格式是地址 03 字节数 数据1高8位 数据1低8位 ... CRC低8位 CRC高8位。注意字节数这一栏等于寄存器数量乘以2很多新手忘了这个字段或者把寄存器数量当成字节数导致对端解析失败。功能码06的写单个寄存器请求帧格式是地址 06 寄存器地址高8位 寄存器地址低8位 数据高8位 数据低8位 CRC。正常响应的格式和请求帧完全一样原样返回。这里有一个很多从站设备共通的细节写操作完成后有些设备会立即更新寄存器有些设备需要几百毫秒的掉电保存时间如果主站连续快速写入可能会丢失中间几次写入。所以我的习惯是在写入Flash类存储之前先判断数据是否发生了变化只有变化时才真正执行写操作同时设置一个忙标志告诉主站当前无法处理新的写请求。异常响应的帧格式是地址 功能码 | 0x80 异常码 CRC。比如从站地址2收到一个不支持的功能码0x05实际不支持写单个线圈应该回复02 85 01 84 55地址02功能码0x850x05 | 0x80异常码01非法功能码CRC是84 55。常见异常码见下表异常码含义典型触发原因01非法功能码从站不支持该功能码02非法数据地址寄存器地址越界03非法数据值寄存器数量超限、写入值超范围04从站设备故障设备内部异常无法处理请求06从站设备忙设备正在处理其他任务稍后重试4. 真实环境下的调试实录与疑难排障4.1 现象一有发无收主站一直超时这是我碰到最多的问题主站用ModbusPoll发请求报文窗口能看到发送的帧但接收区域一直空白直到超时。排查思路我总结成一个固定的顺序先用万用表量RS485的A/B之间是否有电压差。空闲状态下A相对B应该高出2到5伏这是判断485芯片是否正常工作最直接的办法。如果电压是0多半是芯片没供电或者A/B接反了。如果电压正常再用示波器看从站有没有回复波形。如果从站有波形但主站收不到问题在RS485转换器的方向和回环上如果从站根本没波形问题在从站端的代码。从站端最常见的坑是地址不匹配。主站发的是地址1从站代码里写的是地址2从站自然不理会。排查时先确认主站和从站的地址设置一致再确认波特率、数据位、停止位、校验位完全一致。MODBUS RTU默认是8数据位、1停止位、无校验8N1但很多设备出厂设置是8E1偶校验这就导致发送方按8E1发送接收方按8N1接收数据就会错位主站收到的是CRC错误或者根本识别不到帧头。还有一个特别隐蔽的问题从站代码里如果开了串口接收中断但中断服务函数里忘了清标志位会导致收一个字节后就再也不进中断了。我调试一个国产MCU时遇到过串口每接收一帧触发一次中断标志位没清理结果设备上电后只能响应第一次请求之后再无反应。这个问题的排查方式是在中断服务函数入口置一个GPIO翻转用示波器看是否还有中断触发。4.2 现象二偶发CRC校验失败CRC校验偶发失败是最难排查的一种情况因为它不是每次都出现可能是每隔几十帧出现一次也可能是每隔几分钟出现一次。先怀疑波特率误差。如果单片机主频有偏差或者串口波特率配置的分数因子计算不精确导致实际波特率和标称值有2%以上的误差连续发送长帧时就会累积偏差造成接收方在帧中某个字节上发生采样错误。用示波器抓UART引脚的波形量一下实际波特率和标称波特率的误差超过2%就要调整时钟配置。再检查是不是数据被RS485收发切换截断了。如果主站在发送完成后立刻把方向引脚拉低但此时发送移位寄存器里还有最后几个位没发完就会截断帧尾接收方因为收不到完整的对齐位导致这一帧CRC校验失败。我的代码写法是发送最后一个字节后等待发送完成标志位置位再加一个微小延时一般1到2个字节的时间然后才拉低方向引脚。还有一种情况是总线上有其他设备回应冲突。如果同一个总线上有两个设备设置了相同的从站地址主站发一次请求两个设备同时回响应两个帧在总线上叠加CRC必然错误。排查方法是把总线上其他设备逐个断开看CRC错误是否消失。我记得有次调试一个仪表和一块采集板共用一个从站地址查了半天才发现两个设备出厂默认地址都是1我忘了给其中一个改地址。4.3 现象三数据全是0xFF或者明显错位如果主站能收到数据但读出来的寄存器值全部是0xFF或者数值和实际不符这通常是寄存器地址映射不对或者数据字节序解析反了。MODBUS RTU高位字节在前大端序所以一个16位寄存器的值0x012C先收到的是0x01再收到的是0x2C。如果你在代码里把接收缓冲区的第一个字节当成低字节读出来的数值就会完全不同。比如0x012C会被解析成0x2C01也就是11265和实际的300差了十万八千里。排查方法很简单用ModbusPoll写一个已知的值看报文原始字节从原始字节里手工算一遍就能确认字节序是不是反了。数据全是0xFF还有一种可能发送了读保持寄存器请求但从站没这个地址的寄存器按异常响应返回了主站却把异常响应当成正常数据处理了。注意看异常响应的功能码最高位0x80有没有置位这是判断响应是否是异常帧的关键标识。数值错位还可能是地址偏移问题。设备手册里写着寄存器40001对应温度40002对应湿度你以为报文里的起始地址就是0x0001实际上MODBUS工业约定里40001对应的报文地址是0x0000所以你想读40001应该发0x0000结果你发了0x0001读到的是40002的内容。铺垫一个经验所有支持MODBUS的设备手册里给出的地址如果是PLC风格的40001格式报文里一定要减1如果手册直接给出的是0x0000这种十六进制地址就不用减。4.4 排查问题速查表结合我这些年的调试经历把最常见的MODBUS故障原因和解决手段整理成了下面这个表方便排查时快速对照故障现象可能原因快速排查方法完全无响应A/B接反对调A/B线完全无响应从站地址不匹配核对设备地址与请求地址完全无响应波特率/校验位不一致核对串口参数完全无响应485方向控制逻辑错误示波器抓方向引脚波形偶发CRC错误终端电阻缺失总线两端并接120欧姆电阻偶发CRC错误地电位差过大设备间连接GND偶发CRC错误两个从站地址冲突逐个断开设备排查一直CRC错误波特率误差偏大示波器测实际波特率数据全是0xFF地址映射错位手工按报文计算确认数据数值不对字节序解析错误用已知值验证解析逻辑写寄存器不生效写入地址偏移确认40001对应0x0000写寄存器不生效数据格式不符确认是否需要先写命令字再写数据通信时好时坏线缆过长或过细更换屏蔽双绞线检查接头通信时好时坏电源纹波干扰示波器看电源纹波加去耦电容实际调试中还经常遇到一种情况从站能响应但响应时间特别长导致主站侧超时。比如某些仪表在写Flash保存参数的时候会阻塞执行几百毫秒如果主站的超时时间设置得比这个还短就会误报超时。这种情况建议在代码里处理好从站的忙状态及时返回异常码06或者把主站超时时间调长一些。5. 几个值得保留的工程习惯5.1 报文日志是调试的第一生产力我调试MODBUS时无论用不用上位机工具代码里一定会加一套报文日志的输出接口。日志格式很简单就是收发帧的原始字节和方向标记类似这样[RX] 02 03 00 01 00 02 95 F7 [TX] 02 03 04 01 2C 00 96 29 3B有人觉得有ModbusPoll或者串口助手就能看到报文了没必要自己在代码里加日志。但实际上在嵌入式设备端打印日志有一个不可替代的价值你可以把日志打在主站比如PLC或者触摸屏正在请求什么从站你的设备实际回了什么这样就能清楚地定位到问题出在谁身上。有一回调试一个威纶通触摸屏连接我的采集板触摸屏一直显示通信故障我用串口助手抓包发现触摸屏压根没发请求后面才查到是触摸屏的组态配置里串口参数设置错了这问题如果不从设备端打印日志光看上位机界面根本无法判断。日志输出的方式可以选择UART调试串口也可以选择通过蓝牙透传还可以存到Flash里定期读取。考虑到性能损耗不建议在中断里直接打印日志建议把收发数据先Copy到一个环形缓冲区然后在主循环或空闲任务里统一输出避免日志打印阻塞通信。5.2 用模拟器先演练再上真机我自己的习惯是调试代码之前先在PC上用模拟器把协议流程完整跑一遍确认收发、校验、异常处理都符合预期再连真机调试。这样可以省掉很多在真机上反复烧写固件的等待时间。具体做法是在PC上用Node-RED或者Python的pymodbus库写一个模拟从站把你的设备协议文档里描述的寄存器表维护在内存里然后用ModbusPoll按你的测试用例逐条读取写入直接对照模拟结果和真实设备的行为。或者反过来用Python的pymodbus写一个模拟主站发各种正常和异常的请求来测试你设备端代码的健壮性。我用这个习惯抓到过不少自身代码的bug。有一次写一个从站的写多寄存器功能码16模拟器测试时正常但到了真机测试就发现写寄存器数量超过1个时会出错查了半天发现是字节序处理写反了。模拟器测试只需要改一行代码重新跑而真机测试需要重新编译烧写调试效率天差地别。5.3 报文记录文件帮你复盘问题当系统部署到现场后出了问题往往不能像实验室一样随时抓包。我习惯在设备端加一个简单的事件记录功能当通信出错时CRC错误、超时、异常响应把出错的报文和时间戳记录下来存到Flash或者通过串口输出。现场运维人员把日志导出来我拿到后就能分析是什么原因导致的。这个功能其实实现起来很简单就是在接收处理函数里判断到CRC、帧超时等异常时调用一个记录函数把当前时间、错误类型和相关的原始数据写入一个环形缓存。等上位机下发查询命令时可以把缓存一条条吐出。这套机制本质上就是一个微型数据分析系统对后期维护和问题定位帮助极大。5.4 兼容性测试不要只测一家设备MODBUS协议理论上来说四海之内皆标准但实际上不同厂家对协议的理解和实现有不少差异。有的设备的字节序是大端的有的设备却按小端解释16位数据有的设备的寄存器地址从0开始编号有的直接从40001开始有的设备支持广播地址0写操作有的不支持有的设备对1500、1800这种PLC地址里包含功能码的地址描述方式1500表示离散输入1800表示正常输入和实际的报文映射也不同。所以我调自己的通用MODBUS上层逻辑时一定会找不同厂家的设备轮番测一遍。比如一台国产仪表、一台进口变频器、一台工业触摸屏每个厂商都可能给你带来措手不及的兼容性问题。把这个测试做在前面比现场出了问题再来补兼容要舒服得多。我个人在实际操作中的体会是MODBUS协议本身真的很简单容易被忽视的从来都不是协议本身而是物理层、字节序、地址映射这些协议外的细节。很多时候查了半天最后发现是根线接错了、是地址忘了减一、是校验位配置反了心里那种感觉我相信每一个调试过MODBUS的嵌入式工程师都懂。所以这篇笔记里我特意把这些琐碎但致命的细节都记录下来既是给自己留档也希望给正在同学同一条路上摸索的你省下一些宝贵的调试时间。最后再分享一个小技巧调试RS485总线上的MODBUS通信时千万不要相信看着应该没问题这种直觉。一把万用表、一个小型示波器、一个逻辑分析仪这三样东西基本能解决99%的串口通信疑难杂症。把工具用好、把日志打全MODBUS其实是一个调起来很痛快、也很有成就感的东西。