
通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载本指南以 libmodbus 官方 API 文档 modbus_write_and_read_registers 为主体结合仓库源码与单元测试系统讲解modbus_write_and_read_registers()的协议原理、报文格式、参数约束、错误处理与完整实战用法。读完本文你将掌握如何用一次 Modbus 事务原子地完成批量写保持寄存器 批量读保持寄存器并理解功能码 0x17 在客户端与服务端两侧的实现细节。一、函数总览一次事务完成写后读modbus_write_and_read_registers()是 libmodbus 提供的一条组合式 API它先向远程设备从站的保持寄存器区写入一批数据紧接着在同一事务中读取另一批保持寄存器整个过程只占用一次请求 / 一次响应对应 Modbus 功能码0x17Write/Read Multiple Registers。与先用 modbus_write_registers 写、再用 modbus_read_registers 读的两段式方案相比单事务方案的优势在于减少总线往返只发送一条请求帧、接收一条响应帧节省串口 / 网络的交互开销更强的原子性写入与读取在同一条报文里完成中间不会穿插其他设备的请求尤其适合 RTU 半双工总线上的多从站场景。从源码看该函数在 src/modbus.c 中实现声明位于 src/modbus.h功能码常量MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17定义于 src/modbus.h。二、函数原型与参数详解原型声明int modbus_write_and_read_registers( modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb, uint16_t *dest );参数语义参数含义ctx由modbus_new_tcp()、modbus_new_rtu()等创建并已modbus_connect()的 Modbus 上下文write_addr远程设备上待写入保持寄存器的起始地址write_nb待写入的寄存器个数必须 ≥ 1src待写入数据的源数组按uint16_t16 位字存放read_addr远程设备上待读取保持寄存器的起始地址read_nb待读取的寄存器个数必须 ≥ 1dest读取结果存放数组按 16 位字回填缓冲区分配要求官方文档明确指出调用前必须为dest分配足够的内存至少read_nb * sizeof(uint16_t)字节文档中的nb * sizeof(uint16_t)即指本次读取的寄存器个数。read_nb个 16 位值会按顺序写入dest[0] ... dest[read_nb-1]缓冲区不足将导致越界写属于调用方责任。src数组同理需包含至少write_nb个uint16_t元素。三、请求 / 响应报文格式功能码 0x17理解该 API 的底层行为先看报文结构。请求构建代码位于 src/modbus.c先用build_request_basis()装入功能码、读取地址与读取数量再依次追加写入地址、写入数量、字节计数与待写数据。请求 PDU协议数据单元偏移长度内容01功能码0x171–22读取起始地址read_addr高字节在前3–42读取数量read_nb高字节在前5–62写入起始地址write_addr高字节在前7–82写入数量write_nb高字节在前91字节计数byte_count write_nb * 2102×write_nb待写入数据每个寄存器按高字节在前输出/* 对应源码src/modbus.c#L1900-L1910 */ req[req_length] write_addr 8; req[req_length] write_addr 0x00ff; req[req_length] write_nb 8; req[req_length] write_nb 0x00ff; byte_count write_nb * 2; req[req_length] byte_count; for (i 0; i write_nb; i) { req[req_length] src[i] 8; req[req_length] src[i] 0x00FF; }响应 PDU偏移长度内容01功能码0x1711字节计数 read_nb * 222×read_nb读取到的寄存器数据高字节在前响应解析在 src/modbus.c 完成跳过header_lengthTCP 为 6 字节 MBAP 头RTU 为 1 字节从站地址后从字节 1 取字节计数随后逐对拼装回 16 位值offset ctx-backend-header_length; for (i 0; i rc; i) { dest[i] (rsp[offset 2 (i 1)] 8) | rsp[offset 3 (i 1)]; }check_confirmation()也会对 0x17 的响应做一致性校验请求中的read_nbreq[offset3..4]必须与响应字节计数rsp[offset1] / 2相等否则按报文错误处理见 src/modbus.c。注意整个 PDU 之上还会叠加传输层封装——TCP 场景增加 6 字节 MBAP 头事务标识、协议标识、长度、从站地址RTU 场景在报文前后增加 CRC16 校验因此同样的调用在两种后端下走的是同一套逻辑。四、源码级实现原理1. 入口参数校验实现的第一步是防御性检查src/modbus.cctx、src、dest任一为 NULL或write_nb 1、read_nb 1直接返回 -1 并置errno EINVAL。2. 数量上限检查随后分别校验写入与读取数量是否超过 0x17 功能码的容量上限详见下文第五节超限时置errno EMBXILVAL并返回 -1src/modbus.c。3. 发送与接收请求构造完成后调用send_msg()发送成功rc 0后调用_modbus_receive_msg()等待确认帧再经check_confirmation()校验响应与请求的对应关系全部通过后回填dest最后返回实际读取到的寄存器个数。4. 服务端从站侧处理逻辑在从站侧modbus_reply()对 0x17 的处理位于 src/modbus.c其执行顺序与客户端语义严格对应——先写后读从请求中解析nb读取数量、address_write写入地址、nb_write写入数量与nb_write_bytes校验1 ≤ nb_write ≤ MODBUS_MAX_WR_WRITE_REGISTERS、1 ≤ nb ≤ MODBUS_MAX_WR_READ_REGISTERS且nb_write_bytes nb_write * 2否则返回非法数据值0x03异常校验写入地址与读取地址是否落在从站modbus_mapping_t映射表范围内通过start_registers与nb_registers换算越界则返回非法数据地址0x02异常先执行写入把请求数据按高字节在前写入映射表中tab_registers的对应位置再执行读取从映射表读取出数据拼装响应帧字节计数字段为nb 1。这一顺序保证响应中返回的寄存器值反映的是写入完成之后的最终状态与协议语义一致。五、边界与限制可读写寄存器数量上限0x17 功能码对单次写入与读取的数量有独立上限定义于 src/modbus.h常量值含义MODBUS_MAX_WR_WRITE_REGISTERS121单次事务最多写入的寄存器个数MODBUS_MAX_WR_READ_REGISTERS125单次事务最多读取的寄存器个数对照其他 API 的上限src/modbus.hMODBUS_MAX_READ_REGISTERS为 125、MODBUS_MAX_WRITE_REGISTERS为 123。0x17 的写入上限121略低于纯写功能码123原因是请求帧中还要额外携带读取地址与读取数量字段必须满足 src/modbus.c 中MAX_MESSAGE_LENGTH 260的报文缓冲约束。实操建议当批量数据超过上述上限时调用方需自行分片chunk多次调用该函数或改用纯写 / 纯读 API 完成。六、返回值与错误处理返回值成功返回读取到的寄存器个数即read_nb失败返回 -1 并设置errno。错误码官方文档列出的错误码及对应的源码依据如下errno触发条件源码依据EINVALctx为 NULLsrc或dest为 NULL或write_nb/read_nb小于 1src/modbus.cEMBXILVAL请求的寄存器数量过多write_nb 121或read_nb 125src/modbus.c另外若从站以异常帧回应如地址越界返回 0x02、数据非法返回 0x03客户端会将异常码映射为errno后返回 -1。相关映射宏见 src/modbus.hEMBXILADD非法数据地址、EMBXILVAL非法数据值。unit-test-client.c 中即通过该方式验证了越界地址场景调用返回 -1 且errno EMBXILADD。建议错误处理时用modbus_strerror(errno)获取可读错误描述实现见 src/modbus.cEMBXILADD对应 Illegal data addressEMBXILVAL对应 Illegal data value。七、完整可运行示例客户端下面是一个可直接编译运行的 TCP 客户端示例先向从站地址 100 写入 3 个寄存器再从地址 0 读取 5 个寄存器。#include stdio.h #include errno.h #include modbus.h #define SERVER_IP 127.0.0.1 #define SERVER_PORT 1502 #define SLAVE_ID 1 int main(void) { modbus_t *ctx NULL; uint16_t tab_write[3] { 0x1111, 0x2222, 0x3333 }; uint16_t tab_read[5] { 0, 0, 0, 0, 0 }; /* 至少 read_nb 个元素 */ int rc; int i; /* TCP 上下文串口场景改用 modbus_new_rtu(/dev/ttyUSB0, 9600, N, 8, 1) */ ctx modbus_new_tcp(SERVER_IP, SERVER_PORT); if (ctx NULL) { fprintf(stderr, Unable to allocate libmodbus context\n); return -1; } if (modbus_set_slave(ctx, SLAVE_ID) -1) { fprintf(stderr, Invalid slave ID\n); modbus_free(ctx); return -1; } if (modbus_connect(ctx) -1) { fprintf(stderr, Connection failed: %s\n, modbus_strerror(errno)); modbus_free(ctx); return -1; } /* 可选开启调试输出观察收发报文 modbus_set_debug(ctx, TRUE); */ rc modbus_write_and_read_registers(ctx, 100, /* write_addr */ 3, /* write_nb */ tab_write, /* 待写数据 */ 0, /* read_addr */ 5, /* read_nb */ tab_read); /* 读结果存放区 */ if (rc -1) { fprintf(stderr, modbus_write_and_read_registers failed: %s\n, modbus_strerror(errno)); modbus_close(ctx); modbus_free(ctx); return -1; } printf(Read %d registers:\n, rc); for (i 0; i rc; i) { printf( reg[%d] 0x%04X\n, i, tab_read[i]); } modbus_close(ctx); modbus_free(ctx); return 0; }示例要点tab_read预分配 5 个元素恰好满足read_nb * sizeof(uint16_t)的内存要求返回值rc既是成功标志也是实际读取到的寄存器个数可用于循环打印失败时通过modbus_strerror(errno)输出具体原因EINVAL / EMBXILVAL / EMBXILADD 等若要调试协议报文可调用modbus_set_debug(ctx, TRUE)对应文档 modbus_set_debug发送与接收帧会以十六进制打印。八、测试用例验证仓库单元测试对 0x17 的正常路径与异常路径均有覆盖可直接作为行为规范的参考正常读写路径tests/unit-test-client.c 中客户端把tab_rp_registers清零后的缓冲区写入从UT_REGISTERS_ADDRESS 1开始的寄存器同时从UT_REGISTERS_ADDRESS读取相同数量的寄存器。由于写入起始地址比读取起始地址偏移 1 个寄存器读取结果中第一个寄存器保留了原始值UT_REGISTERS_TAB[0]其余均被写成了 0——这恰好验证了先写后读的执行顺序越界地址异常路径tests/unit-test-client.c 分别以地址 0 和超范围地址调用该函数断言返回 -1 且errno EMBXILADD此外random-test-client.c、bandwidth-client.c 与 proxy-test-client.c 也都在各自场景中调用了该 API覆盖随机压力与代理转发modbus_proxy场景下的 0x17 报文透传。九、相关文档与 API本函数官方文档modbus_write_and_read_registers纯读 APImodbus_read_registers单寄存器写 APImodbus_write_register批量写 APImodbus_write_registers从站侧应答入口modbus_reply完整 API 索引docs/index.md使用小结modbus_write_and_read_registers()适合写入后立即校验/读取回读值的原子操作场景调用时务必确保dest缓冲区足够、数量不超过 121写/ 125读的上限并依据返回值与errno做好错误分支处理即可在 TCP 与 RTU 两种后端下安全复用。赞分享通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载相关推荐libmodbus 浮点寄存器写入实战modbus_set_float_badc 函数深度解析libmodbus 浮点寄存器写入实战modbus_set_float_badc 函数深度解析 modbus_set_float_badc 是 libmodb通信嵌入式物联网OpenCore EFI 搭建不再靠手抄OpCore Simplify 四步上手指南OpenCore EFI 搭建不再靠手抄OpCore Simplify 四步上手指南 OpCore Simplify 是一个把 OpenCore EFI 制作通信嵌入式物联网libmodbus 浮点寄存器读取modbus_get_float_badc 的 BADC 字节序解析与实战libmodbus 浮点寄存器读取modbus_get_float_badc 的 BADC 字节序解析与实战 modbus_get_float_badc 是通信嵌入式物联网上一篇PyTorch 分布式队列分诊规范Distributed Triage Rubric子 Oncall 路由、模块分类信号与置信度校准详解下一篇马斯克突然开源Grok 2.5中国AI企业成xAI终极挑战者创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考