avatar

松果工作室

欢迎光临

  • 首页
  • freeRTOS
  • LVGL
  • ESP
  • 开发手册
  • 快速笔记
  • 个人收藏
  • 考察日志
  • 工具
Home 一个好用的Modbus解析函数
文章

一个好用的Modbus解析函数

Posted 12 days ago Updated 12 days ago
By YCP
75~97 min read

一个好用的Modbus解析函数

包含03/10/04/06

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define MODBUS_TCP  1
#define MODBUS_RTU  2
#define MODBUS_MODE MODBUS_RTU

// modbusTCP格式
// MBAP头:事务ID(0x1234) + 协议ID(0x0000) + PDU长度(0x0006) + 单元ID(0x01)
// PDU:从机地址(0x01) + 功能码(0x03) + 起始地址(0x5000) + 寄存器数量(0x0002)

uint8_t addr = 1;

typedef struct {
    uint32_t testdata_0;
    uint32_t testdata_1;
    uint32_t testdata_2;
} modbus_testdata_t;

modbus_testdata_t testdata = {
        .testdata_0 = 0x99999999,
        .testdata_1 = 0x88888888,
        .testdata_2 = 0x77777777,
};


typedef enum {
    Com_AnswerErr0 = 0,    // 成功
    Com_AnswerErr1 = 1,    // 处理失败
    Com_AnswerErr2 = 2,    // 地址/功能码无效
    Com_AnswerErr3 = 3,    // 数量超限
    Com_NoAnswer = 0xFF    // 无响应
} modbus_err_t;

typedef struct {
    uint16_t Addr;         // 寄存器起始地址
    uint8_t len;          // 寄存器数量
    uint16_t pra;          // 参数(保留)
    uint8_t FunIndex;     // 对应处理函数在ModFunList中的索引
} modbus_coretab_t;

typedef modbus_err_t (*modbus_function_t)(uint8_t *buff, uint16_t pra);

typedef struct {
    uint8_t data[256];    // 数据缓冲区
    uint16_t len;        // 有效数据长度
} modbus_data_t;

void modbus_send(int pra, uint8_t *buff, uint16_t len) {
    // 对接发送函数
    //tcp_send(sock,buff, len);
    for (int i = 0; i < len; i++) {
        printf("%02X ", buff[i]);
    }
    printf("\n");
}

uint16_t modbus_crc16(const uint8_t *buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

/**********************************业务处理函数START**********************************/

typedef union {
    uint8_t bytes[4];
    float f;
    uint32_t u32;
} float_union_t;

//modbus功能函数
modbus_err_t ReadWriteData(uint8_t *buff, uint16_t pra) {
    float_union_t data;

    if ((pra & 0x8000) == 0x8000) {
        //可写入
        switch (pra & 0x0F) {
            case 0:
                printf("WriteElecData0\n");
                testdata.testdata_0 = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3];
                break;
            case 1:
                printf("WriteElecData1\n");
                testdata.testdata_1 = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3];
                break;
            default:
                printf("WriteElecData2\n");
                testdata.testdata_2 = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3];
                break;
        }
    } else {
        switch (pra & 0x0F) {
            case 0:
                data.u32 = testdata.testdata_0;
                printf("ReadElecData0: %08X\n", data.u32);
                break;
            case 1:
                data.u32 = testdata.testdata_1;
                printf("ReadElecData1: %08X\n", data.u32);
                break;
            default:
                data.u32 = testdata.testdata_2;
                printf("ReadElecData2: %08X\n", data.u32);
                break;
        }
    }

    *buff++ = data.bytes[3];
    *buff++ = data.bytes[2];
    *buff++ = data.bytes[1];
    *buff++ = data.bytes[0];

    return Com_AnswerErr0;
}

const modbus_coretab_t modbus_coretab_03[] = {
        //    地址        数量   参数(高四位写入低四位索引)  索引
        {0x0000, 2, 0x11, 0},
        {0x0004, 2, 0x11, 0},
};
const modbus_coretab_t modbus_coretab_04[] = {


};

// 处理函数列表(与寄存器表FunIndex对应)
modbus_function_t modbus_function[] = {
        ReadWriteData,
};
/**********************************业务处理函数END**********************************/


