|
- #include "widget.h"
- #include "ui_widget.h"
- #include <QSerialPortInfo>
- #include <QDebug>
- #include <QSerialPort>
- #include <QByteArray>
- #include <QString>
- #include <QVector>
- #include <QMessageBox>
- #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<QSerialPortInfo> 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<bool> 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<char>(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" <<sendData;
- break;
- }
-
- case 3:
- {
- QString sendData = ui->lineEdit->text().trimmed();
- if (sendData.isEmpty())
- {
- QMessageBox::warning(this, "提示", "请输入完整的寄存器数据");
- return;
- }
-
- QStringList sl = sendData.split(',');
- QVector<quint16> 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<char>(values.size() * 2));
- for (quint16 v : values)
- {
- SendCommand.append(static_cast<char>((v >> 8) & 0xFF));
- SendCommand.append(static_cast<char>(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" <<sendData;
- break;
- }
- default:
- {
- QMessageBox::warning(this, "提示", "请将“操作”切换为写线圈或写寄存器");
- break;
- }
-
- }
- }
-
- //处理回复报文
- void Widget::on_SerialData_ReadyToRead()
- {
- timer.stop();
-
- QByteArray revMessage = serialPort->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<quint8>(revMessage.at(revMessage.length() - 2));
- quint8 receivedCrcHigh = static_cast<quint8>(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<quint8>(revMessage[2]);
-
- for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
- {
- quint8 byteValue = static_cast<quint8>(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<quint16> registers;
- for (int i = 0; i < data.size(); i += 2)
- {
- quint16 value = (static_cast<quint8>(data[i]) << 8) | static_cast<quint8>(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 后触发超时
- }
|