@@ -16,10 +16,12 @@ TEMPLATE = app | |||
SOURCES += main.cpp\ | |||
widget.cpp \ | |||
mymodbus.cpp \ | |||
communicationhistory.cpp | |||
communicationhistory.cpp \ | |||
serialcommunicator.cpp | |||
HEADERS += widget.h \ | |||
mymodbus.h \ | |||
communicationhistory.h | |||
communicationhistory.h \ | |||
serialcommunicator.h | |||
FORMS += widget.ui |
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!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> | |||
<data> | |||
<variable>EnvironmentId</variable> | |||
@@ -1,11 +1,3 @@ | |||
#ifndef MYMODBUS_H | |||
#define MYMODBUS_H | |||
#include <QByteArray> | |||
#include <QVector> | |||
#include <QString> | |||
#include <QtGlobal> | |||
/****************************************************************************** | |||
* Copyright (C) 2025-. | |||
* | |||
@@ -18,6 +10,15 @@ | |||
* | |||
******************************************************************************/ | |||
#ifndef MYMODBUS_H | |||
#define MYMODBUS_H | |||
#include <QByteArray> | |||
#include <QVector> | |||
#include <QString> | |||
#include <QtGlobal> | |||
/** | |||
* @class MyModbus | |||
* @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 <QMessageBox> | |||
#include <synchapi.h> | |||
#include "mymodbus.h" | |||
#include "communicationhistory.h" | |||
Widget::Widget(QWidget *parent) : | |||
QWidget(parent), | |||
ui(new Ui::Widget) | |||
{ | |||
ui->setupUi(this); | |||
serialPort = new QSerialPort(this); | |||
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_dataBit->setCurrentIndex(3); | |||
@@ -56,6 +44,7 @@ Widget::Widget(QWidget *parent) : | |||
Widget::~Widget() | |||
{ | |||
delete modbus; | |||
delete serialComm; | |||
delete ui; | |||
} | |||
@@ -64,47 +53,40 @@ void Widget::on_btnConnect_clicked() | |||
{ | |||
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()) | |||
{ | |||
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->btn_read->setEnabled(1); | |||
ui->pushWrite->setEnabled(1); | |||
ui->btn_read->setEnabled(true); | |||
ui->pushWrite->setEnabled(true); | |||
} | |||
else | |||
{ | |||
QMessageBox::warning(this, "提示", "串口连接失败,请检查串口参数配置"); | |||
QMessageBox::warning(this, "提示", "串口连接失败"); | |||
} | |||
} | |||
else | |||
{ | |||
serialPort->close(); | |||
ui->btn_read->setEnabled(0); | |||
ui->pushWrite->setEnabled(0); | |||
qDebug() << "Serial close"; | |||
// 断开串口 | |||
serialComm->close(); | |||
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->WriteCoil(coils); | |||
ui->textEdit_2->append("发送报文"+modbus->SendCommand().toHex().toUpper()); | |||
serialPort->write(modbus->SendCommand()); | |||
serialComm->sendData(modbus->SendCommand()); | |||
ui->btn_read->setEnabled(0); | |||
ui->pushWrite->setEnabled(0); | |||
comCount = 0; | |||
timer.start(1000); // 1 s 后触发超时 | |||
break; | |||
} | |||
@@ -196,14 +174,10 @@ void Widget::on_pushWrite_clicked() | |||
modbus->Set(stationAddress,functionCode,stratAddress,length); | |||
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->pushWrite->setEnabled(0); | |||
comCount = 0; | |||
timer.start(1000); // 1 s 后触发超时 | |||
break; | |||
} | |||
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() | |||
{ | |||
@@ -315,13 +215,10 @@ void Widget::on_btn_read_clicked() | |||
modbus->Set(stationAddress,functionCode,stratAddress,length); | |||
modbus->ReadCoilAndReg(); | |||
serialPort->write(modbus->SendCommand()); //发送报文 | |||
ui->textEdit_2->append("发送报文"+modbus->SendCommand().toHex().toUpper()); | |||
serialComm->sendData(modbus->SendCommand()); | |||
ui->btn_read->setEnabled(0); | |||
ui->pushWrite->setEnabled(0); | |||
comCount = 0; | |||
timer.start(1000); // 1 s 后触发超时 | |||
} | |||
void Widget::on_btn_SaveDate_clicked() | |||
@@ -339,13 +236,73 @@ void Widget::on_btn_ClearDate_clicked() | |||
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 <QTimer> | |||
#include "mymodbus.h" | |||
#include "communicationhistory.h" | |||
#include "serialcommunicator.h" | |||
namespace Ui { | |||
class Widget; | |||
@@ -23,8 +25,6 @@ private slots: | |||
void on_pushWrite_clicked(); | |||
void on_SerialData_ReadyToRead(); | |||
void on_btn_read_clicked(); | |||
void on_btn_SaveDate_clicked(); | |||
@@ -33,18 +33,18 @@ private slots: | |||
void on_btn_ClearDate_clicked(); | |||
void onReadyRead(); | |||
void on_btn_ClearRead_clicked(); | |||
void onSerialDataReceived(const QByteArray &data); | |||
void onSerialStatusChanged(const QString &status); | |||
void onSerialTimeout(); | |||
private: | |||
Ui::Widget *ui; | |||
QSerialPort *serialPort; | |||
SerialCommunicator *serialComm; | |||
MyModbus *modbus; | |||
QTimer timer; | |||
int comCount; | |||
QByteArray recvBuffer; | |||
QTimer *recvTimer; | |||
}; | |||
#endif // WIDGET_H |