/****************************************************************************** * 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 &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(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 &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(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(receive_.at(2)); // 返回异常码 } return 0; // 正常响应 } /** * @brief 解析读取线圈的响应数据 * @return 线圈状态数组 */ QVector MyModbus::analReadCoil() { QVector coil; // 最小长度检查:地址1+功能码1+字节数1+数据1+CRC2 if(receive_.size() < 6) { return coil; } quint8 byteCount = static_cast(receive_.at(2)); // 数据字节数 // 解析每个数据字节 for (int byteIndex = 0; byteIndex < byteCount; byteIndex++) { quint8 byteValue = static_cast(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 MyModbus::analReadReg() { QVector registers; // 最小长度检查:地址1+功能码1+字节数1+数据2+CRC2 if(receive_.size() < 7) { return registers; } int byteCount = static_cast(receive_.at(2)); // 数据字节数 QByteArray data = receive_.mid(3, byteCount); // 提取数据部分 // 每两个字节组合为一个寄存器值(高位在前) for (int i = 0; i < data.size(); i += 2) { quint16 high = static_cast(data[i]); quint16 low = static_cast(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(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(data.at(data.length() - 2)); quint8 receivedCrcHigh = static_cast(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; } }