
通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载导读本文以 libmodbus 官方文档 docs/modbus_read_input_registers.md 为主线系统讲解modbus_read_input_registers()这一核心 API如何通过 Modbus 功能码 0x04 一次批量读取从站Slave/Server的输入寄存器涵盖函数签名、参数约束、返回值与错误码、RTU/TCP 两种后端下的请求帧构造、底层源码实现以及配套测试用例。读完本文你将能在 Linux、macOS、FreeBSD 或 Windows 上编写出可靠的输入寄存器采集程序并理解其与保持寄存器holding registers读取在协议语义上的差异。函数签名与头文件modbus_read_input_registers()声明在公共头文件 src/modbus.h 中属于 libmodbus 公开 API 的一员int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);使用前需#include modbus.h并通过 modbus_new_rtu() 或 modbus_new_tcp() / modbus_new_tcp_pi() 创建modbus_t *ctx上下文再调用 modbus_connect() 建立连接。函数在 src/modbus.c 中实现。功能概述一次读取多个输入寄存器该函数读取远程设备中起始地址为addr、数量为nb的输入寄存器并将读取结果以 16 位字word的形式存入dest数组。它使用 Modbus 功能码0x04Read Input Registers与读取保持寄存器的 0x03 相对。在 Modbus 协议的历史语义中输入寄存器Input Registers只读通常承载设备的测量值、状态量如温度、电压、传感器采样由设备本身维护客户端只能读不能写保持寄存器Holding Registers可读写用于存放可配置参数或可更新的数据区。官方文档也明确指出随着工业实践演进如今更常见的是只使用保持寄存器。因此modbus_read_input_registers()主要用于对接那些仍然遵循输入寄存器只读传统布局的老式设备或特定仪表。参数说明参数含义约束ctxlibmodbus 上下文指针不能为 NULL否则返回 -1 并置errno EINVALaddr起始寄存器地址0 起始需在设备地址范围内越界由从站返回异常响应nb要读取的寄存器数量必须 ≥ 1 且 ≤MODBUS_MAX_READ_REGISTERS125dest存放读取结果的uint16_t数组必须分配至少nb * sizeof(uint16_t)字节缓冲区责任在调用方dest数组必须由调用者预先分配且容量足够容纳全部寄存器这是文档强调的硬性前提Thedestarray must be allocated with at leastnb * sizeof(uint16_t)bytes. It is the callers responsibility to ensure the buffer is large enough to hold all the registers to be read.即uint16_t dest[nb];或malloc(nb * sizeof(uint16_t))均可但绝不能分配不足。返回值成功返回实际读取到的寄存器个数即nb失败返回 -1并通过errno报告具体错误可用 modbus_strerror() 获取可读的错误描述。错误码详解官方文档列出了两种明确的错误场景此外还有网络层面的错误会透传自底层send_msg/_modbus_receive_msgEINVALctx或dest为 NULL或nb小于 1。见 src/modbus.c 中的显式校验EMBXILVAL请求的寄存器数量超出上限nb MODBUS_MAX_READ_REGISTERS。EMBXILVAL在 src/modbus.h 中定义为MODBUS_ENOBASE MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE即非法数据值协议异常对应的本地错误码。另外需注意函数内部虽会对nb做本地校验但addr是否越界属于从站侧裁决的范畴——若地址非法从站会回送MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS0x02异常帧此时函数同样返回 -1errno被置为EMBXILADD见 src/modbus.h这一点在单元测试中也有覆盖下文详述。源码级原理剖析1. 参数校验与功能码分发入口函数在 src/modbus.c 中的逻辑非常清晰先校验ctx、dest非空且nb在[1, MODBUS_MAX_READ_REGISTERS]范围内随后把实际工作委托给静态函数read_registers()并传入功能码MODBUS_FC_READ_INPUT_REGISTERS值为 0x04定义于 src/modbus.hint modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) { int status; if (ctx NULL || dest NULL) { errno EINVAL; return -1; } if (nb 1 || nb MODBUS_MAX_READ_REGISTERS) { if (ctx-debug) { fprintf(stderr, ERROR Too many input registers requested (%d %d)\n, nb, MODBUS_MAX_READ_REGISTERS); } errno EMBXILVAL; return -1; } status read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, dest); return status; }可见启用调试模式modbus_set_debug()时越界请求会在 stderr 打印详细提示方便排障。2. 底层 read_registers建帧、发送、收帧、解析核心的read_registers()位于 src/modbus.c是 0x03 与 0x04 两个功能码共用的实现这正是两个 API 行为对称的原因static int read_registers(modbus_t *ctx, int function, int addr, int nb, uint16_t *dest) { int rc; int req_length; uint8_t req[_MIN_REQ_LENGTH]; uint8_t rsp[MAX_MESSAGE_LENGTH]; if (nb MODBUS_MAX_READ_REGISTERS) { ... errno EMBXILVAL; return -1; } req_length ctx-backend-build_request_basis(ctx, function, addr, nb, req); rc send_msg(ctx, req, req_length); if (rc 0) { unsigned int offset; int i; rc _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); if (rc -1) return -1; rc check_confirmation(ctx, req, rsp, rc); if (rc -1) return -1; offset ctx-backend-header_length; for (i 0; i rc; i) { /* shift reg hi_byte to temp OR with lo_byte */ dest[i] (rsp[offset 2 (i 1)] 8) | rsp[offset 3 (i 1)]; } } return rc; }关键点有三个请求帧由后端backend构造ctx-backend-build_request_basis是抽象接口RTU 与 TCP 各有实现响应校验check_confirmation()负责比对功能码、地址与字节数是否一致任何异常含从站回送的协议异常帧都会在此转化为 -1 与对应errno字节序转换Modbus 线上传输为大端序高位字节在前解析时通过(高字节 8) | 低字节还原为处理器本机字节序存入dest。这一点非常重要dest中的值已经是主机序的 16 位整数可直接参与运算无需再手工翻转。3. 两种后端的请求帧构造RTU 后端串口 / RS-485在 src/modbus-rtu.c 中构造的 PDU 为[从站地址(1)][功能码(1)][起始地址高(1)][起始地址低(1)][数量高(1)][数量低(1)]之后由_modbus_rtu_send追加 CRC16 校验static int _modbus_rtu_build_request_basis( modbus_t *ctx, int function, int addr, int nb, uint8_t *req) { assert(ctx-slave ! -1); req[0] ctx-slave; /* 从站地址 */ req[1] function; /* 0x04 */ req[2] addr 8; /* 起始地址高字节 */ req[3] addr 0x00ff; /* 起始地址低字节 */ req[4] nb 8; /* 数量高字节 */ req[5] nb 0x00ff; /* 数量低字节 */ return _MODBUS_RTU_PRESET_REQ_LENGTH; }注意 RTU 后端要求先通过 modbus_set_slave() 设置从站地址assert(ctx-slave ! -1)即此约束。TCP 后端在 src/modbus-tcp.c 中会在 PDU 前追加 MBAP 头事务 ID、协议 ID、长度、单元 ID并自动递增事务 IDt_idmodbus_new_tcp_pi()创建的 PI 后端src/modbus-tcp.c复用同一套建帧逻辑只是地址解析方式不同。两种 TCP 后端的 ADU 上限均为 260 字节src/modbus.h。4. 服务端从站侧如何应答 0x04在服务端程序中modbus_reply()对MODBUS_FC_READ_HOLDING_REGISTERS与MODBUS_FC_READ_INPUT_REGISTERS走同一分支src/modbus.c通过is_input区分取数来源case MODBUS_FC_READ_HOLDING_REGISTERS: case MODBUS_FC_READ_INPUT_REGISTERS: { unsigned int is_input (function MODBUS_FC_READ_INPUT_REGISTERS); int start_registers is_input ? mb_mapping-start_input_registers : mb_mapping-start_registers; int nb_registers is_input ? mb_mapping-nb_input_registers : mb_mapping-nb_registers; uint16_t *tab_registers is_input ? mb_mapping-tab_input_registers : mb_mapping-tab_registers; ... int mapping_address address - start_registers; if (nb 1 || MODBUS_MAX_READ_REGISTERS nb) { /* 回送 ILLEGAL_DATA_VALUE 异常 */ } else if (mapping_address 0 || (mapping_address nb) nb_registers) { /* 回送 ILLEGAL_DATA_ADDRESS 异常 */ } else { rsp_length ctx-backend-build_response_basis(sft, rsp); rsp[rsp_length] nb 1; /* 数据字节数 寄存器数 × 2 */ for (i mapping_address; i mapping_address nb; i) { rsp[rsp_length] tab_registers[i] 8; /* 高字节 */ rsp[rsp_length] tab_registers[i]; /* 低字节 */ } } } break;这段代码印证了两点协议行为输入寄存器数据存放在映射结构modbus_mapping_t的tab_input_registers数组中src/modbus.h服务端用 modbus_mapping_new() 或 modbus_mapping_new_start_address() 创建映射时需为其分配空间响应中每个寄存器占 2 字节、大端序发送与客户端解析逻辑高字节 8 | 低字节正好一一对应。5. 上限约束的来源MODBUS_MAX_READ_REGISTERS定义为 125src/modbus.h其依据是 Modbus 应用协议规范Modbus_Application_Protocol_V1_1b.pdf第 6 章第 3 节单次读寄存器请求的数量上限为 1125。这一限制源自串行链路 256 字节 ADU 的约束253 字节 PDU见 src/modbus.h。因此无论 RTU 还是 TCP一次modbus_read_input_registers()最多只能取 125 个寄存器超过即触发EMBXILVAL读取更多数据需分段多次调用。完整可运行示例下面是一个 RTU 客户端完整采集输入寄存器的示例可直接作为工程模板#include stdio.h #include stdlib.h #include stdint.h #include modbus.h #define NB_REGISTERS 10 int main(void) { modbus_t *ctx; uint16_t dest[NB_REGISTERS]; int rc, i; /* 1. 创建 RTU 上下文设备、波特率、校验、数据位、停止位 */ ctx modbus_new_rtu(/dev/ttyUSB0, 9600, N, 8, 1); if (ctx NULL) { fprintf(stderr, Unable to create the libmodbus context\n); return -1; } /* 2. 指定从站地址RTU 必需 */ modbus_set_slave(ctx, 1); /* 3. 建立连接打开串口 */ if (modbus_connect(ctx) -1) { fprintf(stderr, Connection failed: %s\n, modbus_strerror(errno)); modbus_free(ctx); return -1; } /* 4. 读取从地址 0 开始的 10 个输入寄存器 */ rc modbus_read_input_registers(ctx, 0, NB_REGISTERS, dest); if (rc -1) { fprintf(stderr, modbus_read_input_registers failed: %s\n, modbus_strerror(errno)); } else { printf(Read %d input registers:\n, rc); for (i 0; i rc; i) { printf(reg[%d] 0x%04X (%u)\n, i, dest[i], dest[i]); } } /* 5. 清理 */ modbus_close(ctx); modbus_free(ctx); return 0; }要点回顾nb取值 1125dest容量nb * sizeof(uint16_t)TCP 场景只需将创建上下文换成modbus_new_tcp(192.168.1.100, 502)并去掉modbus_set_slave()的强制要求TCP 下从站地址经 MBAP 单元 ID 传递默认 255见 modbus_set_slave()返回值是实际读取的寄存器数判断成功请与nb比较或检查是否-1。服务端配套如何提供输入寄存器数据若需模拟从站供上述客户端读取服务端需在映射中初始化tab_input_registers对应功能码 0x04 的数据源。仓库测试 tests/unit-test-server.c 展示了这一模式mb_mapping modbus_mapping_new(UT_BITS_ADDRESS, UT_BITS_NB, UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB, UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX, UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB); ... /* Initialize values of INPUT REGISTERS */ for (i 0; i UT_INPUT_REGISTERS_NB; i) { mb_mapping-tab_input_registers[i] UT_INPUT_REGISTERS_TAB[i]; }随后在modbus_receive()/modbus_reply()循环中即可响应客户端的 0x04 请求。测试用例验证libmodbus 自带完整的单元测试可直接验证本文所述行为运行tests/unit-tests.sh或参考 tests/Makefile.am正常读取客户端 tests/unit-test-client.c 从地址0x108读取UT_INPUT_REGISTERS_NB1 个输入寄存器断言返回值等于nb并逐元素比对期望值{0x000A}定义于 tests/unit-test.h.inrc modbus_read_input_registers( ctx, UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB, tab_rp_registers); printf(1/1 modbus_read_input_registers: ); ASSERT_TRUE(rc UT_INPUT_REGISTERS_NB, FAILED (nb points %d)\n, rc);越界地址tests/unit-test-client.c 验证读取地址 0 及UT_INPUT_REGISTERS_ADDRESS nb超出映射范围时返回 -1 且errno EMBXILADD从站回送非法数据地址异常超量请求tests/unit-test-client.c 验证请求MODBUS_MAX_READ_REGISTERS 1即 126个寄存器时返回 -1 且errno EMBXILVAL与文档 Errors 一节完全对应代理proxy链路tests/proxy-test-client.c 通过modbus_proxy()转发后读取输入寄存器验证经网关代理后功能码 0x04 的数据路径依然正确。常见问题与注意事项为什么返回 -1 但 errno 是EMBXILADD这表示请求已到达从站但从站认为地址越界并回送了异常帧属于协议级错误而非本地参数错误可通过 modbus_strerror() 打印 Illegal data address 类描述。如何读取超过 125 个输入寄存器分段调用例如读取 300 个寄存器可拆成 3 次125 125 50每次用不同的addr与nb组合注意dest的偏移。dest中的值是大端还是小端已由 libmodbus 统一转换为处理器本机字节序直接使用即可只有当你自行构造原始帧modbus_send_raw_request()时才需要关心线上大端序。串口上的从站地址RTU 后端建帧时assert(ctx-slave ! -1)忘记调用 modbus_set_slave() 会在调试构建中直接断言失败。输入寄存器与保持寄存器如何选择优先使用 modbus_read_registers()0x03仅当设备手册明确将数据放在输入寄存器区域只读测量区时才使用本文的 0x04 读取。参见modbus_read_input_bits() 读取输入位功能码 0x02modbus_read_registers() 读取保持寄存器功能码 0x03modbus_write_register() 写单个保持寄存器modbus_write_registers() 写多个保持寄存器公共 API 声明与常量定义src/modbus.h核心实现src/modbus.c单元测试tests/unit-test-client.c、tests/unit-test-server.c、tests/unit-test.h.in赞分享通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载相关推荐libmodbus源码架构分析深入理解Modbus协议实现原理libmodbus源码架构分析深入理解Modbus协议实现原理 libmodbus是一个功能强大的开源Modbus协议库它提供了完整的Modbus协议栈实现通信嵌入式物联网NetworkX 地理空间网络分析实战指南GeoPandas、PySAL、momepy 与 OSMnx 生态协同NetworkX 地理空间网络分析实战指南GeoPandas、PySAL、momepy 与 OSMnx 生态协同 地理空间数据点、线、面的网络化建模是 N通信嵌入式物联网TiXL 上下文变量读取算子 GetVec3Var 完全指南原理、用法与源码剖析TiXL 上下文变量读取算子 GetVec3Var 完全指南原理、用法与源码剖析 本篇技术指南围绕 TiXL开源实时动态图形创作软件算子库 Lib.flo音视频图形学桌面应用上一篇ClaudeComputerCommander 计算机健康检查Linux 只读诊断命令集与解读指南下一篇Obsidian CSS自定义实战指南3个阶段实现界面优化与效率飞跃创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考