
简介这份压缩包面向在Windows平台使用Qt5与MinGW进行libmodbus二次开发的工程师与学习爱好者是一套可直接导入Qt Creator运行的测试工程。项目基于经典博客方案重构以Modbus Slave模拟器配合工程代码演示主机的读写交互与UI控制适合需要快速验证串口通信逻辑或搭建协议调试环境的场景。资源共21个文件以头文件、C/C源码和pro工程文件为主体另有界面ui文件与URL快捷方式整体仅39KB结构轻量清晰方便对照学习。已有978人学习下载。通过该工程可直观获取libmodbus在MinGW下的编译配置、常用API调用方式以及一个带界面的从机模拟示例省去从零搭建与排查环境的时间尤其适合刚接触Qt与modbus整合开发的读者。1. 在 Windows 上把 libmodbus 塞进 Qt5 MinGW 工程先搞清这事的边界做过工业上位机的人都知道Modbus 协议几乎是现场设备通信的默认语言而 Qt5 MinGW 又是国内工控上位机开发里最常见的一套组合。但当这两者碰到一起事情就没那么顺了libmodbus 官方源码虽然跨平台可它的编译脚本、运行时依赖和 MSVC 系的习惯差得很远MinGW 更是藏着不少坑。我见过不止一位同行卡在编译不过、链接报错、程序一跑就找不到 DLL 这几道坎上最后要么换工具链要么放弃治疗。这篇笔记想解决的问题很具体在 Windows 平台、Qt5 MinGW 这个组合下把 libmodbus 从源码编译成库再集成进 Qt 工程里做 Modbus RTU/TCP 通信测试全程跑通需要哪些步骤、参数怎么设、坑在哪。适合正在做设备通信、数据采集类上位机的工程师参考。需要你自己动手敲命令、看报错信息我会用一次完整的测试过程把链路讲透从选型到验证每个环节都给到能抄作业的细节。2. libmodbus 选型与 Qt5 MinGW 交叉点为什么这套组合值得测试2.1 libmodbus 是什么适合什么场景libmodbus 是一个开源的 Modbus 协议栈库用 C 语言编写支持 Modbus RTU串口和 Modbus TCP以太网两种主流模式。对比自己封装串口收发和报文解析它最大的价值是帮你把协议层的脏活累活挡掉了CRC 校验、从站地址处理、功能码组包/解包、异常码映射、超时重试等等库里都已经实现并且经受了大量现场验证。我一般会先用 libmodbus 快速验证设备通信协议是否正确确认之后再决定要不要继续用库还是自己重写精简版。因为它的 API 足够简单核心对象就是modbus_t指针创建、连接、读写、关闭四个动作就能覆盖大多数调试场景。在 Windows 平台上位操作和字节序的处理天然接近底层硬件跨平台迁移时这些逻辑基本不会二次返工。2.2 Qt5 MinGW 与 MSVC 的差异直接影响你编译 libmodbus同样是在 Windows 上开发 Qt 程序编译器不同很多行为的差异会比预想大得多。MinGW 是基于 GCC 的工具链它的链接器、C 运行库msvcrt 或 ucrt、导出符号的修饰规则和 MSVC 都不一致所以用 MSVC 编译出的 .lib/.dll 通常没办法在 MinGW 工程里直接链接反过来也一样。具体到 libmodbus它本身用 CMake 构建支持生成 MinGW 的 Makefile这个过程并不复杂。但要注意三个细节第一MinGW 默认的导入库格式是.a或.dll.a和 MSVC 的.lib不是一回事需要确保 Qt 工程的库路径里放的是 MinGW 编译出来的产物第二MinGW 的 DLL 依赖libwinpthread-1.dll和libgcc_s_seh-1.dll运行时若缺少这两个运行时库程序启动时不会直接报错但会在调用到相关功能时崩溃第三Qt5 的 MinGW 版本自己是一套独立的生态环境它的qmake在做库搜索和依赖解析时会优先按自身工具链的规则来处理。2.3 测试目标与工具链清单这次测试的目标是验证 libmodbus 在 Qt5 MinGW 下能否正常完成三件事编译集成、RTU 串口通信、TCP 网络通信。对应地需要准备libmodbus 源码从官方仓库拉取稳定分支、Qt 5.15 配合 MinGW 8.1 工具链、CMake 3.16 以上版本以及一个 Modbus 从站模拟器用于回环测试。设备方面如果做 RTU 测试还要准备 USB 转 485 的适配器和一台真实从站设备或者直接用串口回环头把 TX 和 RX 接在一起验证链路层的收发。如果手头没有真实设备我建议先跑 TCP 模拟器这个最省事RTU 再用回环头验证报文是否正确。工具链次序上建议先装 Qt 自带的 MinGW再把 CMake 的生成器指定为 “MinGW Makefiles”避免系统里还装了别的 Make 工具导致生成错乱。3. 编译 libmodbus 静态库与动态库两种方式在 Qt 工程里各自怎么接3.1 源码目录结构与 CMake 配置要点拿到 libmodbus 源码后目录结构很清爽核心源码在src/下公开头文件是src/modbus.h、src/modbus-rtu.h、src/modbus-tcp.h和src/modbus-version.h。CMakeLists.txt 在根目录控制整个构建流程。编译前需要确认两个开关MODBUS_BUILD_SHARED和MODBUS_BUILD_STATIC默认情况下库只会生成动态库如果你想把 libmodbus 直接编译进自己的可执行文件就要手动把静态库开关打开。还有一个容易被忽略的配置项是BUILD_TESTING它控制是否生成单元测试目标。我建议在验证阶段打开它跑一下官方自带的测试能快速确认工具链环境是否健康确认无误后再关掉重新编译免得每次 build 都带上测试目标拖慢时间。3.2 用 CMake 命令行产出 MinGW 版本库这里我以库源码目录/path/to/libmodbus构建输出目录/path/to/libmodbus-build为例给出完整的命令行操作序列cd /path/to/libmodbus-build cmake .. -G MinGW Makefiles \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_PREFIX_PATHC:/Qt/Qt5.15.2/5.15.2/mingw81_64 \ -DMODBUS_BUILD_SHAREDON \ -DMODBUS_BUILD_STATICOFF \ -DBUILD_TESTINGOFF mingw32-make -j8逻辑说明-G MinGW Makefiles指定生成器为 MinGW Makefiles这是 CMake 在 Windows 下配合 MinGW 编译器的关键参数CMAKE_PREFIX_PATH指向 Qt 安装目录让 CMake 能找到 Qt 的相关工具和库后面三个-D参数依次控制生成动态库、关闭静态库、关闭测试目标。参数说明-j8根据你的 CPU 核数调整8 核机器用 8 没什么问题如果编译过程中提示找不到mingw32-make说明 Qt 自带的 MinGW 工具链没加到 PATH 环境变量里你需要把类似C:\Qt\Qt5.15.2\Tools\mingw810_64\bin的路径手动追加进去。3.3 生成 Qt 工程能直接用的 .a 导入库编译完成后在构建目录的src/子目录下会看到libmodbus.dll和libmodbus.dll.a两个关键文件。其中libmodbus.dll是运行时动态库libmodbus.dll.a是 MinGW 专用的导入库也就是链接阶段要用的文件。千万别把 MSVC 的.lib文件拿过来用链接时会报一堆无法解析的外部符号。为了后续 Qt 工程引用方便建议把libmodbus.dll、libmodbus.dll.a和src/下的头文件统一拷贝到 Qt 工程的libs/libmodbus/目录保持路径整洁。如果选择编译静态库则只会生成libmodbus.a并且在 Qt 工程里还需要额外链接 WinSock2 库和串口相关库否则会出现和 Windows 底层 API 相关的符号未定义错误实际使用中动态库省心得多不需要关心这些细节。3.4 在 Qt 的 .pro 文件里正确引用 libmodbusQt 工程链接外部库本质上就两个动作告诉编译器头文件在哪个目录告诉链接器导入库在哪个目录。把上面拷贝好的目录整合进.pro文件示例如下INCLUDEPATH $$PWD/libs/libmodbus LIBS -L$$PWD/libs/libmodbus -lmodbus逻辑说明$$PWD在 qmake 里表示当前.pro文件所在目录这样写能确保工程在别的机器上也能相对路径定位到库文件-L后接库目录-lmodbus指示链接器在指定目录下找libmodbus.dll.a或libmodbus.a。参数说明MinGW 下的链接参数不区分动态库还是静态库只要你把对应文件放在LIBS指定的目录里链接器自己会按名字去找。需要注意如果同时存在libmodbus.dll.a和libmodbus.a两个文件链接器优先用导入库如果只想用静态版本建议删掉动态库和导入库只保留一个libmodbus.a避免链接时二选一出错。4. 用 Qt5 MinGW 跑通 Modbus RTU 与 TCP 测试程序核心步骤与参数设置4.1 准备最小可用的 Qt 控制台工程这里选择 Qt Widgets 还是 Qt Console 取决于你的后续需求但测试阶段我建议直接用控制台工程省去界面无关的干扰。新建 Qt Console Application 后把main.cpp替换为下面这个最小验证代码目标只有一个先建立 Modbus TCP 连接并读取从站保持寄存器。先用 TCP 跑通再切到 RTU这种顺序能最大化减少变量干扰。#include QCoreApplication #include QDebug #include modbus.h int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 1. 创建 Modbus TCP 上下文指向本机模拟从站 modbus_t *ctx modbus_new_tcp(127.0.0.1, 502); if (ctx nullptr) { qCritical() modbus_new_tcp failed; return 1; } // 2. 设置从站地址为 1 modbus_set_slave(ctx, 1); // 3. 建立连接超时设置 1000 毫秒 if (modbus_connect(ctx) -1) { qCritical() connect failed: modbus_strerror(errno); modbus_free(ctx); return 1; } // 4. 读取从站地址 0 开始的 10 个保持寄存器 uint16_t dest[10] {0}; int rc modbus_read_registers(ctx, 0, 10, dest); if (rc -1) { qCritical() read failed: modbus_strerror(errno); } else { qInfo() read registers, count rc; for (int i 0; i rc; i) { qInfo() reg[ i ] dest[i]; } } // 5. 清理资源 modbus_close(ctx); modbus_free(ctx); return 0; }逻辑说明modbus_new_tcp创建 TCP 上下文IP 和端口对应从站模拟器的监听地址modbus_set_slave设置要访问的从站 IDmodbus_read_registers是核心读操作参数分别是上下文、起始地址、寄存器数量和存放结果的缓冲区。参数说明如果连接的从站地址不是 1需要改modbus_set_slave的第二个参数。缓冲区dest的大小必须大于等于nb即要读取的寄存器数量× 2 字节否则会有内存越界的风险。rc返回的是实际读取到的寄存器个数失败时返回 -1。4.2 RTU 串口通信的上下文创建与参数设定TCP 跑通后切换到 RTU 只需要更换上下文创建函数和相关参数。RTU 模式的核心差异在于串口参数和字节时序设置。下面的代码演示了通过 COM3 口连接地址为 1 的从站波特率 96008 数据位、偶校验、1 停止位// 创建 RTU 上下文串口号、波特率、校验位、数据位、停止位 modbus_t *ctx modbus_new_rtu(COM3, 9600, E, 8, 1); if (ctx nullptr) { qCritical() modbus_new_rtu failed; return 1; } // 设置串口为从站模式地址 1 modbus_set_slave(ctx, 1); // 关键连接串口 if (modbus_connect(ctx) -1) { qCritical() serial connect failed: modbus_strerror(errno); modbus_free(ctx); return 1; }逻辑说明modbus_new_rtu的第三个参数是校验位N无校验、E偶校验、O奇校验第四个和第五个参数分别是数据位和停止位。这些参数必须和从站设备实际串口参数保持一致否则通信数据会全部错乱。参数说明RTU 模式下modbus_connect实际上是打开串口并设置串口参数如果串口被别的程序占用这里会返回 -1。遇到通信不稳定时优先排查波特率、校验位和停止位这三个参数是否匹配其次是串口号是否被系统识别。4.3 调试 TCP 模拟器与 RTU 回环环境的搭建没有真实从站设备时最快验证方式是用模拟器。Windows 下有多种 Modbus 从站模拟工具可以监听 502 端口或者虚拟串口。TCP 模式最省事直接运行模拟器监听 502 端口用上面的代码连接即可不用任何硬件接线。RTU 模式如果没有真实 485 设备可以使用虚拟串口工具创建一对互联的串口比如 COM3 和 COM4让它们互相连接。从站模拟器监听 COM4我们的测试程序连接 COM3就能形成一个完整的收发回路。注意虚拟串口工具在 Windows 上需要安装驱动且不同的工具创建方式不太一样这里不展开了。如果不想折腾虚拟串口用一根 USB 转 485 线把 A、B 端直接对接也能完成回环测试只是只能验证收发读不到寄存器数据。4.4 测试程序运行后常见输出与错误速查程序运行后可能出现三类典型情况。第一类是最理想的情况读取寄存器成功并打印数值说明库编译和工程配置都没问题。第二类是连接失败此时先检查模拟器是否在监听、IP 和端口是否正确、防火墙是否拦截了 502 端口。第三类是超时无响应这通常是从站地址不对、功能码不支持或者 RTU 参数不匹配。我一般会先在 Qt 的qDebug()里把modbus_strerror(errno)对应的错误信息打出来它能直接告诉我们超时、连接拒绝或从站无应答这类基本原因而不是猜测。经验上 90% 的测试失败都出在从站地址、波特率和寄存器地址这三个基本参数上先把这三项核对清楚再往下深挖。5. libmodbus 在 Qt 工程里的避坑指南能帮你省下两天的五个问题5.1 链接报错“undefined reference tomodbus_new_tcp”但库已经添加现象.pro文件里已经添加了LIBS -lmodbus但编译时仍然提示找不到符号。原因最普遍的情况是链接的库文件路径不对qt 在 MinGW 下找的是libmodbus.dll.a或libmodbus.a这类带lib前缀的文件而有些人把 MSVC 环境拿来的modbus.lib直接放进目录里符号修饰规则不同链接器认不出来。解决确认目录下libmodbus.dll.a存在且LIBS里指定的路径正确。如果用了静态库还需要额外链接系统库这一点我们前面已经讲过。排错时直接用nm 库文件路径 | grep modbus_new_tcp查看导入符号是否存在是最直接的验证方式。5.2 程序启动时报找不到libwinpthread-1.dll或libgcc_s_seh-1.dll现象编译通过、运行闪退或者弹窗提示缺少某个 DLL。原因MinGW 编译的程序依赖 GNU 工具链的运行时库这些库在 Qt 安装目录的 MinGWbin文件夹下。如果系统 PATH 里没有包含 Qt 自带的 MinGW bin 目录程序运行时自然找不到。解决在 Qt Creator 的构建环境或系统环境变量里把类似C:\Qt\Qt5.15.2\Tools\mingw810_64\bin的路径加上。如果程序需要部署到别的机器把这些 DLL 直接拷贝到 exe 同目录下或者用windeployqt工具自动收集依赖。5.3 RTU 通信超时但串口工具测试正常现象用第三方串口调试助手发送 Modbus 报文能收到从站响应但程序里modbus_read_registers始终返回超时。原因RTU 模式下modbus_set_response_timeout没设置合理值同时也可能是串口参数校验位写错了。最常见的是校验位参数写成了E但设备实际无校验导致从站收到数据校验失败不回复任何内容超时时间设置得太短比如默认 0.5 秒数据稍大或设备响应慢就超时。解决先用调试助手确认设备真实串口参数严格匹配后再执行下面的参数设置把超时时间调长到 1 到 3 秒再测试struct timeval response_timeout; response_timeout.tv_sec 3; response_timeout.tv_usec 0; modbus_set_response_timeout(ctx, response_timeout);逻辑说明modbus_set_response_timeout设置的是等待从站回复的最长时间。该结构体的tv_sec是秒tv_usec是微秒。实际测试中串口波特率越低单帧传输时间越长超时时间对应地就要设置得越大。参数说明如果你用 9600 波特率读取大量寄存器建议超时不小于 1 秒115200 波特率下可以适当缩短到 500 毫秒但不要小于 300 毫秒否则在信号不佳的现场容易误判超时。5.4 读取的寄存器值全部为 0但通信计数正常现象modbus_read_registers返回值正常但读出来的值都是 0看起来像通信成功但没有真实数据。原因这种情况多半是寄存器地址不对或从站设备的寄存器映射区域和其他地址重叠。还有一部分原因是字节序的问题这属于 Modbus 协议中常见的 Word 大小端差异某一台设备使用大端存储另一台设备使用小端存储读出来自然不对。解决先拿模拟器配合官方文档核对寄存器地址范围。对于字节序问题一个典型的处理方式是在读到数据后自行翻转字节序不需要改库本身for (int i 0; i rc; i) { dest[i] (dest[i] 8) | (dest[i] 8); }逻辑说明这段代码对每个 16 位寄存器的两个字节做了交换完成大端和小端的转换。实际项目中如果设备厂商说数据是低字节在前就需要这个转换步骤如果不确定可以把已知的一个固定值写入后读回看数值变化来判断是否需要翻转。5.5 从站地址大于 247 时 TCP 模式报错现象某些设备使用自定义的从站地址例如 300在 TCP 模式下调用modbus_set_slave返回失败。原因Modbus 协议标准里规定从站地址范围为 0 到 247其中 0 是广播地址1 到 247 是有效地址。libmodbus 会主动校验这个范围超过就直接报参数非法。解决TCP 模式本身其实不关心从站地址因为 TCP 是一对一连接地址字段更多地是历史留存的习惯。你可以通过modbus_set_slave先设置为 255再调用一次相关操作看设备是否支持。切到 RTU 模式则严格受协议约束需要在从站侧重新配置地址不能绕过库的校验逻辑。6. 进阶验证把 libmodbus 接入 Qt 界面线程的正确姿势与自动化测试走到这一步库已经能在控制台工程里跑通基本读写。接下来真正有价值的两个进阶方向一是把通信逻辑从界面线程里挪出去避免 UI 卡顿二是用自动化脚本做回归验证保证后续修改不断链子。Qt 里最稳妥的通信线程模式是QThread加工作对象用信号槽传递数据。不要直接在按钮槽函数里调用modbus_read_registers因为串口或网络响应慢一点界面就会转圈。下面给一个简单的线程封装思路class ModbusWorker : public QObject { Q_OBJECT public slots: void readRequest(int addr, int count) { modbus_t *ctx modbus_new_tcp(127.0.0.1, 502); modbus_set_slave(ctx, 1); modbus_connect(ctx); uint16_t *dest new uint16_t[count]; int rc modbus_read_registers(ctx, addr, count, dest); emit readDone(rc, dest); delete[] dest; modbus_close(ctx); modbus_free(ctx); } signals: void readDone(int rc, uint16_t *data); };逻辑说明这个ModbusWorker对象可以移动到子线程里运行通过队列连接把请求发送进来。readRequest槽里完成整个 Modbus 交互过程结束之后通过readDone信号把结果传回界面线程。参数说明实际工程里每次调用都重新创建和释放上下文效率不高正确的做法是把modbus_t指针作为工作对象成员保存线程启动时创建线程结束时释放。但需要注意一个modbus_t上下文在同一时间内只能被一个线程使用别多个线程共用同一个上下文否则内部超时状态会被互相覆盖这又是一个常见的并发坑。对于自动化验证可以用脚本连续跑 1000 次读写统计失败率这是判断现场通信质量的基础手段我一般会配合modbus_set_response_timeout做分段统计。最后再说一个最实际的技巧把编译好的libmodbus.dll、libmodbus.dll.a和头文件固定放到一个独立的libs目录里并纳入版本管理每次更新 Qt 或库源码时重新编译一次并跑通上面那个最小测试程序。别把库文件散落在各个工程里那是给自己后续维护挖坑。这套流程我踩过好几次现在养成了先编译测试程序、再写业务代码的习惯。希望帮到你。本文还有配套的精品资源点击获取