@@ -16,10 +16,12 @@ TEMPLATE = app | |||||
SOURCES += main.cpp\ | SOURCES += main.cpp\ | ||||
widget.cpp \ | widget.cpp \ | ||||
mymodbus.cpp \ | mymodbus.cpp \ | ||||
communicationhistory.cpp | |||||
communicationhistory.cpp \ | |||||
serialcommunicator.cpp | |||||
HEADERS += widget.h \ | HEADERS += widget.h \ | ||||
mymodbus.h \ | mymodbus.h \ | ||||
communicationhistory.h | |||||
communicationhistory.h \ | |||||
serialcommunicator.h | |||||
FORMS += widget.ui | FORMS += widget.ui |
@@ -1,6 +1,6 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<!DOCTYPE QtCreatorProject> | <!DOCTYPE QtCreatorProject> | ||||
<!-- Written by QtCreator 4.0.3, 2025-07-28T16:03:38. --> | |||||
<!-- Written by QtCreator 4.0.3, 2025-07-28T19:27:23. --> | |||||
<qtcreator> | <qtcreator> | ||||
<data> | <data> | ||||
<variable>EnvironmentId</variable> | <variable>EnvironmentId</variable> | ||||
@@ -1,11 +1,3 @@ | |||||
#ifndef MYMODBUS_H | |||||
#define MYMODBUS_H | |||||
#include <QByteArray> | |||||
#include <QVector> | |||||
#include <QString> | |||||
#include <QtGlobal> | |||||
/****************************************************************************** | /****************************************************************************** | ||||
* Copyright (C) 2025-. | * Copyright (C) 2025-. | ||||
* | * | ||||
@@ -18,6 +10,15 @@ | |||||
* | * | ||||
******************************************************************************/ | ******************************************************************************/ | ||||
#ifndef MYMODBUS_H | |||||
#define MYMODBUS_H | |||||
#include <QByteArray> | |||||
#include <QVector> | |||||
#include <QString> | |||||
#include <QtGlobal> | |||||
/** | /** | ||||
* @class MyModbus | * @class MyModbus | ||||
* @brief Modbus RTU协议封装类 | * @brief Modbus RTU协议封装类 | ||||
@@ -0,0 +1,268 @@ | |||||
/******************************* | |||||
* Copyright (C) 2023-, XINJE Electronic Co., Ltd. | |||||
* | |||||
* File Name: serialcommunicator.cpp | |||||
* Description: 串口通信管理类实现文件 | |||||
* Others: | |||||
* Version: 1.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-23 | |||||
*******************************/ | |||||
#include "serialcommunicator.h" | |||||
/** | |||||
* @brief 构造函数 | |||||
* @param parent 父对象指针 | |||||
* | |||||
* @details 初始化串口通信组件,包括: | |||||
* 1. 创建串口对象 | |||||
* 2. 创建接收和重发定时器 | |||||
* 3. 设置默认超时参数 | |||||
* 4. 连接信号槽 | |||||
*/ | |||||
SerialCommunicator::SerialCommunicator(QObject *parent) : QObject(parent) | |||||
, serialPort(new QSerialPort(this)) | |||||
, recvTimer(new QTimer(this)) | |||||
, resendTimer(new QTimer(this)) | |||||
, comCount(0) | |||||
, maxRetry(3) | |||||
, recvTimeout(50) | |||||
, resendTimeout(1000) | |||||
{ | |||||
// 配置定时器(单次触发) | |||||
recvTimer->setSingleShot(true); | |||||
resendTimer->setSingleShot(true); | |||||
// 连接信号槽 | |||||
connect(serialPort, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead); | |||||
connect(recvTimer, &QTimer::timeout, this, &SerialCommunicator::onRecvTimeout); | |||||
connect(resendTimer, &QTimer::timeout, this, &SerialCommunicator::onResendTimeout); | |||||
} | |||||
/** | |||||
* @brief 析构函数 | |||||
* @note 确保串口关闭并释放资源 | |||||
*/ | |||||
SerialCommunicator::~SerialCommunicator() | |||||
{ | |||||
close(); | |||||
} | |||||
/** | |||||
* @brief 设置串口参数 | |||||
* @param portName 串口号(如"COM1") | |||||
* @param baudRate 波特率(如9600) | |||||
* @param dataBits 数据位 | |||||
* @param parity 校验位 | |||||
* @param stopBits 停止位 | |||||
* | |||||
* @note 此方法应在打开串口前调用,设置值将在下次open()时生效 | |||||
*/ | |||||
void SerialCommunicator::setPortParams(const QString &portName, qint32 baudRate, | |||||
QSerialPort::DataBits dataBits, | |||||
QSerialPort::Parity parity, | |||||
QSerialPort::StopBits stopBits) | |||||
{ | |||||
this->portName = portName; | |||||
this->baudRate = baudRate; | |||||
this->dataBits = dataBits; | |||||
this->parity = parity; | |||||
this->stopBits = stopBits; | |||||
} | |||||
/** | |||||
* @brief 打开串口 | |||||
* @return true-打开成功, false-打开失败 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 关闭已打开的串口(如果存在) | |||||
* 2. 配置串口参数(波特率、数据位等) | |||||
* 3. 尝试打开串口 | |||||
* 4. 发送状态变化信号 | |||||
*/ | |||||
bool SerialCommunicator::open() | |||||
{ | |||||
// 关闭已打开的串口(如果存在) | |||||
if (serialPort->isOpen()) | |||||
{ | |||||
serialPort->close(); | |||||
} | |||||
// 配置串口参数 | |||||
serialPort->setPortName(portName); | |||||
if (!serialPort->setBaudRate(baudRate)) | |||||
{ | |||||
emit statusChanged("波特率设置失败"); | |||||
return false; | |||||
} | |||||
if (!serialPort->setDataBits(dataBits)) | |||||
{ | |||||
emit statusChanged("数据位设置失败"); | |||||
return false; | |||||
} | |||||
if (!serialPort->setParity(parity)) | |||||
{ | |||||
emit statusChanged("校验位设置失败"); | |||||
return false; | |||||
} | |||||
if (!serialPort->setStopBits(stopBits)) | |||||
{ | |||||
emit statusChanged("停止位设置失败"); | |||||
return false; | |||||
} | |||||
serialPort->setFlowControl(QSerialPort::NoFlowControl); // 无流控制 | |||||
// 打开串口 | |||||
if (serialPort->open(QIODevice::ReadWrite)) | |||||
{ | |||||
emit statusChanged("串口连接成功"); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
emit statusChanged("串口连接失败,请检查参数"); | |||||
return false; | |||||
} | |||||
} | |||||
/** | |||||
* @brief 关闭串口 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 检查串口是否打开 | |||||
* 2. 关闭串口 | |||||
* 3. 发送状态变化信号 | |||||
*/ | |||||
void SerialCommunicator::close() | |||||
{ | |||||
if (serialPort->isOpen()) | |||||
{ | |||||
serialPort->close(); | |||||
emit statusChanged("串口断开"); | |||||
} | |||||
} | |||||
/** | |||||
* @brief 发送数据并启动超时重发机制 | |||||
* @param data 待发送的数据 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 检查串口是否打开 | |||||
* 2. 保存待发送数据(用于重发) | |||||
* 3. 重置重发计数器 | |||||
* 4. 发送数据 | |||||
* 5. 启动重发超时定时器 | |||||
*/ | |||||
void SerialCommunicator::sendData(const QByteArray &data) | |||||
{ | |||||
if (!serialPort->isOpen()) | |||||
{ | |||||
emit statusChanged("串口未打开,无法发送数据"); | |||||
return; | |||||
} | |||||
// 保存待重发数据,初始化计数器,启动重发定时器 | |||||
currentData = data; | |||||
comCount = 0; | |||||
serialPort->write(data); // 发送数据 | |||||
emit statusChanged("发送报文:" + data.toHex().toUpper()); | |||||
resendTimer->start(resendTimeout); // 启动超时计时 | |||||
} | |||||
/** | |||||
* @brief 设置最大重发次数 | |||||
* @param count 最大重发次数 | |||||
*/ | |||||
void SerialCommunicator::setMaxRetry(int count) | |||||
{ | |||||
maxRetry = count; | |||||
} | |||||
/** | |||||
* @brief 设置接收完成超时时间 | |||||
* @param ms 超时时间(毫秒) | |||||
* | |||||
* @note 当串口持续ms毫秒无新数据时,认为接收完成 | |||||
*/ | |||||
void SerialCommunicator::setRecvTimeout(int ms) | |||||
{ | |||||
recvTimeout = ms; | |||||
} | |||||
/** | |||||
* @brief 设置重发间隔时间 | |||||
* @param ms 重发间隔(毫秒) | |||||
*/ | |||||
void SerialCommunicator::setResendTimeout(int ms) | |||||
{ | |||||
resendTimeout = ms; | |||||
} | |||||
/** | |||||
* @brief 检查串口是否打开 | |||||
* @return true-已打开, false-已关闭 | |||||
*/ | |||||
bool SerialCommunicator::isOpen() const | |||||
{ | |||||
return serialPort->isOpen(); | |||||
} | |||||
// ====================== 私有槽函数 ====================== // | |||||
/** | |||||
* @brief 串口数据到达处理 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 读取所有可用数据到接收缓冲区 | |||||
* 2. 重启接收超时定时器 | |||||
* 3. 定时器超时后将触发onRecvTimeout | |||||
*/ | |||||
void SerialCommunicator::onReadyRead() | |||||
{ | |||||
// 读取数据到缓冲区 | |||||
recvBuffer.append(serialPort->readAll()); | |||||
//重置接收超时定时器 | |||||
recvTimer->start(recvTimeout); | |||||
} | |||||
/** | |||||
* @brief 接收完成超时处理 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 发送dataReceived信号包含完整数据 | |||||
* 2. 清空接收缓冲区 | |||||
* 3. 停止重发定时器(表示已收到响应) | |||||
*/ | |||||
void SerialCommunicator::onRecvTimeout() | |||||
{ | |||||
// 接收完成,发送完整数据信号,停止重发定时器 | |||||
emit dataReceived(recvBuffer); | |||||
recvBuffer.clear(); | |||||
resendTimer->stop(); | |||||
} | |||||
/** | |||||
* @brief 重发超时处理 | |||||
* | |||||
* @details 执行流程: | |||||
* 1. 检查是否达到最大重发次数 | |||||
* 2. 未达到则重发数据并更新计数器 | |||||
* 3. 达到最大次数则触发超时信号 | |||||
*/ | |||||
void SerialCommunicator::onResendTimeout() | |||||
{ | |||||
if (comCount < maxRetry) | |||||
{ | |||||
// 重发数据,更新计数器 | |||||
serialPort->write(currentData); | |||||
comCount++; | |||||
emit statusChanged(QString("通信超时,重新发送中(%1/%2)").arg(comCount).arg(maxRetry)); | |||||
resendTimer->start(resendTimeout); // 继续计时 | |||||
} | |||||
else | |||||
{ | |||||
// 达到最大重发次数,触发超时信号 | |||||
resendTimer->stop(); | |||||
emit timeoutOccurred(); | |||||
} | |||||
} |
@@ -0,0 +1,178 @@ | |||||
/******************************* | |||||
* Copyright (C) 2025-. | |||||
* | |||||
* File Name: serialcommunicator.h | |||||
* Description: 串口通信管理类,封装串口操作、数据收发和超时处理 | |||||
* Others: | |||||
* Version: 1.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-23 | |||||
*******************************/ | |||||
#ifndef SERIALCOMMUNICATOR_H | |||||
#define SERIALCOMMUNICATOR_H | |||||
#include <QObject> | |||||
#include <QSerialPort> | |||||
#include <QTimer> | |||||
#include <QByteArray> | |||||
/** | |||||
* @brief 串口通信管理类 | |||||
* @class SerialCommunicator | |||||
* | |||||
* @details 此类封装了串口通信的核心功能,包括: | |||||
* 1. 串口参数配置与开关控制 | |||||
* 2. 数据发送与自动重发机制 | |||||
* 3. 数据接收与完整性判断 | |||||
* 4. 超时处理与状态通知 | |||||
* | |||||
* @note 使用流程: | |||||
* 1. 设置串口参数(setPortParams) | |||||
* 2. 打开串口(open) | |||||
* 3. 发送数据(sendData) | |||||
* 4. 接收数据(通过dataReceived信号) | |||||
* 5. 关闭串口(close) | |||||
*/ | |||||
class SerialCommunicator : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
/** | |||||
* @brief 构造函数 | |||||
* @param parent 父对象指针 | |||||
*/ | |||||
explicit SerialCommunicator(QObject *parent = nullptr); | |||||
/** | |||||
* @brief 析构函数 | |||||
* @note 自动关闭串口并释放资源 | |||||
*/ | |||||
~SerialCommunicator(); | |||||
// ====================== 串口配置 ====================== // | |||||
/** | |||||
* @brief 设置串口参数 | |||||
* @param portName 串口号(如"COM1"或"/dev/ttyS0") | |||||
* @param baudRate 波特率(如9600, 115200) | |||||
* @param dataBits 数据位(通常为QSerialPort::Data8) | |||||
* @param parity 校验位(无校验/奇校验/偶校验) | |||||
* @param stopBits 停止位(通常为QSerialPort::OneStop) | |||||
* @note 此方法应在打开串口前调用 | |||||
*/ | |||||
void setPortParams(const QString &portName, qint32 baudRate, | |||||
QSerialPort::DataBits dataBits, | |||||
QSerialPort::Parity parity, | |||||
QSerialPort::StopBits stopBits); | |||||
/** | |||||
* @brief 打开串口 | |||||
* @return true-打开成功, false-打开失败 | |||||
* @note 成功打开后会发出statusChanged信号 | |||||
*/ | |||||
bool open(); | |||||
/** | |||||
* @brief 关闭串口 | |||||
* @note 关闭后会发出statusChanged信号 | |||||
*/ | |||||
void close(); | |||||
// ====================== 数据操作 ====================== // | |||||
/** | |||||
* @brief 发送数据并启动超时重发机制 | |||||
* @param data 待发送的数据 | |||||
* @note 数据发送后启动重发定时器,如达到最大重发次数仍未收到响应, | |||||
* 将发出timeoutOccurred信号 | |||||
*/ | |||||
void sendData(const QByteArray &data); | |||||
// ====================== 超时配置 ====================== // | |||||
/** | |||||
* @brief 设置最大重发次数 | |||||
* @param count 重发次数(默认3次) | |||||
*/ | |||||
void setMaxRetry(int count); | |||||
/** | |||||
* @brief 设置接收完成超时时间 | |||||
* @param ms 超时时间(毫秒,默认50ms) | |||||
* @note 当串口持续ms毫秒无新数据时,认为接收完成 | |||||
*/ | |||||
void setRecvTimeout(int ms); | |||||
/** | |||||
* @brief 设置重发间隔时间 | |||||
* @param ms 重发间隔(毫秒,默认1000ms) | |||||
*/ | |||||
void setResendTimeout(int ms); | |||||
// ====================== 状态查询 ====================== // | |||||
/** | |||||
* @brief 检查串口是否打开 | |||||
* @return true-已打开, false-已关闭 | |||||
*/ | |||||
bool isOpen() const; | |||||
signals: | |||||
/** | |||||
* @brief 接收到完整数据信号 | |||||
* @param data 接收到的完整数据包 | |||||
*/ | |||||
void dataReceived(const QByteArray &data); | |||||
/** | |||||
* @brief 状态变化信号 | |||||
* @param status 状态描述文本 | |||||
* @note 可能的状态: "已连接", "已断开", "接收超时"等 | |||||
*/ | |||||
void statusChanged(const QString &status); | |||||
/** | |||||
* @brief 通信超时信号 | |||||
* @note 当达到最大重发次数仍未收到响应时触发 | |||||
*/ | |||||
void timeoutOccurred(); | |||||
private slots: | |||||
/** | |||||
* @brief 串口数据到达处理 | |||||
* @note 当串口有数据可读时触发,数据存入缓冲区并启动接收超时定时器 | |||||
*/ | |||||
void onReadyRead(); | |||||
/** | |||||
* @brief 接收完成超时处理 | |||||
* @note 当recvTimer超时,认为数据接收完成,发出dataReceived信号 | |||||
*/ | |||||
void onRecvTimeout(); | |||||
/** | |||||
* @brief 重发超时处理 | |||||
* @note 当resendTimer超时,检查重发次数并决定是否重发数据 | |||||
*/ | |||||
void onResendTimeout(); | |||||
private: | |||||
QSerialPort *serialPort; // 串口对象 | |||||
QTimer *recvTimer; // 接收完成判断定时器 | |||||
QTimer *resendTimer; // 超时重发定时器 | |||||
QByteArray recvBuffer; // 接收缓冲区 | |||||
QByteArray currentData; // 当前待重发数据 | |||||
int comCount; // 重发计数器 | |||||
int maxRetry; // 最大重发次数 | |||||
int recvTimeout; // 接收超时时间(ms) | |||||
int resendTimeout; // 重发间隔(ms) | |||||
// 串口参数 | |||||
QString portName; | |||||
qint32 baudRate; | |||||
QSerialPort::DataBits dataBits; | |||||
QSerialPort::Parity parity; | |||||
QSerialPort::StopBits stopBits; | |||||
}; | |||||
#endif // SERIALCOMMUNICATOR_H |
@@ -8,36 +8,24 @@ | |||||
#include <QVector> | #include <QVector> | ||||
#include <QMessageBox> | #include <QMessageBox> | ||||
#include <synchapi.h> | #include <synchapi.h> | ||||
#include "mymodbus.h" | |||||
#include "communicationhistory.h" | |||||
Widget::Widget(QWidget *parent) : | Widget::Widget(QWidget *parent) : | ||||
QWidget(parent), | QWidget(parent), | ||||
ui(new Ui::Widget) | ui(new Ui::Widget) | ||||
{ | { | ||||
ui->setupUi(this); | |||||
serialPort = new QSerialPort(this); | |||||
modbus = new MyModbus(); | modbus = new MyModbus(); | ||||
recvTimer = new QTimer(this); | |||||
ui->setupUi(this); | |||||
serialComm = new SerialCommunicator(this); // 初始化串口通信类 | |||||
connect(serialPort, &QSerialPort::readyRead, this, &Widget::onReadyRead); | |||||
connect(recvTimer, &QTimer::timeout, this, &Widget::on_SerialData_ReadyToRead); | |||||
QObject::connect(&timer, &QTimer::timeout, [this]{ | |||||
if (comCount < 3) | |||||
{ | |||||
ui->textEdit_2->append("通信超时,重新发送中"); | |||||
// 配置串口参数 | |||||
serialComm->setMaxRetry(3); // 最大重发3次 | |||||
serialComm->setRecvTimeout(50); // 接收超时50ms | |||||
serialComm->setResendTimeout(1000); // 重发间隔1s | |||||
serialPort->write(modbus->SendCommand()); | |||||
comCount ++; | |||||
} | |||||
else | |||||
{ | |||||
QMessageBox::warning(this, "提示", "等待响应超时,请检查设备状态。"); | |||||
ui->btn_read->setEnabled(1); | |||||
ui->pushWrite->setEnabled(1); | |||||
timer.stop(); | |||||
} | |||||
}); | |||||
// 连接信号槽(串口->界面) | |||||
connect(serialComm, &SerialCommunicator::dataReceived, this, &Widget::onSerialDataReceived); | |||||
connect(serialComm, &SerialCommunicator::statusChanged, this, &Widget::onSerialStatusChanged); | |||||
connect(serialComm, &SerialCommunicator::timeoutOccurred, this, &Widget::onSerialTimeout); | |||||
ui->comboBox_baudRate->setCurrentIndex(3); | ui->comboBox_baudRate->setCurrentIndex(3); | ||||
ui->comboBox_dataBit->setCurrentIndex(3); | ui->comboBox_dataBit->setCurrentIndex(3); | ||||
@@ -56,6 +44,7 @@ Widget::Widget(QWidget *parent) : | |||||
Widget::~Widget() | Widget::~Widget() | ||||
{ | { | ||||
delete modbus; | delete modbus; | ||||
delete serialComm; | |||||
delete ui; | delete ui; | ||||
} | } | ||||
@@ -64,47 +53,40 @@ void Widget::on_btnConnect_clicked() | |||||
{ | { | ||||
if (ui->btnConnect->text() == "连接") | if (ui->btnConnect->text() == "连接") | ||||
{ | { | ||||
//配置串口号 | |||||
serialPort->setPortName(ui->comboBox_serialNum->currentText()); | |||||
//配置波特率 | |||||
serialPort->setBaudRate(ui->comboBox_baudRate->currentText().toInt()); | |||||
//配置数据位 | |||||
serialPort->setDataBits(QSerialPort::DataBits(ui->comboBox_dataBit->currentText().toInt())); | |||||
//配置校验位 | |||||
// 获取界面配置的串口参数 | |||||
QString portName = ui->comboBox_serialNum->currentText(); | |||||
qint32 baudRate = ui->comboBox_baudRate->currentText().toInt(); | |||||
QSerialPort::DataBits dataBits = QSerialPort::DataBits(ui->comboBox_dataBit->currentText().toInt()); | |||||
QSerialPort::Parity parity; | |||||
switch (ui->comboBox_xiaoyan->currentIndex()) | switch (ui->comboBox_xiaoyan->currentIndex()) | ||||
{ | { | ||||
case 0: | |||||
serialPort->setParity(QSerialPort::NoParity); | |||||
break; | |||||
case 1: | |||||
serialPort->setParity(QSerialPort::OddParity); | |||||
break; | |||||
case 2: | |||||
serialPort->setParity(QSerialPort::EvenParity); | |||||
case 0: parity = QSerialPort::NoParity; break; | |||||
case 1: parity = QSerialPort::OddParity; break; | |||||
case 2: parity = QSerialPort::EvenParity; break; | |||||
default: parity = QSerialPort::NoParity; | |||||
} | } | ||||
//配置停止位 | |||||
serialPort->setStopBits(QSerialPort::StopBits(ui->comboBox_stopBit->currentText().toInt())); | |||||
//打开串口 | |||||
if (serialPort->open(QIODevice::ReadWrite)) | |||||
QSerialPort::StopBits stopBits = QSerialPort::StopBits(ui->comboBox_stopBit->currentText().toInt()); | |||||
// 配置并打开串口 | |||||
serialComm->setPortParams(portName, baudRate, dataBits, parity, stopBits); | |||||
if (serialComm->open()) | |||||
{ | { | ||||
ui->textEdit_2->append("串口连接成功"); | |||||
ui->btnConnect->setText("断开"); | ui->btnConnect->setText("断开"); | ||||
ui->btn_read->setEnabled(1); | |||||
ui->pushWrite->setEnabled(1); | |||||
ui->btn_read->setEnabled(true); | |||||
ui->pushWrite->setEnabled(true); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
QMessageBox::warning(this, "提示", "串口连接失败,请检查串口参数配置"); | |||||
QMessageBox::warning(this, "提示", "串口连接失败"); | |||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
serialPort->close(); | |||||
ui->btn_read->setEnabled(0); | |||||
ui->pushWrite->setEnabled(0); | |||||
qDebug() << "Serial close"; | |||||
// 断开串口 | |||||
serialComm->close(); | |||||
ui->btnConnect->setText("连接"); | ui->btnConnect->setText("连接"); | ||||
ui->textEdit_2->append("串口断开"); | |||||
ui->btn_read->setEnabled(false); | |||||
ui->pushWrite->setEnabled(false); | |||||
} | } | ||||
} | } | ||||
@@ -149,14 +131,10 @@ void Widget::on_pushWrite_clicked() | |||||
modbus->Set(stationAddress,functionCode,stratAddress,length); | modbus->Set(stationAddress,functionCode,stratAddress,length); | ||||
modbus->WriteCoil(coils); | modbus->WriteCoil(coils); | ||||
ui->textEdit_2->append("发送报文"+modbus->SendCommand().toHex().toUpper()); | |||||
serialPort->write(modbus->SendCommand()); | |||||
serialComm->sendData(modbus->SendCommand()); | |||||
ui->btn_read->setEnabled(0); | ui->btn_read->setEnabled(0); | ||||
ui->pushWrite->setEnabled(0); | ui->pushWrite->setEnabled(0); | ||||
comCount = 0; | |||||
timer.start(1000); // 1 s 后触发超时 | |||||
break; | break; | ||||
} | } | ||||
@@ -196,14 +174,10 @@ void Widget::on_pushWrite_clicked() | |||||
modbus->Set(stationAddress,functionCode,stratAddress,length); | modbus->Set(stationAddress,functionCode,stratAddress,length); | ||||
modbus->WriteRegister(values); //要发送的报文 | modbus->WriteRegister(values); //要发送的报文 | ||||
ui->textEdit_2->append("发送报文"+modbus->SendCommand().toHex().toUpper()); | |||||
serialPort->write(modbus->SendCommand()); | |||||
serialComm->sendData(modbus->SendCommand()); | |||||
ui->btn_read->setEnabled(0); | ui->btn_read->setEnabled(0); | ||||
ui->pushWrite->setEnabled(0); | ui->pushWrite->setEnabled(0); | ||||
comCount = 0; | |||||
timer.start(1000); // 1 s 后触发超时 | |||||
break; | break; | ||||
} | } | ||||
default: | default: | ||||
@@ -215,80 +189,6 @@ void Widget::on_pushWrite_clicked() | |||||
} | } | ||||
} | } | ||||
//处理回复报文 | |||||
void Widget::on_SerialData_ReadyToRead() | |||||
{ | |||||
QByteArray revMessage = recvBuffer; | |||||
recvBuffer.clear(); | |||||
revMessage = modbus->Receive(revMessage); | |||||
if (!revMessage.isEmpty()) | |||||
{ | |||||
ui->btn_read->setEnabled(1); | |||||
ui->pushWrite->setEnabled(1); | |||||
timer.stop(); | |||||
} | |||||
else | |||||
{ | |||||
return; | |||||
} | |||||
QString hexData = revMessage.toHex().toUpper(); | |||||
ui->textEdit_2->append("接收报文"+hexData); | |||||
int exCode = modbus->ErrorCheck(); | |||||
if (exCode) | |||||
{ | |||||
QString errorMsg; | |||||
switch (exCode) | |||||
{ | |||||
case 0x01: errorMsg = "非法功能码"; break; | |||||
case 0x02: errorMsg = "非法数据地址"; break; | |||||
case 0x03: errorMsg = "非法数据值"; break; | |||||
case 0x04: errorMsg = "从站设备故障"; break; | |||||
default: errorMsg = "未知异常"; break; | |||||
} | |||||
QMessageBox::warning(this, "异常响应", | |||||
QString("异常\n错误码: 0x%1 (%2)") | |||||
.arg(QString::number(exCode, 16).toUpper().rightJustified(2, '0')) | |||||
.arg(errorMsg)); | |||||
return; | |||||
} | |||||
switch (ui->comboBox_gongnengma->currentIndex()) | |||||
{ | |||||
//解析读线圈的返回报文 | |||||
case 0: | |||||
{ | |||||
QVector<bool> coil = modbus->AnalReadCoil(); | |||||
ui->textEdit->append("线圈状态:"); | |||||
for (int i = 0; i < coil.size(); i++) | |||||
{ | |||||
bool state = coil.at(i); | |||||
ui->textEdit->append("线圈"+QString::number(i+1)+":"+QString::number(state)); | |||||
} | |||||
break; | |||||
} | |||||
//解析读寄存器的返回报文 | |||||
case 1: | |||||
{ | |||||
QVector<quint16> registers = modbus->AnalReadReg(); | |||||
ui->textEdit->append("寄存器的值:"); | |||||
for (int i = 0; i < registers.size(); i++) | |||||
{ | |||||
ui->textEdit->append("寄存器"+QString::number(i+1)+":"+QString::number(registers.at(i))); | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
//发送读线圈和寄存器报文 | //发送读线圈和寄存器报文 | ||||
void Widget::on_btn_read_clicked() | void Widget::on_btn_read_clicked() | ||||
{ | { | ||||
@@ -315,13 +215,10 @@ void Widget::on_btn_read_clicked() | |||||
modbus->Set(stationAddress,functionCode,stratAddress,length); | modbus->Set(stationAddress,functionCode,stratAddress,length); | ||||
modbus->ReadCoilAndReg(); | modbus->ReadCoilAndReg(); | ||||
serialPort->write(modbus->SendCommand()); //发送报文 | |||||
ui->textEdit_2->append("发送报文"+modbus->SendCommand().toHex().toUpper()); | |||||
serialComm->sendData(modbus->SendCommand()); | |||||
ui->btn_read->setEnabled(0); | ui->btn_read->setEnabled(0); | ||||
ui->pushWrite->setEnabled(0); | ui->pushWrite->setEnabled(0); | ||||
comCount = 0; | |||||
timer.start(1000); // 1 s 后触发超时 | |||||
} | } | ||||
void Widget::on_btn_SaveDate_clicked() | void Widget::on_btn_SaveDate_clicked() | ||||
@@ -339,13 +236,73 @@ void Widget::on_btn_ClearDate_clicked() | |||||
ui->textEdit_2->clear(); | ui->textEdit_2->clear(); | ||||
} | } | ||||
void Widget::onReadyRead() | |||||
void Widget::on_btn_ClearRead_clicked() | |||||
{ | { | ||||
recvBuffer.append(serialPort->readAll()); | |||||
recvTimer->start(50); // 50ms 内无新数据即认为接收完成 | |||||
ui->textEdit->clear(); | |||||
} | } | ||||
void Widget::on_btn_ClearRead_clicked() | |||||
void Widget::onSerialDataReceived(const QByteArray &data) | |||||
{ | { | ||||
ui->textEdit->clear(); | |||||
QByteArray revMessage = modbus->Receive(data); // 交给Modbus解析 | |||||
if (revMessage.isEmpty()) return; | |||||
// 启用操作按钮 | |||||
ui->btn_read->setEnabled(true); | |||||
ui->pushWrite->setEnabled(true); | |||||
ui->textEdit_2->append("接收报文:" + revMessage.toHex().toUpper()); | |||||
// 检查Modbus错误码 | |||||
int exCode = modbus->ErrorCheck(); | |||||
if (exCode) | |||||
{ | |||||
QString errorMsg; | |||||
switch (exCode) { | |||||
case 0x01: errorMsg = "非法功能码"; break; | |||||
case 0x02: errorMsg = "非法数据地址"; break; | |||||
case 0x03: errorMsg = "非法数据值"; break; | |||||
case 0x04: errorMsg = "从站设备故障"; break; | |||||
default: errorMsg = "未知异常"; break; | |||||
} | |||||
QMessageBox::warning(this, "异常响应", | |||||
QString("错误码: 0x%1(%2)").arg(QString::number(exCode, 16).toUpper(), errorMsg)); | |||||
return; | |||||
} | |||||
// 解析并显示数据(根据功能码) | |||||
switch (ui->comboBox_gongnengma->currentIndex()) | |||||
{ | |||||
case 0: | |||||
{ // 读线圈 | |||||
QVector<bool> coils = modbus->AnalReadCoil(); | |||||
ui->textEdit->append("线圈状态:"); | |||||
for (int i = 0; i < coils.size(); i++) | |||||
{ | |||||
ui->textEdit->append(QString("线圈%1: %2").arg(i+1).arg(coils[i] ? "1" : "0")); | |||||
} | |||||
break; | |||||
} | |||||
case 1: | |||||
{ // 读寄存器 | |||||
QVector<quint16> regs = modbus->AnalReadReg(); | |||||
ui->textEdit->append("寄存器值:"); | |||||
for (int i = 0; i < regs.size(); i++) | |||||
{ | |||||
ui->textEdit->append(QString("寄存器%1: %2").arg(i+1).arg(regs[i])); | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
void Widget::onSerialStatusChanged(const QString &status) | |||||
{ | |||||
ui->textEdit_2->append(status); // 显示状态信息(如连接成功、超时重发等) | |||||
} | |||||
void Widget::onSerialTimeout() | |||||
{ | |||||
QMessageBox::warning(this, "提示", "等待响应超时,请检查设备"); | |||||
ui->btn_read->setEnabled(true); | |||||
ui->pushWrite->setEnabled(true); | |||||
} | } |
@@ -5,6 +5,8 @@ | |||||
#include <QWidget> | #include <QWidget> | ||||
#include <QTimer> | #include <QTimer> | ||||
#include "mymodbus.h" | #include "mymodbus.h" | ||||
#include "communicationhistory.h" | |||||
#include "serialcommunicator.h" | |||||
namespace Ui { | namespace Ui { | ||||
class Widget; | class Widget; | ||||
@@ -23,8 +25,6 @@ private slots: | |||||
void on_pushWrite_clicked(); | void on_pushWrite_clicked(); | ||||
void on_SerialData_ReadyToRead(); | |||||
void on_btn_read_clicked(); | void on_btn_read_clicked(); | ||||
void on_btn_SaveDate_clicked(); | void on_btn_SaveDate_clicked(); | ||||
@@ -33,18 +33,18 @@ private slots: | |||||
void on_btn_ClearDate_clicked(); | void on_btn_ClearDate_clicked(); | ||||
void onReadyRead(); | |||||
void on_btn_ClearRead_clicked(); | void on_btn_ClearRead_clicked(); | ||||
void onSerialDataReceived(const QByteArray &data); | |||||
void onSerialStatusChanged(const QString &status); | |||||
void onSerialTimeout(); | |||||
private: | private: | ||||
Ui::Widget *ui; | Ui::Widget *ui; | ||||
QSerialPort *serialPort; | |||||
SerialCommunicator *serialComm; | |||||
MyModbus *modbus; | MyModbus *modbus; | ||||
QTimer timer; | |||||
int comCount; | |||||
QByteArray recvBuffer; | |||||
QTimer *recvTimer; | |||||
}; | }; | ||||
#endif // WIDGET_H | #endif // WIDGET_H |