上一篇我们拆解了 Modbus RTU 报文,看懂了指令、回复、每个字节含义。但很多人到这里会卡壳:道理都懂,代码怎么写?
今天这篇,我给你一套最精简、最稳定、能直接用在网关里的 C 代码。没有复杂架构、不依赖任何库,复制进去就能跑,适合网关、嵌入式、485 采集项目。
一、本篇你能学到什么
- 怎么用代码拼出一条正确的 Modbus 指令(组包)
- 最标准的 CRC16 校验函数(复制就用)
- 怎么解析设备回复的数据
- 网关采集程序的最简框架
全部代码不到 100 行,工业项目量产级可用。
二、Modbus 采集代码 4 步走
网关采集程序永远是这 4 步:
- 拼指令(地址 + 功能码 + 寄存器 + 长度)
- 算 CRC(必须正确,否则设备不回复)
- 发指令 → 收回复
- 解析数据(转成真实温度 / 电流 / 电压)
下面直接给代码。
三、最重要:CRC16 函数(复制即用)
Modbus RTU 必须用这个函数,99% 的开源代码都用它。
c
运行
ini
// 标准 Modbus CRC16 函数(工业通用,复制即可)
unsigned short Modbus_CRC16(unsigned char *buf, int len)
{
unsigned short crc = 0xFFFF;
int i, j;
for (i = 0; i < len; i++) {
crc ^= buf[i];
for (j = 0; j < 8; j++) {
if (crc & 1) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
使用方法:传入指令前 6 字节 → 返回 2 字节 CRC低字节在前,高字节在后(Modbus 标准)
四、组包代码:拼一条读指令
我们要拼的指令:01 03 00 00 00 02 CRC CRC
c
运行
arduino
// 组包:读保持寄存器(功能码03)
void Modbus_Read_03(
unsigned char addr, // 从站地址 1~247
unsigned short reg, // 起始寄存器
unsigned short num, // 读几个
unsigned char *out_buf, // 输出:完整指令
int *out_len // 输出:指令长度
)
{
out_buf[0] = addr; // 1. 从站地址
out_buf[1] = 0x03; // 2. 功能码03
out_buf[2] = reg >> 8; // 3. 起始寄存器高8位
out_buf[3] = reg & 0xFF; // 4. 起始寄存器低8位
out_buf[4] = num >> 8; // 5. 寄存器数量高8位
out_buf[5] = num & 0xFF; // 6. 寄存器数量低8位
// 计算CRC
unsigned short crc = Modbus_CRC16(out_buf, 6);
out_buf[6] = crc & 0xFF; // CRC低字节
out_buf[7] = (crc >> 8) & 0xFF; // CRC高字节
*out_len = 8; // 指令总长度固定8字节
}
调用示例:
c
运行
ini
unsigned char buf[8];
int len;
Modbus_Read_03(1, 0x0000, 2, buf, &len);
拼出来就是:01 03 00 00 00 02 84 0A和我们上一篇讲的一模一样。
五、解析设备回复(代码超简单)
设备回复例子:01 03 04 0B B8 00 65 3C 36
解析代码:
c
运行
arduino
// 解析 03 功能码回复
int Modbus_Parse_03(
unsigned char *buf, // 设备回复数据
int len, // 回复长度
unsigned short *data // 输出解析后的寄存器值
)
{
// 基础校验
if (len < 5) return -1;
if (buf[1] != 0x03) return -2;
int data_len = buf[2]; // 数据长度
int i, idx = 3;
// 按寄存器解析(2字节一个)
for (i = 0; i < data_len / 2; i++) {
data[i] = (buf[idx] << 8) | buf[idx + 1];
idx += 2;
}
return 0;
}
解析后:
- data0 = 0xBB8 → 3000
- data1 = 0x65 → 101
再乘仪表倍率:3000 × 0.1 = 300.0V101 × 0.1 = 10.1A
采集完成!
六、网关采集最简框架(真实项目就是这样写)
c
运行
scss
// 网关采集流程(伪代码)
void Gateway_Collect_Task()
{
unsigned char cmd[8];
unsigned char resp[32];
unsigned short data[16];
int cmd_len, resp_len;
// 1. 组指令
Modbus_Read_03(1, 0x0000, 2, cmd, &cmd_len);
// 2. 串口发送
UART_Send(cmd, cmd_len);
// 3. 等待接收
resp_len = UART_Recv(resp, 32, 100); // 超时100ms
// 4. 解析
Modbus_Parse_03(resp, resp_len, data);
// 5. 转真实值
float voltage = data[0] * 0.1f;
float current = data[1] * 0.1f;
// 6. 上传MQTT
MQTT_Publish(voltage, current);
}
这就是网关最核心的采集逻辑!
七、新手最容易踩的 3 个坑
1. CRC 高低字节写反
设备直接不回复,一定要:低字节在前,高字节在后
2. 寄存器解析时高低字节反了
正确:data = (高8位 << 8) | 低8位
3. 轮询太快
485 总线必须加延时:50ms~100ms 间隔否则会丢包、乱码、冲突。
八、下篇预告
下一篇我们进入 MQTT 上云实战:把采集到的电压、电流,通过 MQTT 上传到阿里云 / 华为云,完成网关完整流程。
结尾
Modbus 开发其实一点都不难。看懂报文 + 会用 CRC + 会组包解析 = 能做 99% 的工业采集项目。