const uint8_t modbus_coretab_size_03 = sizeof(modbus_coretab_03) / sizeof(modbus_coretab_t);
const uint8_t modbus_coretab_size_04 = sizeof(modbus_coretab_04) / sizeof(modbus_coretab_t);


// 核心:Modbus连续读取处理函数
modbus_err_t RWModBusReg(modbus_data_t *modbus_data, uint8_t type) {
    uint8_t uc_RegCnt = modbus_data->data[5];    // 请求的寄存器数量(PDU第5字节)
    uint16_t ui_RegAddr = (modbus_data->data[2] << 8) | modbus_data->data[3];  // 起始地址(PDU第2-3字节)
    uint8_t read_cnt = 0;                   // 已读取的寄存器数量

    // 1. 数量校验(Modbus标准最大125个)
    if (uc_RegCnt == 0 || uc_RegCnt > 125) {
        return Com_AnswerErr3;  // 数量无效
    }

    const modbus_coretab_t *modbus_coretab = NULL;
    uint8_t modbus_coretab_size = 0;
    if (type == 0x03) {
        modbus_coretab = modbus_coretab_03;
        modbus_coretab_size = modbus_coretab_size_03;
    } else if (type == 0x04) {
        modbus_coretab = modbus_coretab_04;
        modbus_coretab_size = modbus_coretab_size_04;
    }

    // 2. 初始化响应头(从机地址+功能码)
    modbus_data->data[0] = addr;  // 复用请求中的从机地址
    modbus_data->data[1] = type;             // 功能码(0x03)
    modbus_data->len = 3;                   // 初始长度:头3字节(地址+功能码+字节计数)

    // 3. 遍历寄存器表处理连续读取
    while (read_cnt < uc_RegCnt) {
        uint16_t current_addr = ui_RegAddr + read_cnt;  // 当前需要读取的地址(动态更新)
        uint8_t found = 0;                              // 是否找到匹配的表项

        for (uint8_t i = 0; i < modbus_coretab_size; i++) {  // 遍历所有表项找匹配
            const modbus_coretab_t *tab = &modbus_coretab[i];
            // 计算当前地址在表项中的偏移(相对于表项起始地址)
            uint16_t offset = current_addr - tab->Addr;
            if (offset < 0 || offset >= tab->len) continue;  // 地址不在当前表项范围内

            // 计算当前表项可读取的最大寄存器数量(避免越界)
            uint8_t max_read = tab->len - offset;
            uint8_t actual_read = (uc_RegCnt - read_cnt) < max_read ? (uc_RegCnt - read_cnt) : max_read;

            // 调用处理函数读取数据(填充到响应缓冲区,每个寄存器2字节)
            modbus_err_t err = modbus_function[tab->FunIndex](modbus_data->data + modbus_data->len, tab->pra);
            if (err != Com_AnswerErr0) return err;

            // 更新已读取数量和数据长度
            read_cnt += actual_read;
            modbus_data->len += actual_read * 2;
            found = 1;
            break;  // 找到匹配表项并处理后,跳出for循环继续下一个地址
        }

        if (!found) {  // 关键修改:未找到表项时填充0值
            // 填充0x0000(两个字节表示一个寄存器的0值)
            modbus_data->data[modbus_data->len] = 0x00;
            modbus_data->data[modbus_data->len + 1] = 0x00;
            modbus_data->len += 2;  // 数据长度增加2字节(一个寄存器)
            read_cnt += 1;      // 已读取数量+1
        }
    }

    // 4. 填充字节计数(总数据字节数=已读数量×2)
    modbus_data->data[2] = read_cnt * 2;

    return Com_AnswerErr0;  // 已完全读取
}

