#include "widget.h" #include "ui_widget.h" #include #include #include #include #include #include #include #include "crc.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); 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) { ui->comboBox_serialNum->addItem(serialInfo.portName()); } } Widget::~Widget() { delete ui; } //串口连接 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())); //配置校验位 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); } //配置停止位 serialPort->setStopBits(QSerialPort::StopBits(ui->comboBox_stopBit->currentText().toInt())); //打开串口 if (serialPort->open(QIODevice::ReadWrite)) { qDebug() << "Serial open success"; ui->btnConnect->setText("断开"); ui->btn_read->setEnabled(1); ui->pushWrite->setEnabled(1); } else { qDebug() << "error"; } } 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: //写多个线圈 { QString sendData = ui->lineEdit->text().trimmed(); if (sendData.isEmpty()) { QMessageBox::warning(this, "提示", "请至少输入一个数据,缺少的数据默认为0"); return; } for (QChar ch : sendData) { if (ch != '0' && ch != '1') { QMessageBox::warning(this, "提示", "只允许输入 0 或 1!"); return; } } QVector coils; for (QChar ch : sendData) { coils.append(ch == '1'); } quint16 coilCount = coils.size(); QByteArray SendCommand; //要发送的报文 SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址 SendCommand.append(0x0f); SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址 SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256); SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度 SendCommand.append(ui->lineEdit_length->text().toInt()%256); int byteCount = (coilCount + 7) / 8; SendCommand.append(byteCount); //字节数 for (int i = 0; i < byteCount; ++i) { quint8 byte = 0; for (int j = 0; j < 8; ++j) { int bitIndex = i * 8 + j; if (bitIndex < coils.size() && coils[bitIndex]) byte |= (1 << j); } SendCommand.append(static_cast(byte)); } quint16 temp = calculateCrc(SendCommand); //计算crc 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()) { QMessageBox::warning(this, "提示", "请输入完整的寄存器数据"); return; } QStringList sl = sendData.split(','); QVector values; bool ok; for (const QString &s : sl) { quint16 v = s.toUShort(&ok, 16); if (!ok) { QMessageBox::warning(this, "提示", "请输入正确的十六进制值,或检查逗号是否是英文格式!"); return; } values.append(v); } QByteArray SendCommand; //要发送的报文 SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址 SendCommand.append(0x10); SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址 SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256); SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度 SendCommand.append(ui->lineEdit_length->text().toInt()%256); SendCommand.append(static_cast(values.size() * 2)); for (quint16 v : values) { SendCommand.append(static_cast((v >> 8) & 0xFF)); SendCommand.append(static_cast(v & 0xFF)); } quint16 temp = calculateCrc(SendCommand); //计算crc 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_2->append("接收报文"+hexData); //首先对接收报文的长度进行检验 if (revMessage.size() < 3) { QMessageBox::warning(this, "警告", "无效的响应格式"); qDebug() << "响应报文过短,长度 =" << revMessage.size(); return; } //对接收的报文进行CRC校验 QByteArray payload = revMessage.left(revMessage.length() - 2); //分离接收值的crc校验位 quint8 receivedCrcLow = static_cast(revMessage.at(revMessage.length() - 2)); quint8 receivedCrcHigh = static_cast(revMessage.at(revMessage.length() - 1)); //计算返回的报文的crc quint16 crc = calculateCrc(payload); quint8 calcCrcLow = crc & 0xFF; quint8 calcCrcHigh = (crc >> 8) & 0xFF; //比较计算的crc值和接收到的crc值是否一致 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()) { //解析读线圈的返回报文 case 0: { quint8 byteCount = static_cast(revMessage[2]); for (int byteIndex = 0; byteIndex < byteCount; byteIndex++) { quint8 byteValue = static_cast(revMessage[3 + byteIndex]); // 解析每个字节的8个位 for (int bitIndex = 0; bitIndex < 8; bitIndex++) { int coilIndex = byteIndex * 8 + bitIndex; if (coilIndex < ui->lineEdit_length->text().toInt()) { bool state = byteValue & (1 << bitIndex); qDebug() << coilIndex <<" " << state; ui->textEdit->append("线圈"+QString::number(coilIndex+1)+":"+QString::number(state)); } } } break; } //解析读寄存器的返回报文 case 1: { if (revMessage.size() >= 5 && revMessage.at(1) == 0x03) { int byteCount = revMessage.at(2); QByteArray data = revMessage.mid(3, byteCount); QVector registers; for (int i = 0; i < data.size(); i += 2) { quint16 value = (static_cast(data[i]) << 8) | static_cast(data[i+1]); registers.append(value); } for (int i = 0; i < registers.size(); i++) { qDebug() << "Register value:" << registers.at(i); ui->textEdit->append("寄存器"+QString::number(i)+":"+QString::number(registers.at(i))); } } break; } } } else { qDebug() << "接收失败"; } } //发送读线圈和寄存器报文 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) //读线圈 { SendCommand.append(0x01); } else if(ui->comboBox_gongnengma->currentIndex() == 1) //读寄存器 { SendCommand.append(0x03); } SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址 SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256); SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度 SendCommand.append(ui->lineEdit_length->text().toInt()%256); quint16 temp = calculateCrc(SendCommand); //计算crc SendCommand.append(temp%256); //加入计算的crc值 SendCommand.append(temp/256); qDebug() << SendCommand.toHex(); ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper()); serialPort->write(SendCommand); //发送报文 timer.isActive(); timer.start(2000); // 2 s 后触发超时 }