@@ -1,54 +1,133 @@ | |||||
/******************************* | |||||
* Copyright (C) 2025-. | |||||
* | |||||
* File Name: communicationhistory.cpp | |||||
* Description: 通信历史记录模块头文件,提供通信数据的保存和读取功能 | |||||
* Others: | |||||
* Version: 1.0.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-25 | |||||
*******************************/ | |||||
#include "communicationhistory.h" | #include "communicationhistory.h" | ||||
/** | |||||
* @brief 保存文本内容到指定文件 | |||||
* @param parent 父窗口指针,用于对话框的模态显示 | |||||
* @param edit 包含待保存数据的文本编辑框指针 | |||||
* @return bool 保存成功返回true,用户取消或失败返回false | |||||
* @note 功能流程: | |||||
* 1. 弹出标准文件保存对话框 | |||||
* 2. 以追加模式打开文件(保留历史记录) | |||||
* 3. 自动处理文件内容分隔(添加换行符) | |||||
* 4. 使用UTF-8编码保存文本 | |||||
* 5. 错误处理包含用户提示 | |||||
*/ | |||||
bool SaveDate(QWidget *parent, QTextEdit *edit) | bool SaveDate(QWidget *parent, QTextEdit *edit) | ||||
{ | { | ||||
// 弹出文件保存对话框,默认保存到用户主目录的note.txt文件 | |||||
QString fileName = QFileDialog::getSaveFileName( | QString fileName = QFileDialog::getSaveFileName( | ||||
parent, | |||||
"保存文本", | |||||
QDir::homePath() + "/note.txt", | |||||
"文本文件 (*.txt);;所有文件 (*)"); | |||||
parent, | |||||
"保存文本", // 对话框标题 | |||||
QDir::homePath() + "/note.txt", // 默认路径和文件名 | |||||
"文本文件 (*.txt);;所有文件 (*)"); // 文件过滤器 | |||||
// 检查用户是否取消操作 | |||||
if (fileName.isEmpty()) | if (fileName.isEmpty()) | ||||
{ | |||||
return false; | return false; | ||||
} | |||||
// 以追加模式和文本模式打开文件 | |||||
QFile file(fileName); | QFile file(fileName); | ||||
if (!file.open(QIODevice::Append | QIODevice::Text)) | if (!file.open(QIODevice::Append | QIODevice::Text)) | ||||
{ | { | ||||
QMessageBox::warning(parent, "错误", "无法打开文件"); | |||||
QMessageBox::warning(parent, | |||||
"错误", | |||||
QString("无法打开文件\n%1").arg(file.errorString())); | |||||
return false; | return false; | ||||
} | } | ||||
// 如果文件已有内容,先追加换行符保持内容分隔 | |||||
if (file.size() > 0) | if (file.size() > 0) | ||||
file.write("\n"); | |||||
{ | |||||
if (file.write("\n") == -1) | |||||
{ | |||||
// 检查换行符写入是否成功 | |||||
QMessageBox::warning(parent, "错误", "无法写入分隔符"); | |||||
file.close(); | |||||
return false; | |||||
} | |||||
} | |||||
// 设置UTF-8编码并写入文本内容 | |||||
QTextStream out(&file); | QTextStream out(&file); | ||||
out.setCodec("UTF-8"); | out.setCodec("UTF-8"); | ||||
out << edit->toPlainText(); | out << edit->toPlainText(); | ||||
// 检查流状态确保写入成功 | |||||
if (out.status() != QTextStream::Ok) | |||||
{ | |||||
QMessageBox::warning(parent, "错误", "写入文件时发生错误"); | |||||
file.close(); | |||||
return false; | |||||
} | |||||
file.close(); | file.close(); | ||||
return true; | return true; | ||||
} | } | ||||
/** | |||||
* @brief 从文件读取文本内容 | |||||
* @param parent 父窗口指针,用于对话框的模态显示 | |||||
* @param edit 用于显示读取内容的文本编辑框指针 | |||||
* @return bool 读取成功返回true,用户取消或失败返回false | |||||
* @note 功能流程: | |||||
* 1. 弹出标准文件打开对话框 | |||||
* 2. 以只读模式打开文件 | |||||
* 3. 使用UTF-8编码读取内容 | |||||
* 4. 错误处理包含用户提示 | |||||
*/ | |||||
bool ReadDate(QWidget *parent, QTextEdit *edit) | bool ReadDate(QWidget *parent, QTextEdit *edit) | ||||
{ | { | ||||
// 弹出文件打开对话框,默认从用户主目录开始浏览 | |||||
QString fileName = QFileDialog::getOpenFileName( | QString fileName = QFileDialog::getOpenFileName( | ||||
parent, | |||||
"打开文本", | |||||
QDir::homePath(), | |||||
"文本文件 (*.txt);;所有文件 (*)"); | |||||
parent, | |||||
"打开文本", // 对话框标题 | |||||
QDir::homePath(), // 默认路径 | |||||
"文本文件 (*.txt);;所有文件 (*)"); // 文件过滤器 | |||||
// 检查用户是否取消操作 | |||||
if (fileName.isEmpty()) | if (fileName.isEmpty()) | ||||
{ | |||||
return false; | return false; | ||||
} | |||||
// 以只读模式和文本模式打开文件 | |||||
QFile file(fileName); | QFile file(fileName); | ||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) | ||||
{ | { | ||||
QMessageBox::warning(parent, "错误", "无法读取文件"); | |||||
QMessageBox::warning(parent, | |||||
"错误", | |||||
QString("无法读取文件\n%1").arg(file.errorString())); | |||||
return false; | return false; | ||||
} | } | ||||
// 设置UTF-8编码并读取全部内容 | |||||
QTextStream in(&file); | QTextStream in(&file); | ||||
in.setCodec("UTF-8"); | in.setCodec("UTF-8"); | ||||
edit->setPlainText(in.readAll()); | |||||
QString content = in.readAll(); | |||||
// 检查流状态确保读取成功 | |||||
if (in.status() != QTextStream::Ok) | |||||
{ | |||||
QMessageBox::warning(parent, "错误", "读取文件时发生错误"); | |||||
file.close(); | |||||
return false; | |||||
} | |||||
// 将内容设置到文本编辑框 | |||||
edit->setPlainText(content); | |||||
file.close(); | file.close(); | ||||
return true; | return true; | ||||
} | } |
@@ -1,3 +1,14 @@ | |||||
/******************************* | |||||
* Copyright (C) 2025-. | |||||
* | |||||
* File Name: communicationhistory.h | |||||
* Description: 通信历史记录模块头文件,提供通信数据的保存和读取功能 | |||||
* Others: | |||||
* Version: 1.0.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-25 | |||||
*******************************/ | |||||
#ifndef COMMUNICATIONHISTORY_H | #ifndef COMMUNICATIONHISTORY_H | ||||
#define COMMUNICATIONHISTORY_H | #define COMMUNICATIONHISTORY_H | ||||
@@ -10,8 +21,30 @@ | |||||
#include <QDir> | #include <QDir> | ||||
#include <QTextEdit> | #include <QTextEdit> | ||||
/** | |||||
* @brief 保存通信数据到文件 | |||||
* @param parent 父窗口指针,用于对话框的模态显示 | |||||
* @param edit 包含待保存通信数据的文本编辑框指针 | |||||
* @return bool 保存成功返回true,失败返回false | |||||
* @note 此函数将: | |||||
* 1. 弹出文件保存对话框让用户选择保存路径 | |||||
* 2. 将文本编辑框内容写入选定文件 | |||||
* 3. 支持UTF-8编码格式保存 | |||||
* 4. 操作失败时会显示错误提示 | |||||
*/ | |||||
bool SaveDate(QWidget *parent, QTextEdit *edit); | bool SaveDate(QWidget *parent, QTextEdit *edit); | ||||
/** | |||||
* @brief 从文件读取通信数据 | |||||
* @param parent 父窗口指针,用于对话框的模态显示 | |||||
* @param edit 用于显示读取内容的文本编辑框指针 | |||||
* @return bool 读取成功返回true,失败返回false | |||||
* @note 此函数将: | |||||
* 1. 弹出文件选择对话框让用户选择文件 | |||||
* 2. 读取选定文件内容到文本编辑框 | |||||
* 3. 支持UTF-8编码格式读取 | |||||
* 4. 操作失败时会显示错误提示 | |||||
*/ | |||||
bool ReadDate(QWidget *parent, QTextEdit *edit); | bool ReadDate(QWidget *parent, QTextEdit *edit); | ||||
#endif // COMMUNICATIONHISTORY_H | #endif // COMMUNICATIONHISTORY_H |
@@ -1,54 +0,0 @@ | |||||
#include "crc.h" | |||||
quint16 CalculateCrc(const QByteArray &data) | |||||
{ | |||||
quint16 crc = 0xFFFF; // MODBUS初始值 | |||||
const quint16 polynomial = 0xA001; // MODBUS多项式(0x8005的反转) | |||||
for (int i = 0; i < data.size(); ++i) | |||||
{ | |||||
crc ^= static_cast<quint8>(data.at(i)); | |||||
for (int bit = 0; bit < 8; ++bit) | |||||
{ | |||||
if (crc & 0x0001) | |||||
{ // 检查最低位 | |||||
crc = (crc >> 1) ^ polynomial; // 右移并异或 | |||||
} else | |||||
{ | |||||
crc >>= 1; // 仅右移 | |||||
} | |||||
} | |||||
} | |||||
return crc; | |||||
} | |||||
bool CrcCheck(const QByteArray &data) | |||||
{ | |||||
//首先对接收报文的长度进行检验 | |||||
if (data.size() < 3) | |||||
{ | |||||
return false; | |||||
} | |||||
//对接收的报文进行CRC校验 | |||||
QByteArray payload = data.left(data.length() - 2); | |||||
//分离接收值的crc校验位 | |||||
quint8 receivedCrcLow = static_cast<quint8>(data.at(data.length() - 2)); | |||||
quint8 receivedCrcHigh = static_cast<quint8>(data.at(data.length() - 1)); | |||||
//计算返回的报文的crc | |||||
quint16 crc = CalculateCrc(payload); | |||||
quint8 calcCrcLow = crc & 0xFF; | |||||
quint8 calcCrcHigh = (crc >> 8) & 0xFF; | |||||
//比较计算的crc值和接收到的crc值是否一致 | |||||
if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh) | |||||
{ | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
} |
@@ -1,10 +0,0 @@ | |||||
#ifndef CRC_H | |||||
#define CRC_H | |||||
#include <QByteArray> | |||||
#include <QtGlobal> | |||||
quint16 CalculateCrc(const QByteArray &data); | |||||
bool CrcCheck(const QByteArray &data); | |||||
#endif // CRC_H |
@@ -15,12 +15,10 @@ TEMPLATE = app | |||||
SOURCES += main.cpp\ | SOURCES += main.cpp\ | ||||
widget.cpp \ | widget.cpp \ | ||||
crc.cpp \ | |||||
mymodbus.cpp \ | mymodbus.cpp \ | ||||
communicationhistory.cpp | communicationhistory.cpp | ||||
HEADERS += widget.h \ | HEADERS += widget.h \ | ||||
crc.h \ | |||||
mymodbus.h \ | mymodbus.h \ | ||||
communicationhistory.h | communicationhistory.h | ||||
@@ -1,6 +1,6 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<!DOCTYPE QtCreatorProject> | <!DOCTYPE QtCreatorProject> | ||||
<!-- Written by QtCreator 4.0.3, 2025-07-28T12:59:31. --> | |||||
<!-- Written by QtCreator 4.0.3, 2025-07-28T16:03:38. --> | |||||
<qtcreator> | <qtcreator> | ||||
<data> | <data> | ||||
<variable>EnvironmentId</variable> | <variable>EnvironmentId</variable> | ||||
@@ -1,14 +1,37 @@ | |||||
#include "mymodbus.h" | |||||
/****************************************************************************** | |||||
* Copyright (C) 2025-. | |||||
* | |||||
* File Name: mymodbus.cpp | |||||
* Description: Modbus RTU协议封装类,支持线圈和寄存器的读写操作 | |||||
* Others: | |||||
* Version: 1.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-23 | |||||
* | |||||
******************************************************************************/ | |||||
#include "mymodbus.h" | |||||
/** | |||||
* @brief MyModbus类构造函数 | |||||
* @note 初始化默认Modbus参数:站地址=1,功能码=0x01(读线圈), | |||||
* 起始地址=256,数据长度=1 | |||||
*/ | |||||
MyModbus::MyModbus() | MyModbus::MyModbus() | ||||
{ | { | ||||
this->stationAddress_ = 1; | |||||
this->functionCode_ = 0x01; | |||||
this->startAdress_ = 256; | |||||
this->length_ = 1; | |||||
this->stationAddress_ = 1; // 默认站地址 | |||||
this->functionCode_ = 0x01; // 默认功能码(读线圈) | |||||
this->startAdress_ = 256; // 默认起始地址 | |||||
this->length_ = 1; // 默认数据长度 | |||||
} | } | ||||
/** | |||||
* @brief 设置Modbus通信参数 | |||||
* @param stationAddress 从站地址 | |||||
* @param functionCode 功能码 | |||||
* @param startAdress 起始地址 | |||||
* @param length 数据长度 | |||||
*/ | |||||
void MyModbus::Set(quint16 stationAddress, quint16 functionCode, quint16 startAdress, quint16 length) | void MyModbus::Set(quint16 stationAddress, quint16 functionCode, quint16 startAdress, quint16 length) | ||||
{ | { | ||||
this->stationAddress_ = stationAddress; | this->stationAddress_ = stationAddress; | ||||
@@ -18,92 +41,129 @@ void MyModbus::Set(quint16 stationAddress, quint16 functionCode, quint16 startAd | |||||
} | } | ||||
/** | |||||
* @brief 生成读取线圈/寄存器的Modbus命令 | |||||
* @note 命令结构:[站地址][功能码][起始地址Hi][起始地址Lo][长度Hi][长度Lo][CRC Lo][CRC Hi] | |||||
*/ | |||||
void MyModbus::ReadCoilAndReg() | void MyModbus::ReadCoilAndReg() | ||||
{ | { | ||||
sendCommand_.clear(); | sendCommand_.clear(); | ||||
// 构建标准读命令帧 | |||||
sendCommand_.append(stationAddress_%256); | sendCommand_.append(stationAddress_%256); | ||||
sendCommand_.append(functionCode_%256); | sendCommand_.append(functionCode_%256); | ||||
sendCommand_.append(startAdress_/256); | sendCommand_.append(startAdress_/256); | ||||
sendCommand_.append(startAdress_%256); | sendCommand_.append(startAdress_%256); | ||||
sendCommand_.append(length_/256); | sendCommand_.append(length_/256); | ||||
sendCommand_.append(length_%256); | sendCommand_.append(length_%256); | ||||
// 计算并附加CRC校验 | |||||
quint16 temp = CalculateCrc(sendCommand_); | quint16 temp = CalculateCrc(sendCommand_); | ||||
sendCommand_.append(temp%256); | |||||
sendCommand_.append(temp/256); | |||||
sendCommand_.append(temp%256); // CRC低字节在前 | |||||
sendCommand_.append(temp/256); // CRC高字节在后 | |||||
} | } | ||||
/** | |||||
* @brief 生成写线圈命令 | |||||
* @param coils 要写入的线圈状态数组 | |||||
* @note 命令结构:[站地址][0x0F][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据...][CRC] | |||||
*/ | |||||
void MyModbus::WriteCoil(QVector<bool> &coils) | void MyModbus::WriteCoil(QVector<bool> &coils) | ||||
{ | { | ||||
quint16 coilCount = coils.size(); | quint16 coilCount = coils.size(); | ||||
int byteCount = (coilCount + 7) / 8; | |||||
int byteCount = (coilCount + 7) / 8; // 计算所需字节数(每字节8位) | |||||
sendCommand_.clear(); | sendCommand_.clear(); | ||||
sendCommand_.append(stationAddress_%256); | sendCommand_.append(stationAddress_%256); | ||||
sendCommand_.append(0x0f); | |||||
sendCommand_.append(0x0F); // 写多个线圈功能码 | |||||
sendCommand_.append(startAdress_/256); | sendCommand_.append(startAdress_/256); | ||||
sendCommand_.append(startAdress_%256); | sendCommand_.append(startAdress_%256); | ||||
sendCommand_.append(length_/256); | |||||
sendCommand_.append(length_%256); | |||||
sendCommand_.append(byteCount); | |||||
sendCommand_.append(coilCount/256); // 线圈数量 | |||||
sendCommand_.append(coilCount%256); | |||||
sendCommand_.append(byteCount); // 后续字节数 | |||||
// 打包线圈状态到字节 | |||||
for (int i = 0; i < byteCount; ++i) | for (int i = 0; i < byteCount; ++i) | ||||
{ | { | ||||
quint8 byte = 0; | quint8 byte = 0; | ||||
for (int j = 0; j < 8; ++j) | for (int j = 0; j < 8; ++j) | ||||
{ | { | ||||
int bitIndex = i * 8 + j; | int bitIndex = i * 8 + j; | ||||
// 只处理有效范围内的线圈 | |||||
if (bitIndex < coils.size() && coils[bitIndex]) | if (bitIndex < coils.size() && coils[bitIndex]) | ||||
byte |= (1 << j); | |||||
{ | |||||
byte |= (1 << j); // 设置对应位 | |||||
} | |||||
} | } | ||||
sendCommand_.append(static_cast<char>(byte)); | sendCommand_.append(static_cast<char>(byte)); | ||||
} | } | ||||
quint16 temp = CalculateCrc(sendCommand_); //计算crc | |||||
sendCommand_.append(temp%256); //加入计算的crc值 | |||||
// 计算并附加CRC校验 | |||||
quint16 temp = CalculateCrc(sendCommand_); | |||||
sendCommand_.append(temp%256); | |||||
sendCommand_.append(temp/256); | sendCommand_.append(temp/256); | ||||
} | } | ||||
/** | |||||
* @brief 生成写寄存器命令 | |||||
* @param values 要写入的寄存器值数组 | |||||
* @note 命令结构:[站地址][0x10][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据Hi][数据Lo...][CRC] | |||||
*/ | |||||
void MyModbus::WriteRegister(QVector<quint16> &values) | void MyModbus::WriteRegister(QVector<quint16> &values) | ||||
{ | { | ||||
sendCommand_.clear(); | sendCommand_.clear(); | ||||
sendCommand_.append(stationAddress_%256); | sendCommand_.append(stationAddress_%256); | ||||
sendCommand_.append(0x10); | |||||
sendCommand_.append(0x10); // 写多个寄存器功能码 | |||||
sendCommand_.append(startAdress_/256); | sendCommand_.append(startAdress_/256); | ||||
sendCommand_.append(startAdress_%256); | sendCommand_.append(startAdress_%256); | ||||
sendCommand_.append(length_/256); | |||||
sendCommand_.append(length_%256); | |||||
sendCommand_.append(static_cast<char>(values.size() * 2)); | |||||
sendCommand_.append(values.size()/256); // 寄存器数量 | |||||
sendCommand_.append(values.size()%256); | |||||
sendCommand_.append(static_cast<char>(values.size() * 2)); // 后续字节数(每个寄存器2字节) | |||||
// 打包寄存器值(高位在前) | |||||
for (quint16 v : values) | for (quint16 v : values) | ||||
{ | { | ||||
sendCommand_.append(static_cast<char>((v >> 8) & 0xFF)); | |||||
sendCommand_.append(static_cast<char>(v & 0xFF)); | |||||
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值 | |||||
// 计算并附加CRC校验 | |||||
quint16 temp = CalculateCrc(sendCommand_); | |||||
sendCommand_.append(temp%256); | |||||
sendCommand_.append(temp/256); | sendCommand_.append(temp/256); | ||||
} | } | ||||
/** | |||||
* @brief 获取生成的Modbus命令 | |||||
* @return 完整的Modbus命令字节数组 | |||||
*/ | |||||
QByteArray MyModbus::SendCommand() | QByteArray MyModbus::SendCommand() | ||||
{ | { | ||||
return sendCommand_; | return sendCommand_; | ||||
} | } | ||||
/** | |||||
* @brief 处理接收到的Modbus响应 | |||||
* @param revMessage 接收到的原始报文 | |||||
* @return 处理后的有效数据部分(移除地址/功能码/CRC) | |||||
* @note 执行站地址匹配和CRC校验 | |||||
*/ | |||||
QByteArray MyModbus::Receive(const QByteArray &revMessage) | QByteArray MyModbus::Receive(const QByteArray &revMessage) | ||||
{ | { | ||||
receive_.clear(); | receive_.clear(); | ||||
// 最小Modbus响应长度检查 | |||||
// 最小长度检查:站地址1 + 功能码1 + CRC2 | |||||
if(revMessage.size() < 4) | if(revMessage.size() < 4) | ||||
{ // 地址1 + 功能码1 + CRC2 | |||||
return receive_; | |||||
{ | |||||
return receive_; // 返回空数组表示无效报文 | |||||
} | } | ||||
// 检查站地址匹配 | |||||
// 站地址匹配检查 | |||||
if(static_cast<quint8>(revMessage[0]) != stationAddress_) | if(static_cast<quint8>(revMessage[0]) != stationAddress_) | ||||
{ | { | ||||
return receive_; | return receive_; | ||||
} | } | ||||
// CRC校验 | |||||
// CRC校验检查 | |||||
if (!CrcCheck(revMessage)) | if (!CrcCheck(revMessage)) | ||||
{ | { | ||||
return receive_; | return receive_; | ||||
@@ -113,72 +173,148 @@ QByteArray MyModbus::Receive(const QByteArray &revMessage) | |||||
return receive_; | return receive_; | ||||
} | } | ||||
/** | |||||
* @brief 检查Modbus异常响应 | |||||
* @return 0-正常响应, >0-Modbus异常码, -1-响应报文错误 | |||||
*/ | |||||
int MyModbus::ErrorCheck() | int MyModbus::ErrorCheck() | ||||
{ | { | ||||
// 异常响应最小长度检查 | |||||
// 异常响应最小长度:地址1 + 异常功能码1 + 异常码1 + CRC2 | |||||
if(receive_.size() < 5) | if(receive_.size() < 5) | ||||
{ | { | ||||
// 地址1 + 异常功能码1 + 异常码1 + CRC2 | |||||
return -1; // 特殊错误码表示协议错误 | |||||
return -1; // 响应报文错误 | |||||
} | } | ||||
// 检查功能码高位(异常响应标志) | |||||
if ((receive_.at(1) & 0x80) == 0x80) | if ((receive_.at(1) & 0x80) == 0x80) | ||||
{ | { | ||||
// MODBUS异常响应结构:地址 | 功能码+0x80 | 异常码 | CRC | |||||
quint8 exCode = receive_.at(2); | |||||
return exCode; | |||||
} | |||||
else | |||||
{ | |||||
return 0; | |||||
return static_cast<quint8>(receive_.at(2)); // 返回异常码 | |||||
} | } | ||||
return 0; // 正常响应 | |||||
} | } | ||||
/** | |||||
* @brief 解析读取线圈的响应数据 | |||||
* @return 线圈状态数组 | |||||
*/ | |||||
QVector<bool> MyModbus::AnalReadCoil() | QVector<bool> MyModbus::AnalReadCoil() | ||||
{ | { | ||||
QVector<bool> coil; | QVector<bool> coil; | ||||
// 最小长度检查:地址1+功能码1+字节数1+数据1+CRC2 | |||||
if(receive_.size() < 6) | if(receive_.size() < 6) | ||||
{ | { | ||||
// 最小长度: 地址1 + 功能码1 + 长度1 + 数据1 + CRC2 | |||||
return coil; | return coil; | ||||
} | } | ||||
quint8 byteCount = static_cast<quint8>(receive_[2]); | |||||
quint8 byteCount = static_cast<quint8>(receive_[2]); // 数据字节数 | |||||
// 解析每个数据字节 | |||||
for (int byteIndex = 0; byteIndex < byteCount; byteIndex++) | for (int byteIndex = 0; byteIndex < byteCount; byteIndex++) | ||||
{ | { | ||||
quint8 byteValue = static_cast<quint8>(receive_[3 + byteIndex]); | quint8 byteValue = static_cast<quint8>(receive_[3 + byteIndex]); | ||||
// 解析每个字节的8个位 | |||||
// 解析字节中的每个位(线圈状态) | |||||
for (int bitIndex = 0; bitIndex < 8; bitIndex++) | for (int bitIndex = 0; bitIndex < 8; bitIndex++) | ||||
{ | { | ||||
int coilIndex = byteIndex * 8 + bitIndex; | int coilIndex = byteIndex * 8 + bitIndex; | ||||
if (coilIndex < length_) | if (coilIndex < length_) | ||||
{ | { | ||||
bool state = byteValue & (1 << bitIndex); | |||||
coil.append(state); | |||||
// 只处理请求的长度 | |||||
coil.append(byteValue & (1 << bitIndex)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return coil; | return coil; | ||||
} | } | ||||
/** | |||||
* @brief 解析读取寄存器的响应数据 | |||||
* @return 寄存器值数组 | |||||
*/ | |||||
QVector<quint16> MyModbus::AnalReadReg() | QVector<quint16> MyModbus::AnalReadReg() | ||||
{ | { | ||||
QVector<quint16> registers; | QVector<quint16> registers; | ||||
// 最小长度检查:地址1+功能码1+字节数1+数据2+CRC2 | |||||
if(receive_.size() < 7) | if(receive_.size() < 7) | ||||
{ | { | ||||
// 最小长度: 地址1 + 功能码1 + 长度1 + 数据2 + CRC2 | |||||
return registers; | return registers; | ||||
} | } | ||||
int byteCount = receive_.at(2); | |||||
QByteArray data = receive_.mid(3, byteCount); | |||||
int byteCount = receive_.at(2); // 数据字节数 | |||||
QByteArray data = receive_.mid(3, byteCount); // 提取数据部分 | |||||
// 每两个字节组合为一个寄存器值(高位在前) | |||||
for (int i = 0; i < data.size(); i += 2) | 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); | |||||
quint16 high = static_cast<quint8>(data[i]); | |||||
quint16 low = static_cast<quint8>(data[i+1]); | |||||
registers.append((high << 8) | low); | |||||
} | } | ||||
return registers; | return registers; | ||||
} | } | ||||
/** | |||||
* @brief 计算Modbus CRC16校验码 | |||||
* @param data 输入数据字节数组 | |||||
* @return CRC16校验值 | |||||
* @note 使用MODBUS标准多项式0xA001(0x8005的反转) | |||||
*/ | |||||
quint16 MyModbus::CalculateCrc(const QByteArray &data) | |||||
{ | |||||
quint16 crc = 0xFFFF; // MODBUS初始值 | |||||
const quint16 polynomial = 0xA001; // MODBUS多项式(0x8005的反转) | |||||
for (int i = 0; i < data.size(); ++i) | |||||
{ | |||||
crc ^= static_cast<quint8>(data.at(i)); // 异或当前字节 | |||||
// 处理每个字节的8位 | |||||
for (int bit = 0; bit < 8; ++bit) | |||||
{ | |||||
if (crc & 0x0001) | |||||
{ | |||||
// 检查最低位 | |||||
crc = (crc >> 1) ^ polynomial; // 右移并且异或多项式 | |||||
} else | |||||
{ | |||||
crc >>= 1; // 仅右移 | |||||
} | |||||
} | |||||
} | |||||
return crc; | |||||
} | |||||
/** | |||||
* @brief 校验接收数据的CRC | |||||
* @param data 完整接收数据(包含末尾CRC) | |||||
* @return true-CRC校验通过, false-校验失败 | |||||
*/ | |||||
bool MyModbus::CrcCheck(const QByteArray &data) | |||||
{ | |||||
//首先对接收报文的长度进行检验 | |||||
if (data.size() < 3) | |||||
{ | |||||
return false; | |||||
} | |||||
//对接收的报文进行CRC校验 | |||||
QByteArray payload = data.left(data.length() - 2); | |||||
//分离接收值的crc校验位 | |||||
quint8 receivedCrcLow = static_cast<quint8>(data.at(data.length() - 2)); | |||||
quint8 receivedCrcHigh = static_cast<quint8>(data.at(data.length() - 1)); | |||||
//计算返回的报文的crc | |||||
quint16 crc = CalculateCrc(payload); | |||||
quint8 calcCrcLow = crc & 0xFF; | |||||
quint8 calcCrcHigh = (crc >> 8) & 0xFF; | |||||
//比较计算的crc值和接收到的crc值是否一致 | |||||
if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh) | |||||
{ | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
} |
@@ -4,29 +4,124 @@ | |||||
#include <QByteArray> | #include <QByteArray> | ||||
#include <QVector> | #include <QVector> | ||||
#include <QString> | #include <QString> | ||||
#include "crc.h" | |||||
#include <QtGlobal> | |||||
/****************************************************************************** | |||||
* Copyright (C) 2025-. | |||||
* | |||||
* File Name: mymodbus.h | |||||
* Description: Modbus RTU协议封装类,支持线圈和寄存器的读写操作 | |||||
* Others: | |||||
* Version: 1.0 | |||||
* Author: lipengpeng | |||||
* Date: 2025-7-23 | |||||
* | |||||
******************************************************************************/ | |||||
/** | |||||
* @class MyModbus | |||||
* @brief Modbus RTU协议封装类 | |||||
* | |||||
* 提供Modbus RTU协议的核心功能实现,包括: | |||||
* 1. 读线圈/寄存器(功能码0x01/0x03) | |||||
* 2. 写多个线圈(功能码0x0F) | |||||
* 3. 写多个寄存器(功能码0x10) | |||||
* 4. CRC校验计算与验证 | |||||
* 5. 响应报文解析 | |||||
* | |||||
* @note 使用时需先通过Set()方法设置从站参数 | |||||
*/ | |||||
class MyModbus | class MyModbus | ||||
{ | { | ||||
private: | private: | ||||
quint16 stationAddress_; | |||||
quint16 functionCode_; | |||||
quint16 startAdress_; | |||||
quint16 length_; | |||||
QByteArray sendCommand_; | |||||
QByteArray receive_; | |||||
quint16 stationAddress_; // 从站地址 | |||||
quint16 functionCode_; // 功能码 | |||||
quint16 startAdress_; // 起始地址 | |||||
quint16 length_; // 数据长度 | |||||
QByteArray sendCommand_; // 待发送的命令报文 | |||||
QByteArray receive_; // 接收到的响应报文 | |||||
public: | public: | ||||
/** | |||||
* @brief 默认构造函数 | |||||
*/ | |||||
MyModbus(); | MyModbus(); | ||||
void Set(quint16 stationAddress_,quint16 functionCode_,quint16 startAdress_,quint16 length_); | |||||
/** | |||||
* @brief 设置Modbus通信参数 | |||||
* @param stationAddress 从站地址 | |||||
* @param functionCode 功能码 | |||||
* @param startAdress 起始地址 | |||||
* @param length 数据长度 | |||||
*/ | |||||
void Set(quint16 stationAddress, quint16 functionCode, | |||||
quint16 startAdress, quint16 length); | |||||
/** | |||||
* @brief 生成读线圈/寄存器命令 | |||||
* | |||||
* 根据当前参数生成读取命令(功能码01或03) | |||||
*/ | |||||
void ReadCoilAndReg(); | void ReadCoilAndReg(); | ||||
/** | |||||
* @brief 生成写多个线圈命令 | |||||
* @param coils 要写入的线圈状态数组 | |||||
*/ | |||||
void WriteCoil(QVector<bool> &coils); | void WriteCoil(QVector<bool> &coils); | ||||
/** | |||||
* @brief 生成写多个寄存器命令 | |||||
* @param values 要写入的寄存器值数组 | |||||
*/ | |||||
void WriteRegister(QVector<quint16> &values); | void WriteRegister(QVector<quint16> &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(); | int ErrorCheck(); | ||||
/** | |||||
* @brief 解析读线圈响应报文 | |||||
* @return 线圈状态数组 | |||||
*/ | |||||
QVector<bool> AnalReadCoil(); | QVector<bool> AnalReadCoil(); | ||||
/** | |||||
* @brief 解析读寄存器响应报文 | |||||
* @return 寄存器值数组 | |||||
*/ | |||||
QVector<quint16> AnalReadReg(); | QVector<quint16> AnalReadReg(); | ||||
/** | |||||
* @brief 计算CRC校验值 | |||||
* @param data 待计算的数据 | |||||
* @return CRC校验值 | |||||
*/ | |||||
quint16 CalculateCrc(const QByteArray &data); | |||||
/** | |||||
* @brief 校验报文的CRC值 | |||||
* @param data 待校验的报文 | |||||
* @return true表示校验通过,false表示校验失败 | |||||
*/ | |||||
bool CrcCheck(const QByteArray &data); | |||||
}; | }; | ||||
#endif // MYMODBUS_H | #endif // MYMODBUS_H |
@@ -8,7 +8,6 @@ | |||||
#include <QVector> | #include <QVector> | ||||
#include <QMessageBox> | #include <QMessageBox> | ||||
#include <synchapi.h> | #include <synchapi.h> | ||||
#include "crc.h" | |||||
#include "mymodbus.h" | #include "mymodbus.h" | ||||
#include "communicationhistory.h" | #include "communicationhistory.h" | ||||
@@ -142,6 +141,11 @@ void Widget::on_pushWrite_clicked() | |||||
quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); | quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); | ||||
quint16 length = ui->lineEdit_length->text().toInt(); | quint16 length = ui->lineEdit_length->text().toInt(); | ||||
if (coils.size() != length) | |||||
{ | |||||
QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配"); | |||||
return; | |||||
} | |||||
modbus->Set(stationAddress,functionCode,stratAddress,length); | modbus->Set(stationAddress,functionCode,stratAddress,length); | ||||
modbus->WriteCoil(coils); | modbus->WriteCoil(coils); | ||||
@@ -184,6 +188,11 @@ void Widget::on_pushWrite_clicked() | |||||
quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); | quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt(); | ||||
quint16 length = ui->lineEdit_length->text().toInt(); | quint16 length = ui->lineEdit_length->text().toInt(); | ||||
if (values.size() != length) | |||||
{ | |||||
QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配"); | |||||
return; | |||||
} | |||||
modbus->Set(stationAddress,functionCode,stratAddress,length); | modbus->Set(stationAddress,functionCode,stratAddress,length); | ||||
modbus->WriteRegister(values); //要发送的报文 | modbus->WriteRegister(values); //要发送的报文 | ||||