// 核心:Modbus写寄存器处理函数(支持0x06写单个、0x10写多个)
modbus_err_t WriteModBusReg(modbus_data_t *modbus_data) {
    uint8_t func_code = modbus_data->data[1];  // 功能码(0x06/0x10)
    uint16_t reg_addr = (modbus_data->data[2] << 8) | modbus_data->data[3];  // 起始地址
    uint16_t reg_cnt, byte_cnt;
    uint8_t data_offset;

    // 1. 参数解析与校验
    if (func_code == 0x06) {  // 写单个寄存器
        reg_cnt = 1;
        byte_cnt = 2;
        data_offset = 4;  // 数据从PDU第4字节开始
    } else if (func_code == 0x10) {  // 写多个寄存器
        reg_cnt = (modbus_data->data[4] << 8) | modbus_data->data[5];  // 寄存器数量
        byte_cnt = modbus_data->data[6];                                // 字节计数
        data_offset = 7;  // 数据从PDU第7字节开始
        if (byte_cnt != reg_cnt * 2 || reg_cnt > 123) {  // Modbus标准限制
            return Com_AnswerErr3;  // 数量超限或数据长度错误
        }
    } else {
        return Com_AnswerErr2;  // 不支持的功能码
    }

    // 2. 遍历寄存器表验证地址并执行写入
    const modbus_coretab_t *coretab = modbus_coretab_03;  // 使用0x03功能码的寄存器表
    uint8_t coretab_size = modbus_coretab_size_03;
    uint16_t remaining = reg_cnt;

    while (remaining > 0) {
        uint8_t found = 0;
        for (uint8_t i = 0; i < coretab_size; i++) {
            const modbus_coretab_t *tab = &coretab[i];
            if (reg_addr < tab->Addr || reg_addr >= tab->Addr + tab->len) continue;  // 地址不匹配
            if (!(tab->pra & 0x10)) return Com_AnswerErr2;

            // 计算当前表项可处理的寄存器数量
            uint8_t process_cnt = (tab->Addr + tab->len - reg_addr) < remaining
                                  ? (tab->Addr + tab->len - reg_addr)
                                  : remaining;

            // 调用写处理函数(pra | 0x8000表示写操作)
            modbus_err_t err = modbus_function[tab->FunIndex](
                    modbus_data->data + data_offset,  // 指向写入数据
                    tab->pra | 0x8000);  // 写操作标志
            if (err != Com_AnswerErr0) return err;

            // 更新状态
            remaining -= process_cnt;
            reg_addr += process_cnt;
            data_offset += process_cnt * 2;  // 数据偏移后移(每个寄存器2字节)
            found = 1;
            break;
        }
        if (!found) return Com_AnswerErr2;  // 地址无效
    }

    // 3. 构造响应(Modbus标准格式)
    modbus_data->len = 6;  // 响应固定6字节(地址+功能码+起始地址+数量)
    modbus_data->data[2] = (reg_addr - reg_cnt) >> 8;  // 起始地址高字节
    modbus_data->data[3] = (reg_addr - reg_cnt) & 0xFF; // 起始地址低字节
    modbus_data->data[4] = reg_cnt >> 8;                // 数量高字节
    modbus_data->data[5] = reg_cnt & 0xFF;              // 数量低字节

    return Com_AnswerErr0;
}

void modbus_parse(int pra, uint8_t *data, uint16_t len) {
    modbus_data_t modbus_data;
    uint16_t pdu_len;

#if MODBUS_MODE == MODBUS_TCP
    // --- TCP 模式 ---
    memcpy(modbus_data.data, data + 6, len - 6);  // 去掉 MBAP
    pdu_len = len - 6;

#elif MODBUS_MODE == MODBUS_RTU
    // --- RTU 模式 ---
    if (len < 4) return;  // 最少 地址+功能码+CRC
    uint16_t crc_calc = modbus_crc16(data, len - 2);
    uint16_t crc_recv = (data[len - 2]) | (data[len - 1] << 8);
    if (crc_calc != crc_recv) {
        printf("CRC ERROR %04X %04X\n", crc_calc, crc_recv);
        return;
    } else{
        printf("CRC OK %04X %04X\n", crc_calc, crc_recv);
    }
    memcpy(modbus_data.data, data, len - 2);  // 去掉 CRC
    pdu_len = len - 2;
#endif

    // 调用已有逻辑
    modbus_data.len = pdu_len;
    modbus_err_t err;
    switch (modbus_data.data[1]) {
        case 0x03:
        case 0x04:
            err = RWModBusReg(&modbus_data, modbus_data.data[1]);
            break;
        case 0x06:
        case 0x10:
            err = WriteModBusReg(&modbus_data);
            break;
        default:
            err = Com_AnswerErr2;
            break;
    }

    // 构造响应
    if (err == Com_AnswerErr0) {
#if MODBUS_MODE == MODBUS_TCP
        uint8_t senddata[300];
        senddata[0] = data[0]; senddata[1] = data[1]; // 事务ID
        senddata[2] = 0x00; senddata[3] = 0x00;      // 协议ID
        senddata[4] = 0x00; senddata[5] = modbus_data.len;
        memcpy(senddata + 6, modbus_data.data, modbus_data.len);
        modbus_send(pra, senddata, modbus_data.len + 6);

#elif MODBUS_MODE == MODBUS_RTU
        uint8_t senddata[300];
        memcpy(senddata, modbus_data.data, modbus_data.len);
        uint16_t crc = modbus_crc16(senddata, modbus_data.len);
        senddata[modbus_data.len] = crc & 0xFF;
        senddata[modbus_data.len + 1] = crc >> 8;
        modbustcp_send(pra, senddata, modbus_data.len + 2);
#endif
    }
}


