modbus_build_request函数原型

免费免费技术资料

这篇内容可直接阅读,适合用于基础学习和搜索引流。

modbus_build_request函数原型缩略图
本文目录
  1. 1. 一、函数原型
  2. 2. 二、功能码与参数对应关系
  3. 3. 三、请求帧结构
  4. 4. 四、基础使用示例
  5. 5. 五、手动发送请求与接收响应
  6. 6. 六、自定义功能码实现
  7. 7. 七、RTU模式下的使用
  8. 8. 八、调试与抓包分析
  9. 9. 九、相关函数
  10. 10. 十、常见问题

modbus_build_request是libmodbus库中的一个底层函数,用于手动构建Modbus请求帧。在大多数情况下,开发者使用modbus_read_registers、modbus_write_bit等高层函数即可完成Modbus通讯,但在某些特殊场景(如自定义功能码、调试协议、实现非标准功能)下,需要手动构建请求帧。本文详细介绍modbus_build_request函数的原型、参数含义、使用方法以及完整的示例代码。

一、函数原型

int modbus_build_request(
    modbus_t *ctx,      // Modbus上下文对象
    int function,       // Modbus功能码
    int addr,           // 起始地址
    int nb,             // 数量/数值
    uint8_t *req        // 输出的请求帧缓冲区
);

1.1 参数详解

参数类型说明
ctxmodbus_t *Modbus上下文对象,由modbus_new_tcp()或modbus_new_rtu()创建
functionintModbus功能码,如0x01(读线圈)、0x03(读保持寄存器)等
addrint起始地址(0-based),对于写单线圈/寄存器则是目标地址
nbint读取数量,或写入的值(功能码05时为0xFF00/0x0000,功能码06时为寄存器值)
requint8_t *输出缓冲区,用于存储构建的请求帧,大小至少为MODBUS_TCP_MAX_ADU_LENGTH(260字节)

1.2 返回值

  • 成功:返回请求帧的长度(字节数)
  • 失败:返回-1,并设置errno

二、功能码与参数对应关系

不同功能码下,addr和nb参数的含义不同:

功能码名称addr含义nb含义
0x01读线圈起始线圈地址读取数量(1-2000)
0x02读离散输入起始输入地址读取数量(1-2000)
0x03读保持寄存器起始寄存器地址读取数量(1-125)
0x04读输入寄存器起始寄存器地址读取数量(1-125)
0x05写单个线圈线圈地址0xFF00=吸合,0x0000=断开
0x06写单个寄存器寄存器地址写入的值(0-65535)
0x0F写多个线圈起始线圈地址写入数量(1-1968)※需手动构建
0x10写多个寄存器起始寄存器地址写入数量(1-123)※需手动构建

三、请求帧结构

modbus_build_request构建的请求帧格式取决于使用的协议模式:

3.1 Modbus TCP模式(MBAP头 + PDU)

// TCP请求帧结构(共12字节,以读保持寄存器为例)
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
// |事务ID高|事务ID低|协议ID高|协议ID低|长度高  |长度低  |从站地址|功能码  |地址高  |地址低  |数量高  |数量低  |
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
// | 0x00   | 0x01   | 0x00   | 0x00   | 0x00   | 0x06   | 0x01   | 0x03   | 0x00   | 0x00   | 0x00   | 0x0A   |
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+

// MBAP头(7字节):
// - 事务标识符(2字节):用于匹配请求和响应
// - 协议标识符(2字节):Modbus协议为0x0000
// - 长度(2字节):后续字节数(从站地址+PDU)
// - 从站地址(1字节):Unit ID

// PDU(协议数据单元):
// - 功能码(1字节)
// - 数据(N字节)

3.2 Modbus RTU模式(地址 + PDU + CRC)

// RTU请求帧结构(共8字节,以读保持寄存器为例)
// +--------+--------+--------+--------+--------+--------+--------+--------+
// |从站地址|功能码  |地址高  |地址低  |数量高  |数量低  |CRC低   |CRC高   |
// +--------+--------+--------+--------+--------+--------+--------+--------+
// | 0x01   | 0x03   | 0x00   | 0x00   | 0x00   | 0x0A   | 0xC5   | 0xCD   |
// +--------+--------+--------+--------+--------+--------+--------+--------+

