From 31d4eab368d64ce9a68bc4d725a1c92b65451157 Mon Sep 17 00:00:00 2001 From: lipengpeng Date: Sat, 2 Aug 2025 17:23:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- communicationhistory.cpp | 4 +- communicationhistory.h | 4 +- modbus.pro.user | 2 +- mymodbus.cpp | 125 +++++++++-------- mymodbus.h | 30 ++-- serialcommunicator.cpp | 42 ++++-- serialcommunicator.h | 2 +- widget.cpp | 287 ++++++++++++++++++++++++--------------- widget.h | 10 +- widget.ui | 96 +++++++++++-- 10 files changed, 379 insertions(+), 223 deletions(-) diff --git a/communicationhistory.cpp b/communicationhistory.cpp index e9103cd..78a6832 100644 --- a/communicationhistory.cpp +++ b/communicationhistory.cpp @@ -23,7 +23,7 @@ * 4. 使用UTF-8编码保存文本 * 5. 错误处理包含用户提示 */ -bool SaveDate(QWidget *parent, QTextEdit *edit) +bool saveDate(QWidget *parent, QTextEdit *edit) { // 弹出文件保存对话框,默认保存到用户主目录的note.txt文件 QString fileName = QFileDialog::getSaveFileName( @@ -88,7 +88,7 @@ bool SaveDate(QWidget *parent, QTextEdit *edit) * 3. 使用UTF-8编码读取内容 * 4. 错误处理包含用户提示 */ -bool ReadDate(QWidget *parent, QTextEdit *edit) +bool readDate(QWidget *parent, QTextEdit *edit) { // 弹出文件打开对话框,默认从用户主目录开始浏览 QString fileName = QFileDialog::getOpenFileName( diff --git a/communicationhistory.h b/communicationhistory.h index ec6691b..141885c 100644 --- a/communicationhistory.h +++ b/communicationhistory.h @@ -32,7 +32,7 @@ * 3. 支持UTF-8编码格式保存 * 4. 操作失败时会显示错误提示 */ -bool SaveDate(QWidget *parent, QTextEdit *edit); +bool saveDate(QWidget *parent, QTextEdit *edit); /** * @brief 从文件读取通信数据 @@ -45,6 +45,6 @@ bool SaveDate(QWidget *parent, QTextEdit *edit); * 3. 支持UTF-8编码格式读取 * 4. 操作失败时会显示错误提示 */ -bool ReadDate(QWidget *parent, QTextEdit *edit); +bool readDate(QWidget *parent, QTextEdit *edit); #endif // COMMUNICATIONHISTORY_H diff --git a/modbus.pro.user b/modbus.pro.user index ff0a176..85caf27 100644 --- a/modbus.pro.user +++ b/modbus.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/mymodbus.cpp b/mymodbus.cpp index f326702..bd53de6 100644 --- a/mymodbus.cpp +++ b/mymodbus.cpp @@ -17,12 +17,12 @@ * @note 初始化默认Modbus参数:站地址=1,功能码=0x01(读线圈), * 起始地址=256,数据长度=1 */ -MyModbus::MyModbus() +MyModbus::MyModbus(QObject *parent) : QObject(parent) + , stationAddress_(0x01) + , functionCode_(0x01) + , startAddress_(0x01) + , length_(0x01) { - this->stationAddress_ = 1; // 默认站地址 - this->functionCode_ = 0x01; // 默认功能码(读线圈) - this->startAdress_ = 256; // 默认起始地址 - this->length_ = 1; // 默认数据长度 } /** @@ -32,34 +32,43 @@ MyModbus::MyModbus() * @param startAdress 起始地址 * @param length 数据长度 */ -void MyModbus::Set(quint16 stationAddress, quint16 functionCode, quint16 startAdress, quint16 length) +bool MyModbus::setStation(quint16 stationAddress, quint16 functionCode, + quint16 startAddress, quint16 length) { - this->stationAddress_ = stationAddress; - this->functionCode_ = functionCode; - this->startAdress_ = startAdress; - this->length_ = length; - + if ((stationAddress > 0) && + (functionCode > 0) && + (startAddress >0) && + (length > 0)) + { + this->stationAddress_ = stationAddress; + this->functionCode_ = functionCode; + this->startAddress_ = startAddress; + this->length_ = length; + sendCommand_.clear(); + sendCommand_.append(stationAddress_ % 256); + sendCommand_.append(functionCode_ % 256); + sendCommand_.append(startAddress_ / 256); + sendCommand_.append(startAddress_ % 256); + sendCommand_.append(length_ / 256); + sendCommand_.append(length_ % 256); + return true; + } + else + { + return false; + } } /** * @brief 生成读取线圈/寄存器的Modbus命令 * @note 命令结构:[站地址][功能码][起始地址Hi][起始地址Lo][长度Hi][长度Lo][CRC Lo][CRC Hi] */ -void MyModbus::ReadCoilAndReg() +void MyModbus::readCoilAndReg() { - sendCommand_.clear(); - // 构建标准读命令帧 - sendCommand_.append(stationAddress_%256); - sendCommand_.append(functionCode_%256); - sendCommand_.append(startAdress_/256); - sendCommand_.append(startAdress_%256); - sendCommand_.append(length_/256); - sendCommand_.append(length_%256); - // 计算并附加CRC校验 - quint16 temp = CalculateCrc(sendCommand_); - sendCommand_.append(temp%256); // CRC低字节在前 - sendCommand_.append(temp/256); // CRC高字节在后 + quint16 temp = calculateCrc(sendCommand_); + sendCommand_.append(temp % 256); // CRC低字节在前 + sendCommand_.append(temp / 256); // CRC高字节在后 } /** @@ -67,18 +76,11 @@ void MyModbus::ReadCoilAndReg() * @param coils 要写入的线圈状态数组 * @note 命令结构:[站地址][0x0F][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据...][CRC] */ -void MyModbus::WriteCoil(QVector &coils) +void MyModbus::writeCoil(QVector &coils) { quint16 coilCount = coils.size(); int byteCount = (coilCount + 7) / 8; // 计算所需字节数(每字节8位) - sendCommand_.clear(); - sendCommand_.append(stationAddress_%256); - sendCommand_.append(0x0F); // 写多个线圈功能码 - sendCommand_.append(startAdress_/256); - sendCommand_.append(startAdress_%256); - sendCommand_.append(coilCount/256); // 线圈数量 - sendCommand_.append(coilCount%256); sendCommand_.append(byteCount); // 后续字节数 // 打包线圈状态到字节 @@ -91,16 +93,17 @@ void MyModbus::WriteCoil(QVector &coils) // 只处理有效范围内的线圈 if (bitIndex < coils.size() && coils[bitIndex]) { - byte |= (1 << j); // 设置对应位 + byte |= static_cast(1 << j); // 设置对应位 } } - sendCommand_.append(static_cast(byte)); + sendCommand_.append(byte); + } // 计算并附加CRC校验 - quint16 temp = CalculateCrc(sendCommand_); - sendCommand_.append(temp%256); - sendCommand_.append(temp/256); + quint16 temp = calculateCrc(sendCommand_); + sendCommand_.append(temp % 256); + sendCommand_.append(temp / 256); } /** @@ -108,35 +111,28 @@ void MyModbus::WriteCoil(QVector &coils) * @param values 要写入的寄存器值数组 * @note 命令结构:[站地址][0x10][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据Hi][数据Lo...][CRC] */ -void MyModbus::WriteRegister(QVector &values) +void MyModbus::writeRegister(QVector &values) { - sendCommand_.clear(); - sendCommand_.append(stationAddress_%256); - sendCommand_.append(0x10); // 写多个寄存器功能码 - sendCommand_.append(startAdress_/256); - sendCommand_.append(startAdress_%256); - sendCommand_.append(values.size()/256); // 寄存器数量 - sendCommand_.append(values.size()%256); - sendCommand_.append(static_cast(values.size() * 2)); // 后续字节数(每个寄存器2字节) + sendCommand_.append(values.size() * 2); // 后续字节数(每个寄存器2字节) // 打包寄存器值(高位在前) for (quint16 v : values) { - sendCommand_.append(static_cast((v >> 8) & 0xFF)); // 高字节 - sendCommand_.append(static_cast(v & 0xFF)); // 低字节 + sendCommand_.append(v / 256); // 高字节 + sendCommand_.append(v % 256); // 低字节 } // 计算并附加CRC校验 - quint16 temp = CalculateCrc(sendCommand_); - sendCommand_.append(temp%256); - sendCommand_.append(temp/256); + quint16 temp = calculateCrc(sendCommand_); + sendCommand_.append(temp % 256); + sendCommand_.append(temp / 256); } /** * @brief 获取生成的Modbus命令 * @return 完整的Modbus命令字节数组 */ -QByteArray MyModbus::SendCommand() +QByteArray MyModbus::sendCommand() { return sendCommand_; } @@ -147,7 +143,7 @@ QByteArray MyModbus::SendCommand() * @return 处理后的有效数据部分(移除地址/功能码/CRC) * @note 执行站地址匹配和CRC校验 */ -QByteArray MyModbus::Receive(const QByteArray &revMessage) +QByteArray MyModbus::receive(const QByteArray &revMessage) { receive_.clear(); @@ -164,7 +160,7 @@ QByteArray MyModbus::Receive(const QByteArray &revMessage) } // CRC校验检查 - if (!CrcCheck(revMessage)) + if (!crcCheck(revMessage)) { return receive_; } @@ -177,7 +173,7 @@ QByteArray MyModbus::Receive(const QByteArray &revMessage) * @brief 检查Modbus异常响应 * @return 0-正常响应, >0-Modbus异常码, -1-响应报文错误 */ -int MyModbus::ErrorCheck() +quint8 MyModbus::errorCheck() { // 异常响应最小长度:地址1 + 异常功能码1 + 异常码1 + CRC2 if(receive_.size() < 5) @@ -197,7 +193,7 @@ int MyModbus::ErrorCheck() * @brief 解析读取线圈的响应数据 * @return 线圈状态数组 */ -QVector MyModbus::AnalReadCoil() +QVector MyModbus::analReadCoil() { QVector coil; // 最小长度检查:地址1+功能码1+字节数1+数据1+CRC2 @@ -231,7 +227,7 @@ QVector MyModbus::AnalReadCoil() * @brief 解析读取寄存器的响应数据 * @return 寄存器值数组 */ -QVector MyModbus::AnalReadReg() +QVector MyModbus::analReadReg() { QVector registers; // 最小长度检查:地址1+功能码1+字节数1+数据2+CRC2 @@ -240,7 +236,7 @@ QVector MyModbus::AnalReadReg() return registers; } - int byteCount = receive_.at(2); // 数据字节数 + int byteCount = static_cast(receive_.at(2)); // 数据字节数 QByteArray data = receive_.mid(3, byteCount); // 提取数据部分 // 每两个字节组合为一个寄存器值(高位在前) @@ -248,7 +244,8 @@ QVector MyModbus::AnalReadReg() { quint16 high = static_cast(data[i]); quint16 low = static_cast(data[i+1]); - registers.append((high << 8) | low); + quint16 value = (high * 256) + low; + registers.append(value); } return registers; } @@ -259,7 +256,7 @@ QVector MyModbus::AnalReadReg() * @return CRC16校验值 * @note 使用MODBUS标准多项式0xA001(0x8005的反转) */ -quint16 MyModbus::CalculateCrc(const QByteArray &data) +quint16 MyModbus::calculateCrc(const QByteArray &data) { quint16 crc = 0xFFFF; // MODBUS初始值 const quint16 polynomial = 0xA001; // MODBUS多项式(0x8005的反转) @@ -289,7 +286,7 @@ quint16 MyModbus::CalculateCrc(const QByteArray &data) * @param data 完整接收数据(包含末尾CRC) * @return true-CRC校验通过, false-校验失败 */ -bool MyModbus::CrcCheck(const QByteArray &data) +bool MyModbus::crcCheck(const QByteArray &data) { //首先对接收报文的长度进行检验 if (data.size() < 3) @@ -304,9 +301,9 @@ bool MyModbus::CrcCheck(const QByteArray &data) quint8 receivedCrcHigh = static_cast(data.at(data.length() - 1)); //计算返回的报文的crc - quint16 crc = CalculateCrc(payload); - quint8 calcCrcLow = crc & 0xFF; - quint8 calcCrcHigh = (crc >> 8) & 0xFF; + quint16 crc = calculateCrc(payload); + quint8 calcCrcLow = crc % 256; + quint8 calcCrcHigh = crc / 256; //比较计算的crc值和接收到的crc值是否一致 if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh) diff --git a/mymodbus.h b/mymodbus.h index afc12c0..980232e 100644 --- a/mymodbus.h +++ b/mymodbus.h @@ -13,6 +13,7 @@ #ifndef MYMODBUS_H #define MYMODBUS_H +#include #include #include #include @@ -33,13 +34,14 @@ * @note 使用时需先通过Set()方法设置从站参数 */ -class MyModbus +class MyModbus : public QObject { + Q_OBJECT public: /** * @brief 默认构造函数 */ - MyModbus(); + MyModbus(QObject *parent = nullptr); /** * @brief 设置Modbus通信参数 @@ -48,7 +50,7 @@ public: * @param startAdress 起始地址 * @param length 数据长度 */ - void Set(quint16 stationAddress, quint16 functionCode, + bool setStation(quint16 stationAddress, quint16 functionCode, quint16 startAdress, quint16 length); /** @@ -56,69 +58,69 @@ public: * * 根据当前参数生成读取命令(功能码01或03) */ - void ReadCoilAndReg(); + void readCoilAndReg(); /** * @brief 生成写多个线圈命令 * @param coils 要写入的线圈状态数组 */ - void WriteCoil(QVector &coils); + void writeCoil(QVector &coils); /** * @brief 生成写多个寄存器命令 * @param values 要写入的寄存器值数组 */ - void WriteRegister(QVector &values); + void writeRegister(QVector &values); /** * @brief 获取待发送的命令报文 * @return 完整的Modbus命令报文 */ - QByteArray SendCommand(); + QByteArray sendCommand(); /** * @brief 处理接收到的响应报文 * @param revMessage 接收到的原始报文 * @return 通过CRC校验的有效报文 */ - QByteArray Receive(const QByteArray &revMessage); + QByteArray receive(const QByteArray &revMessage); /** * @brief 检查响应报文中的错误码 * @return 0表示正常,非0表示异常码 */ - int ErrorCheck(); + quint8 errorCheck(); /** * @brief 解析读线圈响应报文 * @return 线圈状态数组 */ - QVector AnalReadCoil(); + QVector analReadCoil(); /** * @brief 解析读寄存器响应报文 * @return 寄存器值数组 */ - QVector AnalReadReg(); + QVector analReadReg(); /** * @brief 计算CRC校验值 * @param data 待计算的数据 * @return CRC校验值 */ - quint16 CalculateCrc(const QByteArray &data); + quint16 calculateCrc(const QByteArray &data); /** * @brief 校验报文的CRC值 * @param data 待校验的报文 * @return true表示校验通过,false表示校验失败 */ - bool CrcCheck(const QByteArray &data); + bool crcCheck(const QByteArray &data); private: quint16 stationAddress_; // 从站地址 quint16 functionCode_; // 功能码 - quint16 startAdress_; // 起始地址 + quint16 startAddress_; // 起始地址 quint16 length_; // 数据长度 QByteArray sendCommand_; // 待发送的命令报文 QByteArray receive_; // 接收到的响应报文 diff --git a/serialcommunicator.cpp b/serialcommunicator.cpp index 80bf77b..3b1c996 100644 --- a/serialcommunicator.cpp +++ b/serialcommunicator.cpp @@ -48,19 +48,7 @@ SerialCommunicator::SerialCommunicator(QObject *parent) : QObject(parent) */ SerialCommunicator::~SerialCommunicator() { - //停止所有定时器,避免在对象销毁后触发定时器事件 - if (recvTimer_->isActive()) - { - recvTimer_->stop(); - } - if (resendTimer_->isActive()) - { - resendTimer_->stop(); - } close(); - delete recvTimer_; - delete resendTimer_; - delete serialPort_; } /** @@ -144,16 +132,28 @@ bool SerialCommunicator::open() * @brief 关闭串口 * * @details 执行流程: - * 1. 检查串口是否打开 - * 2. 关闭串口 - * 3. 发送状态变化信号 + * 1. 关闭所有定时器 + * 2. 清理待处理数据 + * 3. 检查串口是否打开 + * 4. 关闭串口 + * 5. 发送状态变化信号 */ void SerialCommunicator::close() { if (serialPort_->isOpen()) { + //关闭所有定时器 + recvTimer_->stop(); + resendTimer_->stop(); + + // 清除可能存在的待处理数据 + serialPort_->clear(); + + // 关闭串口 serialPort_->close(); emit statusChanged("串口断开"); + + comCount_ = 0; // 重置通信计数器 } } @@ -189,6 +189,10 @@ void SerialCommunicator::sendData(const QByteArray &data) */ void SerialCommunicator::setMaxRetry(int count) { + if (count < 0) + { + return; + } maxRetry_ = count; } @@ -200,6 +204,10 @@ void SerialCommunicator::setMaxRetry(int count) */ void SerialCommunicator::setRecvTimeout(int ms) { + if (ms < 0) + { + return; + } recvTimeout_ = ms; } @@ -209,6 +217,10 @@ void SerialCommunicator::setRecvTimeout(int ms) */ void SerialCommunicator::setResendTimeout(int ms) { + if (ms < 0) + { + return; + } resendTimeout_ = ms; } diff --git a/serialcommunicator.h b/serialcommunicator.h index 7aaedd2..f19092f 100644 --- a/serialcommunicator.h +++ b/serialcommunicator.h @@ -41,7 +41,7 @@ public: * @brief 构造函数 * @param parent 父对象指针 */ - explicit SerialCommunicator(QObject *parent = nullptr); + SerialCommunicator(QObject *parent = nullptr); /** * @brief 析构函数 diff --git a/widget.cpp b/widget.cpp index 9f7df4d..bf609a1 100644 --- a/widget.cpp +++ b/widget.cpp @@ -17,7 +17,6 @@ #include #include #include -#include /** * @brief Widget 构造函数 @@ -29,45 +28,51 @@ */ Widget::Widget(QWidget *parent) : QWidget(parent), - ui_(new Ui::Widget) + ui_(new Ui::Widget), + serialComm_(new SerialCommunicator(this)), + modbus_(new MyModbus(this)) { - modbus_ = new MyModbus(); ui_->setupUi(this); setFixedSize(700,500); //设置窗口界面大小 - serialComm_ = new SerialCommunicator(this); // 初始化串口通信类 - - // 配置串口参数 - serialComm_->setMaxRetry(3); // 最大重发3次 - serialComm_->setRecvTimeout(50); // 接收超时50ms - serialComm_->setResendTimeout(1000); // 重发间隔1s // 连接信号槽(串口->界面) - connect(ui_->btn_connect, &QPushButton::clicked, this, &Widget::btnConnectClicked); - connect(ui_->btn_write, &QPushButton::clicked, this, &Widget::onBtnWriteClicked); - connect(ui_->btn_read, &QPushButton::clicked, this, &Widget::onBtnReadClicked); - connect(ui_->btn_clear_read, &QPushButton::clicked, this, &Widget::onBtnClearreadClicked); - connect(ui_->btn_clear_date, &QPushButton::clicked, this, &Widget::onBtnCleardateClicked); - connect(ui_->btn_save_date, &QPushButton::clicked, this, &Widget::onBtnSavedateClicked); - connect(ui_->btn_read_date, &QPushButton::clicked, this, &Widget::onBtnReaddateClicked); - connect(ui_->btn_refresh, &QPushButton::clicked, this, &Widget::onBtnRefreshClicked); - //当串口接收到完整数据后,SerialCommunicator会发出dataReceived信号 - connect(serialComm_, &SerialCommunicator::dataReceived, this, &Widget::onSerialDataReceived); - //当串口状态改变时,SerialCommunicator会发出statusChanged信号 - connect(serialComm_, &SerialCommunicator::statusChanged, this, &Widget::onSerialStatusChanged); - //当发送数据超过超时重发次数后仍未收到数据,SerialCommunicator会发出timeoutOccurred信号 - connect(serialComm_, &SerialCommunicator::timeoutOccurred, this, &Widget::onSerialTimeout); - //当串口连接出现错误时(例如串口突然断开),SerialCommunicator会发出physicalDisconnected信号 - connect(serialComm_, &SerialCommunicator::physicalDisconnected, this, [=](){ - ui_->btn_connect->setText("连接"); - ui_->btn_read->setEnabled(false); - ui_->btn_write->setEnabled(false); - QMessageBox::warning(this, "警告", "串口已断开连接"); - }); + connect(ui_->btn_connect, &QPushButton::clicked, + this, &Widget::btnConnectClicked); + connect(ui_->btn_write, &QPushButton::clicked, + this, &Widget::onBtnWriteClicked); + connect(ui_->btn_read, &QPushButton::clicked, + this, &Widget::onBtnReadClicked); + connect(ui_->btn_clear_read, &QPushButton::clicked, + this, &Widget::onBtnClearreadClicked); + connect(ui_->btn_clear_date, &QPushButton::clicked, + this, &Widget::onBtnCleardateClicked); + connect(ui_->btn_save_date, &QPushButton::clicked, + this, &Widget::onBtnSavedateClicked); + connect(ui_->btn_read_date, &QPushButton::clicked, + this, &Widget::onBtnReaddateClicked); + connect(ui_->btn_refresh, &QPushButton::clicked, + this, &Widget::onBtnRefreshClicked); + connect(ui_->btn_batch_input, &QPushButton::clicked, + this, &Widget::onBtnBatchInputClicked); + connect(ui_->line_input, &QLineEdit::textChanged, + this, &Widget::onTextChanged); + //当串口接收到完整数据后,serialComm_会发出dataReceived信号 + connect(serialComm_, &SerialCommunicator::dataReceived, + this, &Widget::onSerialDataReceived); + //当串口状态改变时,serialComm_会发出statusChanged信号 + connect(serialComm_, &SerialCommunicator::statusChanged, + this, &Widget::onSerialStatusChanged); + //当发送数据超过超时重发次数后仍未收到数据,serialComm_会发出timeoutOccurred信号 + connect(serialComm_, &SerialCommunicator::timeoutOccurred, + this, &Widget::onSerialTimeout); + //当串口连接出现错误时(例如串口突然断开),serialComm_会发出physicalDisconnected信号 + connect(serialComm_, &SerialCommunicator::physicalDisconnected, + this, &Widget::sicalDisconnected); //设置默认波特率、数据位、校验位 - ui_->comboBox_baudRate->setCurrentIndex(3); - ui_->comboBox_dataBit->setCurrentIndex(3); - ui_->comboBox_xiaoyan->setCurrentIndex(2); + ui_->combo_baud_rate->setCurrentIndex(3); + ui_->combo_data_bit->setCurrentIndex(3); + ui_->combo_check->setCurrentIndex(2); //串口未连接前锁定读取和写入按钮 ui_->btn_read->setEnabled(false); @@ -75,24 +80,67 @@ Widget::Widget(QWidget *parent) : //检查当前可用串口 QList serialList = QSerialPortInfo::availablePorts(); - for(QSerialPortInfo serialInfo : serialList) + for(const QSerialPortInfo &serialInfo : serialList) { - ui_->comboBox_serialNum->addItem(serialInfo.portName()); + ui_->combo_serial_num->addItem(serialInfo.portName()); } } /** * @brief Widget 析构函数 * - * 清理分配的资源:删除UI、Modbus和串口通信对象。 + * 清理分配的资源:删除UI对象。 */ Widget::~Widget() { - delete modbus_; - delete serialComm_; + serialComm_->close(); delete ui_; } +bool Widget::readLineEdit() +{ + bool ok; + //配置从站参数 + quint16 stationAddress = ui_->line_station_address->text().toUShort(&ok); + if (!ok) + { + QMessageBox::warning(this, "输入错误", "从站地址应为大于0的整数"); + return false; + } + quint16 functionCode; + switch (ui_->combo_function_code->currentIndex()) + { + case 0: + functionCode = 0x01; break; + case 1: + functionCode = 0x03; break; + case 2: + functionCode = 0x0F; break; + case 3: + functionCode = 0x10; break; + } + quint16 startAddress = ui_->line_strat_address->text().toUShort(&ok); + if (!ok || startAddress > 19999) + { + QMessageBox::warning(this, "输入错误", "起始地址必须为0-19999之间的整数"); + return false; + } + quint16 length = ui_->line_length->text().toUShort(&ok); + if (!ok) + { + QMessageBox::warning(this, "输入错误", "数据长度应为大于0的整数"); + return false; + } + + //将从站参数传入modbus中 + if (!modbus_->setStation(stationAddress,functionCode,startAddress,length)) + { + QMessageBox::warning(this, "输入错误", "从站数据配置应大于0"); + return false; + } + return true; +} + /** * @brief 串口连接/断开按钮点击处理 * @@ -104,12 +152,12 @@ void Widget::btnConnectClicked() { if (ui_->btn_connect->text() == "连接") { - // 获取界面配置的串口参数 - QString portName = ui_->comboBox_serialNum->currentText(); - qint32 baudRate = ui_->comboBox_baudRate->currentText().toInt(); - QSerialPort::DataBits dataBits = QSerialPort::DataBits(ui_->comboBox_dataBit->currentText().toInt()); + // 获取界面配置的串口参数2 + QString portName = ui_->combo_serial_num->currentText(); + QSerialPort::BaudRate baudRate = QSerialPort::BaudRate(ui_->combo_baud_rate->currentText().toInt()); + QSerialPort::DataBits dataBits = QSerialPort::DataBits(ui_->combo_data_bit->currentText().toInt()); QSerialPort::Parity parity; - switch (ui_->comboBox_xiaoyan->currentIndex()) + switch (ui_->combo_check->currentIndex()) { case 0: parity = QSerialPort::NoParity; break; case 1: parity = QSerialPort::OddParity; break; @@ -139,7 +187,6 @@ void Widget::btnConnectClicked() ui_->btn_read->setEnabled(false); ui_->btn_write->setEnabled(false); } - } /** @@ -151,11 +198,12 @@ void Widget::btnConnectClicked() */ void Widget::onBtnWriteClicked() { - switch (ui_->comboBox_gongnengma->currentIndex()) //判断当前功能码 + // 校验起始地址 + switch (ui_->combo_function_code->currentIndex()) //判断当前功能码 { case 2: //写多个线圈 { - QString sendData = ui_->lineEdit->text().trimmed(); //读取输入框中的数据 + QString sendData = ui_->line_input->text().trimmed(); //读取输入框中的数据 //判断输入框中是否为空 if (sendData.isEmpty()) { @@ -180,23 +228,20 @@ void Widget::onBtnWriteClicked() coils.append(ch == '1'); } - //配置从站参数 - quint16 stationAddress = ui_->lineEdit_stationAddress->text().toInt(); - quint16 functionCode = 0x0f; - quint16 stratAddress = ui_->lineEdit_stratAddress->text().toInt(); - quint16 length = ui_->lineEdit_length->text().toInt(); - //判断输入数据长度与设置的长度是否一致 - if (coils.size() != length) + if (coils.size() != ui_->line_length->text().toUShort()) { QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配"); return; } - //将从站参数传入modbus中 - modbus_->Set(stationAddress,functionCode,stratAddress,length); - modbus_->WriteCoil(coils); //使用WriteCoil方法生成命令报文 - serialComm_->sendData(modbus_->SendCommand()); //发送生成的命令报文 + if (!readLineEdit()) + { + return; + } + modbus_->writeCoil(coils); //使用WriteCoil方法生成命令报文 + + serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文 //锁定按钮 ui_->btn_read->setEnabled(false); ui_->btn_write->setEnabled(false); @@ -206,7 +251,7 @@ void Widget::onBtnWriteClicked() case 3: //写多个寄存器 { - QString sendData = ui_->lineEdit->text().trimmed(); //读取数据 + QString sendData = ui_->line_input->text().trimmed(); //读取数据 //判断输入框中是否为空 if (sendData.isEmpty()) { @@ -221,7 +266,7 @@ void Widget::onBtnWriteClicked() // 检查输入是否是十进制 bool isDecimal = true; - for (const QString &s : sl) + for (QString s : sl) { s.toInt(&ok); if (!ok) @@ -232,7 +277,7 @@ void Widget::onBtnWriteClicked() } //若判断输入的数据不是10进制,则按16进制数据处理 - for (const QString &s : sl) + for (QString s : sl) { quint16 v; if (isDecimal) @@ -257,24 +302,21 @@ void Widget::onBtnWriteClicked() } values.append(v); } - - //配置从站参数 - quint16 stationAddress = ui_->lineEdit_stationAddress->text().toInt(); - quint16 functionCode = 0x10; - quint16 stratAddress = ui_->lineEdit_stratAddress->text().toInt(); - quint16 length = ui_->lineEdit_length->text().toInt(); - //判断输入数据长度与设置的长度是否一致 - if (values.size() != length) + if (values.size() != ui_->line_length->text().toUShort()) { QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配"); return; } - //将从站参数传入modbus中 - modbus_->Set(stationAddress,functionCode,stratAddress,length); - modbus_->WriteRegister(values); //使用WriteRegister方法生成命令报文 - serialComm_->sendData(modbus_->SendCommand()); //发送生成的命令报文 + if (!readLineEdit()) + { + return; + } + + modbus_->writeRegister(values); //使用WriteRegister方法生成命令报文 + + serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文 //锁定按钮 ui_->btn_read->setEnabled(false); ui_->btn_write->setEnabled(false); @@ -300,31 +342,19 @@ void Widget::onBtnWriteClicked() void Widget::onBtnReadClicked() { //若当前操作设置错误则进行提示 - if (ui_->comboBox_gongnengma->currentIndex() == 2 || - ui_->comboBox_gongnengma->currentIndex() == 3) + if (ui_->combo_function_code->currentIndex() == 2 || + ui_->combo_function_code->currentIndex() == 3) { QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器"); return; } - //设置从站参数 - quint16 stationAddress = ui_->lineEdit_stationAddress->text().toInt(); - quint16 functionCode; - quint16 stratAddress = ui_->lineEdit_stratAddress->text().toInt(); - quint16 length = ui_->lineEdit_length->text().toInt(); - if (ui_->comboBox_gongnengma->currentIndex() == 0) //读线圈 - { - functionCode = 0x01; - } - else if(ui_->comboBox_gongnengma->currentIndex() == 1) //读寄存器 + if (!readLineEdit()) { - functionCode = 0x03; + return; } + modbus_->readCoilAndReg(); //使用ReadCoilAndReg方法生成命令报文 - //将从站参数传入modbus中 - modbus_->Set(stationAddress,functionCode,stratAddress,length); - modbus_->ReadCoilAndReg(); //使用ReadCoilAndReg方法生成命令报文 - - serialComm_->sendData(modbus_->SendCommand()); //发送生成的命令报文 + serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文 //锁定按钮 ui_->btn_read->setEnabled(false); ui_->btn_write->setEnabled(false); @@ -338,7 +368,7 @@ void Widget::onBtnReadClicked() */ void Widget::onBtnSavedateClicked() { - SaveDate(this,ui_->textEdit_2); + saveDate(this,ui_->text_information); } /** @@ -348,7 +378,7 @@ void Widget::onBtnSavedateClicked() */ void Widget::onBtnReaddateClicked() { - ReadDate(this,ui_->textEdit_2); + readDate(this,ui_->text_information); } /** @@ -356,7 +386,7 @@ void Widget::onBtnReaddateClicked() */ void Widget::onBtnCleardateClicked() { - ui_->textEdit_2->clear(); + ui_->text_information->clear(); } /** @@ -364,7 +394,15 @@ void Widget::onBtnCleardateClicked() */ void Widget::onBtnClearreadClicked() { - ui_->textEdit->clear(); + ui_->text_data->clear(); +} + +void Widget::sicalDisconnected() +{ + ui_->btn_connect->setText("连接"); + ui_->btn_read->setEnabled(false); + ui_->btn_write->setEnabled(false); + QMessageBox::warning(this, "警告", "串口已断开连接"); } /** @@ -376,7 +414,7 @@ void Widget::onBtnClearreadClicked() */ void Widget::onSerialDataReceived(const QByteArray &data) { - QByteArray revMessage = modbus_->Receive(data); // 接收的原始数据交给Modbus解析 + QByteArray revMessage = modbus_->receive(data); // 接收的原始数据交给Modbus解析 // 启用操作按钮 ui_->btn_read->setEnabled(true); ui_->btn_write->setEnabled(true); @@ -388,18 +426,20 @@ void Widget::onSerialDataReceived(const QByteArray &data) } //显示接收到的响应报文 - ui_->textEdit_2->append("接收报文:" + revMessage.toHex().toUpper()); + ui_->text_information->append("接收报文:" + revMessage.toHex().toUpper()); // 检查响应报文中是否带有错误码 - int exCode = modbus_->ErrorCheck(); + quint8 exCode = modbus_->errorCheck(); if (exCode) { QString errorMsg; - switch (exCode) { + switch (exCode) + { case 0x01: errorMsg = "非法功能码"; break; case 0x02: errorMsg = "非法数据地址"; break; case 0x03: errorMsg = "非法数据值"; break; case 0x04: errorMsg = "从站设备故障"; break; + case 0x11: errorMsg = "数据域个数异常"; break; default: errorMsg = "未知异常"; break; } QMessageBox::warning(this, "异常响应", @@ -408,25 +448,25 @@ void Widget::onSerialDataReceived(const QByteArray &data) } // 解析并显示数据(根据功能码) - switch (ui_->comboBox_gongnengma->currentIndex()) + switch (ui_->combo_function_code->currentIndex()) { case 0: { // 读线圈 - QVector coils = modbus_->AnalReadCoil(); //使用AnalReadCoil方法解析线圈数据 - ui_->textEdit->append("线圈状态:"); - for (int i = 0; i < ui_->lineEdit_length->text().toInt(); i++) + QVector coils = modbus_->analReadCoil(); //使用AnalReadCoil解析线圈数据 + ui_->text_data->append("线圈状态:"); + for (int i = 0; i < ui_->line_length->text().toInt(); i++) { - ui_->textEdit->append(QString("线圈%1: %2").arg(i+1).arg(coils[i] ? "1" : "0")); + ui_->text_data->append(QString("线圈%1: %2").arg(i+1).arg(coils[i] ? "1" : "0")); } break; } case 1: { // 读寄存器 - QVector regs = modbus_->AnalReadReg(); //使用AnalReadReg方法解析寄存器数据 - ui_->textEdit->append("寄存器值:"); - for (int i = 0; i < ui_->lineEdit_length->text().toInt(); i++) + QVector regs = modbus_->analReadReg(); //使用AnalReadReg解析寄存器数据 + ui_->text_data->append("寄存器值:"); + for (int i = 0; i < ui_->line_length->text().toInt(); i++) { - ui_->textEdit->append(QString("寄存器%1: %2").arg(i+1).arg(regs[i])); + ui_->text_data->append(QString("寄存器%1: %2").arg(i+1).arg(regs[i])); } break; } @@ -441,7 +481,15 @@ void Widget::onSerialDataReceived(const QByteArray &data) */ void Widget::onSerialStatusChanged(const QString &status) { - ui_->textEdit_2->append(status); // 显示状态信息(如连接成功、超时重发等) + ui_->text_information->append(status); // 显示状态信息(如连接成功、超时重发等) +} + +void Widget::onTextChanged() +{ + QString data = ui_->line_input->text(); + data.remove(" "); + data.remove(","); + ui_->line_number->setText(QString::number(data.size())); } /** @@ -463,10 +511,27 @@ void Widget::onSerialTimeout() */ void Widget::onBtnRefreshClicked() { - ui_->comboBox_serialNum->clear(); + ui_->combo_serial_num->clear(); QList serialList = QSerialPortInfo::availablePorts(); - for(QSerialPortInfo serialInfo : serialList) + for(const QSerialPortInfo &serialInfo : serialList) + { + ui_->combo_serial_num->addItem(serialInfo.portName()); + } +} + +void Widget::onBtnBatchInputClicked() +{ + bool ok; + int number = ui_->line_data_num->text().toInt(&ok); + if (!ok || number <= 0 || number > 1000) + { + QMessageBox::warning(this, "输入错误", "请输入1-1000之间的整数"); + return; + } + QString inputData = ui_->line_data->text(); + + for (int i = 0; i < number; i++) { - ui_->comboBox_serialNum->addItem(serialInfo.portName()); + ui_->line_input->insert(inputData); } } diff --git a/widget.h b/widget.h index 8748771..bba776e 100644 --- a/widget.h +++ b/widget.h @@ -38,12 +38,14 @@ public: * @brief 构造函数,初始化界面和通信对象 * @param parent 父窗口指针,默认为空 */ - explicit Widget(QWidget *parent = 0); + Widget(QWidget *parent = 0); /** * @brief 析构函数,清理资源 */ ~Widget(); + bool readLineEdit(); + private slots: //连接/断开串口按钮槽函数 void btnConnectClicked(); @@ -66,6 +68,8 @@ private slots: //清空解析数据文本框按钮槽函数 void onBtnClearreadClicked(); + void sicalDisconnected(); + /** * @brief 串口数据接收槽函数 * @param data 接收到的字节数据 @@ -78,11 +82,15 @@ private slots: */ void onSerialStatusChanged(const QString &status); + void onTextChanged(); + // 串口通信超时处理槽函数 void onSerialTimeout(); void onBtnRefreshClicked(); + void onBtnBatchInputClicked(); + private: Ui::Widget *ui_; // 指向用户界面的指针 SerialCommunicator *serialComm_; // 串口通信处理对象 diff --git a/widget.ui b/widget.ui index f3cf060..40b5b2c 100644 --- a/widget.ui +++ b/widget.ui @@ -69,7 +69,7 @@ - + 1 @@ -83,7 +83,7 @@ - + 读线圈 @@ -114,7 +114,7 @@ - + 256 @@ -128,7 +128,7 @@ - + 1 @@ -149,7 +149,7 @@ 串口配置 - + 10 @@ -167,7 +167,7 @@ - + @@ -177,7 +177,7 @@ - + 1200 @@ -218,7 +218,7 @@ - + 5 @@ -249,7 +249,7 @@ - + 无校验(N) @@ -317,7 +317,7 @@ 数据读取 - + 10 @@ -343,7 +343,7 @@ 状态通知 - + 10 @@ -385,7 +385,7 @@ 相邻寄存器的值之间用英文","分离。 - + 10 @@ -436,6 +436,78 @@ + + + + 590 + 400 + 93 + 28 + + + + 批量输入 + + + + + + 590 + 440 + 71 + 21 + + + + + + + 590 + 470 + 71 + 21 + + + + + + + 670 + 440 + 21 + 16 + + + + + + + + + + 590 + 350 + 51 + 41 + + + + 数据数: + + + + + + 640 + 350 + 51 + 31 + + + + true + +