#define TEST 0x06

int main() {
#if (TEST == 0x01)
    // 03 功能码连读测试 modbusTCP
    uint8_t request_data[] = {
            0x12, 0x34, 0x00, 0x00, 0x00, 0x06,  // MBAP头(7字节)
            0x00, 0x03, 0x00, 0x00, 0x00, 0x02         // PDU(6字节)
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数

#elif (TEST == 0x02)
    // 03 功能码连读测试 modbusRTU
    uint8_t request_data[] = {
            //01 03 00 00 00 06 C5 C8
            0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0xC5, 0xC8
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数

#elif (TEST == 0x03)
    // 10 功能码连写测试 modbusTCP
    uint8_t request_data[] = {
            0x12, 0x34, 0x00, 0x00, 0x00, 0x06,  // MBAP头(7字节)
            0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x11, 0x11, 0x11, 0x11
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数
    printf("10 功能码连写测试 modbusTCP %08X\n", testdata.testdata_1);
#elif (TEST == 0x04)
    // 10 功能码连写测试 modbusRTU
        uint8_t request_data[] = {
                // 00 10 00 00 00 02 04 01 00 01 00 F7 3F
                0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x01, 0x00, 0x01, 0x00, 0xF7, 0x3F
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数
    printf("10 功能码连写测试 modbusRTU %08X\n", testdata.testdata_1);


#elif (TEST == 0x05)
    // 06 功能码写测试 modbusTCP
    uint8_t request_data[] = {
            0x12, 0x34, 0x00, 0x00, 0x00, 0x06,  // MBAP头(7字节)
            0x00, 0x06, 0x00, 0x00, 0x00, 0x02         // PDU(6字节)
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数
    printf("06 功能码写测试 modbusTCP %08X\n", testdata.testdata_1);
#elif (TEST == 0x06)
    // 06 功能码写测试 modbusRTU
    uint8_t request_data[] = {
            //00 06 00 00 00 22 08 02
            0x00, 0x06, 0x00, 0x00, 0x00, 0x22, 0x08, 0x02
    };
    modbus_parse(0, request_data, sizeof(request_data));  // 调用解析函数
    printf("06 功能码写测试 modbusRTU %08X\n", testdata.testdata_1);

#endif
    return 0;
}



坑和笔记
Others
License:  CC BY 4.0
Share

Further Reading

Sep 27, 2025

一个好用的Modbus解析函数

一个好用的Modbus解析函数 包含03/10/04/06 #include <stdio.h> #include <stdint.h> #include <string.h> #

Dec 23, 2024

其他笔记

EC800K AT连接移远云 配置过程 # 配置产品信息(初次连接需配置) AT+QIOTCFG="productinfo","pxxxxt","cDVTxxxxxxxxWGVB" # 连接开发者中心 AT+QIOTREG=1 # 查询当前连接状态(+QIOTSTATE: 8为正常) AT+QI

Jun 21, 2024

环形滤波算法

#include <stdio.h> #include <stdlib.h> #define BUFFER_SIZE 10 // 缓冲区大小 #define THRESHOLD 180

OLDER

ESP32(十) BLE OTA

NEWER

Recently Updated

  • 一个好用的Modbus解析函数
  • ESP32(十) BLE OTA
  • ESP32(九) BLE GATTS
  • LVGL(四) 动画
  • LVGL(三) 对象中创建对象

Trending Tags

LVGL WCH Linux Elec freeRTOS STM ESP Flutter Others SwiftUI

Contents

©2025 松果工作室. Some rights reserved.

Using the Halo theme Chirpy