标签:#Modbus协议 #嵌入式开发 #RTU通信 #串口开发 #工业通信 #C语言实战

阅读时长:10分钟 | 适用人群:嵌入式开发者、工控开发、物联网设备开发、在校实训

前言

在工业控制、智能仪表、电力终端、物联网嵌入式设备开发中,Modbus是绝对的通用标准通信协议,没有之一。

绝大多数传感器、采集模块、PLC、工控终端、智能融合终端,均默认支持Modbus通信。很多嵌入式开发者看似会Modbus,实际项目中却频繁遇到:

  • 串口收发正常,但是设备始终不应答;

  • CRC校验错误导致数据丢包、解析异常;

  • 寄存器地址混淆、功能码乱用导致读写失败;

  • 不会自主实现协议,只会调用第三方库,无法排查底层问题。

本文带你从零手写Modbus RTU完整协议栈,吃透协议原理、CRC校验算法、主机读写逻辑、从机应答逻辑,全程纯C语言实现,无依赖、可直接移植到STM32、Linux、工控终端等所有嵌入式平台,完全适配实际项目开发。


一、Modbus协议核心基础(开发必懂)

1.1 协议分类与选型

Modbus主流分为三种,嵌入式工控95%场景使用Modbus RTU

  • Modbus RTU:二进制协议、效率高、无冗余字符、串口通信首选(RS485/RS232),工业设备通用;

  • Modbus ASCII:字符型协议、传输效率低、基本淘汰;

  • Modbus TCP:基于以太网,网口通信场景使用。

1.2 主从架构(核心机制)

Modbus为一问一答主从架构,不支持主动上报:

  • 主机(Master):主动发送读写指令(上位机、主控设备);

  • 从机(Slave):被动监听、解析指令、应答数据(传感器、仪表、子设备);

一个主机可挂载多个从机,依靠设备地址(SlaveAddr)区分设备。

1.3 常用功能码(项目高频使用)

实际开发只需掌握4个核心功能码,覆盖99%场景:

  • 03功能码:读取保持寄存器(最常用,读设备参数、采集数据);

  • 04功能码:读取输入寄存器(只读采集数据);

  • 06功能码:单个寄存器写入(设置设备参数);

  • 16功能码:多个寄存器批量写入。

1.4 Modbus RTU标准帧格式

主机发送帧(读寄存器): 【从机地址】【功能码】【起始地址高8位】【起始地址低8位】【读取个数高8位】【读取个数低8位】【CRC低8位】【CRC高8位】

从机应答帧: 【从机地址】【功能码】【数据长度】【数据1】【数据2】...【CRC校验】


二、核心底层:Modbus CRC16校验算法实现

CRC校验是Modbus通信最核心、最容易出错的环节,校验错误会直接导致通信失败。下面是工业标准Modbus CRC16算法,可直接商用。


#include <stdint.h> #include <string.h> // Modbus 标准CRC16校验 uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len) { uint16_t crc = 0xFFFF; uint8_t i, j; for(i = 0; i < len; i++) { crc ^= buf[i]; for(j = 0; j < 8; j++) { if(crc & 0x0001) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; }

重点注意:Modbus协议中CRC校验位为低字节在前、高字节在后,拼接帧时必须严格遵守,这是90%开发者的踩坑点。


三、Modbus主机(Master)完整实战代码

实现核心功能:03功能码读取从机保持寄存器数据,适配传感器、仪表数据采集。


// 构造Modbus主机读寄存器帧 void Modbus_Master_ReadReg(uint8_t slave_addr, uint16_t start_reg, uint16_t reg_num, uint8_t *tx_buf) { uint16_t crc; // 填充帧数据 tx_buf[0] = slave_addr; // 从机地址 tx_buf[1] = 0x03; // 03读保持寄存器 tx_buf[2] = (start_reg >> 8) & 0xFF; // 起始地址高8位 tx_buf[3] = start_reg & 0xFF; // 起始地址低8位 tx_buf[4] = (reg_num >> 8) & 0xFF; // 寄存器个数高8位 tx_buf[5] = reg_num & 0xFF; // 寄存器个数低8位 // CRC校验 crc = Modbus_CRC16(tx_buf, 6); tx_buf[6] = crc & 0xFF; // CRC低字节在前 tx_buf[7] = (crc >> 8) & 0xFF;// CRC高字节在后 }

主机使用示例


uint8_t tx_buffer[8] = {0}; // 读取1号从机,0000寄存器开始,2个寄存器数据 Modbus_Master_ReadReg(0x01, 0x0000, 0x0002, tx_buffer); // 此处调用串口发送函数 USART_SendData(tx_buffer, 8);


