diff --git a/modbus.pro.user b/modbus.pro.user index 5ffbe48..491e3ce 100644 --- a/modbus.pro.user +++ b/modbus.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/serialcommunicator.cpp b/serialcommunicator.cpp index 4e7b691..8816bc0 100644 --- a/serialcommunicator.cpp +++ b/serialcommunicator.cpp @@ -22,22 +22,24 @@ * 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) + , 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); + 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); + connect(serialPort_, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead); + connect(recvTimer_, &QTimer::timeout, this, &SerialCommunicator::onRecvTimeout); + connect(resendTimer_, &QTimer::timeout, this, &SerialCommunicator::onResendTimeout); + connect(serialPort_, SIGNAL(error(QSerialPort::SerialPortError)), + this, SLOT(handleSerialError(QSerialPort::SerialPortError))); } /** @@ -64,11 +66,11 @@ void SerialCommunicator::setPortParams(const QString &portName, qint32 baudRate, QSerialPort::Parity parity, QSerialPort::StopBits stopBits) { - this->portName = portName; - this->baudRate = baudRate; - this->dataBits = dataBits; - this->parity = parity; - this->stopBits = stopBits; + this->portName_ = portName; + this->baudRate_ = baudRate; + this->dataBits_ = dataBits; + this->parity_ = parity; + this->stopBits_ = stopBits; } /** @@ -84,37 +86,37 @@ void SerialCommunicator::setPortParams(const QString &portName, qint32 baudRate, bool SerialCommunicator::open() { // 关闭已打开的串口(如果存在) - if (serialPort->isOpen()) + if (serialPort_->isOpen()) { - serialPort->close(); + serialPort_->close(); } // 配置串口参数 - serialPort->setPortName(portName); - if (!serialPort->setBaudRate(baudRate)) + serialPort_->setPortName(portName_); + if (!serialPort_->setBaudRate(baudRate_)) { emit statusChanged("波特率设置失败"); return false; } - if (!serialPort->setDataBits(dataBits)) + if (!serialPort_->setDataBits(dataBits_)) { emit statusChanged("数据位设置失败"); return false; } - if (!serialPort->setParity(parity)) + if (!serialPort_->setParity(parity_)) { emit statusChanged("校验位设置失败"); return false; } - if (!serialPort->setStopBits(stopBits)) + if (!serialPort_->setStopBits(stopBits_)) { emit statusChanged("停止位设置失败"); return false; } - serialPort->setFlowControl(QSerialPort::NoFlowControl); // 无流控制 + serialPort_->setFlowControl(QSerialPort::NoFlowControl); // 无流控制 // 打开串口 - if (serialPort->open(QIODevice::ReadWrite)) + if (serialPort_->open(QIODevice::ReadWrite)) { emit statusChanged("串口连接成功"); return true; @@ -136,9 +138,9 @@ bool SerialCommunicator::open() */ void SerialCommunicator::close() { - if (serialPort->isOpen()) + if (serialPort_->isOpen()) { - serialPort->close(); + serialPort_->close(); emit statusChanged("串口断开"); } } @@ -156,17 +158,17 @@ void SerialCommunicator::close() */ void SerialCommunicator::sendData(const QByteArray &data) { - if (!serialPort->isOpen()) + if (!serialPort_->isOpen()) { emit statusChanged("串口未打开,无法发送数据"); return; } // 保存待重发数据,初始化计数器,启动重发定时器 - currentData = data; - comCount = 0; - serialPort->write(data); // 发送数据 + currentData_ = data; + comCount_ = 0; + serialPort_->write(data); // 发送数据 emit statusChanged("发送报文:" + data.toHex().toUpper()); - resendTimer->start(resendTimeout); // 启动超时计时 + resendTimer_->start(resendTimeout_); // 启动超时计时 } /** @@ -175,7 +177,7 @@ void SerialCommunicator::sendData(const QByteArray &data) */ void SerialCommunicator::setMaxRetry(int count) { - maxRetry = count; + maxRetry_ = count; } /** @@ -186,7 +188,7 @@ void SerialCommunicator::setMaxRetry(int count) */ void SerialCommunicator::setRecvTimeout(int ms) { - recvTimeout = ms; + recvTimeout_ = ms; } /** @@ -195,7 +197,7 @@ void SerialCommunicator::setRecvTimeout(int ms) */ void SerialCommunicator::setResendTimeout(int ms) { - resendTimeout = ms; + resendTimeout_ = ms; } /** @@ -204,7 +206,7 @@ void SerialCommunicator::setResendTimeout(int ms) */ bool SerialCommunicator::isOpen() const { - return serialPort->isOpen(); + return serialPort_->isOpen(); } // ====================== 私有槽函数 ====================== // @@ -220,9 +222,9 @@ bool SerialCommunicator::isOpen() const void SerialCommunicator::onReadyRead() { // 读取数据到缓冲区 - recvBuffer.append(serialPort->readAll()); + recvBuffer_.append(serialPort_->readAll()); //重置接收超时定时器 - recvTimer->start(recvTimeout); + recvTimer_->start(recvTimeout_); } /** @@ -236,9 +238,9 @@ void SerialCommunicator::onReadyRead() void SerialCommunicator::onRecvTimeout() { // 接收完成,发送完整数据信号,停止重发定时器 - emit dataReceived(recvBuffer); - recvBuffer.clear(); - resendTimer->stop(); + emit dataReceived(recvBuffer_); + recvBuffer_.clear(); + resendTimer_->stop(); } /** @@ -251,18 +253,108 @@ void SerialCommunicator::onRecvTimeout() */ void SerialCommunicator::onResendTimeout() { - if (comCount < maxRetry) + if (comCount_ < maxRetry_) { // 重发数据,更新计数器 - serialPort->write(currentData); - comCount++; - emit statusChanged(QString("通信超时,重新发送中(%1/%2)").arg(comCount).arg(maxRetry)); - resendTimer->start(resendTimeout); // 继续计时 + serialPort_->write(currentData_); + comCount_++; + emit statusChanged(QString("通信超时,重新发送中(%1/%2)").arg(comCount_).arg(maxRetry_)); + resendTimer_->start(resendTimeout_); // 继续计时 } else { // 达到最大重发次数,触发超时信号 - resendTimer->stop(); + resendTimer_->stop(); emit timeoutOccurred(); } } + +/** + * @brief 处理串口错误 + * @param error 串口错误代码 + * + * @details 执行流程: + * 1. 忽略无错误和超时错误(超时由其他机制处理) + * 2. 根据错误代码生成错误描述 + * 3. 对于物理断开错误(ResourceError): + * - 自动关闭串口 + * - 停止所有定时器 + * - 清空接收缓冲区 + * - 发送物理断开信号 + * 4. 其他错误只发送状态通知 + */ +void SerialCommunicator::handleSerialError(QSerialPort::SerialPortError error) +{ + // 忽略无错误和超时错误(超时由其他机制处理) + if (error == QSerialPort::NoError || error == QSerialPort::TimeoutError) + { + return; + } + + QString errorMsg; + switch (error) + { + case QSerialPort::DeviceNotFoundError: + errorMsg = "设备未找到"; + break; + case QSerialPort::PermissionError: + errorMsg = "没有权限访问设备"; + break; + case QSerialPort::OpenError: + errorMsg = "设备已打开"; + break; + case QSerialPort::ParityError: + errorMsg = "奇偶校验错误"; + break; + case QSerialPort::FramingError: + errorMsg = "帧错误"; + break; + case QSerialPort::BreakConditionError: + errorMsg = "Break条件错误"; + break; + case QSerialPort::WriteError: + errorMsg = "写入错误"; + break; + case QSerialPort::ReadError: + errorMsg = "读取错误"; + break; + case QSerialPort::ResourceError: + errorMsg = "资源错误(设备已断开)"; + break; + case QSerialPort::UnsupportedOperationError: + errorMsg = "不支持的操作"; + break; + case QSerialPort::UnknownError: + errorMsg = "未知错误"; + break; + case QSerialPort::NotOpenError: + errorMsg = "设备未打开"; + break; + default: + errorMsg = "未定义错误"; + } + + // 物理断开处理(资源错误) + if (error == QSerialPort::ResourceError) + { + // 自动关闭串口 + close(); + + // 停止所有定时器 + recvTimer_->stop(); + resendTimer_->stop(); + + // 清空缓冲区 + recvBuffer_.clear(); + + // 通知上层 + emit statusChanged("物理连接断开: " + errorMsg); + emit physicalDisconnected(); + } + else + { + // 其他错误只通知不自动断开 + emit statusChanged("串口错误: " + errorMsg); + } + +} diff --git a/serialcommunicator.h b/serialcommunicator.h index 72307f6..1730669 100644 --- a/serialcommunicator.h +++ b/serialcommunicator.h @@ -60,10 +60,10 @@ public: * @param stopBits 停止位(通常为QSerialPort::OneStop) * @note 此方法应在打开串口前调用 */ - void setPortParams(const QString &portName, qint32 baudRate, - QSerialPort::DataBits dataBits, - QSerialPort::Parity parity, - QSerialPort::StopBits stopBits); + void setPortParams(const QString &portName_, qint32 baudRate_, + QSerialPort::DataBits dataBits_, + QSerialPort::Parity parity_, + QSerialPort::StopBits stopBits_); /** * @brief 打开串口 @@ -137,6 +137,12 @@ signals: */ void timeoutOccurred(); + /** + * @brief 串口错误信号 + * @note 当串口发生异常错误时触发 + */ + void physicalDisconnected(); + private slots: /** * @brief 串口数据到达处理 @@ -156,23 +162,29 @@ private slots: */ void onResendTimeout(); + /** + * @brief 处理串口错误 + * @param error 串口错误代码 + */ + void handleSerialError(QSerialPort::SerialPortError error); + private: - QSerialPort *serialPort; // 串口对象 - QTimer *recvTimer; // 接收完成判断定时器 - QTimer *resendTimer; // 超时重发定时器 - QByteArray recvBuffer; // 接收缓冲区 - QByteArray currentData; // 当前待重发数据 - int comCount; // 重发计数器 - int maxRetry; // 最大重发次数 - int recvTimeout; // 接收超时时间(ms) - int resendTimeout; // 重发间隔(ms) + 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; + QString portName_; + qint32 baudRate_; + QSerialPort::DataBits dataBits_; + QSerialPort::Parity parity_; + QSerialPort::StopBits stopBits_; }; #endif // SERIALCOMMUNICATOR_H diff --git a/widget.cpp b/widget.cpp index fe99b7c..2728b44 100644 --- a/widget.cpp +++ b/widget.cpp @@ -15,6 +15,7 @@ Widget::Widget(QWidget *parent) : { modbus = new MyModbus(); ui->setupUi(this); + setFixedSize(700,500); serialComm = new SerialCommunicator(this); // 初始化串口通信类 // 配置串口参数 @@ -26,6 +27,12 @@ Widget::Widget(QWidget *parent) : connect(serialComm, &SerialCommunicator::dataReceived, this, &Widget::onSerialDataReceived); connect(serialComm, &SerialCommunicator::statusChanged, this, &Widget::onSerialStatusChanged); connect(serialComm, &SerialCommunicator::timeoutOccurred, this, &Widget::onSerialTimeout); + connect(serialComm, &SerialCommunicator::physicalDisconnected, this, [=](){ + ui->btnConnect->setText("连接"); + ui->btn_read->setEnabled(false); + ui->pushWrite->setEnabled(false); + QMessageBox::warning(this, "警告", "物理连接已断开"); + }); ui->comboBox_baudRate->setCurrentIndex(3); ui->comboBox_dataBit->setCurrentIndex(3); @@ -97,7 +104,6 @@ void Widget::on_pushWrite_clicked() switch (ui->comboBox_gongnengma->currentIndex()) { case 2: //写多个线圈 - { QString sendData = ui->lineEdit->text().trimmed(); if (sendData.isEmpty()) @@ -118,7 +124,7 @@ void Widget::on_pushWrite_clicked() coils.append(ch == '1'); } - quint16 stationAddress = ui->comboBox_stationAddress->currentText().toInt(); + quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt(); quint16 functionCode = 0x0f; quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); quint16 length = ui->lineEdit_length->text().toInt(); @@ -138,7 +144,7 @@ void Widget::on_pushWrite_clicked() break; } - case 3: + case 3: //写多个寄存器 { QString sendData = ui->lineEdit->text().trimmed(); if (sendData.isEmpty()) @@ -150,18 +156,46 @@ void Widget::on_pushWrite_clicked() QStringList sl = sendData.split(','); QVector values; bool ok; + + // 检查输入是否是十进制 + bool isDecimal = true; for (const QString &s : sl) { - quint16 v = s.toUShort(&ok, 16); + s.toInt(&ok); if (!ok) { - QMessageBox::warning(this, "提示", "请输入正确的十六进制值,或检查逗号是否是英文格式!"); - return; + isDecimal = false; + break; + } + } + + for (const QString &s : sl) + { + quint16 v; + if (isDecimal) + { + // 如果是十进制输入 + v = s.toUShort(&ok, 10); + if (!ok) + { + QMessageBox::warning(this, "提示", "请输入正确的十进制值(0-65535)"); + return; + } + } + else + { + // 默认按十六进制处理 + v = s.toUShort(&ok, 16); + if (!ok) + { + QMessageBox::warning(this, "提示", "请输入正确的十六进制值(0-FFFF),或检查逗号格式"); + return; + } } values.append(v); } - quint16 stationAddress = ui->comboBox_stationAddress->currentText().toInt(); + quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt(); quint16 functionCode = 0x10; quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); quint16 length = ui->lineEdit_length->text().toInt(); @@ -198,7 +232,7 @@ void Widget::on_btn_read_clicked() QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器"); return; } - quint16 stationAddress = ui->comboBox_stationAddress->currentText().toInt(); + quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt(); quint16 functionCode; quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); quint16 length = ui->lineEdit_length->text().toInt(); diff --git a/widget.ui b/widget.ui index 29fa7e7..5fe2879 100644 --- a/widget.ui +++ b/widget.ui @@ -7,7 +7,7 @@ 0 0 700 - 486 + 500 @@ -17,7 +17,7 @@ 30 - 240 + 260 181 231 @@ -25,13 +25,13 @@ 从站配置 - + 10 30 159 - 131 + 141 @@ -42,55 +42,6 @@ - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - @@ -150,13 +101,20 @@ + + + + 1 + + + 10 - 160 + 170 71 51 @@ -169,7 +127,7 @@ 90 - 160 + 170 71 51 @@ -185,165 +143,159 @@ 30 19 181 - 211 + 221 串口配置 - - - - 10 - 20 - 161 - 181 - - - - - - - 串口 - - - - - - - - - - 波特率 - - - - - - + + + + + - 1200 + 串口 - - + + + + + + + - 2400 + 波特率 - - + + + + + + + 1200 + + + + + 2400 + + + + + 4800 + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + + - 4800 + 数据位 - - + + + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + NONE + + + + + + - 9600 + 校验位 - - + + + + + + + 无校验(N) + + + + + 奇校验(O) + + + + + 偶校验(E) + + + + + + - 19200 + 停止位 - - + + + + + + + 1 + + + + + 2 + + + + + + - 38400 + 连接 - - - - - - - 数据位 - - - - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - NONE - - - - - - - - 校验位 - - - - - - - - 无校验(N) - - - - - 奇校验(O) - - - - - 偶校验(E) - - - - - - - - 停止位 - - - - - - - - 1 - - - - - 2 - - - - - - - - 连接 - - - - - + + + + + @@ -403,7 +355,7 @@ 240 340 341 - 131 + 151 @@ -413,15 +365,15 @@ 10 - 0 + 20 311 - 101 + 91 写线圈时,使用1、0代表线圈的开关状态, 从左到右依次输入每个线圈的开关; -写寄存器时要写入所有寄存器的16进制数值, +写寄存器时要同时写入所有寄存器的数值, 相邻寄存器的值之间用英文","分离。 @@ -429,14 +381,14 @@ 10 - 90 + 110 301 31 - + 240