// RTU帧 = 从站地址(1字节) + PDU + CRC16(2字节)
// CRC低字节在前,高字节在后

四、基础使用示例

4.1 构建读保持寄存器请求

#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>

int main() {
    modbus_t *ctx;
    uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
    int req_len;
    int i;

    // 创建TCP上下文
    ctx = modbus_new_tcp("192.168.1.100", 502);
    if (ctx == NULL) {
        fprintf(stderr, "创建上下文失败\n");
        return -1;
    }

    // 设置从站地址
    modbus_set_slave(ctx, 1);

    // 构建读保持寄存器请求:地址0,读取10个
    req_len = modbus_build_request(ctx, 0x03, 0, 10, req);
    if (req_len == -1) {
        fprintf(stderr, "构建请求失败:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 打印请求帧(十六进制)
    printf("请求帧长度:%d字节\n", req_len);
    printf("请求帧内容:");
    for (i = 0; i < req_len; i++) {
        printf("%02X ", req[i]);
    }
    printf("\n");

    // 输出示例(TCP模式):
    // 请求帧长度:12字节
    // 请求帧内容:00 01 00 00 00 06 01 03 00 00 00 0A

    modbus_free(ctx);
    return 0;
}

4.2 构建写单个线圈请求

// 构建写单个线圈请求:地址0,吸合(0xFF00)
req_len = modbus_build_request(ctx, 0x05, 0, 0xFF00, req);
printf("写线圈请求:");
for (i = 0; i < req_len; i++) printf("%02X ", req[i]);
printf("\n");
// 输出:00 01 00 00 00 06 01 05 00 00 FF 00

// 构建写单个线圈请求:地址0,断开(0x0000)
req_len = modbus_build_request(ctx, 0x05, 0, 0x0000, req);
// 输出:00 01 00 00 00 06 01 05 00 00 00 00

4.3 构建写单个寄存器请求

// 构建写单个寄存器请求:地址0,写入1234
req_len = modbus_build_request(ctx, 0x06, 0, 1234, req);
printf("写寄存器请求:");
for (i = 0; i < req_len; i++) printf("%02X ", req[i]);
printf("\n");
// 输出:00 01 00 00 00 06 01 06 00 00 04 D2
// 1234 = 0x04D2

五、手动发送请求与接收响应

构建请求帧后,可以使用modbus_send和modbus_receive手动发送和接收:

#include <stdio.h>
#include <modbus.h>

int main() {
    modbus_t *ctx;
    uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
    uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
    int req_len, rsp_len;

    ctx = modbus_new_tcp("192.168.1.100", 502);
    modbus_set_slave(ctx, 1);

    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "连接失败:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 1. 构建读保持寄存器请求
    req_len = modbus_build_request(ctx, 0x03, 0, 10, req);
    printf("请求长度:%d\n", req_len);

    // 2. 发送请求
    int sent = modbus_send(ctx, req, req_len);
    if (sent != req_len) {
        fprintf(stderr, "发送失败\n");
        modbus_close(ctx);
        modbus_free(ctx);
        return -1;
    }

    // 3. 接收响应
    rsp_len = modbus_receive(ctx, rsp);
    if (rsp_len == -1) {
        fprintf(stderr, "接收失败:%s\n", modbus_strerror(errno));
        modbus_close(ctx);
        modbus_free(ctx);
        return -1;
    }

    // 4. 解析响应(TCP模式,跳过MBAP头7字节)
    printf("响应长度:%d\n", rsp_len);
    printf("功能码:0x%02X\n", rsp[7]);
    
    if (rsp[7] == 0x03) {
        // 读保持寄存器响应
        // rsp[7] = 功能码
        // rsp[8] = 字节数
        // rsp[9...] = 寄存器数据(每个寄存器2字节,高字节在前)
        int byte_count = rsp[8];
        int reg_count = byte_count / 2;
        printf("寄存器数量:%d\n", reg_count);
        for (int i = 0; i < reg_count; i++) {
            uint16_t value = (rsp[9 + i*2] << 8) | rsp[9 + i*2 + 1];
            printf("寄存器%d:%d\n", i, value);
        }
    } else if (rsp[7] & 0x80) {
        // 异常响应(功能码最高位为1)
        printf("异常响应,异常码:0x%02X\n", rsp[8]);
    }

    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

六、自定义功能码实现

modbus_build_request的一个重要用途是实现设备厂商自定义的功能码。以下示例实现一个自定义功能码0x43(读取设备序列号):

// 自定义功能码:读取设备序列号
// 请求:功能码0x43,子功能码0x01
// 响应:功能码0x43,子功能码0x01,序列号长度,序列号数据

int read_device_serial(modbus_t *ctx, uint8_t *serial, int max_len) {
    uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
    uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
    int req_len, rsp_len;
    
    // 注意:modbus_build_request只能构建标准功能码
    // 对于自定义功能码,需要手动构建PDU部分
    
    // 手动构建TCP请求帧
    // MBAP头
    req[0] = 0x00; req[1] = 0x01;  // 事务ID
    req[2] = 0x00; req[3] = 0x00;  // 协议ID
    req[4] = 0x00; req[5] = 0x04;  // 长度(从站地址+功能码+子功能码)
    req[6] = 0x01; // 从站地址(需与modbus_set_slave设置一致,libmodbus无公开getter)
    // PDU
    req[7] = 0x43;  // 自定义功能码
    req[8] = 0x01;  // 子功能码:读序列号
    
    req_len = 9;
    
    // 发送请求
    modbus_send(ctx, req, req_len);
    
    // 接收响应
    rsp_len = modbus_receive(ctx, rsp);
    if (rsp_len == -1) return -1;
    
    // 解析响应(跳过MBAP头7字节)
    if (rsp[7] == 0x43 && rsp[8] == 0x01) {
        int serial_len = rsp[9];
        if (serial_len > max_len) serial_len = max_len;
        memcpy(serial, &rsp[10], serial_len);
        return serial_len;
    }
    
    return -1;
}

// 使用示例
int main() {
    modbus_t *ctx = modbus_new_tcp("192.168.1.100", 502);
    modbus_connect(ctx);
    
    uint8_t serial[32];
    int len = read_device_serial(ctx, serial, sizeof(serial));
    if (len > 0) {
        printf("设备序列号:");
        for (int i = 0; i < len; i++) printf("%02X ", serial[i]);
        printf("\n");
    }
    
    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

七、RTU模式下的使用

#include <modbus.h>
#include <modbus-rtu.h>

int main() {
    modbus_t *ctx;
    uint8_t req[MODBUS_RTU_MAX_ADU_LENGTH];
    uint8_t rsp[MODBUS_RTU_MAX_ADU_LENGTH];
    int req_len, rsp_len;

    // 创建RTU上下文
    ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
    modbus_set_slave(ctx, 1);
    modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    modbus_connect(ctx);

    // 构建读线圈请求(RTU模式会自动添加CRC)
    req_len = modbus_build_request(ctx, 0x01, 0, 8, req);
    
    printf("RTU请求长度:%d\n", req_len);
    printf("RTU请求帧:");
    for (int i = 0; i < req_len; i++) printf("%02X ", req[i]);
    printf("\n");
    // 输出(8字节):01 01 00 00 00 08 3D C9
    // 最后2字节3D C9是CRC16校验

    // 发送并接收
    modbus_send(ctx, req, req_len);
    rsp_len = modbus_receive(ctx, rsp);
    
    // RTU响应帧:从站地址 + 功能码 + 数据 + CRC
    if (rsp[1] == 0x01) {
        int byte_count = rsp[2];
        printf("线圈状态字节数:%d\n", byte_count);
        for (int i = 0; i < byte_count; i++) {
            printf("字节%d:0x%02X\n", i, rsp[3 + i]);
        }
    }

    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

八、调试与抓包分析

modbus_build_request在协议调试中非常有用,可以打印出完整的请求帧用于分析:

// 调试函数:打印请求帧详情
void print_request_detail(modbus_t *ctx, int function, int addr, int nb) {
    uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
    int len = modbus_build_request(ctx, function, addr, nb, req);
    
    printf("=== 请求帧详情 ===\n");
    printf("功能码:0x%02X\n", function);
    printf("地址:%d (0x%04X)\n", addr, addr);
    printf("数量/值:%d (0x%04X)\n", nb, nb);
    printf("帧长度:%d字节\n", len);
    
    // TCP模式解析MBAP头
    if (len >= 7) {
        uint16_t trans_id = (req[0] << 8) | req[1];
        uint16_t proto_id = (req[2] << 8) | req[3];
        uint16_t length = (req[4] << 8) | req[5];
        uint8_t slave_id = req[6];
        printf("事务ID:%d\n", trans_id);
        printf("协议ID:%d\n", proto_id);
        printf("长度字段:%d\n", length);
        printf("从站地址:%d\n", slave_id);
    }
    
    printf("原始数据:");
    for (int i = 0; i < len; i++) printf("%02X ", req[i]);
    printf("\n\n");
}

// 使用
print_request_detail(ctx, 0x03, 0, 10);   // 读保持寄存器
print_request_detail(ctx, 0x05, 0, 0xFF00); // 写线圈
print_request_detail(ctx, 0x06, 5, 100);   // 写寄存器

九、相关函数

函数功能
modbus_send()发送请求帧
modbus_receive()接收响应帧
modbus_reply()从站模式下回复请求
modbus_read_registers()高层函数:读保持寄存器(内部调用build_request)
modbus_write_bit()高层函数:写单个线圈
modbus_write_register()高层函数:写单个寄存器
modbus_set_slave()设置从站地址
modbus_strerror()获取错误描述

十、常见问题

  • 返回-1:检查ctx是否有效,功能码是否受支持,缓冲区是否足够大
  • 请求帧长度不对:确认协议模式(TCP/RTU),TCP有7字节MBAP头,RTU有2字节CRC
  • 设备无响应:检查从站地址是否正确,功能码和地址是否在设备支持范围内
  • CRC错误:RTU模式下modbus_build_request会自动计算CRC,不要手动添加
  • 事务ID不匹配:TCP模式下每次调用会自动递增事务ID,无需手动管理
  • 自定义功能码:modbus_build_request不支持自定义功能码,需手动构建PDU
  • 0x0F/0x10功能码:modbus_build_request仅支持01-06功能码,写多个线圈/寄存器需手动构建完整帧(含数据字节)
  • modbus_get_slave不存在:libmodbus无公开的从站地址getter函数,需自行记录设置的值

modbus_build_request是libmodbus库中理解Modbus协议帧结构的关键函数。通过它,开发者可以深入了解Modbus请求的构建过程,实现自定义功能码、协议调试和底层优化。在实际开发中,建议优先使用高层函数(如modbus_read_registers),只有在特殊需求下才使用modbus_build_request手动构建请求帧。

技术术语(共 7 个)—— 点击展开
Modbus RTU基于串行链路的Modbus协议,使用二进制编码和CRC校验
Modbus TCP基于以太网的Modbus协议变体,使用TCP/IP传输
RS485工业常用的差分串行通信标准,支持多点通信
功能码Modbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
寄存器Modbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— 点击展开
来源 Modbus中文网(modbus.cn) —— 国内领先的Modbus通信协议技术社区 分类 Modbus编程开发 字数 8155 字 · 阅读约 21 分钟 更新 2026-08-22 永久链接 https://www.modbus.cn/11787.html
推荐工具:Modbus调试助手 微信小程序
Modbus中文网官方推出的Modbus调试工具,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 无需安装,微信搜索「Modbus调试助手」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
📝 作者声明
本文由 Modbus中文网技术团队 原创撰写,内容基于实际项目案例与技术文档,力求为读者提供准确、实用的参考信息。
把这篇资料用于真实项目?

进入工具中心进行报文解析、CRC 校验和设备调试,或提交需求获取选型与接入建议。

工程师会员

把这篇文章变成可执行的调试资料

开通后可使用高级报文解析、资料包下载、代码示例、工程案例和优先技术支持,适合真实项目交付。

高级工具不限次
资料包与代码包
完整工程案例库
优先技术支持入口

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注