modbus_read_registers和modbus_write_register是直接入口但需配合modbus_new_tcp、modbus_connect、modbus_set_slave等正确配置,且须处理字节序转换、端口地址、心跳保活、超时设置及PLC功能启用等细节才能稳定运行。

modbus_read_registers 和 modbus_write_register 是最直接可用的入口,但光调这两个函数根本跑不起来——工业现场掉线、字节序错位、负数溢出、寄存器地址偏移错一位,全都会让你在上位机里看到“超时”或“非法数据地址”这种毫无意义的错误。
为什么 modbus_new_tcp 成功了却读不到数据?
常见现象是 modbus_connect 返回 0,但后续 modbus_read_registers 总返回 -1,modbus_strerror(errno) 却只报 “Connection reset by peer” 或空字符串。
- 真实原因往往不是网络不通,而是 PLC 没启用 Modbus TCP 功能(比如西门子 S7-1200 需手动使能“允许从远程伙伴使用 PUT/GET”并配置访问权限)
- IP 地址写对了,但端口不是 502:某些国产 PLC 默认用 503、8888 或自定义端口,得查手册确认
-
modbus_set_slave必须调用:即使 TCP 不显式用 slave ID,很多 PLC(如三菱 FX 系列)仍会校验 MBAP 头里的 unit ID 字段,默认为 0x01;若 PLC 配置为 0x02,不设就直接拒收 - 防火墙或杀软拦截了出站连接:Windows 上尤其常见,
modbus_connect成功只是建链,真正发 PDU 时被拦住
modbus_read_registers 读出来全是 0 或乱码?
这基本锁定在字节序和地址映射两处。Modbus 协议本身规定寄存器是大端(Big-Endian),但 x86/x64 主机默认小端,libmodbus 不自动翻转——你拿到的是原始字节流。
- 读 16 位保持寄存器:
uint16_t reg_values[10]接收后,必须用ntohs()转换每个元素,否则低高字节颠倒 - 读 32 位有符号整数(如 DT 寄存器):需连续读两个寄存器,再按高低位拼接:
int32_t val = (static_cast<int32_t>(ntohs(reg_values[0])) ,然后判断符号位扩展</int32_t> - 地址偏移陷阱:PLC 手册写的“D100 对应 4x00101”,实际传给
modbus_read_registers的起始地址是 100(不是 101,也不是 0x00101),因为 Modbus 地址从 0 开始编号,4x 前缀仅是标识类型
写负数寄存器老是写成正数?
Modbus 寄存器本质是无符号 16 位存储,负数靠补码表示。直接传 -123 给 modbus_write_register 会被截断为 65413(即 0xFF85),但如果你没做主机侧转换,PLC 解释时可能当正数处理。
- 写 16 位有符号值:先确保值在 [-32768, 32767] 区间,再强转为
uint16_t:uint16_t raw = static_cast<uint16_t>(val & 0xFFFF);</uint16_t> - 写 32 位有符号值:拆成两个 16 位,高位 =
(val >> 16) & 0xFFFF,低位 =val & 0xFFFF,再分别用htons()转为网络字节序后写入相邻地址 - 别信“PLC 自动识别符号位”:除非明确文档写支持 signed 类型,否则一律按无符号 + 补码逻辑处理
连接不稳定,轮询几分钟就断?
libmodbus 默认不保活,TCP 空闲超时(通常 2 小时)或中间设备(交换机、防火墙)主动踢连接,会导致下次读写直接失败。
立即学习“C++免费学习笔记(深入)”;
- 必须自己加心跳:每 30–60 秒发一次
modbus_read_input_registers(ctx, 0, 1, dummy_buf)(读一个无关寄存器),比modbus_get_socket+send更可靠 - 重连不能裸写
modbus_free+modbus_new_tcp:旧 ctx 的 socket 可能还在 CLOSE_WAIT 状态,要先modbus_close(ctx)再modbus_free(ctx),否则 fd 泄漏 - 超时参数要分层设:
modbus_set_response_timeout(ctx, 1, 0)(响应超时 1 秒) +modbus_set_byte_timeout(ctx, 0, 30000)(字节间超时 30ms),避免单字节卡死整条链路
ntohs 的那个 reg_values[0] 里。


















