网关开发从入门到落地(05)Modbus 最简 C 代码实现:组包 + CRC + 解析(直接移植可用)

上一篇我们拆解了 Modbus RTU 报文,看懂了指令、回复、每个字节含义。但很多人到这里会卡壳:道理都懂,代码怎么写?

今天这篇,我给你一套最精简、最稳定、能直接用在网关里的 C 代码。没有复杂架构、不依赖任何库,复制进去就能跑,适合网关、嵌入式、485 采集项目。


一、本篇你能学到什么

  1. 怎么用代码拼出一条正确的 Modbus 指令(组包)
  2. 最标准的 CRC16 校验函数(复制就用)
  3. 怎么解析设备回复的数据
  4. 网关采集程序的最简框架

全部代码不到 100 行,工业项目量产级可用。


二、Modbus 采集代码 4 步走

网关采集程序永远是这 4 步:

  1. 拼指令(地址 + 功能码 + 寄存器 + 长度)
  2. 算 CRC(必须正确,否则设备不回复)
  3. 发指令 → 收回复
  4. 解析数据(转成真实温度 / 电流 / 电压)

下面直接给代码。


三、最重要: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% 的工业采集项目。


相关推荐
神奇小汤圆1 小时前
聊聊Java中的of
后端
foggyprojects1 小时前
SQL 模板写到这里,为什么 Mongo 也可以用同一种方式接进来
后端
卷无止境1 小时前
零信任架构与传统边界安全:一场关于"信任"的根本分歧
后端
风止何安啊1 小时前
我一个前端仔,居然用 Python 搞起了 AI?从零到一,撸了个 AI 聊天框小 demo
前端·人工智能·后端
逍遥运德1 小时前
PostgreSQL ---【序列】用法详解
后端·sql·postgresql
回家路上绕了弯2 小时前
AgentScope Harness 深度实战:让Java智能体从“Demo可用”走向“生产可用”
后端
卷心菜投手ovo2 小时前
RAG 为什么引用总是对不上?
后端·github
foggyprojects2 小时前
动态 SQL 模板里,权限条件为什么要注入而不是散落在业务代码里
后端
无风听海2 小时前
ASP.NET Core .NET 10 错误响应体系全景:从 BadRequest 到编译器基础设施
后端·asp.net·.net