- 1. 你为什么要看这篇文章
- 2. 总览对比表
- 3. FreeModbus —— 嵌入式裸机的标准答案
- 4. libmodbus —— Linux 工控机的不二之选
- 5. pymodbus —— Python 生态的事实标准
- 6. NModbus —— .NET 平台的唯一严肃选择
- 7. jamod —— Java 生态的「前辈」,但建议你别用
- 8. modbus-tk —— 快速原型的好帮手
- 9. QtModbus —— Qt 开发者的原生方案
- 10. 新手推荐路径
- 11. 经典组合方案
- 12. 避坑清单
- 13. 选型决策速查
你为什么要看这篇文章
这个问题在 Stack Overflow、control.com、CSDN 上被问了几千次——「我该用哪个开源 Modbus 库?」。每次回答都是零散的几句,没人系统对比过。中文社区更惨,搜出来的帖子一半在贴十年前 NModbus4 的代码,连 NModbus 主仓库已经迁移到 3.0.x 了都不知道。
我花了三天时间把这七个协议栈的仓库翻了一遍,跑了每个的最小示例,确认了维护状态。下面是结果。如果你赶时间,直接跳到「新手推荐路径」那一段,选你的平台对应的库,开工。
总览对比表
| 协议栈 | 语言 | 许可 | Star 数 | 维护状态 | 主站 | 从站 | RTU | TCP | 适合谁 |
|---|---|---|---|---|---|---|---|---|---|
| FreeModbus | C | BSD | ~1.5k | 低活跃 | 付费 | ✅ | ✅ | ✅ | STM32 裸机/RTOS |
| libmodbus | C | LGPL v2.1+ | ~3.5k | 活跃 | ✅ | ✅ | ✅ | ✅ | Linux 工控机/网关 |
| pymodbus | Python | BSD | ~2.2k | 非常活跃 | ✅ | ✅ | ✅ | ✅ | 上位机/测试脚本 |
| NModbus | C# | MIT | ~700 | 活跃 | ✅ | ✅ | ✅ | ✅ | .NET/WinForm 上位机 |
| jamod | Java | Apache 2.0 | ~300 | 停滞 | ✅ | ✅ | ✅ | ✅ | Java 遗留系统 |
| modbus-tk | Python | LGPL | ~500 | 低活跃 | ✅ | ✅ | ✅ | ✅ | 快速原型 |
| QtModbus | C++ | LGPL/GPL | Qt 内置 | 活跃 | ✅ | ✅ | ❌ | ✅ | Qt 跨平台应用 |
Star 数是我在 2026 年 6 月抓的,大致数量级对,具体数字你去 GitHub 看一眼就知道。
FreeModbus —— 嵌入式裸机的标准答案
GitHub: https://github.com/cwalter-at/freemodbus
FreeModbus 是 Christian Walter 写的,一个奥地利嵌入式工程师。这东西在 STM32 圈子里的地位相当于 Linux 内核里的 ext4——你不是不能用别的,但用它是最不会出错的。
**代码体积**:编译完大约 6-12KB ROM,取决于你开了哪些功能码和传输模式。RAM 开销几百字节,主要是那几块数据缓冲区和事件队列。基本上是个 Cortex-M0 就能跑。
**功能码支持**:03(读保持寄存器)、04(读输入寄存器)、06(写单个寄存器)、16(写多个寄存器)、01(读线圈)、02(读离散输入)、05(写单个线圈)、15(写多个线圈)。还有 17(报告从站 ID)。注意,没有 22(掩码写寄存器)和 23(读写多个寄存器),需要的话得自己加。
**传输模式**:RTU、ASCII、TCP 都支持,通过编译宏开关。
**主站/从站**:这是 FreeModbus 最容易被误解的地方。官方仓库只开源了从站代码,主站是付费的。GitHub 上有不少社区魔改版加了主站功能,比如 armink 的 FreeModbus_Slave-Master-RTT-STM32,开源的,质量不错,但不是官方维护。用这些第三方版本的时候注意:超时处理和重试逻辑实现得很粗糙,别直接上生产。
**代码示例**——STM32 上初始化一个 RTU 从站,地址 1,波特率 9600,无校验:
#include "mb.h"
int main(void) {
eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);
eMBEnable();
while (1) {
eMBPoll();
}
}
就这三行。`eMBPoll()` 里跑着整个协议栈的状态机,非阻塞的,你需要在定时器中断里喂 3.5T 超时信号给它。这个 3.5T 定时器是 FreeModbus 移植的头号坑,下面会专门讲。
**已知问题**: – 官方仓库更新极慢,最后的大版本 v1.6 已经好几年没动了,bug 修不修看缘分 – 只支持单串口,一个协议栈实例只能绑定一个 UART,多串口场景要改代码 – TCP 模式用的是 lwIP raw API,不是 socket,这意味着 FreeRTOS+LWIP 环境下能用,但 Linux 上根本跑不了 TCP 模式 – 没有浮点数处理辅助函数,大小端转换自己写
**文档**:只有一份 API 文档,HTML 格式的,够用但谈不上好。中文社区教程倒是多,CSDN 搜一下一堆。
libmodbus —— Linux 工控机的不二之选
GitHub: https://github.com/stephane/libmodbus
维护者 Stéphane Raimbault,法国人。libmodbus 是 C 语言写的最成熟的 Modbus 库,没有之一。3.5k star 不是白来的。
这个库的设计思路跟 FreeModbus 完全不同。FreeModbus 是为资源受限的 MCU 设计的,用回调函数和状态机。libmodbus 是 POSIX 风格的,阻塞 API,`modbus_read_registers()` 调用会一直等到数据回来或超时。写工控机程序的人喜欢这种风格——简单直接,不用管什么状态机。
**功能码支持**:几乎所有。01/02/03/04/05/06/07/0F/10/11/16/17,还支持 22(掩码写)和 23(读写多个)。有 `modbus_set_float()` / `modbus_get_float()` 可以直接按 IEEE 754 处理浮点数,支持 ABCD/DCBA/BADC/CDAB 四种字节序。
**传输模式**:RTU + TCP 全支持。同一套 API,`modbus_new_rtu()` 创建串口上下文,`modbus_new_tcp()` 创建 TCP 上下文,之后读写接口完全一样。
**主站/从站**:都支持。TCP 从站用 `modbus_tcp_listen()` + `modbus_tcp_accept()`,和写 Linux socket 服务端一个套路。
**代码示例**——读从站 1 的保持寄存器 0x0000,返回 1 个 16 位值:
#include <modbus.h>
#include <stdio.h>
int main() {
modbus_t *ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
modbus_set_slave(ctx, 1);
modbus_connect(ctx);
uint16_t val;
modbus_read_registers(ctx, 0, 1, &val);
printf("Register 0 = %dn", val);
modbus_close(ctx);
modbus_free(ctx);
}
**已知问题**: – `modbus_connect()` 的语义容易踩坑。RTU 模式下调的是 `open()` 打开串口,TCP 模式下调的是 `connect()` 连服务器。TCP 从站模式不需要也不能调 `modbus_connect()`,而是用 `modbus_tcp_listen()`。搞混了连不上还不知道为什么 – `modbus_free()` 之后指针不会自动置空。如果你在循环里重连,先 free 再 new,不手动 `ctx = NULL` 的话,偶尔会指向已释放的内存,表现为随机崩溃或疯狂重连。这个我吃过亏 – 线程不安全。一个 `modbus_t*` 上下文不能被两个线程同时操作,得自己加锁 – Windows 下串口名是 `\\.\COM10` 这种格式,Linux 是 `/dev/ttyUSB0`,跨平台编译要注意
**文档**:官网 libmodbus.org 有一套 manual page 风格的文档,外加 mkdocs 在线版。清晰但没什么示例项目,就 `tests/` 目录下那几个。
pymodbus —— Python 生态的事实标准
GitHub: https://github.com/pymodbus-dev/pymodbus
pymodbus 是目前维护最活跃的 Modbus 库,没有之一。pymodbus-dev 组织接手后,3.x 重写了异步架构,支持 asyncio,还在持续发版。2024 年底最新的稳定版是 v3.6.x。
**功能码**:全支持。01/02/03/04/05/06/15/16/22/23,连 43(读设备识别)都有。
**传输模式**:RTU + TCP + TLS。TLS 支持是 pymodbus 3.x 新加的,可以直接跑 `modbus+tls://`。ASCII 模式也支持但在 3.x 里被标记为废弃。
**主站/从站**:都支持,而且支持同步和异步两种 API。从站可以跑一个完整的模拟器——`pymodbus.simulator` 能从 JSON 配置文件启动一个带多块寄存器的虚拟设备,上位机开发调试的时候非常好用。
**代码示例**——同步模式读一个保持寄存器:
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient(port="/dev/ttyUSB0", baudrate=9600)
client.connect()
rr = client.read_holding_registers(address=0, count=1, slave=1)
print(rr.registers[0])
client.close()
异步版稍微多几行,用 `async with` 上下文管理器,配合 asyncio 跑。
**已知问题**: – 2.x 到 3.x 的 API 变化巨大。`pymodbus.client.sync.ModbusSerialClient` 变成了 `pymodbus.client.ModbusSerialClient`,`read_holding_registers` 返回值从 `ReadHoldingRegistersResponse` 变成了 `ModbusResponse`。如果你在网上搜到的示例代码用的是 `from pymodbus.client.sync import …`,那肯定是 2.x 的,别直接抄 – 同步模式和异步模式不能混用。同一个进程里如果已经跑了 asyncio event loop,同步客户端会阻塞事件循环导致超时 – RTU 模式下串口超时配置比较敏感。默认的 3 秒超时对于低速设备(4800bps)可能不够,需要自己调到 5-10 秒 – pip 安装时 `pip install pymodbus`,不是 `pymodbus3` 或 `pymodbus2`,别装错了
**文档**:readthedocs 上有完整的文档,`examples/` 目录下有几十个示例脚本。没有中文文档,但英文很清晰。
NModbus —— .NET 平台的唯一严肃选择
GitHub: https://github.com/NModbus/NModbus
NModbus 的历史有点绕。最早的 NModbus 是 Google Code 上(对,就是那个 Google Code)的一个项目,后来迁移到 GitHub 变成 NModbus4,再后来 NModbus4 也停更了。现在的 NModbus/NModbus 是 NModbus4 的继任者,维护者 rquackenbush,活跃开发中,nuget 包名 `NModbus`,最新版 3.0.x,支持 .NET 6+。
**功能码**:01/02/03/04/05/06/15/16 全支持。22 和 23 需要自定义功能码处理。
**传输模式**:RTU、ASCII、TCP、UDP 全支持。串口通过 `NModbus.Serial` 包,支持 Windows/Linux。
**主站/从站**:都支持。从站支持自定义数据存储,可以把寄存器映射到内存、数据库甚至 PLC。
**代码示例**——TCP 主站读一个寄存器:
using NModbus;
var client = new TcpClient("192.168.1.100", 502);
var factory = new ModbusFactory();
var master = factory.CreateMaster(client);
ushort[] result = master.ReadHoldingRegisters(1, 0, 1);
Console.WriteLine(result[0]);
**已知问题**: – 串口支持依赖 `System.IO.Ports`,Linux 上需要额外配置权限 – `SlaveDataStore` 在多线程下不是线程安全的,高并发读写时要自己加锁 – 异步 API(`ReadHoldingRegistersAsync`)返回 `Task`,但底层 I/O 实际上是同步的,没有真正的 async I/O
**文档**:README 够用,`Samples/` 目录有几个示例。主要靠 Stack Overflow 上的老帖子。
jamod —— Java 生态的「前辈」,但建议你别用
SourceForge: https://sourceforge.net/projects/jamod/ GitHub (openHAB fork): https://github.com/openhab/jamod
jamod 是 Dieter Wimberger 在 2002 年写的,比在座很多工程师的工龄都长。最后一次实质性更新是 2010 年,之后基本就没有了。openHAB 社区 fork 了一个版本修了几个 bug 给自己用,但那个 fork 也只是被动维护。
如果你现在新开一个 Java 项目要做 Modbus,直接跳过 jamod,看 j2mod(GitHub: steveohara/j2mod)。j2mod 是 jamod 的重写版本,Apache 2.0 许可,Java 8+,还在更新(2024 年 7 月最后一次提交),RTU + TCP 全支持,主站从站都有。
但既然这篇文章要覆盖 jamod,我还是写上。jamod 支持的功能码:01/02/03/04/05/06/15/16。传输模式 RTU + ASCII + TCP。串口通信依赖 `javax.comm`(巨古老的 API,JDK 都不自带了),替代方案是 RXTX 或 jSerialComm。
代码示例:
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.io.ModbusTCPTransaction;
import net.wimpi.modbus.msg.ReadInputRegistersRequest;
import net.wimpi.modbus.msg.ReadInputRegistersResponse;
import net.wimpi.modbus.net.TCPMasterConnection;
import java.net.InetAddress;
TCPMasterConnection conn = new TCPMasterConnection(
InetAddress.getByName("192.168.1.100"));
conn.connect();
ReadInputRegistersRequest req = new ReadInputRegistersRequest(0, 1);
ModbusTCPTransaction trans = new ModbusTCPTransaction(conn);
trans.setRequest(req);
trans.execute();
ReadInputRegistersResponse res = (ReadInputRegistersResponse) trans.getResponse();
System.out.println(res.getRegisterValue(0));
conn.close();
「已知问题」这个词对 jamod 来说太轻了,它本身就是个问题。但如果你维护的是 2012 年的老系统,又不得不用它,那 openHAB 的 fork 比原版靠谱。
modbus-tk —— 快速原型的好帮手
GitHub: https://github.com/ljean/modbus-tk
法国人 Luc Jean 写的,名字里的 tk 是 TestKit 的缩写——定位非常明确:测试工具。不是给生产环境用的。但实际上很多人拿它做了生产,因为它确实简单。
**功能码**:01/02/03/04/05/06/15/16。不支持 22/23。
**传输模式**:RTU + TCP。ASCII 不支持。
**主站/从站**:都支持。从站可以很方便地 `add_slave` + `add_block` 创建模拟设备。内置一个 hook 函数机制,可以在收到读写请求时插入自定义逻辑——这个设计很聪明,写测试脚本的时候可以直接在 hook 里注入故障场景。
**代码示例**——RTU 主站读寄存器:
import serial
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
master = modbus_rtu.RtuMaster(
serial.Serial(port="/dev/ttyUSB0", baudrate=9600))
val = master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 1)
print(val[0])
**已知问题**: – 最后更新在 2020 年左右,bug fix 基本靠社区 PR。Python 3.12 的兼容性可能有问题 – 没有异步支持,所有操作都是同步阻塞的 – 浮点数没有内置辅助,你得自己 `struct.pack/unpack` – 主站 `execute()` 方法的超时行为不太可控,底层依赖 pyserial 的超时,遇到无响应的从站可能卡很久
**文档**:`examples/` 目录里几个示例就是全部文档了。好在代码量小,花十分钟就能看完。
QtModbus —— Qt 开发者的原生方案
这不是一个「库」,是 Qt 官方 `qtserialbus` 模块的一部分。Qt 5.8 引入,现在是 Qt 6 的标配模块。
因为是 Qt 官方的,API 设计完全是 Qt 风格:信号槽、事件循环、`QModbusReply`。跨平台天然支持——同一套代码在 Windows、Linux、macOS、嵌入式 Linux(Boot2Qt)上都能跑。
**功能码**:通过 `QModbusDataUnit::RegisterType` 枚举支持所有标准类型:Coils、DiscreteInputs、InputRegisters、HoldingRegisters。底层可以发自定义功能码。
**传输模式**:只有 TCP(`QModbusTcpClient` / `QModbusTcpServer`)。**没有 RTU**。这是一个重要的限制——Qt 官方没有内置 Modbus RTU 支持,你得用 `QSerialPort` 自己在应用层写 RTU 帧解析,或者找第三方实现。有些开发者用 `QModbusRtuSerialMaster`(一个社区项目)来填补这个空缺。
**主站/从站**:都支持。TCP 从站可以通过 `QModbusTcpServer` 实现,自定义数据映射到 `QModbusServer` 的注册表。
**代码示例**——TCP 客户端读一个寄存器:
#include <QModbusTcpClient>
#include <QModbusDataUnit>
auto client = new QModbusTcpClient(this);
client->setConnectionParameter(
QModbusDevice::NetworkAddressParameter, "192.168.1.100");
client->setConnectionParameter(
QModbusDevice::NetworkPortParameter, 502);
client->connectDevice();
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 0, 1);
auto *reply = client->sendReadRequest(unit, 1);
connect(reply, &QModbusReply::finished, this, [reply]() {
qDebug() << reply->result().values().at(0);
});
异步的,用信号槽连接结果。注意 `sendReadRequest` 返回的 `QModbusReply*` 生命周期归 Qt 管理,别手动 delete。
**已知问题**: – 没有 RTU,这是最大的一个。你没看错,Qt 官方的 Modbus 模块没有 RTU – 连接断开后不会自动重连,你得在 `stateChanged` 信号里自己写重连逻辑 – `QModbusTcpServer` 的 TCP 连接数有上限,默认是 1。要通过 `setMaxClients()` 改 – 许可问题:Qt 是 LGPL/GPL 双许可,商业闭源项目需要买 Qt Commercial License
**文档**:Qt 官方文档一流。示例项目在 Qt Creator 欢迎页就能找到。
新手推荐路径
别纠结了,按你的技术栈选:
– **STM32 裸机 / FreeRTOS** → FreeModbus。你没别的选择,它就是标准答案。移植花半天,调通 3.5T 定时器再加半天,之后就不用管了 – **Linux 工控机 / 网关(C)** → libmodbus。如果是树莓派跑 Debian 做 Modbus 网关,libmodbus + MQTT 桥接是经典方案。`libmodbus` 采集,`mosquitto` 上云 – **Python 上位机 / 测试脚本** → pymodbus。pymodbus.simulator 仿真 + pymodbus client 读写,一个脚本搞定设备调试。配合 pytest 可以做 Modbus 自动化测试 – **C# WinForm / WPF 上位机** → NModbus。NuGet 安装,三行代码开始读寄存器。配合 ScottPlot 做实时曲线,工厂里最常见的技术栈组合 – **Qt 跨平台桌面应用** → QtModbus。如果你的应用已经用了 Qt,别引入额外的 C 库依赖,直接用 Qt 官方的 – **Java 企业系统(新项目)** → 不要用 jamod。用 j2mod(GitHub: steveohara/j2mod),它是 jamod 的现代化重写,API 清晰得多 – **快速验证想法** → modbus-tk。三分钟搭一个从站模拟器,验证寄存器映射对不对,然后切到 pymodbus 做正式的
经典组合方案
**「FreeModbus 从站 + pymodbus 上位机测试」**:嵌入式设备跑 FreeModbus 做从站,开发阶段用 Python 写 pymodbus 脚本读写寄存器做功能验证。比用 Modbus Poll 灵活——你能在脚本里加数据校验、边界测试、压力测试。调通之后,再换成真正的上位机(C# 或 Qt)。
**「libmodbus 网关 + pymodbus 配置工具」**:工控机跑 libmodbus 对接下面几十台 Modbus RTU 设备,数据收集后转 MQTT 上云。Web 后台用 pymodbus 做设备参数读写——不要求实时性,Python 的开发效率碾压一切。
**「QtModbus 做界面 + libmodbus 做底层」**:Qt 写跨平台桌面 SCADA,人机交互和图表用 Qt Charts,但 Modbus 通信不直接用 QtModbus(因为没有 RTU),而是通过 libmodbus 的 C API 做底层采集,Qt 只负责显示。这个组合在厂区监控系统里很常见。
避坑清单
**第一坑:FreeModbus 的 3.5T 定时器**
Modbus RTU 协议规定帧与帧之间至少间隔 3.5 个字符时间。波特率 9600 时一个字符大约 1ms,3.5T ≈ 3.5ms。很多人移植时直接把定时器设成 3.5ms 周期中断。错了。FreeModbus 的 3.5T 定时器用法是:每收到一个字节就重置定时器,如果定时器溢出(说明 3.5T 内没有新字节),则认为一帧结束。所以定时器要设成单次模式,不是周期模式。在收到字节的中断里调 `vMBPortTimersEnable()` 重新启动定时器。
波特率不同时 3.5T 对应的时间不同:9600bps → ~3.65ms,19200bps → ~1.83ms,115200bps → ~304μs。115200 下 3.5T 只有 300 微秒左右,如果你的定时器最小粒度是 1ms,校准不了那么精细,帧间隔检测就会出错。这时候要么降波特率到 38400,要么用硬件定时器的高精度模式。
**第二坑:libmodbus 的 connect vs new_tcp vs listen 语义**
`modbus_new_tcp(“192.168.1.100”, 502)` 创建上下文,但还没连接。 `modbus_connect(ctx)` 在 RTU 模式下打开串口,在 TCP 主站模式下 connect 到服务器。 TCP 从站模式不要调 `modbus_connect()`,而是 `modbus_tcp_listen(ctx, 1)` + `modbus_tcp_accept(ctx, &socket)`。
最常见的错误:写 TCP 从站的时候习惯性调了 `modbus_connect()`,然后发现怎么都收不到主站的请求。因为 `connect()` 是去连别人,不是等别人来连你。
另外一个坑:`modbus_free(ctx)` 之后 `ctx` 指针还在。如果你要在循环里重连(比如网络断了又恢复),必须先 `ctx = NULL` 再 `modbus_new_tcp()`,否则有概率访问已释放的内存。Wireshark 抓包会看到 TCP SYN 疯狂重发几万次——那是 `modbus_connect()` 在访问野指针。
**第三坑:pymodbus 同步 vs 异步模式混用**
pymodbus 3.x 有两套客户端 API:同步的 `ModbusSerialClient` / `ModbusTcpClient`,异步的 `AsyncModbusSerialClient` / `AsyncModbusTcpClient`。
如果你在一个已经有 asyncio 事件循环的进程里用同步客户端,底层的 socket I/O 会阻塞事件循环,导致所有异步任务暂停。反过来,在纯同步脚本里用异步客户端,`await` 语法会报错。
规则很简单:要么全同步,要么全异步。测试脚本一般用同步就够了。生产环境如果是一个进程要同时连几十台设备,必须用异步模式,不然一个设备超时拖着其他 49 个一起等。
**第四坑:字节序问题——不是库的锅,但每次都是坑**
Modbus 协议本身只定义了 16 位寄存器的传输格式——大端(Big-Endian),高字节在前。但当你用两个寄存器传一个 32 位浮点数时,谁在前谁在后,协议没说。不同厂商的处理方式不一样:
– 施耐德 PLC 用大端双字(ABCD):寄存器 N 存高 16 位,N+1 存低 16 位 – 西门子 S7-1200 用字节交换后的格式(CDAB 或 BADC) – 有些国产仪表用纯小端(DCBA)
libmodbus 提供了 `modbus_set_float()` 和四种字节序常量,pymodbus 有 `BinaryPayloadDecoder` 可以指定字节序,FreeModbus 什么都没有——自己用 `union` 或者 `memcpy` 拼。
血的教训:调了两天发现温度读数是个天文数字,最后是字节序反了。先确认对方设备的手册上有没有写 Float 的存放格式。如果没写,读两个寄存器自己拼,换四种排列总有一个是对的。
**第五坑:广播地址 0 的坑**
Modbus 规定地址 0 是广播地址,主站发广播帧,所有从站执行但不回复。但实际上很多从站设备根本不支持广播,发地址 0 过去直接没反应。还有的设备虽然支持广播但不全支持——06(写单个寄存器)广播能执行,16(写多个寄存器)广播就忽略。
如果你在用 `libmodbus_set_slave(ctx, 0)` 发广播,别指望有回复。`modbus_read_registers()` 在广播模式下的行为是未定义的。
选型决策速查
如果你不想看上面洋洋洒洒几千字,这里有个决策树:
1. 目标平台是 MCU 裸机 → FreeModbus 2. 目标平台是 Linux → libmodbus 3. 你用 Python → pymodbus 4. 你用 C# / .NET → NModbus 5. 你用 Qt → QtModbus(TCP 场景)或 libmodbus(RTU 场景) 6. 你用 Java 新项目 → j2mod,别碰 jamod 7. 你只是要快速搭个测试 → modbus-tk,验证完切 pymodbus
所有库我都贴了最小可运行示例,复制粘贴改一下设备地址就能跑。通信调不通的时候,先拿 Modbus Poll 或 pymodbus 搭个纯软件环回确认硬件链路没问题,再怀疑协议栈的 bug。
有问题到 modbus.cn 论坛聊。
发表回复