四、Modbus从机(Slave)数据解析应答实战

从机核心逻辑:接收主机帧 - 校验CRC - 解析功能码 - 组装应答帧,适配设备被上位机采集场景。


// Modbus从机帧解析与应答处理 void Modbus_Slave_Process(uint8_t *rx_buf, uint16_t rx_len, uint8_t slave_addr, uint8_t *tx_buf, uint16_t *tx_len) { uint16_t crc_recv, crc_calc; uint16_t reg_addr, reg_num; *tx_len = 0; // 1. 校验从机地址 if(rx_buf[0] != slave_addr) return; // 2. CRC校验校验 crc_recv = (rx_buf[rx_len-1] << 8) | rx_buf[rx_len-2]; crc_calc = Modbus_CRC16(rx_buf, rx_len - 2); if(crc_recv != crc_calc) return; // 3. 处理03功能码 读保持寄存器 if(rx_buf[1] == 0x03) { reg_addr = (rx_buf[2] << 8) | rx_buf[3]; reg_num = (rx_buf[4] << 8) | rx_buf[5]; // 组装应答帧 tx_buf[0] = slave_addr; tx_buf[1] = 0x03; tx_buf[2] = reg_num * 2; // 单寄存器2字节 // 模拟寄存器数据,实际项目替换为真实设备参数 for(int i = 0; i < reg_num; i++) { tx_buf[3 + i*2] = 0x00; tx_buf[4 + i*2] = 0x10 + i; } *tx_len = 3 + reg_num * 2; // 追加CRC校验 uint16_t crc = Modbus_CRC16(tx_buf, *tx_len); tx_buf[*tx_len] = crc & 0xFF; tx_buf[*tx_len+1] = (crc >> 8) & 0xFF; *tx_len += 2; } }


五、Modbus读写寄存器通用工具函数

5.1 16位寄存器数据高低字节合并


uint16_t Modbus_GetRegValue(uint8_t *data_buf, uint16_t index) { return (data_buf[index] << 8) | data_buf[index+1]; }

5.2 浮点数数据解析(工程常用)

很多工控设备通过两个寄存器传输浮点数,可直接使用如下解析函数:


float Modbus_BufToFloat(uint8_t *buf) { union { float f; uint8_t b[4]; }temp; temp.b[0] = buf[3]; temp.b[1] = buf[2]; temp.b[2] = buf[1]; temp.b[3] = buf[0]; return temp.f; }


六、项目开发高频踩坑总结(必看)

6.1 通信不通核心原因

  • CRC字节顺序错误:必须低字节在前,高字节在后

  • 波特率、数据位、校验位、停止位不匹配(双方串口参数必须完全一致);

  • 从机地址不对应,主机查询地址与设备地址不符;

  • 寄存器地址偏移:手册地址为十进制,代码未转换十六进制。

6.2 数据错乱问题

  • 未做帧头帧尾过滤,串口缓存脏数据导致解析错乱;

  • 高低字节颠倒,未按照大端模式解析寄存器数据;

  • 批量读取寄存器时,长度计算错误导致数据错位。

6.3 稳定性问题

  • 无超时机制,从机死机、掉线导致主机卡死;

  • 频繁收发未做帧缓冲区清零,数据叠加错乱;

  • 多线程/中断收发未加互斥锁,缓冲区数据冲突。


七、工程落地优化方案

  1. 增加超时重传机制:主机发送指令后,超时未收到应答自动重发,提升通信稳定性;

  2. 帧数据过滤机制:通过地址、功能码、长度三重校验,过滤干扰脏数据;

  3. 寄存器分区管理:将设备参数、采集数据、控制寄存器分区存储,方便读写管理;

  4. 异常帧丢弃:CRC错误、长度异常、地址不匹配帧直接丢弃,不做解析;

  5. 485收发切换优化:RS485通信增加微小延时,避免收发切换瞬间数据截断。


八、总结

Modbus RTU协议看似简单,却是工控、嵌入式、物联网开发的基本功。真正的工程开发,不能依赖现成库函数,必须掌握底层CRC校验、帧组装、数据解析逻辑,才能快速排查通信异常、适配各类非标设备、保障产品长期稳定运行。

本文实现的全套代码零依赖、可跨平台移植,支持STM32、Linux嵌入式、工控终端、各类单片机,可直接用于课程设计、项目开发、毕业设计、工程迭代。

后续将更新:Modbus异常码处理、16功能码批量写入、RS485硬件驱动适配、高稳定轮询框架完整实战。


源码福利:需要本文全套Modbus工程源码、完整寄存器框架、485驱动适配代码,可私信领取!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