跳到主要内容Visual Studio 中基于 libmodbus 的 C++ Modbus RTU 主站示例 | 极客日志C++
Visual Studio 中基于 libmodbus 的 C++ Modbus RTU 主站示例
本文介绍在 Visual Studio 环境下使用 C++ 和 libmodbus 库开发 Modbus RTU 及 TCP 主站程序。涵盖环境搭建(vcpkg/CMake)、代码实现(串口连接、寄存器读写、超时设置、错误处理)、单元测试方法以及常见问题排查。示例支持 Ctrl+C 优雅退出,适用于工业控制与硬件调试场景。
怪力乱神0 浏览 Visual Studio 中完整的 C++ Demo:基于 libmodbus 的串口 Modbus RTU 主站示例
引言
在 Visual Studio (VS) 中开发 C++ 串口应用非常常见,尤其是在 Windows 环境下。libmodbus 是一个优秀的开源库,支持 Modbus RTU 协议,通过它可以轻松实现串口通信。本 Demo 将展示一个完整的 Modbus RTU 主站程序:连接串口、配置参数、轮询从站寄存器、读取数据、处理错误,并支持退出。
这个 Demo 适用于:
- 半导体测试机数据采集(读取传感器电压/电流)
- 工业控制(PLC 通信)
- 硬件调试
:
前提
- VS 2022+(Community 版免费)
- 已安装 libmodbus(下面详解)
- 一个串口设备(如 USB 转串口 + Modbus 从站模拟器)
- C++ 标准:C++17
步骤 1: 在 VS 中安装/配置 libmodbus
- 下载 libmodbus 源码:
- 使用 CMake 构建 libmodbus(推荐):
- 安装 CMake(VS Installer 中添加 'C++ CMake tools')
- 打开 VS,File > Open > CMake… 打开 libmodbus/CMakeLists.txt
- Build > Build All (生成 libmodbus.lib / dll)
- 输出路径:build/Release 或 Debug
- vcpkg 安装(更简单):
- 安装 vcpkg:git clone https://github.com/microsoft/vcpkg
- .\vcpkg\bootstrap-vcpkg.bat
- .\vcpkg\vcpkg install libmodbus:x64-windows
- 在 VS 项目属性 > C/C++ > Additional Include Directories 添加 vcpkg/installed/x64-windows/include
- Linker > Additional Library Directories 添加 vcpkg/installed/x64-windows/lib
- Linker > Input 添加 libmodbus.lib
- 项目配置:
- 新建 VS C++ 控制台项目
- 属性 > C/C++ > Code Generation > Runtime Library: Multi-threaded DLL (/MD)
- 链接 libmodbus.lib + ws2_32.lib(Windows 串口需要)
步骤 2: 完整 Demo 代码(modbus_rtu_master.cpp)
#include <modbus.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <windows.h>
#include <signal.h>
#define SERIAL_PORT "COM3"
#define BAUDRATE 115200
#define SLAVE_ID 1
#define REG_ADDR 0
#define REG_COUNT 10
#define TIMEOUT_SEC 1
#define TIMEOUT_USEC 0
#define POLL_INTERVAL 1000
volatile sig_atomic_t running = 1;
void signal_handler(int sig) {
running = 0;
printf("收到中断信号,停止轮询...\n");
}
int main() {
modbus_t *ctx = NULL;
uint16_t regs[REG_COUNT];
int rc;
signal(SIGINT, signal_handler);
ctx = modbus_new_rtu(SERIAL_PORT, BAUDRATE, 'N', 8, 1);
if(ctx == NULL){
fprintf(stderr,"创建上下文失败:%s\n",modbus_strerror(errno));
return 1;
}
modbus_set_slave(ctx, SLAVE_ID);
modbus_set_response_timeout(ctx, TIMEOUT_SEC, TIMEOUT_USEC);
if(modbus_connect(ctx)==-1){
fprintf(stderr,"连接失败:%s\n",modbus_strerror(errno));
modbus_free(ctx);
return 1;
}
printf("连接成功:%s @ %d baud\n", SERIAL_PORT, BAUDRATE);
while(running){
rc = modbus_read_registers(ctx, REG_ADDR, REG_COUNT, regs);
if(rc == -1){
fprintf(stderr,"读取失败:%s\n",modbus_strerror(errno));
}else{
printf("读取成功 (%d 个寄存器):\n", rc);
for(int i = 0; i < rc; i++){
printf("寄存器 %02d: %u (0x%04X)\n", i, regs[i], regs[i]);
}
}
Sleep(POLL_INTERVAL);
}
modbus_close(ctx);
modbus_free(ctx);
printf("程序退出。\n");
return 0;
}
- 步骤 1-4:初始化上下文、设置参数、连接串口(标准流程)
- 步骤 5:无限轮询读取保持寄存器(功能码 03),每秒一次
- 信号处理:Ctrl+C 优雅退出(避免资源泄漏)
- 错误处理:每步检查 rc == -1,并打印 modbus_strerror(errno)
- 兼容 Windows:使用 Sleep(),signal() 处理中断
步骤 3: 在 VS 中编译运行
- 新建 VS C++ 项目(Console App)
- 添加源文件:modbus_rtu_master.cpp
- 项目属性:
- C/C++ > General > Additional Include Directories: libmodbus 头文件路径
- Linker > General > Additional Library Directories: libmodbus.lib 路径
- Linker > Input > Additional Dependencies: libmodbus.lib; ws2_32.lib
- Build > Build Solution
- Run(F5):观察控制台输出
连接成功:COM3 @ 115200 baud 读取成功 (10 个寄存器): 寄存器 00: 123 (0x007B) ... (根据从站数据)
步骤 4: 测试代码
单元测试(使用 Google Test,VS 集成简单):
- 安装 Google Test(NuGet 或源码)
- 新建测试项目,添加 test_modbus.cpp
#include <gtest/gtest.h>
#include <modbus.h>
TEST(ModbusTest, CreateRtuContext){
modbus_t *ctx = modbus_new_rtu("COM3", 115200, 'N', 8, 1);
ASSERT_NE(ctx, nullptr);
modbus_free(ctx);
}
TEST(ModbusTest, ConnectFailInvalidPort){
modbus_t *ctx = modbus_new_rtu("INVALID", 9600, 'N', 8, 1);
ASSERT_EQ(modbus_connect(ctx), -1);
ASSERT_EQ(errno, ERROR_PATH_NOT_FOUND);
modbus_free(ctx);
}
TEST(ModbusTest, ReadRegisters){
modbus_t *ctx = modbus_new_rtu("COM3", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1);
modbus_connect(ctx);
uint16_t regs[2];
int rc = modbus_read_registers(ctx, 0, 2, regs);
ASSERT_GE(rc, 0);
modbus_close(ctx);
modbus_free(ctx);
}
int main(int argc, char** argv){
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
- VS 测试浏览器运行所有测试
- 预期:所有通过(需真实串口设备)
- 用串口助手发送数据,验证程序接收
- 或用 pymodbus 模拟从站
步骤 5: 常见问题与调试
- 打开失败:检查端口是否占用(设备管理器)、权限(管理员运行 VS)
- 读写失败:确认波特率匹配、从站 ID 正确
- 超时:增加 modbus_set_response_timeout
- 调试:VS 附加进程调试、printf 日志、modbus_strerror
以下是一个完整的 Modbus TCP 主站 Demo,使用 C++ 实现,基于 libmodbus 库(目前最成熟、性能最好的 C/C++ Modbus 开源库)。代码包含:
- 连接 Modbus TCP 从站
- 读取保持寄存器(Holding Registers,功能码 03)
- 写入单个寄存器(功能码 06)
- 错误处理与重试
- 完整中文注释
- 测试用例(单元测试 + 集成测试)
1. 环境准备
推荐方式:使用 vcpkg 安装 libmodbus(最简单)
git clone https://github.com/microsoft/vcpkg
cd vcpkg
.ootstrap-vcpkg.bat
.ootstrap-vcpkg.bat
.ootstrap-vcpkg.bat
.ootstrap-vcpkg.bat
注:实际命令应为 .ootstrap-vcpkg.bat 后接 .ootstrap-vcpkg.bat 或直接 .ootstrap-vcpkg.bat 然后 .ootstrap-vcpkg.bat。此处保留原文逻辑但修正格式。
- 新建 C++ 控制台项目
- 项目属性 → C/C++ → 常规 → 附加包含目录:
$(VCPKG_ROOT)\installed\x64-windows\include
- 链接器 → 常规 → 附加库目录:
$(VCPKG_ROOT)\installed\x64-windows\lib
- 链接器 → 输入 → 附加依赖项:
libmodbus.lib;ws2_32.lib
2. 完整 Demo 代码(modbus_tcp_master.cpp)
#include <modbus.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <windows.h>
#include <signal.h>
#define SERVER_IP "192.168.1.100"
#define SERVER_PORT 502
#define SLAVE_ID 1
#define REG_READ_ADDR 0
#define REG_READ_COUNT 10
#define REG_WRITE_ADDR 100
#define REG_WRITE_VALUE 12345
#define TIMEOUT_SEC 2
#define TIMEOUT_USEC 0
#define MAX_RETRIES 3
#define POLL_INTERVAL 1000
volatile sig_atomic_t running = 1;
void signal_handler(int sig) {
running = 0;
printf("\n收到中断信号,程序退出...\n");
}
int main() {
modbus_t *ctx = NULL;
uint16_t regs[REG_READ_COUNT];
int rc;
signal(SIGINT, signal_handler);
printf("Modbus TCP 主站启动...\n");
printf("目标从站:%s:%d Unit ID: %d\n", SERVER_IP, SERVER_PORT, SLAVE_ID);
ctx = modbus_new_tcp(SERVER_IP, SERVER_PORT);
if(ctx == NULL){
fprintf(stderr,"创建上下文失败:%s\n",modbus_strerror(errno));
return 1;
}
modbus_set_slave(ctx, SLAVE_ID);
modbus_set_response_timeout(ctx, TIMEOUT_SEC, TIMEOUT_USEC);
if(modbus_connect(ctx)==-1){
fprintf(stderr,"连接失败:%s\n",modbus_strerror(errno));
modbus_free(ctx);
return 1;
}
printf("连接成功!开始轮询...\n\n");
int loop_count = 0;
while(running){
loop_count++;
printf("=== 第 %d 次轮询 ===\n", loop_count);
int retries = MAX_RETRIES;
rc = -1;
while(retries-->0 && rc == -1){
rc = modbus_read_registers(ctx, REG_READ_ADDR, REG_READ_COUNT, regs);
if(rc == -1){
fprintf(stderr,"读取失败 (剩余重试 %d): %s\n", retries,modbus_strerror(errno));
Sleep(500);
}
}
if(rc > 0){
printf("读取成功 (%d 个寄存器):\n", rc);
for(int i = 0; i < rc; i++){
printf(" 寄存器 %03d: %5u (0x%04X)\n", REG_READ_ADDR + i, regs[i], regs[i]);
}
}else{
printf("读取失败,已重试 %d 次\n", MAX_RETRIES);
}
rc = modbus_write_register(ctx, REG_WRITE_ADDR, REG_WRITE_VALUE);
if(rc == 1){
printf("写入成功:寄存器 %d = %d\n", REG_WRITE_ADDR, REG_WRITE_VALUE);
}else{
fprintf(stderr,"写入失败:%s\n",modbus_strerror(errno));
}
printf("\n");
Sleep(POLL_INTERVAL);
}
modbus_close(ctx);
modbus_free(ctx);
printf("程序正常退出。\n");
return 0;
}
编译与运行(Visual Studio 2022)
- 新建 C++ 控制台项目
- 添加源文件:modbus_rtu_master.cpp(或改名)
- 项目属性配置:
- C/C++ → 常规 → 附加包含目录:
$(VCPKG_ROOT)\installed\x64-windows\include
- 链接器 → 常规 → 附加库目录:
$(VCPKG_ROOT)\installed\x64-windows\lib
- 链接器 → 输入 → 附加依赖项:
libmodbus.lib;ws2_32.lib
- 生成 → 生成解决方案(Release / Debug)
- 运行(F5)
Modbus TCP 主站启动...
目标从站:192.168.1.100:502 Unit ID: 1
连接成功!开始轮询...
=== 第 1 次轮询 ===
读取成功 (10 个寄存器):
寄存器 000: 123 (0x007B)
寄存器 001: 456 (0x01C8)
...
写入成功:寄存器 100 = 12345
=== 第 2 次轮询 ===
...
测试用例(集成测试 + 单元测试)
测试 1:连接与读取(手动集成测试)
- 使用 Modbus Poll 或 Modbus Slave 软件模拟从站:
- IP: 192.168.1.100
- 端口:502
- 从站 ID: 1
- 寄存器 0~9 填入测试值(如 100, 200, …)
- 运行 Demo,观察控制台是否正确打印寄存器值
- 断开从站网络 → 观察重试与错误日志
测试 2:单元测试(Google Test)
test_modbus.cpp(需安装 Google Test via NuGet)
#include <gtest/gtest.h>
#include <modbus.h>
TEST(ModbusTcpTest, CreateContext){
modbus_t *ctx = modbus_new_tcp("127.0.0.1", 502);
ASSERT_NE(ctx, nullptr);
modbus_free(ctx);
}
TEST(ModbusTcpTest, ConnectFailInvalidIP){
modbus_t *ctx = modbus_new_tcp("invalid.ip", 502);
ASSERT_EQ(modbus_connect(ctx), -1);
ASSERT_EQ(errno, EHOSTUNREACH);
modbus_free(ctx);
}
int main(int argc, char** argv){
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
常见问题与解决(Windows 环境)
| 问题 | 原因 | 解决方法 |
|---|
| 连接失败:Connection refused | 从站未启动或 IP/端口错误 | 检查从站软件是否运行,确认 IP:502 |
| 读取超时 | 超时时间太短或网络延迟 | modbus_set_response_timeout(ctx, 3, 0) |
| 编译错误:undefined reference | 未链接 libmodbus.lib | 项目属性 → 链接器 → 输入 → 添加 libmodbus.lib |
| 端口被占用 | 其他软件占用 502 端口 | 关闭 Modbus Poll/Slave 等工具 |
| Windows 防火墙阻挡 | 系统防火墙未放行 | 防火墙 → 允许应用通过 → 添加你的 exe |
扩展建议(半导体测试机场景)
- 多从站轮询:循环
modbus_set_slave(ctx, id) + 读取
- 数据解析:将寄存器值转为浮点数(IEEE 754)
- 阈值报警:读取后判断电压 > 3.6V → 打印报警
- 日志:使用 spdlog 或简单文件记录
- 多线程:采集用线程,UI 用 Qt 主线程
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online