运行于 WSL2 (Ubuntu) 环境的Modbus TCP 服务端通讯测试小程序
使用Qt Create18基于 Qt 6 + QtSerialBus ,运行于 WSL2 (Ubuntu) 环境,记录下最初调通了的的测试版,使用tcp工具运行在windows端,可与运行在WSL2下的程序进行modbus tcp通讯。先记录下初版,后期有机会加了应用层调通的话再记录。备注下:代码基本上都是基础普通的AI助手生成的。
之前两个文档介绍了前期准备工作
Windows Qt Creator + WSL Ubuntu 编译 x86_64 Linux 程序环境搭建说明-CSDN博客
MobaXterm测试运行Qt Create+wsl编译的执行文件-CSDN博客
一:小程序整体结构
EmsDiagnoseModbus
├── main.cpp // 程序入口、信号处理
├── mainwindow.h/cpp // 主窗口、定时器调度、模块整合
├── modbusserver/ // Qt Modbus TCP 服务端模块
│ ├── modbusserver.h
│ └── modbusserver.cpp
└── diagnosedata/ // 诊断数据管理层(点表、数据交互)
├── diagnosedatamanager.h
└── diagnosedatamanager.cpp
服务接口 → diagnosedatamanager(统一维护点表+缓存数据)
↓(定时器定时拉取/更新)
MainWindow 定时同步任务
↓
ModbusServer::syncDataFromDiagnoseData()
↓(setData写入寄存器)
Qt Modbus 寄存器映射
↓
Windows上位机(Modbus TCP客户端)读写数据
- 数据管理层(diagnosedatamanager) 采用单例模式实现全局数据中心,统一维护全部诊断点表、寄存器地址定义、测点缓存。封装线程安全的读写接口与结构体式批量数据更新接口,解决多参数传参臃肿问题,同时区分保持寄存器、输入寄存器、线圈、离散输入四类 Modbus 标准数据区,适配工业点表规范。
- Modbus 通讯层(modbusserver) 基于 Qt 原生
QModbusTcpServer实现标准 Modbus TCP 服务端,监听全网网卡(0.0.0.0)+ 标准 502 端口,无需端口转发即可被局域网内 Windows 上位机直连访问。规避 QtSerialBus 框架死循环问题,仅保留读请求回调,配置固定从站地址为 1,兼容主流 Modbus 客户端工具。 - 主逻辑调度层(MainWindow) 作为程序主载体,通过多定时器分工完成业务调度:定时采集 EMS 诊断数据、定时将缓存数据同步至 Modbus 寄存器、定时刷新本地 UI 界面展示实时数据。将数据采集逻辑封装为独立槽函数,替代 Lambda 表达式,代码规范易迭代。
- 程序入口层(main.cpp) 实现 Linux/WSL 环境下
SIGINT(Ctrl+C)信号捕获,保障终端快捷键退出、窗口关闭两种方式均可释放端口与资源,彻底解决端口残留占用问题。
二:具体代码
2.1:main.cpp
#include <signal.h>
#include <QCoreApplication>
#include <QDebug>
#include "mainwindow.h"
// 信号处理函数
void sigintHandler(int sig)
{
Q_UNUSED(sig);
qDebug() << "\n收到Ctrl+C信号,退出...";
QCoreApplication::quit();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 注册SIGINT信号处理
signal(SIGINT, sigintHandler);
MainWindow w;
w.show();
return a.exec();
}
2.2:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "modbusserver/modbusserver.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onRefreshUiData(); // UI刷新槽函数
void onCollectData(); // 数据采集槽函数
private:
Ui::MainWindow *ui;
ModbusServer *m_modbusServer;
QTimer *m_uiRefreshTimer;
QTimer *m_collectTimer;
QTimer *m_syncTimer;
};
#endif // MAINWINDOW_H
2.3:mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "diagnosedata/diagnosedatamanager.h"
#include <QDebug>
#include <QTimer>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("诊断数据 + Modbus TCP 服务端");
// ====================== Modbus服务启动 ======================
m_modbusServer = new ModbusServer(this);
bool startResult = m_modbusServer->startServer();
if (!startResult)
{
qDebug() << "[ERROR] Modbus服务启动失败";
}
// ====================== UI刷新定时器(1秒刷新一次界面) ======================
m_uiRefreshTimer = new QTimer(this);
m_uiRefreshTimer->setInterval(1000);
connect(m_uiRefreshTimer, &QTimer::timeout, this, &MainWindow::onRefreshUiData);
m_uiRefreshTimer->start();
// ====================== 数据采集定时器(2秒更新一次诊断数据) ======================
m_collectTimer = new QTimer(this);
m_collectTimer->setInterval(2000);
// 【修改点】连接到独立槽函数,替代lambda表达式
connect(m_collectTimer, &QTimer::timeout, this, &MainWindow::onCollectData);
m_collectTimer->start();
// ====================== 寄存器同步定时器(1秒同步一次数据到Modbus寄存器) ======================
m_syncTimer = new QTimer(this);
m_syncTimer->setInterval(1000);
connect(m_syncTimer, &QTimer::timeout, m_modbusServer, &ModbusServer::syncDataFromDiagnoseData);
m_syncTimer->start();
}
MainWindow::~MainWindow()
{
if (m_modbusServer) {
m_modbusServer->stopServer();
delete m_modbusServer;
m_modbusServer = nullptr;
qDebug() << "[INFO] Modbus服务已停止,端口已释放";
}
DiagnoseDataManager::destroyInstance();
delete ui;
}
// ====================== UI刷新函数 ======================
void MainWindow::onRefreshUiData()
{
auto* dataMgr = DiagnoseDataManager::getInstance();
double volt = dataMgr->getHoldingData(DiagnoseDataManager::REG_VOLTAGE) / 10.0;
double curr = dataMgr->getHoldingData(DiagnoseDataManager::REG_CURRENT) / 10.0;
double pAct = dataMgr->getHoldingData(DiagnoseDataManager::REG_POWER_ACTIVE) / 10.0;
int temp = dataMgr->getHoldingData(DiagnoseDataManager::REG_TEMP);
int status = dataMgr->getInputData(DiagnoseDataManager::REG_RUN_STATUS);
ui->lab_volt->setText(QString("电压:%1 V").arg(volt));
ui->lab_curr->setText(QString("电流:%1 A").arg(curr));
ui->lab_power->setText(QString("有功功率:%1 kW").arg(pAct));
ui->lab_temp->setText(QString("设备温度:%2 ℃").arg(temp));
ui->lab_status->setText(QString("运行状态:%1").arg(status == 1 ? "正常运行" : "停止"));
}
// ====================== 【新增】数据采集函数(原lambda逻辑迁移到此处) ======================
void MainWindow::onCollectData()
{
auto* dataMgr = DiagnoseDataManager::getInstance();
// 构造业务数据结构体
DiagnoseDataManager::DiagnoseBizData bizData;
bizData.voltage = 2203; // 电压 220.3V(放大10倍)
bizData.current = 142; // 电流 14.2A(放大10倍)
bizData.powerActive = 2860; // 有功功率 286.0kW(放大10倍)
bizData.powerReactive = 175; // 无功功率 17.5kvar(放大10倍)
bizData.powerFactor = 930; // 功率因数 0.93(放大1000倍)
bizData.temperature = 36; // 设备温度 36℃
// 批量更新所有诊断数据
dataMgr->batchUpdateDiagnoseData(bizData);
// 更新输入寄存器(状态、告警码)
dataMgr->setInputData(DiagnoseDataManager::REG_RUN_STATUS, 1);
dataMgr->setInputData(DiagnoseDataManager::REG_ALARM_CODE, 0);
}
2.4:diagnosedatamanager.h
#ifndef DIAGNOSEDATAMANAGER_H
#define DIAGNOSEDATAMANAGER_H
#include <QObject>
#include <QMutex>
#include <QMutexLocker>
class DiagnoseDataManager : public QObject
{
Q_OBJECT
public:
static DiagnoseDataManager* getInstance();
static void destroyInstance();
// ====================== 寄存器地址枚举(点表定义) ======================
enum HoldingRegAddr {
REG_VOLTAGE = 0,
REG_CURRENT = 1,
REG_POWER_ACTIVE = 2,
REG_POWER_REACTIVE = 3,
REG_POWER_FACTOR = 4,
REG_TEMP = 5,
HOLD_REG_TOTAL = 20 // 总长度预留,后续新增测点直接用
};
enum InputRegAddr {
REG_RUN_STATUS = 0,
REG_ALARM_CODE = 1,
INPUT_REG_TOTAL = 10
};
enum CoilsAddr {
COIL_SWITCH_1 = 0,
COIL_SWITCH_2 = 1,
COIL_TOTAL = 8
};
enum DiscreteInputAddr {
DI_FAULT_1 = 0,
DI_FAULT_2 = 1,
DI_TOTAL = 8
};
// ====================== 业务数据结构体(批量更新用) ======================
struct DiagnoseBizData
{
// 保持寄存器对应测点 - 后续新增直接在这里加成员
quint16 voltage; // 电压
quint16 current; // 电流
quint16 powerActive; // 有功功率
quint16 powerReactive; // 无功功率
quint16 powerFactor; // 功率因数
quint16 temperature; // 设备温度
};
// ====================== 单个测点读写接口 ======================
quint16 getHoldingData(int addr);
void setHoldingData(int addr, quint16 val);
quint16 getInputData(int addr);
void setInputData(int addr, quint16 val);
bool getCoilData(int addr);
void setCoilData(int addr, bool val);
bool getDiscreteInputData(int addr);
void setDiscreteInputData(int addr, bool val);
// ====================== 批量更新接口(替换原多参数版本) ======================
void batchUpdateDiagnoseData(const DiagnoseBizData& data);
private:
explicit DiagnoseDataManager(QObject *parent = nullptr);
~DiagnoseDataManager() override;
static DiagnoseDataManager* m_instance;
static QMutex m_staticMutex;
// 寄存器数据缓存
quint16 m_holdingReg[HOLD_REG_TOTAL] = {0};
quint16 m_inputReg[INPUT_REG_TOTAL] = {0};
bool m_coils[COIL_TOTAL] = {false};
bool m_discreteInput[DI_TOTAL] = {false};
// 数据读写锁
QMutex m_dataMutex;
};
#endif // DIAGNOSEDATAMANAGER_H
2.5:diagnosedatamanager.cpp
#include "diagnosedatamanager.h"
// 单例初始化
DiagnoseDataManager* DiagnoseDataManager::m_instance = nullptr;
QMutex DiagnoseDataManager::m_staticMutex;
DiagnoseDataManager* DiagnoseDataManager::getInstance()
{
QMutexLocker locker(&m_staticMutex);
if (m_instance == nullptr) {
m_instance = new DiagnoseDataManager();
}
return m_instance;
}
void DiagnoseDataManager::destroyInstance()
{
QMutexLocker locker(&m_staticMutex);
if (m_instance != nullptr) {
delete m_instance;
m_instance = nullptr;
}
}
DiagnoseDataManager::DiagnoseDataManager(QObject *parent)
: QObject(parent)
{
}
DiagnoseDataManager::~DiagnoseDataManager()
{
}
// ====================== 保持寄存器读写 ======================
quint16 DiagnoseDataManager::getHoldingData(int addr)
{
if (addr < 0 || addr >= HOLD_REG_TOTAL)
return 0;
QMutexLocker locker(&m_dataMutex);
return m_holdingReg[addr];
}
void DiagnoseDataManager::setHoldingData(int addr, quint16 val)
{
if (addr < 0 || addr >= HOLD_REG_TOTAL)
return;
QMutexLocker locker(&m_dataMutex);
m_holdingReg[addr] = val;
}
// ====================== 输入寄存器读写 ======================
quint16 DiagnoseDataManager::getInputData(int addr)
{
if (addr < 0 || addr >= INPUT_REG_TOTAL)
return 0;
QMutexLocker locker(&m_dataMutex);
return m_inputReg[addr];
}
void DiagnoseDataManager::setInputData(int addr, quint16 val)
{
if (addr < 0 || addr >= INPUT_REG_TOTAL)
return;
QMutexLocker locker(&m_dataMutex);
m_inputReg[addr] = val;
}
// ====================== 线圈读写 ======================
bool DiagnoseDataManager::getCoilData(int addr)
{
if (addr < 0 || addr >= COIL_TOTAL)
return false;
QMutexLocker locker(&m_dataMutex);
return m_coils[addr];
}
void DiagnoseDataManager::setCoilData(int addr, bool val)
{
if (addr < 0 || addr >= COIL_TOTAL)
return;
QMutexLocker locker(&m_dataMutex);
m_coils[addr] = val;
}
// ====================== 离散输入读写 ======================
bool DiagnoseDataManager::getDiscreteInputData(int addr)
{
if (addr < 0 || addr >= DI_TOTAL)
return false;
QMutexLocker locker(&m_dataMutex);
return m_discreteInput[addr];
}
void DiagnoseDataManager::setDiscreteInputData(int addr, bool val)
{
if (addr < 0 || addr >= DI_TOTAL)
return;
QMutexLocker locker(&m_dataMutex);
m_discreteInput[addr] = val;
}
// ====================== 批量更新业务数据 ======================
void DiagnoseDataManager::batchUpdateDiagnoseData(const DiagnoseBizData& data)
{
QMutexLocker locker(&m_dataMutex);
// 一次性赋值所有测点,保证数据一致性
m_holdingReg[REG_VOLTAGE] = data.voltage;
m_holdingReg[REG_CURRENT] = data.current;
m_holdingReg[REG_POWER_ACTIVE] = data.powerActive;
m_holdingReg[REG_POWER_REACTIVE] = data.powerReactive;
m_holdingReg[REG_POWER_FACTOR] = data.powerFactor;
m_holdingReg[REG_TEMP] = data.temperature;
}
2.6:modbusserver.h
#ifndef MODBUSSERVER_H
#define MODBUSSERVER_H
#include <QObject>
#include <QtSerialBus/QModbusTcpServer>
#include "../diagnosedata/diagnosedatamanager.h"
class ModbusServer : public QModbusTcpServer
{
Q_OBJECT
public:
explicit ModbusServer(QObject *parent = nullptr);
~ModbusServer() override;
/**
* @brief 启动Modbus服务
* @param port 监听端口,默认502(标准Modbus端口)
* @param listenAddress 监听地址,默认0.0.0.0(所有网卡)
* @return 启动成功返回true
*/
bool startServer(quint16 port = 502, const QString& listenAddress = "0.0.0.0");
// 停止服务,释放端口
void stopServer();
// 同步诊断数据到Modbus寄存器
void syncDataFromDiagnoseData();
protected:
// 重写读寄存器回调,保留调试日志
bool readData(QModbusDataUnit *newData) const override;
private slots:
// 捕获服务错误信息
void onErrorOccurred(QModbusDevice::Error error);
private:
// 初始化寄存器映射表
void initRegisterMap();
};
#endif // MODBUSSERVER_H
2.7:modbusserver.cpp
#include "modbusserver.h"
#include <QDebug>
#include <QModbusPdu>
ModbusServer::ModbusServer(QObject *parent)
: QModbusTcpServer(parent)
{
// 绑定错误信号
connect(this, &QModbusServer::errorOccurred, this, &ModbusServer::onErrorOccurred);
// 绑定状态变化信号(调试用)
connect(this, &QModbusServer::stateChanged, this, [](QModbusDevice::State state){
qDebug() << "[DEBUG] Modbus服务状态变化:" << state;
});
// 初始化寄存器映射
initRegisterMap();
// 强制设置从站地址为1,和上位机指令匹配
setServerAddress(1);
qDebug() << "[DEBUG] 服务端从站地址设置为:" << serverAddress();
}
ModbusServer::~ModbusServer()
{
stopServer();
}
void ModbusServer::initRegisterMap()
{
QModbusDataUnitMap regMap;
// 保持寄存器映射
regMap.insert(QModbusDataUnit::HoldingRegisters,
QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 0, DiagnoseDataManager::HOLD_REG_TOTAL));
// 输入寄存器映射
regMap.insert(QModbusDataUnit::InputRegisters,
QModbusDataUnit(QModbusDataUnit::InputRegisters, 0, DiagnoseDataManager::INPUT_REG_TOTAL));
// 线圈映射
regMap.insert(QModbusDataUnit::Coils,
QModbusDataUnit(QModbusDataUnit::Coils, 0, DiagnoseDataManager::COIL_TOTAL));
// 离散输入映射
regMap.insert(QModbusDataUnit::DiscreteInputs,
QModbusDataUnit(QModbusDataUnit::DiscreteInputs, 0, DiagnoseDataManager::DI_TOTAL));
// 设置映射表
bool mapOk = setMap(regMap);
if (!mapOk) {
qDebug() << "[ERROR] 寄存器映射设置失败:" << errorString();
} else {
qDebug() << "[INFO] 寄存器映射初始化成功";
}
}
bool ModbusServer::startServer(quint16 port, const QString& listenAddress)
{
// 显式设置监听地址 - 0.0.0.0表示监听所有网卡
setConnectionParameter(QModbusDevice::NetworkAddressParameter, listenAddress);
// 显式设置监听端口
setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
// 启动服务
if (!connectDevice()) {
qDebug() << "[ERROR] Modbus服务启动失败:" << errorString();
return false;
}
qDebug() << "[INFO] ========================================";
qDebug() << "[INFO] Modbus TCP服务启动成功";
qDebug() << "[INFO] 监听地址:" << listenAddress;
qDebug() << "[INFO] 监听端口:" << port;
qDebug() << "[INFO] 从站地址:" << serverAddress();
qDebug() << "[INFO] ========================================";
return true;
}
void ModbusServer::stopServer()
{
if (state() == QModbusDevice::ConnectedState) {
disconnectDevice();
qDebug() << "[INFO] Modbus服务已停止,端口已释放";
}
}
bool ModbusServer::readData(QModbusDataUnit *newData) const
{
qDebug() << "\n[DEBUG] 收到读寄存器请求";
qDebug() << "[DEBUG] 寄存器类型:" << newData->registerType();
qDebug() << "[DEBUG] 起始地址:" << newData->startAddress();
qDebug() << "[DEBUG] 读取数量:" << newData->valueCount();
// 调用父类原生实现,从寄存器映射中读取数据
bool result = QModbusTcpServer::readData(newData);
qDebug() << "[DEBUG] 读操作结果:" << (result ? "成功" : "失败");
if (result && newData->valueCount() > 0) {
qDebug() << "[DEBUG] 首个寄存器值:" << newData->value(0);
}
return result;
}
void ModbusServer::onErrorOccurred(QModbusDevice::Error error)
{
qDebug() << "[ERROR] 服务发生错误,错误码:" << error;
qDebug() << "[ERROR] 错误详情:" << errorString();
}
void ModbusServer::syncDataFromDiagnoseData()
{
auto* dataMgr = DiagnoseDataManager::getInstance();
// 同步保持寄存器
for (int i = 0; i < DiagnoseDataManager::HOLD_REG_TOTAL; ++i) {
quint16 val = dataMgr->getHoldingData(i);
setData(QModbusDataUnit::HoldingRegisters, i, val);
}
// 同步输入寄存器
for (int i = 0; i < DiagnoseDataManager::INPUT_REG_TOTAL; ++i) {
quint16 val = dataMgr->getInputData(i);
setData(QModbusDataUnit::InputRegisters, i, val);
}
// 同步线圈
for (int i = 0; i < DiagnoseDataManager::COIL_TOTAL; ++i) {
bool val = dataMgr->getCoilData(i);
setData(QModbusDataUnit::Coils, i, val ? 1 : 0);
}
// 同步离散输入
for (int i = 0; i < DiagnoseDataManager::DI_TOTAL; ++i) {
bool val = dataMgr->getDiscreteInputData(i);
setData(QModbusDataUnit::DiscreteInputs, i, val ? 1 : 0);
}
}
三:测试结果
网络调试助手运行在windows
modbus tcp小程序运行在WSL2
注意在WSL2中运行程序时需要使用sudo指令运行(端口号小于1024,如502得使用sudo权限)
使用MobaXterm工具用cd 进入程序所在路径,使用下面的指令可以将小程序窗体显示出来
sudo DISPLAY=:0 ./程序名称xxx -platform xcb

四:注意事项
4.1:使用Qt Create18建立程序时要将程序建立在WSL路径下,(按住shift再点浏览按钮可选择WSL路径)。
4.2:WSL中解决sudo 运行X11问题,保证GUI能弹出来,需要使用下面的命令
# 1. 先给所有用户开放X11访问权限(只需要执行一次)
xhost +
# 2. 以root权限运行程序,绑定502端口
sudo DISPLAY=:0 ./程序名xxx -platform xcb
4.3:实测,使用ubuntu终端也可以直接运行小程序,不确定是不是执行了xhost +指令

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)