一、系统架构设计
┌─────────────────────────────────────────────────────────────┐
│ Qt Modbus TCP 通讯系统 │
├─────────────────────────────────────────────────────────────┤
│ 客户端层 │ 协议层 │ 网络层 │ 数据层 │
│ │ │ │ │
│ • UI界面 │ • Modbus协议 │ • TCP连接 │ • 寄存器读写 │
│ • 参数配置 │ • 功能码处理 │ • 数据收发 │ • 线圈控制 │
│ • 状态显示 │ • 异常处理 │ • 超时重连 │ • 数据解析 │
│ • 日志记录 │ • CRC校验 │ • 多线程 │ • 数据缓存 │
└─────────────────────────────────────────────────────────────┘
二、源码实现
2.1 项目文件结构
ModbusTCPClient/
├── CMakeLists.txt
├── ModbusTCPClient.pro
├── include/
│ ├── modbustcpclient.h
│ ├── modbusprotocol.h
│ ├── modbusregister.h
│ └── modbusexception.h
├── src/
│ ├── main.cpp
│ ├── modbustcpclient.cpp
│ ├── modbusprotocol.cpp
│ ├── modbusregister.cpp
│ └── modbusexception.cpp
├── ui/
│ └── mainwindow.ui
└── resources/
└── icons/
2.2 主窗口界面 (mainwindow.h/cpp)
cpp
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#include <QTableWidgetItem>
#include "modbustcpclient.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 on_pushButton_connect_clicked();
void on_pushButton_disconnect_clicked();
// 数据读写
void on_pushButton_readCoils_clicked();
void on_pushButton_readInputs_clicked();
void on_pushButton_readHoldingRegisters_clicked();
void on_pushButton_readInputRegisters_clicked();
void on_pushButton_writeSingleCoil_clicked();
void on_pushButton_writeSingleRegister_clicked();
void on_pushButton_writeMultipleCoils_clicked();
void on_pushButton_writeMultipleRegisters_clicked();
// 状态更新
void updateConnectionStatus(bool connected);
void updateRegisterTable(const QVector<quint16> ®isters);
void updateCoilTable(const QVector<bool> &coils);
void logMessage(const QString &message);
// 定时刷新
void on_pushButton_autoRefresh_clicked();
void autoRefreshTimeout();
private:
Ui::MainWindow *ui;
ModbusTcpClient *modbusClient;
QTimer *autoRefreshTimer;
bool autoRefreshEnabled;
void setupUI();
void setupConnections();
void initializeTables();
void clearTables();
};
#endif // MAINWINDOW_H
cpp
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, autoRefreshEnabled(false)
{
ui->setupUi(this);
// 初始化Modbus客户端
modbusClient = new ModbusTcpClient(this);
// 初始化定时器
autoRefreshTimer = new QTimer(this);
// 设置UI
setupUI();
setupConnections();
initializeTables();
// 设置默认值
ui->lineEdit_ip->setText("192.168.1.100");
ui->spinBox_port->setValue(502);
ui->spinBox_slaveId->setValue(1);
ui->spinBox_startAddress->setValue(0);
ui->spinBox_quantity->setValue(10);
logMessage("Modbus TCP Client 已启动");
}
void MainWindow::setupUI() {
setWindowTitle("Modbus TCP 通讯客户端 v1.0");
resize(1000, 700);
// 设置表格列宽
ui->tableWidget_holdingRegisters->setColumnWidth(0, 80);
ui->tableWidget_holdingRegisters->setColumnWidth(1, 80);
ui->tableWidget_holdingRegisters->setColumnWidth(2, 100);
ui->tableWidget_holdingRegisters->setColumnWidth(3, 100);
ui->tableWidget_coils->setColumnWidth(0, 80);
ui->tableWidget_coils->setColumnWidth(1, 80);
ui->tableWidget_coils->setColumnWidth(2, 100);
}
void MainWindow::setupConnections() {
// Modbus客户端信号连接
connect(modbusClient, &ModbusTcpClient::connectionStatusChanged,
this, &MainWindow::updateConnectionStatus);
connect(modbusClient, &ModbusTcpClient::logMessage,
this, &MainWindow::logMessage);
connect(modbusClient, &ModbusTcpClient::holdingRegistersRead,
this, &MainWindow::updateRegisterTable);
connect(modbusClient, &ModbusTcpClient::coilsRead,
this, &MainWindow::updateCoilTable);
// 定时器连接
connect(autoRefreshTimer, &QTimer::timeout,
this, &MainWindow::autoRefreshTimeout);
}
void MainWindow::initializeTables() {
// 初始化保持寄存器表格
QStringList registerHeaders = {"地址", "值(十进制)", "值(十六进制)", "值(二进制)"};
ui->tableWidget_holdingRegisters->setHorizontalHeaderLabels(registerHeaders);
ui->tableWidget_holdingRegisters->setRowCount(10);
// 初始化线圈表格
QStringList coilHeaders = {"地址", "状态", "值"};
ui->tableWidget_coils->setHorizontalHeaderLabels(coilHeaders);
ui->tableWidget_coils->setRowCount(10);
}
void MainWindow::on_pushButton_connect_clicked() {
QString ipAddress = ui->lineEdit_ip->text();
quint16 port = ui->spinBox_port->value();
quint8 slaveId = ui->spinBox_slaveId->value();
if (modbusClient->connectToServer(ipAddress, port, slaveId)) {
logMessage(QString("正在连接服务器 %1:%2").arg(ipAddress).arg(port));
} else {
QMessageBox::warning(this, "连接失败", "无法连接到Modbus服务器!");
}
}
void MainWindow::on_pushButton_disconnect_clicked() {
modbusClient->disconnectFromServer();
logMessage("已断开连接");
}
void MainWindow::on_pushButton_readHoldingRegisters_clicked() {
if (!modbusClient->isConnected()) {
QMessageBox::warning(this, "未连接", "请先连接到服务器!");
return;
}
quint16 startAddress = ui->spinBox_startAddress->value();
quint16 quantity = ui->spinBox_quantity->value();
logMessage(QString("读取保持寄存器: 起始地址=%1, 数量=%2").arg(startAddress).arg(quantity));
modbusClient->readHoldingRegisters(startAddress, quantity);
}
void MainWindow::on_pushButton_writeSingleRegister_clicked() {
if (!modbusClient->isConnected()) {
QMessageBox::warning(this, "未连接", "请先连接到服务器!");
return;
}
quint16 address = ui->spinBox_singleAddress->value();
quint16 value = ui->spinBox_singleValue->value();
logMessage(QString("写入单个寄存器: 地址=%1, 值=%2").arg(address).arg(value));
modbusClient->writeSingleRegister(address, value);
}
void MainWindow::updateConnectionStatus(bool connected) {
if (connected) {
ui->label_connectionStatus->setText("已连接");
ui->label_connectionStatus->setStyleSheet("QLabel { color: green; }");
ui->pushButton_connect->setEnabled(false);
ui->pushButton_disconnect->setEnabled(true);
} else {
ui->label_connectionStatus->setText("未连接");
ui->label_connectionStatus->setStyleSheet("QLabel { color: red; }");
ui->pushButton_connect->setEnabled(true);
ui->pushButton_disconnect->setEnabled(false);
}
}
void MainWindow::updateRegisterTable(const QVector<quint16> ®isters) {
ui->tableWidget_holdingRegisters->setRowCount(registers.size());
for (int i = 0; i < registers.size(); i++) {
quint16 address = ui->spinBox_startAddress->value() + i;
quint16 value = registers[i];
// 地址列
QTableWidgetItem *addressItem = new QTableWidgetItem(QString::number(address));
addressItem->setTextAlignment(Qt::AlignCenter);
ui->tableWidget_holdingRegisters->setItem(i, 0, addressItem);
// 十进制值
QTableWidgetItem *decimalItem = new QTableWidgetItem(QString::number(value));
decimalItem->setTextAlignment(Qt::AlignCenter);
ui->tableWidget_holdingRegisters->setItem(i, 1, decimalItem);
// 十六进制值
QTableWidgetItem *hexItem = new QTableWidgetItem(QString("0x%1").arg(value, 4, 16, QChar('0')));
hexItem->setTextAlignment(Qt::AlignCenter);
ui->tableWidget_holdingRegisters->setItem(i, 2, hexItem);
// 二进制值
QString binaryStr = QString("%1").arg(value, 16, 2, QChar('0'));
QTableWidgetItem *binaryItem = new QTableWidgetItem(binaryStr);
binaryItem->setTextAlignment(Qt::AlignCenter);
ui->tableWidget_holdingRegisters->setItem(i, 3, binaryItem);
}
}
void MainWindow::logMessage(const QString &message) {
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit_log->append(QString("[%1] %2").arg(timestamp).arg(message));
}
void MainWindow::on_pushButton_autoRefresh_clicked() {
autoRefreshEnabled = !autoRefreshEnabled;
if (autoRefreshEnabled) {
ui->pushButton_autoRefresh->setText("停止自动刷新");
autoRefreshTimer->start(1000); // 1秒刷新一次
logMessage("自动刷新已启动");
} else {
ui->pushButton_autoRefresh->setText("自动刷新");
autoRefreshTimer->stop();
logMessage("自动刷新已停止");
}
}
void MainWindow::autoRefreshTimeout() {
if (modbusClient->isConnected()) {
quint16 startAddress = ui->spinBox_startAddress->value();
quint16 quantity = ui->spinBox_quantity->value();
modbusClient->readHoldingRegisters(startAddress, quantity);
}
}
MainWindow::~MainWindow() {
delete ui;
}
2.3 Modbus TCP 客户端核心类 (modbustcpclient.h/cpp)
cpp
// modbustcpclient.h
#ifndef MODBUSTCPCLIENT_H
#define MODBUSTCPCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QTimer>
#include <QVector>
#include <QMutex>
#include "modbusprotocol.h"
class ModbusTcpClient : public QObject
{
Q_OBJECT
public:
explicit ModbusTcpClient(QObject *parent = nullptr);
~ModbusTcpClient();
// 连接管理
bool connectToServer(const QString &ipAddress, quint16 port, quint8 slaveId);
void disconnectFromServer();
bool isConnected() const { return connected; }
// 读取功能
bool readCoils(quint16 startAddress, quint16 quantity);
bool readDiscreteInputs(quint16 startAddress, quint16 quantity);
bool readHoldingRegisters(quint16 startAddress, quint16 quantity);
bool readInputRegisters(quint16 startAddress, quint16 quantity);
// 写入功能
bool writeSingleCoil(quint16 address, bool value);
bool writeSingleRegister(quint16 address, quint16 value);
bool writeMultipleCoils(quint16 startAddress, const QVector<bool> &values);
bool writeMultipleRegisters(quint16 startAddress, const QVector<quint16> &values);
// 配置
void setTimeout(quint32 timeoutMs) { responseTimeout = timeoutMs; }
void setAutoReconnect(bool enable) { autoReconnect = enable; }
signals:
void connectionStatusChanged(bool connected);
void logMessage(const QString &message);
void coilsRead(const QVector<bool> &coils);
void discreteInputsRead(const QVector<bool> &inputs);
void holdingRegistersRead(const QVector<quint16> ®isters);
void inputRegistersRead(const QVector<quint16> ®isters);
void writeComplete(bool success);
private slots:
void onConnected();
void onDisconnected();
void onReadyRead();
void onErrorOccurred(QAbstractSocket::SocketError socketError);
void onTimeout();
private:
QTcpSocket *tcpSocket;
QTimer *responseTimer;
QMutex mutex;
// 连接状态
bool connected;
QString serverIp;
quint16 serverPort;
quint8 slaveId;
// 协议参数
quint16 transactionId;
quint32 responseTimeout;
bool autoReconnect;
// 当前请求信息
ModbusRequest currentRequest;
QByteArray pendingResponse;
// 私有方法
bool sendRequest(const ModbusRequest &request);
void processResponse(const QByteArray &response);
void handleException(quint8 exceptionCode);
void reconnect();
// 事务ID管理
quint16 generateTransactionId();
void resetTransactionId() { transactionId = 0; }
};
#endif // MODBUSTCPCLIENT_H
cpp
// modbustcpclient.cpp
#include "modbustcpclient.h"
#include "modbusprotocol.h"
#include <QHostAddress>
#include <QThread>
ModbusTcpClient::ModbusTcpClient(QObject *parent)
: QObject(parent)
, connected(false)
, transactionId(0)
, responseTimeout(3000)
, autoReconnect(true)
{
// 创建TCP套接字
tcpSocket = new QTcpSocket(this);
// 创建响应定时器
responseTimer = new QTimer(this);
responseTimer->setSingleShot(true);
// 连接信号槽
connect(tcpSocket, &QTcpSocket::connected, this, &ModbusTcpClient::onConnected);
connect(tcpSocket, &QTcpSocket::disconnected, this, &ModbusTcpClient::onDisconnected);
connect(tcpSocket, &QTcpSocket::readyRead, this, &ModbusTcpClient::onReadyRead);
connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
this, &ModbusTcpClient::onErrorOccurred);
connect(responseTimer, &QTimer::timeout, this, &ModbusTcpClient::onTimeout);
}
ModbusTcpClient::~ModbusTcpClient() {
disconnectFromServer();
}
bool ModbusTcpClient::connectToServer(const QString &ipAddress, quint16 port, quint8 slaveId) {
QMutexLocker locker(&mutex);
if (connected) {
emit logMessage("已经连接到服务器");
return true;
}
serverIp = ipAddress;
serverPort = port;
this->slaveId = slaveId;
emit logMessage(QString("正在连接服务器 %1:%2").arg(ipAddress).arg(port));
tcpSocket->connectToHost(QHostAddress(ipAddress), port);
// 等待连接完成
if (tcpSocket->waitForConnected(responseTimeout)) {
return true;
} else {
emit logMessage(QString("连接失败: %1").arg(tcpSocket->errorString()));
return false;
}
}
void ModbusTcpClient::disconnectFromServer() {
QMutexLocker locker(&mutex);
if (connected) {
tcpSocket->disconnectFromHost();
if (tcpSocket->state() != QAbstractSocket::UnconnectedState) {
tcpSocket->waitForDisconnected(1000);
}
connected = false;
emit connectionStatusChanged(false);
emit logMessage("已断开服务器连接");
}
}
bool ModbusTcpClient::readHoldingRegisters(quint16 startAddress, quint16 quantity) {
QMutexLocker locker(&mutex);
if (!connected) {
emit logMessage("未连接到服务器");
return false;
}
if (quantity == 0 || quantity > 125) {
emit logMessage("读取寄存器数量无效 (1-125)");
return false;
}
ModbusRequest request;
request.functionCode = MODBUS_FUNC_READ_HOLDING_REGISTERS;
request.slaveId = slaveId;
request.startAddress = startAddress;
request.quantity = quantity;
request.transactionId = generateTransactionId();
return sendRequest(request);
}
bool ModbusTcpClient::writeSingleRegister(quint16 address, quint16 value) {
QMutexLocker locker(&mutex);
if (!connected) {
emit logMessage("未连接到服务器");
return false;
}
ModbusRequest request;
request.functionCode = MODBUS_FUNC_WRITE_SINGLE_REGISTER;
request.slaveId = slaveId;
request.startAddress = address;
request.values.append(value);
request.transactionId = generateTransactionId();
return sendRequest(request);
}
bool ModbusTcpClient::sendRequest(const ModbusRequest &request) {
QByteArray mbapHeader = ModbusProtocol::buildMBAPHeader(request.transactionId, request.slaveId, request);
QByteArray pdu = ModbusProtocol::buildPDU(request);
QByteArray fullFrame = mbapHeader + pdu;
currentRequest = request;
pendingResponse.clear();
// 发送数据
qint64 bytesWritten = tcpSocket->write(fullFrame);
if (bytesWritten != fullFrame.size()) {
emit logMessage(QString("发送数据失败: %1").arg(tcpSocket->errorString()));
return false;
}
// 启动响应超时定时器
responseTimer->start(responseTimeout);
emit logMessage(QString("发送请求: 事务ID=%1, 功能码=0x%2, 地址=%3, 数量=%4")
.arg(request.transactionId)
.arg(request.functionCode, 2, 16, QChar('0'))
.arg(request.startAddress)
.arg(request.quantity));
return true;
}
void ModbusTcpClient::onConnected() {
connected = true;
resetTransactionId();
emit connectionStatusChanged(true);
emit logMessage(QString("成功连接到服务器 %1:%2").arg(serverIp).arg(serverPort));
}
void ModbusTcpClient::onDisconnected() {
connected = false;
responseTimer->stop();
emit connectionStatusChanged(false);
emit logMessage("服务器连接已断开");
// 自动重连
if (autoReconnect) {
QTimer::singleShot(5000, this, &ModbusTcpClient::reconnect);
}
}
void ModbusTcpClient::onReadyRead() {
QMutexLocker locker(&mutex);
pendingResponse.append(tcpSocket->readAll());
// 检查是否接收到完整的响应
if (pendingResponse.size() >= 8) { // MBAP头(7字节) + 功能码(1字节) + 数据
quint16 expectedLength = ModbusProtocol::getExpectedResponseLength(pendingResponse);
if (pendingResponse.size() >= expectedLength) {
responseTimer->stop();
processResponse(pendingResponse.left(expectedLength));
pendingResponse.remove(0, expectedLength);
}
}
}
void ModbusTcpClient::processResponse(const QByteArray &response) {
ModbusResponse modbusResponse = ModbusProtocol::parseResponse(response);
if (modbusResponse.isValid) {
emit logMessage(QString("收到响应: 事务ID=%1, 功能码=0x%2")
.arg(modbusResponse.transactionId)
.arg(modbusResponse.functionCode, 2, 16, QChar('0')));
// 检查是否为异常响应
if (modbusResponse.isException) {
handleException(modbusResponse.exceptionCode);
return;
}
// 根据功能码处理响应数据
switch (modbusResponse.functionCode) {
case MODBUS_FUNC_READ_COILS:
case MODBUS_FUNC_READ_DISCRETE_INPUTS:
emit coilsRead(modbusResponse.coilValues);
break;
case MODBUS_FUNC_READ_HOLDING_REGISTERS:
case MODBUS_FUNC_READ_INPUT_REGISTERS:
emit holdingRegistersRead(modbusResponse.registerValues);
break;
case MODBUS_FUNC_WRITE_SINGLE_COIL:
case MODBUS_FUNC_WRITE_SINGLE_REGISTER:
case MODBUS_FUNC_WRITE_MULTIPLE_COILS:
case MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS:
emit writeComplete(true);
break;
default:
emit logMessage(QString("未知的功能码: 0x%1").arg(modbusResponse.functionCode, 2, 16, QChar('0')));
break;
}
} else {
emit logMessage("无效的响应数据");
}
}
void ModbusTcpClient::handleException(quint8 exceptionCode) {
QString exceptionMsg = ModbusProtocol::getExceptionMessage(exceptionCode);
emit logMessage(QString("Modbus异常: %1 (代码: 0x%2)")
.arg(exceptionMsg)
.arg(exceptionCode, 2, 16, QChar('0')));
}
void ModbusTcpClient::reconnect() {
if (!connected) {
emit logMessage("尝试重新连接...");
connectToServer(serverIp, serverPort, slaveId);
}
}
quint16 ModbusTcpClient::generateTransactionId() {
transactionId++;
if (transactionId > 65535) {
transactionId = 1;
}
return transactionId;
}
void ModbusTcpClient::onErrorOccurred(QAbstractSocket::SocketError socketError) {
Q_UNUSED(socketError)
emit logMessage(QString("网络错误: %1").arg(tcpSocket->errorString()));
}
void ModbusTcpClient::onTimeout() {
emit logMessage("响应超时");
pendingResponse.clear();
}
2.4 Modbus 协议处理类 (modbusprotocol.h/cpp)
cpp
// modbusprotocol.h
#ifndef MODBUSPROTOCOL_H
#define MODBUSPROTOCOL_H
#include <QByteArray>
#include <QVector>
#include <QString>
// Modbus功能码
#define MODBUS_FUNC_READ_COILS 0x01
#define MODBUS_FUNC_READ_DISCRETE_INPUTS 0x02
#define MODBUS_FUNC_READ_HOLDING_REGISTERS 0x03
#define MODBUS_FUNC_READ_INPUT_REGISTERS 0x04
#define MODBUS_FUNC_WRITE_SINGLE_COIL 0x05
#define MODBUS_FUNC_WRITE_SINGLE_REGISTER 0x06
#define MODBUS_FUNC_WRITE_MULTIPLE_COILS 0x0F
#define MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS 0x10
// Modbus异常码
#define MODBUS_EXCEPTION_ILLEGAL_FUNCTION 0x01
#define MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS 0x02
#define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE 0x03
#define MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE 0x04
#define MODBUS_EXCEPTION_ACKNOWLEDGE 0x05
#define MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY 0x06
// Modbus请求结构体
struct ModbusRequest {
quint16 transactionId;
quint8 slaveId;
quint8 functionCode;
quint16 startAddress;
quint16 quantity;
QVector<quint16> values;
QVector<bool> coilValues;
};
// Modbus响应结构体
struct ModbusResponse {
bool isValid;
bool isException;
quint16 transactionId;
quint8 slaveId;
quint8 functionCode;
quint8 exceptionCode;
QVector<quint16> registerValues;
QVector<bool> coilValues;
quint16 byteCount;
};
class ModbusProtocol {
public:
// MBAP头处理
static QByteArray buildMBAPHeader(quint16 transactionId, quint8 slaveId, const ModbusRequest &request);
static quint16 getExpectedResponseLength(const QByteArray &response);
// PDU处理
static QByteArray buildPDU(const ModbusRequest &request);
static ModbusResponse parseResponse(const QByteArray &response);
// 异常信息
static QString getExceptionMessage(quint8 exceptionCode);
private:
static QByteArray encodeRegisterValues(const QVector<quint16> &values);
static QByteArray encodeCoilValues(const QVector<bool> &values);
static QVector<quint16> decodeRegisterValues(const QByteArray &data, quint16 byteCount);
static QVector<bool> decodeCoilValues(const QByteArray &data, quint16 quantity);
};
#endif // MODBUSPROTOCOL_H
cpp
// modbusprotocol.cpp
#include "modbusprotocol.h"
QByteArray ModbusProtocol::buildMBAPHeader(quint16 transactionId, quint8 slaveId, const ModbusRequest &request) {
QByteArray header;
header.reserve(7);
// 事务标识符 (2字节)
header.append(static_cast<char>(transactionId >> 8));
header.append(static_cast<char>(transactionId & 0xFF));
// 协议标识符 (2字节) - 0表示Modbus协议
header.append(static_cast<char>(0x00));
header.append(static_cast<char>(0x00));
// 长度字段 (2字节) - 后面字节数
quint16 length = 1 + 2 + 2; // 功能码(1) + 起始地址(2) + 数量(2)
if (request.functionCode == MODBUS_FUNC_WRITE_SINGLE_REGISTER ||
request.functionCode == MODBUS_FUNC_WRITE_SINGLE_COIL) {
length += 2; // 值(2字节)
} else if (request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS ||
request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_COILS) {
length += 2 + 1 + request.values.size() * 2; // 数量(2) + 字节数(1) + 值
}
header.append(static_cast<char>(length >> 8));
header.append(static_cast<char>(length & 0xFF));
// 单元标识符 (1字节)
header.append(static_cast<char>(slaveId));
return header;
}
QByteArray ModbusProtocol::buildPDU(const ModbusRequest &request) {
QByteArray pdu;
// 功能码
pdu.append(static_cast<char>(request.functionCode));
// 起始地址
pdu.append(static_cast<char>(request.startAddress >> 8));
pdu.append(static_cast<char>(request.startAddress & 0xFF));
// 数量
pdu.append(static_cast<char>(request.quantity >> 8));
pdu.append(static_cast<char>(request.quantity & 0xFF));
// 数据(如果有)
if (request.functionCode == MODBUS_FUNC_WRITE_SINGLE_REGISTER) {
pdu.append(static_cast<char>(request.values[0] >> 8));
pdu.append(static_cast<char>(request.values[0] & 0xFF));
} else if (request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS) {
// 字节数
quint8 byteCount = request.values.size() * 2;
pdu.append(static_cast<char>(byteCount));
// 寄存器值
for (quint16 value : request.values) {
pdu.append(static_cast<char>(value >> 8));
pdu.append(static_cast<char>(value & 0xFF));
}
}
return pdu;
}
ModbusResponse ModbusProtocol::parseResponse(const QByteArray &response) {
ModbusResponse result;
result.isValid = false;
result.isException = false;
if (response.size() < 9) {
return result;
}
// 解析MBAP头
result.transactionId = (static_cast<quint8>(response[0]) << 8) | static_cast<quint8>(response[1]);
result.slaveId = static_cast<quint8>(response[6]);
result.functionCode = static_cast<quint8>(response[7]);
// 检查是否为异常响应
if (result.functionCode & 0x80) {
result.isException = true;
result.exceptionCode = static_cast<quint8>(response[8]);
result.isValid = true;
return result;
}
// 正常响应
quint8 byteCount = static_cast<quint8>(response[8]);
QByteArray data = response.mid(9, byteCount);
switch (result.functionCode) {
case MODBUS_FUNC_READ_HOLDING_REGISTERS:
case MODBUS_FUNC_READ_INPUT_REGISTERS:
result.registerValues = decodeRegisterValues(data, byteCount);
break;
case MODBUS_FUNC_READ_COILS:
case MODBUS_FUNC_READ_DISCRETE_INPUTS:
result.coilValues = decodeCoilValues(data, result.byteCount);
break;
}
result.isValid = true;
return result;
}
QString ModbusProtocol::getExceptionMessage(quint8 exceptionCode) {
switch (exceptionCode) {
case MODBUS_EXCEPTION_ILLEGAL_FUNCTION:
return "非法功能码";
case MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS:
return "非法数据地址";
case MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE:
return "非法数据值";
case MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE:
return "从站设备故障";
case MODBUS_EXCEPTION_ACKNOWLEDGE:
return "确认";
case MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY:
return "从站设备忙";
default:
return QString("未知异常 (0x%1)").arg(exceptionCode, 2, 16, QChar('0'));
}
}
QVector<quint16> ModbusProtocol::decodeRegisterValues(const QByteArray &data, quint16 byteCount) {
QVector<quint16> values;
quint16 registerCount = byteCount / 2;
for (quint16 i = 0; i < registerCount; i++) {
quint16 value = (static_cast<quint8>(data[i * 2]) << 8) |
static_cast<quint8>(data[i * 2 + 1]);
values.append(value);
}
return values;
}
quint16 ModbusProtocol::getExpectedResponseLength(const QByteArray &response) {
if (response.size() < 8) return 0;
quint16 length = (static_cast<quint8>(response[4]) << 8) | static_cast<quint8>(response[5]);
return length + 6; // MBAP头(6字节) + 长度字段(2字节) + 数据
}
2.5 主程序 (main.cpp)
cpp
#include "mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QLocale>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置应用程序信息
QApplication::setApplicationName("Modbus TCP Client");
QApplication::setApplicationVersion("1.0");
QApplication::setOrganizationName("Qt Modbus Tools");
// 设置语言
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages) {
const QString baseName = "ModbusTCPClient_" + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName)) {
a.installTranslator(&translator);
break;
}
}
// 创建并显示主窗口
MainWindow w;
w.show();
return a.exec();
}
三、Qt Creator 项目文件
3.1 .pro 文件
qmake
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = ModbusTCPClient
TEMPLATE = app
# 源文件
SOURCES += \
src/main.cpp \
src/mainwindow.cpp \
src/modbustcpclient.cpp \
src/modbusprotocol.cpp
# 头文件
HEADERS += \
include/mainwindow.h \
include/modbustcpclient.h \
include/modbusprotocol.h
# UI文件
FORMS += \
ui/mainwindow.ui
# 资源文件
RESOURCES += \
resources/resources.qrc
# 编译配置
CONFIG += c++11
CONFIG += warn_on
# 输出目录
DESTDIR = bin
# 包含路径
INCLUDEPATH += include
# 调试信息
CONFIG(debug, debug|release) {
CONFIG += console
DEFINES += QT_DEBUG
}
# 发布版本优化
CONFIG(release, debug|release) {
DEFINES += QT_NO_DEBUG_OUTPUT
QMAKE_CXXFLAGS_RELEASE += -O2
}
3.2 CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.16)
project(ModbusTCPClient VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 COMPONENTS Widgets Network REQUIRED)
# 包含目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# 源文件
set(SOURCES
src/main.cpp
src/mainwindow.cpp
src/modbustcpclient.cpp
src/modbusprotocol.cpp
)
# 头文件
set(HEADERS
include/mainwindow.h
include/modbustcpclient.h
include/modbusprotocol.h
)
# UI文件
set(UIS
ui/mainwindow.ui
)
# 资源文件
set(RESOURCES
resources/resources.qrc
)
# 创建可执行文件
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${UIS} ${RESOURCES})
# 链接库
target_link_libraries(${PROJECT_NAME}
Qt5::Widgets
Qt5::Network
)
# 安装配置
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)
参考代码 Modbus tcp 通讯源码 QT编写 www.youwenfan.com/contentcsu/60515.html
四、UI 设计文件 (mainwindow.ui)
xml
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Modbus TCP 通讯客户端</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<!-- 左侧控制面板 -->
<widget class="QGroupBox" name="groupBox_control">
<property name="title">
<string>连接配置</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_ip">
<property name="text">
<string>服务器IP:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_ip">
<property name="text">
<string>192.168.1.100</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_port">
<property name="text">
<string>端口:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinBox_port">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>502</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_slaveId">
<property name="text">
<string>从站ID:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_slaveId">
<property name="maximum">
<number>247</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="pushButton_connect">
<property name="text">
<string>连接</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="pushButton_disconnect">
<property name="text">
<string>断开连接</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<!-- 右侧数据显示 -->
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_registers">
<attribute name="title">
<string>寄存器</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<widget class="QTableWidget" name="tableWidget_holdingRegisters"/>
</layout>
</widget>
<widget class="QWidget" name="tab_coils">
<attribute name="title">
<string>线圈</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<widget class="QTableWidget" name="tableWidget_coils"/>
</layout>
</widget>
<widget class="QWidget" name="tab_log">
<attribute name="title">
<string>日志</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<widget class="QTextEdit" name="textEdit_log"/>
</layout>
</widget>
</widget>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="showMessage">
<string>就绪</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
五、功能扩展建议
5.1 高级功能
- 数据记录:添加SQLite数据库存储历史数据
- 图表显示:集成QCustomPlot显示实时趋势图
- 报警功能:设置阈值报警和邮件通知
- 多设备支持:同时连接多个Modbus服务器
5.2 性能优化
- 异步处理:使用QThread处理耗时操作
- 连接池:支持多个并发连接
- 数据缓存:减少重复请求
- 压缩传输:支持数据压缩
5.3 用户体验
- 主题切换:支持深色/浅色主题
- 多语言:支持中英文切换
- 快捷键:添加常用快捷键
- 帮助文档:集成在线帮助
六、编译和运行
bash
# 使用qmake编译
qmake ModbusTCPClient.pro
make
# 使用CMake编译
mkdir build
cd build
cmake ..
make
# 运行程序
./ModbusTCPClient