Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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