|
- /******************************************************************************
- * 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(QObject *parent) : QObject(parent)
- , stationAddress_(0x01)
- , functionCode_(0x01)
- , startAddress_(0x01)
- , length_(0x01)
- {
- }
-
- /**
- * @brief 设置Modbus通信参数
- * @param stationAddress 从站地址
- * @param functionCode 功能码
- * @param startAdress 起始地址
- * @param length 数据长度
- */
- bool MyModbus::setStation(quint16 stationAddress, quint16 functionCode,
- quint16 startAddress, quint16 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()
- {
- // 计算并附加CRC校验
- quint16 temp = calculateCrc(sendCommand_);
- 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)
- {
- quint16 coilCount = coils.size();
- int byteCount = (coilCount + 7) / 8; // 计算所需字节数(每字节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 |= static_cast<quint8>(1 << j); // 设置对应位
- }
- }
- sendCommand_.append(byte);
-
- }
-
- // 计算并附加CRC校验
- quint16 temp = calculateCrc(sendCommand_);
- 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)
- {
- sendCommand_.append(values.size() * 2); // 后续字节数(每个寄存器2字节)
-
- // 打包寄存器值(高位在前)
- for (quint16 v : values)
- {
- sendCommand_.append(v / 256); // 高字节
- sendCommand_.append(v % 256); // 低字节
- }
-
- // 计算并附加CRC校验
- quint16 temp = calculateCrc(sendCommand_);
- sendCommand_.append(temp % 256);
- sendCommand_.append(temp / 256);
- }
-
- /**
- * @brief 获取生成的Modbus命令
- * @return 完整的Modbus命令字节数组
- */
- QByteArray MyModbus::sendCommand()
- {
- return sendCommand_;
- }
-
- /**
- * @brief 处理接收到的Modbus响应
- * @param revMessage 接收到的原始报文
- * @return 处理后的有效数据部分(移除地址/功能码/CRC)
- * @note 执行站地址匹配和CRC校验
- */
- QByteArray MyModbus::receive(const QByteArray &revMessage)
- {
- receive_.clear();
-
- // 最小长度检查:站地址1 + 功能码1 + CRC2
- if(revMessage.size() < 4)
- {
- return receive_; // 返回空数组表示无效报文
- }
-
- // 站地址匹配检查
- if(static_cast<quint8>(revMessage[0]) != stationAddress_)
- {
- return receive_;
- }
-
- // CRC校验检查
- if (!crcCheck(revMessage))
- {
- return receive_;
- }
-
- this->receive_ = revMessage;
- return receive_;
- }
-
- /**
- * @brief 检查Modbus异常响应
- * @return 0-正常响应, >0-Modbus异常码, -1-响应报文错误
- */
- quint8 MyModbus::errorCheck()
- {
- // 异常响应最小长度:地址1 + 异常功能码1 + 异常码1 + CRC2
- if(receive_.size() < 5)
- {
- return -1; // 响应报文错误
- }
-
- // 检查功能码高位(异常响应标志)
- if ((receive_.at(1) & 0x80) == 0x80)
- {
- return static_cast<quint8>(receive_.at(2)); // 返回异常码
- }
- return 0; // 正常响应
- }
-
- /**
- * @brief 解析读取线圈的响应数据
- * @return 线圈状态数组
- */
- QVector<bool> MyModbus::analReadCoil()
- {
- QVector<bool> coil;
- // 最小长度检查:地址1+功能码1+字节数1+数据1+CRC2
- if(receive_.size() < 6)
- {
- return coil;
- }
-
- quint8 byteCount = static_cast<quint8>(receive_.at(2)); // 数据字节数
-
- // 解析每个数据字节
- for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
- {
- quint8 byteValue = static_cast<quint8>(receive_[3 + byteIndex]);
-
- // 解析字节中的每个位(线圈状态)
- for (int bitIndex = 0; bitIndex < 8; bitIndex++)
- {
- int coilIndex = byteIndex * 8 + bitIndex;
- if (coilIndex < length_)
- {
- // 只处理请求的长度
- coil.append(byteValue & (1 << bitIndex));
- }
- }
- }
- return coil;
- }
-
- /**
- * @brief 解析读取寄存器的响应数据
- * @return 寄存器值数组
- */
- QVector<quint16> MyModbus::analReadReg()
- {
- QVector<quint16> registers;
- // 最小长度检查:地址1+功能码1+字节数1+数据2+CRC2
- if(receive_.size() < 7)
- {
- return registers;
- }
-
- int byteCount = static_cast<quint8>(receive_.at(2)); // 数据字节数
- QByteArray data = receive_.mid(3, byteCount); // 提取数据部分
-
- // 每两个字节组合为一个寄存器值(高位在前)
- for (int i = 0; i < data.size(); i += 2)
- {
- quint16 high = static_cast<quint8>(data[i]);
- quint16 low = static_cast<quint8>(data[i+1]);
- quint16 value = (high * 256) + low;
- registers.append(value);
- }
- 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 % 256;
- quint8 calcCrcHigh = crc / 256;
-
- //比较计算的crc值和接收到的crc值是否一致
- if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
|