diff --git a/modbus.pro.user b/modbus.pro.user index 475d687..a90843a 100644 --- a/modbus.pro.user +++ b/modbus.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/widget.cpp b/widget.cpp index 80328e2..3eb3bf1 100644 --- a/widget.cpp +++ b/widget.cpp @@ -17,11 +17,18 @@ Widget::Widget(QWidget *parent) : serialPort = new QSerialPort(this); connect(serialPort,&QSerialPort::readyRead,this,&Widget::on_SerialData_ReadyToRead); + QObject::connect(&timer, &QTimer::timeout, [&]{ + QMessageBox::warning(this, "提示", "等待响应超时,请检查设备状态。"); + timer.stop(); + }); ui->comboBox_baudRate->setCurrentIndex(3); ui->comboBox_dataBit->setCurrentIndex(3); ui->comboBox_xiaoyan->setCurrentIndex(2); + ui->btn_read->setEnabled(0); + ui->pushWrite->setEnabled(0); + QList serialList = QSerialPortInfo::availablePorts(); for(QSerialPortInfo serialInfo : serialList) { @@ -34,6 +41,7 @@ Widget::~Widget() delete ui; } +//串口连接 void Widget::on_btnConnect_clicked() { if (ui->btnConnect->text() == "连接") @@ -63,6 +71,8 @@ void Widget::on_btnConnect_clicked() { qDebug() << "Serial open success"; ui->btnConnect->setText("断开"); + ui->btn_read->setEnabled(1); + ui->pushWrite->setEnabled(1); } else { @@ -72,21 +82,27 @@ void Widget::on_btnConnect_clicked() else { serialPort->close(); + ui->btn_read->setEnabled(0); + ui->pushWrite->setEnabled(0); qDebug() << "Serial close"; ui->btnConnect->setText("连接"); } } +//写线圈和写寄存器 void Widget::on_pushWrite_clicked() { switch (ui->comboBox_gongnengma->currentIndex()) { - case (2): //写多个线圈 + case 2: //写多个线圈 { QString sendData = ui->lineEdit->text().trimmed(); - if (sendData.isEmpty()) return; - + if (sendData.isEmpty()) + { + QMessageBox::warning(this, "提示", "请至少输入一个数据,缺少的数据默认为0"); + return; + } for (QChar ch : sendData) { if (ch != '0' && ch != '1') { QMessageBox::warning(this, "提示", "只允许输入 0 或 1!"); @@ -131,13 +147,24 @@ void Widget::on_pushWrite_clicked() SendCommand.append(temp%256); //加入计算的crc值 SendCommand.append(temp/256); + ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper()); serialPort->write(SendCommand); + + timer.isActive(); + timer.start(2000); // 2 s 后触发超时 + qDebug() << "SenOk" <lineEdit->text().trimmed(); - if (sendData.isEmpty()) return; + if (sendData.isEmpty()) + { + QMessageBox::warning(this, "提示", "请输入完整的寄存器数据"); + return; + } QStringList sl = sendData.split(','); QVector values; @@ -147,7 +174,7 @@ void Widget::on_pushWrite_clicked() quint16 v = s.toUShort(&ok, 16); if (!ok) { - QMessageBox::warning(this, "提示", "请输入正确的十六进制值!"); + QMessageBox::warning(this, "提示", "请输入正确的十六进制值,或检查逗号是否是英文格式!"); return; } values.append(v); @@ -172,20 +199,40 @@ void Widget::on_pushWrite_clicked() SendCommand.append(temp%256); //加入计算的crc值 SendCommand.append(temp/256); + ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper()); serialPort->write(SendCommand); + + timer.isActive(); + timer.start(2000); // 2 s 后触发超时 + qDebug() << "SenOk" <readAll(); QString hexData = revMessage.toHex().toUpper(); qDebug() << hexData; - ui->textEdit->append(hexData); + ui->textEdit_2->append("接收报文"+hexData); + + //首先对接收报文的长度进行检验 + if (revMessage.size() < 3) { + QMessageBox::warning(this, "警告", "无效的响应格式"); + qDebug() << "响应报文过短,长度 =" << revMessage.size(); + return; + } //对接收的报文进行CRC校验 QByteArray payload = revMessage.left(revMessage.length() - 2); @@ -203,6 +250,33 @@ void Widget::on_SerialData_ReadyToRead() if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh) { qDebug() << "接收成功"; + if ((revMessage.at(1) & 0x80) == 0x80) { + // MODBUS异常响应结构:地址 | 功能码+0x80 | 异常码 | CRC + quint8 originalFunctionCode = revMessage.at(1) & 0x7F; // 去掉最高位 + quint8 exCode = revMessage.at(2); + + QString errorMsg; + switch (exCode) { + case 0x01: errorMsg = "非法功能码"; break; + case 0x02: errorMsg = "非法数据地址"; break; + case 0x03: errorMsg = "非法数据值"; break; + case 0x04: errorMsg = "从站设备故障"; break; + case 0x05: errorMsg = "确认"; break; + case 0x06: errorMsg = "从站设备忙"; break; + case 0x07: errorMsg = "负确认"; break; + case 0x08: errorMsg = "存储奇偶错误"; break; + default: errorMsg = "未知异常"; break; + } + + QMessageBox::warning(this, "异常响应", + QString("功能码 0x%1 异常\n错误码: 0x%2 (%3)") + .arg(QString::number(originalFunctionCode, 16).toUpper()) + .arg(QString::number(exCode, 16).toUpper().rightJustified(2, '0')) + .arg(errorMsg)); + + return; + } + switch (ui->comboBox_gongnengma->currentIndex()) { //解析读线圈的返回报文 @@ -251,17 +325,6 @@ void Widget::on_SerialData_ReadyToRead() } break; } - case 2: - { - - if (revMessage.at(1) == '\x8f') - { - quint8 exCode = revMessage.at(2); - qDebug() << "异常响应,异常码 =" << exCode; - return; - } - } - } } @@ -274,6 +337,12 @@ void Widget::on_SerialData_ReadyToRead() //发送读线圈和寄存器报文 void Widget::on_btn_read_clicked() { + if (ui->comboBox_gongnengma->currentIndex() == 2 || + ui->comboBox_gongnengma->currentIndex() == 3) + { + QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器"); + return; + } QByteArray SendCommand; //要发送的报文 SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址 if (ui->comboBox_gongnengma->currentIndex() == 0) //读线圈 @@ -295,5 +364,9 @@ void Widget::on_btn_read_clicked() SendCommand.append(temp/256); qDebug() << SendCommand.toHex(); + ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper()); serialPort->write(SendCommand); //发送报文 + + timer.isActive(); + timer.start(2000); // 2 s 后触发超时 } diff --git a/widget.h b/widget.h index 22fc51a..8464491 100644 --- a/widget.h +++ b/widget.h @@ -3,6 +3,7 @@ #include #include +#include namespace Ui { class Widget; @@ -28,6 +29,7 @@ private slots: private: Ui::Widget *ui; QSerialPort *serialPort; + QTimer timer; }; #endif // WIDGET_H diff --git a/widget.ui b/widget.ui index 746685e..65ee4c6 100644 --- a/widget.ui +++ b/widget.ui @@ -224,8 +224,8 @@ - 340 - 250 + 330 + 410 301 31 @@ -234,8 +234,8 @@ - 340 - 290 + 330 + 460 93 28 @@ -248,9 +248,9 @@ 40 - 170 + 230 251 - 271 + 211 @@ -336,7 +336,7 @@ - 对象 + 操作 @@ -397,17 +397,27 @@ - 340 - 160 + 330 + 300 311 - 91 + 101 - 写线圈时,使用1、0代表线圈的开关, + 写线圈时,使用1、0代表线圈的开关状态, 从左到右依次输入每个线圈的开关; -写寄存器时,每个寄存器的值用16进制表示, -相邻寄存器的值之间用","分离。 +写寄存器时要写入所有寄存器的16进制数值, +相邻寄存器的值之间用英文","分离。 + + + + + + 40 + 150 + 671 + 61 +