You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

319 lines
8.6 KiB

  1. /******************************************************************************
  2. * Copyright (C) 2025-.
  3. *
  4. * File Name: mymodbus.cpp
  5. * Description: Modbus RTU协议封装类,支持线圈和寄存器的读写操作
  6. * Others:
  7. * Version: 1.0
  8. * Author: lipengpeng
  9. * Date: 2025-7-23
  10. *
  11. ******************************************************************************/
  12. #include "mymodbus.h"
  13. /**
  14. * @brief MyModbus类构造函数
  15. * @note 初始化默认Modbus参数:站地址=1,功能码=0x01(读线圈),
  16. * 起始地址=256,数据长度=1
  17. */
  18. MyModbus::MyModbus(QObject *parent) : QObject(parent)
  19. , stationAddress_(0x01)
  20. , functionCode_(0x01)
  21. , startAddress_(0x01)
  22. , length_(0x01)
  23. {
  24. }
  25. /**
  26. * @brief 设置Modbus通信参数
  27. * @param stationAddress 从站地址
  28. * @param functionCode 功能码
  29. * @param startAdress 起始地址
  30. * @param length 数据长度
  31. */
  32. bool MyModbus::setStation(quint16 stationAddress, quint16 functionCode,
  33. quint16 startAddress, quint16 length)
  34. {
  35. if ((stationAddress > 0) &&
  36. (functionCode > 0) &&
  37. (startAddress >0) &&
  38. (length > 0))
  39. {
  40. this->stationAddress_ = stationAddress;
  41. this->functionCode_ = functionCode;
  42. this->startAddress_ = startAddress;
  43. this->length_ = length;
  44. sendCommand_.clear();
  45. sendCommand_.append(stationAddress_ % 256);
  46. sendCommand_.append(functionCode_ % 256);
  47. sendCommand_.append(startAddress_ / 256);
  48. sendCommand_.append(startAddress_ % 256);
  49. sendCommand_.append(length_ / 256);
  50. sendCommand_.append(length_ % 256);
  51. return true;
  52. }
  53. else
  54. {
  55. return false;
  56. }
  57. }
  58. /**
  59. * @brief 生成读取线圈/寄存器的Modbus命令
  60. * @note 命令结构:[站地址][功能码][起始地址Hi][起始地址Lo][长度Hi][长度Lo][CRC Lo][CRC Hi]
  61. */
  62. void MyModbus::readCoilAndReg()
  63. {
  64. // 计算并附加CRC校验
  65. quint16 temp = calculateCrc(sendCommand_);
  66. sendCommand_.append(temp % 256); // CRC低字节在前
  67. sendCommand_.append(temp / 256); // CRC高字节在后
  68. }
  69. /**
  70. * @brief 生成写线圈命令
  71. * @param coils 要写入的线圈状态数组
  72. * @note 命令结构:[站地址][0x0F][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据...][CRC]
  73. */
  74. void MyModbus::writeCoil(QVector<bool> &coils)
  75. {
  76. quint16 coilCount = coils.size();
  77. int byteCount = (coilCount + 7) / 8; // 计算所需字节数(每字节8位)
  78. sendCommand_.append(byteCount); // 后续字节数
  79. // 打包线圈状态到字节
  80. for (int i = 0; i < byteCount; ++i)
  81. {
  82. quint8 byte = 0;
  83. for (int j = 0; j < 8; ++j)
  84. {
  85. int bitIndex = i * 8 + j;
  86. // 只处理有效范围内的线圈
  87. if (bitIndex < coils.size() && coils[bitIndex])
  88. {
  89. byte |= static_cast<quint8>(1 << j); // 设置对应位
  90. }
  91. }
  92. sendCommand_.append(byte);
  93. }
  94. // 计算并附加CRC校验
  95. quint16 temp = calculateCrc(sendCommand_);
  96. sendCommand_.append(temp % 256);
  97. sendCommand_.append(temp / 256);
  98. }
  99. /**
  100. * @brief 生成写寄存器命令
  101. * @param values 要写入的寄存器值数组
  102. * @note 命令结构:[站地址][0x10][起始地址Hi][起始地址Lo][数量Hi][数量Lo][字节数][数据Hi][数据Lo...][CRC]
  103. */
  104. void MyModbus::writeRegister(QVector<quint16> &values)
  105. {
  106. sendCommand_.append(values.size() * 2); // 后续字节数(每个寄存器2字节)
  107. // 打包寄存器值(高位在前)
  108. for (quint16 v : values)
  109. {
  110. sendCommand_.append(v / 256); // 高字节
  111. sendCommand_.append(v % 256); // 低字节
  112. }
  113. // 计算并附加CRC校验
  114. quint16 temp = calculateCrc(sendCommand_);
  115. sendCommand_.append(temp % 256);
  116. sendCommand_.append(temp / 256);
  117. }
  118. /**
  119. * @brief 获取生成的Modbus命令
  120. * @return 完整的Modbus命令字节数组
  121. */
  122. QByteArray MyModbus::sendCommand()
  123. {
  124. return sendCommand_;
  125. }
  126. /**
  127. * @brief 处理接收到的Modbus响应
  128. * @param revMessage 接收到的原始报文
  129. * @return 处理后的有效数据部分(移除地址/功能码/CRC)
  130. * @note 执行站地址匹配和CRC校验
  131. */
  132. QByteArray MyModbus::receive(const QByteArray &revMessage)
  133. {
  134. receive_.clear();
  135. // 最小长度检查:站地址1 + 功能码1 + CRC2
  136. if(revMessage.size() < 4)
  137. {
  138. return receive_; // 返回空数组表示无效报文
  139. }
  140. // 站地址匹配检查
  141. if(static_cast<quint8>(revMessage[0]) != stationAddress_)
  142. {
  143. return receive_;
  144. }
  145. // CRC校验检查
  146. if (!crcCheck(revMessage))
  147. {
  148. return receive_;
  149. }
  150. this->receive_ = revMessage;
  151. return receive_;
  152. }
  153. /**
  154. * @brief 检查Modbus异常响应
  155. * @return 0-正常响应, >0-Modbus异常码, -1-响应报文错误
  156. */
  157. quint8 MyModbus::errorCheck()
  158. {
  159. // 异常响应最小长度:地址1 + 异常功能码1 + 异常码1 + CRC2
  160. if(receive_.size() < 5)
  161. {
  162. return -1; // 响应报文错误
  163. }
  164. // 检查功能码高位(异常响应标志)
  165. if ((receive_.at(1) & 0x80) == 0x80)
  166. {
  167. return static_cast<quint8>(receive_.at(2)); // 返回异常码
  168. }
  169. return 0; // 正常响应
  170. }
  171. /**
  172. * @brief 解析读取线圈的响应数据
  173. * @return 线圈状态数组
  174. */
  175. QVector<bool> MyModbus::analReadCoil()
  176. {
  177. QVector<bool> coil;
  178. // 最小长度检查:地址1+功能码1+字节数1+数据1+CRC2
  179. if(receive_.size() < 6)
  180. {
  181. return coil;
  182. }
  183. quint8 byteCount = static_cast<quint8>(receive_.at(2)); // 数据字节数
  184. // 解析每个数据字节
  185. for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
  186. {
  187. quint8 byteValue = static_cast<quint8>(receive_[3 + byteIndex]);
  188. // 解析字节中的每个位(线圈状态)
  189. for (int bitIndex = 0; bitIndex < 8; bitIndex++)
  190. {
  191. int coilIndex = byteIndex * 8 + bitIndex;
  192. if (coilIndex < length_)
  193. {
  194. // 只处理请求的长度
  195. coil.append(byteValue & (1 << bitIndex));
  196. }
  197. }
  198. }
  199. return coil;
  200. }
  201. /**
  202. * @brief 解析读取寄存器的响应数据
  203. * @return 寄存器值数组
  204. */
  205. QVector<quint16> MyModbus::analReadReg()
  206. {
  207. QVector<quint16> registers;
  208. // 最小长度检查:地址1+功能码1+字节数1+数据2+CRC2
  209. if(receive_.size() < 7)
  210. {
  211. return registers;
  212. }
  213. int byteCount = static_cast<quint8>(receive_.at(2)); // 数据字节数
  214. QByteArray data = receive_.mid(3, byteCount); // 提取数据部分
  215. // 每两个字节组合为一个寄存器值(高位在前)
  216. for (int i = 0; i < data.size(); i += 2)
  217. {
  218. quint16 high = static_cast<quint8>(data[i]);
  219. quint16 low = static_cast<quint8>(data[i+1]);
  220. quint16 value = (high * 256) + low;
  221. registers.append(value);
  222. }
  223. return registers;
  224. }
  225. /**
  226. * @brief 计算Modbus CRC16校验码
  227. * @param data 输入数据字节数组
  228. * @return CRC16校验值
  229. * @note 使用MODBUS标准多项式0xA001(0x8005的反转)
  230. */
  231. quint16 MyModbus::calculateCrc(const QByteArray &data)
  232. {
  233. quint16 crc = 0xFFFF; // MODBUS初始值
  234. const quint16 polynomial = 0xA001; // MODBUS多项式(0x8005的反转)
  235. for (int i = 0; i < data.size(); ++i)
  236. {
  237. crc ^= static_cast<quint8>(data.at(i)); // 异或当前字节
  238. // 处理每个字节的8位
  239. for (int bit = 0; bit < 8; ++bit)
  240. {
  241. if (crc & 0x0001)
  242. {
  243. // 检查最低位
  244. crc = (crc >> 1) ^ polynomial; // 右移并且异或多项式
  245. }
  246. else
  247. {
  248. crc >>= 1; // 仅右移
  249. }
  250. }
  251. }
  252. return crc;
  253. }
  254. /**
  255. * @brief 校验接收数据的CRC
  256. * @param data 完整接收数据(包含末尾CRC)
  257. * @return true-CRC校验通过, false-校验失败
  258. */
  259. bool MyModbus::crcCheck(const QByteArray &data)
  260. {
  261. //首先对接收报文的长度进行检验
  262. if (data.size() < 3)
  263. {
  264. return false;
  265. }
  266. //对接收的报文进行CRC校验
  267. QByteArray payload = data.left(data.length() - 2);
  268. //分离接收值的crc校验位
  269. quint8 receivedCrcLow = static_cast<quint8>(data.at(data.length() - 2));
  270. quint8 receivedCrcHigh = static_cast<quint8>(data.at(data.length() - 1));
  271. //计算返回的报文的crc
  272. quint16 crc = calculateCrc(payload);
  273. quint8 calcCrcLow = crc % 256;
  274. quint8 calcCrcHigh = crc / 256;
  275. //比较计算的crc值和接收到的crc值是否一致
  276. if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh)
  277. {
  278. return true;
  279. }
  280. else
  281. {
  282. return false;
  283. }
  284. }