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.
 
 
 
 
 
 

1238 line
37 KiB

  1. #include "modbus_rtu_slave.h"
  2. #include "ucos_ii.h"
  3. #include <string.h>
  4. #define MODBUS_RTU_ADU_SIZE_MAX (256U) // Modbus RTU 最大 ADU 长度,单位为字节
  5. #define MODBUS_RTU_T15_US (750UL) /* 高波特率下固定T1.5时间,单位us */
  6. #define MODBUS_RTU_T35_US (1750UL) /* 高波特率下固定T3.5时间,单位us */
  7. #define MODBUS_RTU_BITS_PER_CHAR (11UL) /* 8E1包含11个传输位 */
  8. #define MODBUS_RTU_HIGH_BAUD_LIMIT (19200UL) /* 高低波特率计算方式的分界值 */
  9. #define MODBUS_BROADCAST_ADDRESS (0U) // 广播地址
  10. #define MODBUS_EX_ILLEGAL_FUNCTION (0x01U) // 非法功能码的异常码
  11. #define MODBUS_EX_ILLEGAL_ADDRESS (0x02U) // 非法地址的异常码
  12. #define MODBUS_EX_ILLEGAL_VALUE (0x03U) // 非法数据的异常码
  13. #define MODBUS_READ_COILS_MAX (2000U) // 一次ADU最大线圈读取数量
  14. #define MODBUS_READ_REGS_MAX (125U) // 一次ADU最大寄存器读取数量
  15. #define MODBUS_WRITE_COILS_MAX (1968U) // 一次ADU最大线圈写入数量
  16. #define MODBUS_WRITE_REGS_MAX (123U) // 一次ADU最大寄存器写入数量
  17. #define MODBUS_COIL_VALUE_ON (0xFF00U)
  18. #define MODBUS_COIL_VALUE_OFF (0x0000U)
  19. static UART_HandleTypeDef *ModbusUart;
  20. static uint8_t ModbusSlaveAddress;
  21. static void ModbusHistorySave(uint32_t tick, const uint8_t *request,
  22. uint16_t requestLength, const uint8_t *response,
  23. uint16_t responseLength);
  24. static MODBUS_HISTORY_ITEM ModbusHistory[16];
  25. static uint16_t ModbusHistoryWriteIndex;
  26. /**
  27. * DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照,
  28. * 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据
  29. */
  30. static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX];
  31. /* 保存被UART IDLE事件分开的DMA片段,达到T3.5后再提交解析 */
  32. static uint8_t ModbusRxAssemblyBuffer[MODBUS_RTU_ADU_SIZE_MAX];
  33. static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX];
  34. static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX];
  35. /* D100~D120 上一次已保存的值,用于检测数据是否变化 */
  36. static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT];
  37. static volatile uint16_t ModbusRxFrameLength;
  38. static volatile uint8_t ModbusRxFrameReady;
  39. static volatile uint8_t ModbusTxBusy;
  40. static volatile uint16_t ModbusRxAssemblyLength; /* 当前拼帧长度 */
  41. static volatile uint8_t ModbusRxAssemblyInvalid; /* 帧内间隔超过T1.5时置1 */
  42. static volatile uint32_t ModbusRxLastByteCycle; /* 上一片段末字节结束时刻 */
  43. static uint32_t ModbusRtuT15Cycles; /* T1.5对应的CPU周期数 */
  44. static uint32_t ModbusRtuT35Cycles; /* T3.5对应的CPU周期数 */
  45. static uint32_t ModbusRtuCharCycles; /* 一个UART字符对应的CPU周期数 */
  46. static volatile uint32_t ModbusLastValidFrameTick;
  47. static volatile uint8_t ModbusHasReceivedValidFrame;
  48. static volatile MODBUS_BACKUP_DATA *ModbusBackupData =
  49. (volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE;
  50. /**
  51. * 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储,
  52. * 占用 1250 字节
  53. */
  54. static uint16_t ModbusHoldingRegisters[40000];
  55. #pragma location = ".ccmram"
  56. #pragma data_alignment = 4
  57. __root static uint16_t ModbusRegistersCcm[29999];
  58. static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U];
  59. volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics;
  60. /**
  61. * @brief 计算 Modbus RTU CRC16 校验值
  62. * @param[in] data 待校验数据
  63. * @param[in] length 待校验数据长度
  64. * @return CRC16 校验值
  65. */
  66. static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length)
  67. {
  68. uint16_t crc = 0xFFFFU;
  69. uint16_t index;
  70. uint8_t bit;
  71. for (index = 0U; index < length; index++)
  72. {
  73. crc ^= data[index];
  74. for (bit = 0U; bit < 8U; bit++)
  75. {
  76. if ((crc & 0x0001U) != 0U)
  77. {
  78. crc = (uint16_t)((crc >> 1U) ^ 0xA001U);
  79. }
  80. else
  81. {
  82. crc >>= 1U;
  83. }
  84. }
  85. }
  86. return crc;
  87. }
  88. /**
  89. * @brief 提取一个 16 位无符号整数
  90. * @param[in] data 两个字节的数据地址
  91. * @return 转换后的 16 位无符号整数
  92. */
  93. static uint16_t ModbusGetU16Be(const uint8_t *data)
  94. {
  95. return (uint16_t)(((uint16_t)data[0] << 8U) | data[1]);
  96. }
  97. /**
  98. * @brief 提取一个 24 位无符号整数
  99. * @param[in] data 3个字节的数据地址
  100. * @return 转换后的 24 位无符号整数
  101. */
  102. static uint32_t ModbusGetU24Be(const uint8_t *data)
  103. {
  104. return (uint32_t)(((uint32_t)data[0] << 16U) | ((uint32_t)data[1] << 8U)
  105. | (uint32_t)data[2]);
  106. }
  107. /**
  108. * @brief 检查连续数据地址范围是否合法
  109. * @param[in] start 起始地址
  110. * @param[in] quantity 数据项数量
  111. */
  112. static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity)
  113. {
  114. if (start >= MODBUS_MAP_ITEM_COUNT)
  115. {
  116. return 0U;
  117. }
  118. // 先做减法再比较,避免溢出
  119. return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U;
  120. }
  121. /**
  122. * @brief 读取已经确认地址合法的线圈
  123. * @param[in] address 线圈地址
  124. * @return 线圈状态,取值为 0 或 1
  125. */
  126. static uint8_t ModbusCoilGetUnchecked(uint16_t address)
  127. {
  128. uint8_t mask = (uint8_t)(1U << (address & 0x0007U));
  129. return ((ModbusCoils[address >> 3U] & mask) != 0U) ? 1U : 0U;
  130. }
  131. /**
  132. * @brief 设置已经确认地址合法的线圈
  133. * @param[in] address 线圈地址
  134. * @param[in] state 0 表示复位,非 0 表示置位
  135. */
  136. static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state)
  137. {
  138. uint8_t mask = (uint8_t)(1U << (address & 0x0007U));
  139. if (state != 0U)
  140. {
  141. ModbusCoils[address >> 3U] |= mask;
  142. }
  143. else
  144. {
  145. ModbusCoils[address >> 3U] &= (uint8_t)(~mask);
  146. }
  147. }
  148. /**
  149. * @brief 初始化用于RTU帧间隔测量的DWT周期计数器
  150. * @param[in] baudRate 当前串口波特率
  151. */
  152. static void ModbusRtuTimingInit(uint32_t baudRate)
  153. {
  154. uint64_t coreClock;
  155. /* 开启DWT周期计数器,CYCCNT每经过一个CPU时钟周期自动加1 */
  156. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  157. DWT->CYCCNT = 0U;
  158. DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
  159. coreClock = SystemCoreClock;
  160. /* 一个字符周期数=CPU频率*每字符位数/波特率 */
  161. ModbusRtuCharCycles =
  162. (uint32_t)((coreClock * MODBUS_RTU_BITS_PER_CHAR) / baudRate);
  163. if (baudRate > MODBUS_RTU_HIGH_BAUD_LIMIT)
  164. {
  165. /* 高波特率使用固定750us和1750us,999999用于向上取整 */
  166. ModbusRtuT15Cycles =
  167. (uint32_t)((coreClock * MODBUS_RTU_T15_US + 999999UL) / 1000000UL);
  168. ModbusRtuT35Cycles =
  169. (uint32_t)((coreClock * MODBUS_RTU_T35_US + 999999UL) / 1000000UL);
  170. }
  171. else
  172. {
  173. /* 低波特率按照1.5个字符时间和3.5个字符时间计算 */
  174. ModbusRtuT15Cycles =
  175. (uint32_t)(((uint64_t)ModbusRtuCharCycles * 3UL + 1UL) / 2UL);
  176. ModbusRtuT35Cycles =
  177. (uint32_t)(((uint64_t)ModbusRtuCharCycles * 7UL + 1UL) / 2UL);
  178. }
  179. }
  180. /**
  181. * @brief 结束当前RTU接收组帧
  182. * @note 调用本函数时必须保证不会与串口接收中断并发执行
  183. */
  184. static void ModbusRxAssemblyFinalize(void)
  185. {
  186. if (ModbusRxAssemblyLength == 0U)
  187. {
  188. return;
  189. }
  190. /*拼帧区是否有数据*/
  191. if ((ModbusRxAssemblyInvalid == 0U) && (ModbusRxFrameReady == 0U))
  192. {
  193. /* 当前帧未违反T1.5且解析缓冲区空闲时提交完整帧 */
  194. (void)memcpy(ModbusRxFrame, ModbusRxAssemblyBuffer,
  195. ModbusRxAssemblyLength);
  196. ModbusRxFrameLength = ModbusRxAssemblyLength;
  197. ModbusRxFrameReady = 1U;
  198. }
  199. else
  200. {
  201. /* T1.5无效帧或上一帧尚未处理完成时丢弃 */
  202. ModbusSlaveStatistics.droppedFrameCount++;
  203. }
  204. ModbusRxAssemblyLength = 0U;
  205. ModbusRxAssemblyInvalid = 0U;
  206. }
  207. /**
  208. * @brief 静默时间达到T3.5后,将接收数据交给协议解析任务
  209. */
  210. static void ModbusTryFinalizeReceive(void)
  211. {
  212. uint32_t now;
  213. if (ModbusRxAssemblyLength == 0U)
  214. {
  215. return;
  216. }
  217. /* DMA缓冲区出现新数据时,说明串口仍在接收当前片段 */
  218. if ((ModbusUart->hdmarx != NULL) && /*串口DMA使能*/
  219. ((ModbusUart->Instance->CR3 & USART_CR3_DMAR) != 0U)
  220. && (__HAL_DMA_GET_COUNTER(ModbusUart->hdmarx)
  221. < MODBUS_RTU_ADU_SIZE_MAX))
  222. {
  223. return;
  224. }
  225. now = DWT->CYCCNT;
  226. /* 从末字节结束时刻开始计算静默时间,未达到T3.5时继续等待 */
  227. if ((uint32_t)(now - ModbusRxLastByteCycle) < ModbusRtuT35Cycles)
  228. {
  229. return;
  230. }
  231. /* 静默达到T3.5,当前RTU帧结束,发送响应前停止接收DMA */
  232. (void)HAL_UART_AbortReceive(ModbusUart);
  233. __disable_irq();
  234. ModbusRxAssemblyFinalize();
  235. __enable_irq();
  236. }
  237. /**
  238. * @brief 启动 USART DMA 空闲接收
  239. * @retval HAL_OK DMA 接收启动成功
  240. * @retval HAL_BUSY 串口未配置或发送尚未结束
  241. * @return 其他 HAL 状态表示 DMA 接收启动失败
  242. */
  243. static HAL_StatusTypeDef ModbusStartReceive(void)
  244. {
  245. HAL_StatusTypeDef status;
  246. if (ModbusTxBusy != 0U)
  247. {
  248. return HAL_BUSY;
  249. }
  250. status = HAL_UARTEx_ReceiveToIdle_DMA(ModbusUart, ModbusRxDmaBuffer,
  251. sizeof(ModbusRxDmaBuffer));
  252. if ((status == HAL_OK) && (ModbusUart->hdmarx != NULL))
  253. {
  254. /*
  255. * 普通 Modbus 帧只应在 IDLE 或缓冲区满时交给应用
  256. * 关闭 DMA 半传输中断,避免长帧在一半位置被误认为完整帧
  257. */
  258. __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT);
  259. }
  260. return status;
  261. }
  262. /**
  263. * @brief 在 RTU 帧末尾追加 CRC 低字节和高字节
  264. * @param[in,out] frame 待追加 CRC 的帧缓冲区
  265. * @param[in] payloadLength 不包含 CRC 的有效载荷长度
  266. */
  267. static void ModbusAppendCrc(uint8_t *frame, uint16_t payloadLength)
  268. {
  269. uint16_t crc = ModbusCrc16(frame, payloadLength);
  270. /* Modbus RTU 在线路上传输 CRC 低字节在前、高字节在后 */
  271. frame[payloadLength] = (uint8_t)(crc & 0x00FFU);
  272. frame[payloadLength + 1U] = (uint8_t)(crc >> 8U);
  273. }
  274. /**
  275. * @brief 构造 Modbus 异常响应
  276. * @param[in] function 请求功能码
  277. * @param[in] exception 异常码
  278. * @return 异常响应 ADU 长度
  279. */
  280. static uint16_t ModbusBuildException(uint8_t function, uint8_t exception)
  281. {
  282. ModbusTxFrame[0] = ModbusSlaveAddress;
  283. ModbusTxFrame[1] = (uint8_t)(function | 0x80U);
  284. ModbusTxFrame[2] = exception;
  285. ModbusAppendCrc(ModbusTxFrame, 3U);
  286. return 5U;
  287. }
  288. /**
  289. * @brief 处理读线圈功能码 0x01
  290. * @param[in] request RTU 请求帧
  291. * @param[in] requestLength 请求帧长度
  292. * @return 待发送响应长度,异常请求返回异常响应长度
  293. */
  294. static uint16_t ModbusProcessReadCoils(const uint8_t *request,
  295. uint16_t requestLength)
  296. {
  297. uint16_t start;
  298. uint16_t quantity;
  299. uint16_t index;
  300. uint8_t byteCount;
  301. if (requestLength != 8U)
  302. {
  303. ModbusSlaveStatistics.illegalValueCount++;
  304. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  305. }
  306. start = ModbusGetU16Be(&request[2]);
  307. quantity = ModbusGetU16Be(&request[4]);
  308. if ((quantity == 0U)
  309. || (quantity > MODBUS_READ_COILS_MAX)) // 数量0或者数量大于最大值
  310. {
  311. ModbusSlaveStatistics.illegalValueCount++;
  312. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  313. }
  314. if (ModbusAddressRangeIsValid(start, quantity) == 0U) // 地址检查
  315. {
  316. ModbusSlaveStatistics.illegalAddressCount++;
  317. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  318. }
  319. byteCount = (uint8_t)((quantity + 7U) / 8U);
  320. ModbusTxFrame[0] = ModbusSlaveAddress;
  321. ModbusTxFrame[1] = 0X01;
  322. ModbusTxFrame[2] = byteCount;
  323. (void)memset(&ModbusTxFrame[3], 0, byteCount);
  324. for (index = 0U; index < quantity; index++)
  325. {
  326. if (ModbusCoilGetUnchecked((uint16_t)(start + index)) != 0U)
  327. {
  328. ModbusTxFrame[3U + (index >> 3U)] |=
  329. (uint8_t)(1U << (index & 0x0007U));
  330. }
  331. }
  332. ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + byteCount));
  333. return (uint16_t)(5U + byteCount);
  334. }
  335. /**
  336. * @brief 处理读保持寄存器功能码 0x03
  337. * @param[in] request RTU 请求帧
  338. * @param[in] requestLength 请求帧长度
  339. * @return 待发送响应长度,异常请求返回异常响应长度
  340. */
  341. static uint16_t ModbusProcessReadHolding(const uint8_t *request,
  342. uint16_t requestLength)
  343. {
  344. uint16_t start;
  345. uint16_t quantity;
  346. uint16_t index;
  347. uint16_t value;
  348. if (requestLength != 8U)
  349. {
  350. ModbusSlaveStatistics.illegalValueCount++;
  351. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  352. }
  353. start = ModbusGetU16Be(&request[2]);
  354. quantity = ModbusGetU16Be(&request[4]);
  355. if ((quantity == 0U)
  356. || (quantity > MODBUS_READ_REGS_MAX)) // 数量0或者数量大于最大值
  357. {
  358. ModbusSlaveStatistics.illegalValueCount++;
  359. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  360. }
  361. if (ModbusAddressRangeIsValid(start, quantity) == 0U) // 地址检查
  362. {
  363. if ((start >= 20000U) && (start < 25000U) && (quantity > 0U)
  364. && (quantity <= (25000U - start)))
  365. goto tx;
  366. ModbusSlaveStatistics.illegalAddressCount++;
  367. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  368. }
  369. tx:
  370. ModbusTxFrame[0] = ModbusSlaveAddress;
  371. ModbusTxFrame[1] = 0X03;
  372. ModbusTxFrame[2] = (uint8_t)(quantity * 2U);
  373. for (index = 0U; index < quantity; index++)
  374. {
  375. uint16_t address = start + index;
  376. /* D20000~D24999映射到D1、D3、D5……D9999 */
  377. if (address >= 20000U)
  378. {
  379. address = (address - 20000U) * 2U + 1U;
  380. }
  381. value = ModbusHoldingRegisters[address];
  382. ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U);
  383. ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU);
  384. }
  385. ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U));
  386. return (uint16_t)(5U + quantity * 2U);
  387. }
  388. /*
  389. * 返回帧
  390. *从站地址 | 14 | 总字节数 | 子响应长度 | 06 | 寄存器数据 | CRC
  391. *
  392. * 01 从站地址
  393. * 14 功能码
  394. * 06 后续响应数据共6字节
  395. * 05 当前子响应长度,共5字节
  396. * 06 引用类型
  397. * 12 34 第一个寄存器
  398. * 56 78 第二个寄存器
  399. */
  400. static uint16_t ModbusProcessFile(const uint8_t *request,
  401. uint16_t requestLength)
  402. {
  403. uint16_t fileNumber;
  404. uint16_t recordNumber;
  405. uint16_t quantity;
  406. uint16_t index;
  407. uint16_t value;
  408. uint16_t *historyRegister;
  409. if (requestLength != 12U)
  410. {
  411. ModbusSlaveStatistics.illegalValueCount++;
  412. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  413. }
  414. if ((request[2] != 7U) || (request[3] != 0x06U))
  415. {
  416. ModbusSlaveStatistics.illegalValueCount++;
  417. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  418. }
  419. quantity = ModbusGetU16Be(&request[8]);
  420. fileNumber = ModbusGetU16Be(&request[4]);
  421. recordNumber = ModbusGetU16Be(&request[6]);
  422. if ((quantity == 0U) || (quantity > 124U)) // 数量0或者数量大于最大值
  423. {
  424. ModbusSlaveStatistics.illegalValueCount++;
  425. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  426. }
  427. /* 检查文件号、记录号和读取范围 */
  428. if ((fileNumber > 15U) || (recordNumber >= 260U)
  429. || (((uint32_t)recordNumber + quantity) > 260UL))
  430. {
  431. ModbusSlaveStatistics.illegalAddressCount++;
  432. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  433. }
  434. ModbusTxFrame[0] = ModbusSlaveAddress;
  435. ModbusTxFrame[1] = 0X14;
  436. ModbusTxFrame[2] = (uint8_t)(2U + quantity * 2U);
  437. ModbusTxFrame[3] = (uint8_t)(1U + quantity * 2U);
  438. ModbusTxFrame[4] = 0X06;
  439. historyRegister = (uint16_t *)ModbusHistory;
  440. for (index = 0U; index < quantity; index++)
  441. {
  442. uint16_t address = fileNumber * 260 + recordNumber + index;
  443. value = *(historyRegister + address);
  444. ModbusTxFrame[5U + index * 2U] = (uint8_t)(value & 0x00FFU);
  445. ModbusTxFrame[6U + index * 2U] = (uint8_t)(value >> 8U);
  446. }
  447. ModbusAppendCrc(ModbusTxFrame, (uint16_t)(5U + quantity * 2U));
  448. return (uint16_t)(7U + quantity * 2U);
  449. }
  450. /**
  451. * @brief 处理写单个保持寄存器功能码 0x06
  452. * @param[in] request RTU 请求帧
  453. * @param[in] requestLength 请求帧长度
  454. * @param[in] isBroadcast 非 0 表示当前请求为广播
  455. * @return 单播响应长度;广播或无法响应时返回 0
  456. */
  457. static uint16_t ModbusProcessWriteSingleRegister(const uint8_t *request,
  458. uint16_t requestLength,
  459. uint8_t isBroadcast)
  460. {
  461. uint16_t address;
  462. uint16_t value;
  463. if (requestLength != 8U)
  464. {
  465. ModbusSlaveStatistics.illegalValueCount++;
  466. return (isBroadcast != 0U)
  467. ? 0U
  468. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  469. }
  470. address = ModbusGetU16Be(&request[2]);
  471. value = ModbusGetU16Be(&request[4]);
  472. if (address >= MODBUS_MAP_ITEM_COUNT)
  473. {
  474. ModbusSlaveStatistics.illegalAddressCount++;
  475. return (isBroadcast != 0U) ? 0U
  476. : ModbusBuildException(
  477. request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  478. }
  479. ModbusHoldingRegisters[address] = value;
  480. if (isBroadcast != 0U)
  481. {
  482. return 0U;
  483. }
  484. /* 0x06 的正常响应必须原样回显请求的前 6 个字节 */
  485. (void)memcpy(ModbusTxFrame, request, 6U);
  486. ModbusAppendCrc(ModbusTxFrame, 6U);
  487. return 8U;
  488. }
  489. /**
  490. * @brief 处理写单个线圈功能码 0x05
  491. * @param[in] request RTU 请求帧
  492. * @param[in] requestLength 请求帧长度
  493. * @param[in] isBroadcast 非 0 表示当前请求为广播
  494. * @return 单播响应长度;广播或无法响应时返回 0
  495. */
  496. static uint16_t ModbusProcessWriteSingleCoil(const uint8_t *request,
  497. uint16_t requestLength,
  498. uint8_t isBroadcast)
  499. {
  500. uint16_t address;
  501. uint16_t value;
  502. if (requestLength != 8U)
  503. {
  504. ModbusSlaveStatistics.illegalValueCount++;
  505. return (isBroadcast != 0U)
  506. ? 0U
  507. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  508. }
  509. address = ModbusGetU16Be(&request[2]);
  510. value = ModbusGetU16Be(&request[4]);
  511. /*
  512. * 0x05 只接受 0xFF00(线圈置位)和 0x0000(线圈复位);
  513. * 其他数值属于非法数据值
  514. */
  515. if ((value != MODBUS_COIL_VALUE_ON) && (value != MODBUS_COIL_VALUE_OFF))
  516. {
  517. ModbusSlaveStatistics.illegalValueCount++;
  518. return (isBroadcast != 0U)
  519. ? 0U
  520. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  521. }
  522. if (address >= MODBUS_MAP_ITEM_COUNT)
  523. {
  524. ModbusSlaveStatistics.illegalAddressCount++;
  525. return (isBroadcast != 0U) ? 0U
  526. : ModbusBuildException(
  527. request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  528. }
  529. ModbusCoilSetUnchecked(address, (value == MODBUS_COIL_VALUE_ON) ? 1U : 0U);
  530. if (isBroadcast != 0U)
  531. {
  532. return 0U;
  533. }
  534. /* 0x05 正常应答原样回显请求的前 6 个字节,再追加 CRC */
  535. (void)memcpy(ModbusTxFrame, request, 6U);
  536. ModbusAppendCrc(ModbusTxFrame, 6U);
  537. return 8U;
  538. }
  539. /**
  540. * @brief 处理写多个线圈功能码 0x0F
  541. * @param[in] request RTU 请求帧
  542. * @param[in] requestLength 请求帧长度
  543. * @param[in] isBroadcast 非 0 表示当前请求为广播
  544. * @return 单播响应长度;广播或无法响应时返回 0
  545. */
  546. static uint16_t ModbusProcessWriteMultipleCoils(const uint8_t *request,
  547. uint16_t requestLength,
  548. uint8_t isBroadcast)
  549. {
  550. uint16_t start;
  551. uint16_t quantity;
  552. uint16_t index;
  553. uint8_t byteCount;
  554. uint8_t expectedByteCount;
  555. if (requestLength < 9U)
  556. {
  557. ModbusSlaveStatistics.illegalValueCount++;
  558. return (isBroadcast != 0U)
  559. ? 0U
  560. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  561. }
  562. start = ModbusGetU16Be(&request[2]);
  563. quantity = ModbusGetU16Be(&request[4]);
  564. byteCount = request[6];
  565. expectedByteCount = (uint8_t)((quantity + 7U) / 8U);
  566. if ((quantity == 0U) || (quantity > MODBUS_WRITE_COILS_MAX)
  567. || (byteCount != expectedByteCount)
  568. || (requestLength != (uint16_t)(9U + byteCount)))
  569. {
  570. ModbusSlaveStatistics.illegalValueCount++;
  571. return (isBroadcast != 0U)
  572. ? 0U
  573. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  574. }
  575. if (ModbusAddressRangeIsValid(start, quantity) == 0U)
  576. {
  577. ModbusSlaveStatistics.illegalAddressCount++;
  578. return (isBroadcast != 0U) ? 0U
  579. : ModbusBuildException(
  580. request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  581. }
  582. for (index = 0U; index < quantity; index++)
  583. {
  584. uint8_t state;
  585. state = (uint8_t)((request[7U + (index >> 3U)] >> (index & 0x0007U))
  586. & 0x01U);
  587. ModbusCoilSetUnchecked((uint16_t)(start + index), state);
  588. }
  589. if (isBroadcast != 0U)
  590. {
  591. return 0U;
  592. }
  593. ModbusTxFrame[0] = ModbusSlaveAddress;
  594. ModbusTxFrame[1] = 0X0F;
  595. (void)memcpy(&ModbusTxFrame[2], &request[2], 4U);
  596. ModbusAppendCrc(ModbusTxFrame, 6U);
  597. return 8U;
  598. }
  599. /**
  600. * @brief 处理写多个保持寄存器功能码 0x10
  601. * @param[in] request RTU 请求帧
  602. * @param[in] requestLength 请求帧长度
  603. * @param[in] isBroadcast 非 0 表示当前请求为广播
  604. * @return 单播响应长度;广播或无法响应时返回 0
  605. */
  606. static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request,
  607. uint16_t requestLength,
  608. uint8_t isBroadcast)
  609. {
  610. uint16_t start;
  611. uint16_t quantity;
  612. uint16_t index;
  613. uint8_t byteCount;
  614. if (requestLength < 9U)
  615. {
  616. ModbusSlaveStatistics.illegalValueCount++;
  617. return (isBroadcast != 0U)
  618. ? 0U
  619. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  620. }
  621. start = ModbusGetU16Be(&request[2]);
  622. quantity = ModbusGetU16Be(&request[4]);
  623. byteCount = request[6];
  624. if ((quantity == 0U) || (quantity > MODBUS_WRITE_REGS_MAX)
  625. || (byteCount != (uint8_t)(quantity * 2U))
  626. || (requestLength != (uint16_t)(9U + byteCount)))
  627. {
  628. ModbusSlaveStatistics.illegalValueCount++;
  629. return (isBroadcast != 0U)
  630. ? 0U
  631. : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  632. }
  633. if (ModbusAddressRangeIsValid(start, quantity) == 0U)
  634. {
  635. ModbusSlaveStatistics.illegalAddressCount++;
  636. return (isBroadcast != 0U) ? 0U
  637. : ModbusBuildException(
  638. request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  639. }
  640. for (index = 0U; index < quantity; index++)
  641. {
  642. ModbusHoldingRegisters[start + index] =
  643. ModbusGetU16Be(&request[7U + index * 2U]);
  644. }
  645. if (isBroadcast != 0U)
  646. {
  647. return 0U;
  648. }
  649. ModbusTxFrame[0] = ModbusSlaveAddress;
  650. ModbusTxFrame[1] = 0X10;
  651. (void)memcpy(&ModbusTxFrame[2], &request[2], 4U);
  652. ModbusAppendCrc(ModbusTxFrame, 6U);
  653. return 8U;
  654. }
  655. /**
  656. * @brief 处理读取扩展地址保持寄存器功能码0x48
  657. * @param[in] request RTU请求帧
  658. * @param[in] requestLength 请求帧长度
  659. * @return 待发送响应长度,异常请求返回异常响应长度
  660. */
  661. static uint16_t ModbusProcessReadBigHolding(const uint8_t *request,
  662. uint16_t requestLength)
  663. {
  664. uint32_t start;
  665. uint32_t currentAddress;
  666. uint16_t quantity;
  667. uint16_t index;
  668. uint16_t value;
  669. /* 请求帧:站号1 + 功能码1 + 地址3 + 数量2 + CRC2 */
  670. if (requestLength != 9U)
  671. {
  672. ModbusSlaveStatistics.illegalValueCount++;
  673. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  674. }
  675. /* 提取24位起始地址和16位寄存器数量 */
  676. start = ModbusGetU24Be(&request[2]);
  677. quantity = ModbusGetU16Be(&request[5]);
  678. /* 一次最多读取125个寄存器 */
  679. if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX))
  680. {
  681. ModbusSlaveStatistics.illegalValueCount++;
  682. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
  683. }
  684. /*
  685. * 普通SRAM有40000个寄存器,CCMRAM有29999个寄存器,
  686. * 总地址范围为0~69998
  687. */
  688. if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start)))
  689. {
  690. ModbusSlaveStatistics.illegalAddressCount++;
  691. return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
  692. }
  693. /* 组成正常响应帧头 */
  694. ModbusTxFrame[0] = ModbusSlaveAddress;
  695. ModbusTxFrame[1] = 0x48U;
  696. ModbusTxFrame[2] = (uint8_t)(quantity * 2U);
  697. for (index = 0U; index < quantity; index++)
  698. {
  699. currentAddress = start + (uint32_t)index;
  700. /*
  701. * 地址0~39999位于普通SRAM;
  702. * 地址40000~69998位于CCMRAM
  703. */
  704. if (currentAddress < 40000UL)
  705. {
  706. value = ModbusHoldingRegisters[currentAddress];
  707. }
  708. else
  709. {
  710. value = ModbusRegistersCcm[currentAddress - 40000UL];
  711. }
  712. /* 每个寄存器按照高字节、低字节装入响应帧 */
  713. ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U);
  714. ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU);
  715. }
  716. /* 添加CRC */
  717. ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U));
  718. return (uint16_t)(5U + quantity * 2U);
  719. }
  720. /**
  721. * @brief 校验并分发一帧 Modbus RTU 请求
  722. * @param[in] request RTU 请求帧
  723. * @param[in] requestLength 请求帧长度
  724. * @return 待发送响应长度;无需响应时返回 0
  725. */
  726. static uint16_t ModbusProcessRequest(const uint8_t *request,
  727. uint16_t requestLength)
  728. {
  729. uint16_t calculatedCrc;
  730. uint16_t receivedCrc;
  731. uint8_t isBroadcast;
  732. if (requestLength < 4U)
  733. {
  734. return 0U;
  735. }
  736. calculatedCrc = ModbusCrc16(request, (uint16_t)(requestLength - 2U));
  737. receivedCrc = (uint16_t)(request[requestLength - 2U]
  738. | ((uint16_t)request[requestLength - 1U] << 8U));
  739. if (calculatedCrc != receivedCrc) // CRC校验
  740. {
  741. ModbusSlaveStatistics.crcErrorCount++;
  742. return 0U;
  743. }
  744. if ((request[0] != ModbusSlaveAddress)
  745. && (request[0] != MODBUS_BROADCAST_ADDRESS)) // 非法从站地址
  746. {
  747. ModbusSlaveStatistics.ignoredAddressCount++;
  748. return 0U;
  749. }
  750. isBroadcast =
  751. (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; // 是否广播请求
  752. ModbusSlaveStatistics.validFrameCount++;
  753. ModbusLastValidFrameTick = HAL_GetTick();
  754. ModbusHasReceivedValidFrame = 1U;
  755. switch (request[1])
  756. {
  757. case 0X01U: // 读线圈
  758. // 广播请求不允许读取,从站不作响应
  759. return (isBroadcast != 0U)
  760. ? 0U
  761. : ModbusProcessReadCoils(request, requestLength);
  762. case 0X03U: // 读保持寄存器
  763. return (isBroadcast != 0U)
  764. ? 0U
  765. : ModbusProcessReadHolding(request, requestLength);
  766. case 0x14U:
  767. return (isBroadcast != 0U)
  768. ? 0U
  769. : ModbusProcessFile(request, requestLength);
  770. case 0x05U: // 写单个线圈
  771. return ModbusProcessWriteSingleCoil(request, requestLength,
  772. isBroadcast);
  773. case 0x06U: // 写单个保持寄存器
  774. return ModbusProcessWriteSingleRegister(request, requestLength,
  775. isBroadcast);
  776. case 0x0FU: // 写多个线圈
  777. return ModbusProcessWriteMultipleCoils(request, requestLength,
  778. isBroadcast);
  779. case 0x10U: // 写多个保持寄存器
  780. return ModbusProcessWriteMultipleRegisters(request, requestLength,
  781. isBroadcast);
  782. case 0x48U: // 读大地址保持寄存器
  783. return ModbusProcessReadBigHolding(request, requestLength);
  784. default: // 未知功能码
  785. ModbusSlaveStatistics.illegalFunctionCount++;
  786. return (isBroadcast != 0U)
  787. ? 0U
  788. : ModbusBuildException(request[1],
  789. MODBUS_EX_ILLEGAL_FUNCTION);
  790. }
  791. }
  792. HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart,
  793. uint8_t slaveAddress)
  794. {
  795. ModbusUart = huart;
  796. ModbusSlaveAddress = slaveAddress;
  797. /* 清空拼帧状态并根据当前波特率初始化T1.5和T3.5 */
  798. ModbusRxAssemblyLength = 0U;
  799. ModbusRxAssemblyInvalid = 0U;
  800. ModbusRxFrameReady = 0U;
  801. ModbusTxBusy = 0U;
  802. ModbusRtuTimingInit(huart->Init.BaudRate);
  803. ModbusHoldingRegisters[HMI_REG_DEVICE_ID] = 0xF407U;
  804. return ModbusStartReceive();
  805. }
  806. void ModbusSlavePoll(void)
  807. {
  808. uint16_t responseLength;
  809. /* 检查末字节后的静默时间是否已经达到T3.5 */
  810. ModbusTryFinalizeReceive();
  811. if ((ModbusRxFrameReady == 0U) || (ModbusTxBusy != 0U))
  812. {
  813. return;
  814. }
  815. responseLength = ModbusProcessRequest(ModbusRxFrame, ModbusRxFrameLength);
  816. ModbusHistorySave(OSTimeGet() / 1000, ModbusRxFrame, ModbusRxFrameLength,
  817. ModbusTxFrame, responseLength);
  818. if (responseLength > 0U)
  819. {
  820. ModbusTxBusy = 1U;
  821. if (HAL_UART_Transmit_DMA(ModbusUart, ModbusTxFrame, responseLength)
  822. == HAL_OK)
  823. {
  824. ModbusSlaveStatistics.txFrameCount++;
  825. }
  826. else
  827. {
  828. ModbusTxBusy = 0U;
  829. ModbusSlaveStatistics.uartErrorCount++;
  830. (void)ModbusStartReceive(); // 重启DMA接收
  831. }
  832. }
  833. else
  834. {
  835. (void)ModbusStartReceive();
  836. }
  837. /*
  838. * 当前帧;处理完成后再释放帧槽
  839. */
  840. ModbusRxFrameReady = 0U;
  841. }
  842. void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size)
  843. {
  844. HAL_UART_RxEventTypeTypeDef eventType;
  845. uint32_t now;
  846. uint32_t lastByteCycle;
  847. uint32_t firstByteCycle;
  848. uint32_t chunkCycles;
  849. uint32_t interFrameGap;
  850. ModbusSlaveStatistics.rxEventCount++; // 串口接收事件计数
  851. if ((huart != ModbusUart) || (ModbusUart == NULL))
  852. {
  853. return;
  854. }
  855. if ((size > 0U) && (size <= MODBUS_RTU_ADU_SIZE_MAX))
  856. {
  857. now = DWT->CYCCNT;
  858. eventType = HAL_UARTEx_GetRxEventType(huart);
  859. /* IDLE事件比末字节结束晚约一个字符时间,减去字符时间得到末字节时刻 */
  860. lastByteCycle = now;
  861. if (eventType == HAL_UART_RXEVENT_IDLE)
  862. {
  863. lastByteCycle -= ModbusRtuCharCycles;
  864. }
  865. /* 根据本次接收字节数反推DMA片段首字节的开始时刻 */
  866. chunkCycles = (uint32_t)((uint64_t)size * ModbusRtuCharCycles);
  867. firstByteCycle = lastByteCycle - chunkCycles;
  868. if (ModbusRxAssemblyLength > 0U)
  869. {
  870. interFrameGap = (uint32_t)(firstByteCycle - ModbusRxLastByteCycle);
  871. if (interFrameGap >= ModbusRtuT35Cycles)
  872. {
  873. /* 间隔达到T3.5,结束上一帧,本片段作为新帧开始 */
  874. ModbusRxAssemblyFinalize();
  875. }
  876. else if (interFrameGap > ModbusRtuT15Cycles)
  877. {
  878. /* 帧内静默超过T1.5但不足T3.5,标记整帧无效 */
  879. ModbusRxAssemblyInvalid = 1U;
  880. }
  881. else
  882. {
  883. /* 间隔不超过T1.5,当前片段继续拼入同一帧 */
  884. }
  885. }
  886. if (size
  887. <= (uint16_t)(MODBUS_RTU_ADU_SIZE_MAX - ModbusRxAssemblyLength))
  888. {
  889. (void)memcpy(&ModbusRxAssemblyBuffer[ModbusRxAssemblyLength],
  890. ModbusRxDmaBuffer, size);
  891. ModbusRxAssemblyLength += size;
  892. }
  893. else
  894. {
  895. ModbusRxAssemblyInvalid = 1U;
  896. }
  897. /* 保存末字节时刻并立即重启DMA,继续等待可能的后续片段 */
  898. ModbusRxLastByteCycle = lastByteCycle;
  899. (void)ModbusStartReceive();
  900. }
  901. else
  902. {
  903. // 长度异常帧
  904. ModbusSlaveStatistics.droppedFrameCount++;
  905. ModbusRxAssemblyLength = 0U;
  906. ModbusRxAssemblyInvalid = 0U;
  907. (void)ModbusStartReceive();
  908. }
  909. }
  910. void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart)
  911. {
  912. if ((huart != ModbusUart) || (ModbusUart == NULL))
  913. {
  914. return;
  915. }
  916. ModbusTxBusy = 0U;
  917. (void)ModbusStartReceive(); // 重启DMA接收
  918. }
  919. void ModbusSlaveOnUartError(UART_HandleTypeDef *huart)
  920. {
  921. if ((huart != ModbusUart) || (ModbusUart == NULL))
  922. {
  923. return;
  924. }
  925. ModbusSlaveStatistics.uartErrorCount++;
  926. ModbusTxBusy = 0U;
  927. ModbusRxAssemblyLength = 0U;
  928. ModbusRxAssemblyInvalid = 0U;
  929. (void)HAL_UART_Abort(huart); // 立即终止这个串口当前正在进行的发送和接收操作
  930. (void)ModbusStartReceive(); // 重启DMA接收
  931. }
  932. uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value)
  933. {
  934. if (address >= MODBUS_MAP_ITEM_COUNT)
  935. {
  936. return 0U;
  937. }
  938. ModbusHoldingRegisters[address] = value;
  939. return 1U;
  940. }
  941. uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value)
  942. {
  943. if ((address >= MODBUS_MAP_ITEM_COUNT) || (value == NULL))
  944. {
  945. return 0U;
  946. }
  947. *value = ModbusHoldingRegisters[address];
  948. return 1U;
  949. }
  950. uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state)
  951. {
  952. if (address >= MODBUS_MAP_ITEM_COUNT)
  953. {
  954. return 0U;
  955. }
  956. ModbusCoilSetUnchecked(address, state);
  957. return 1U;
  958. }
  959. uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state)
  960. {
  961. if ((address >= MODBUS_MAP_ITEM_COUNT) || (state == NULL))
  962. {
  963. return 0U;
  964. }
  965. *state = ModbusCoilGetUnchecked(address);
  966. return 1U;
  967. }
  968. uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs)
  969. {
  970. if (ModbusHasReceivedValidFrame == 0U)
  971. {
  972. return 0U;
  973. }
  974. return ((HAL_GetTick() - ModbusLastValidFrameTick) <= timeoutMs) ? 1U : 0U;
  975. }
  976. // 上电恢复函数
  977. void ModbusRetainedRegistersLoad(void)
  978. {
  979. uint16_t index;
  980. if (ModbusBackupData->magic == MODBUS_BACKUP_MAGIC)
  981. {
  982. for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++)
  983. {
  984. ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] =
  985. ModbusBackupData->retainedD[index];
  986. }
  987. }
  988. else
  989. {
  990. for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++)
  991. {
  992. ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] = 0U;
  993. ModbusBackupData->retainedD[index] = 0U;
  994. }
  995. /*
  996. * 数据初始化完成后最后写magic,避免初始化中途断电
  997. * 却把不完整数据标记成有效
  998. */
  999. ModbusBackupData->magic = MODBUS_BACKUP_MAGIC;
  1000. }
  1001. }
  1002. void ModbusRetainedRegistersPoll(void)
  1003. {
  1004. uint16_t index;
  1005. uint16_t value;
  1006. for (index = 0; index < MODBUS_RETAINED_D_COUNT; index++)
  1007. {
  1008. value = ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index];
  1009. if (value != ModbusRetainedSnapshot[index])
  1010. {
  1011. ModbusBackupData->retainedD[index] = value;
  1012. }
  1013. ModbusRetainedSnapshot[index] = value;
  1014. }
  1015. }
  1016. static void ModbusHistorySave(uint32_t tick, const uint8_t *request,
  1017. uint16_t requestLength, const uint8_t *response,
  1018. uint16_t responseLength)
  1019. {
  1020. MODBUS_HISTORY_ITEM *item;
  1021. if (requestLength > 256U)
  1022. {
  1023. requestLength = 256U;
  1024. }
  1025. if (responseLength > 256U)
  1026. {
  1027. responseLength = 256U;
  1028. }
  1029. item = &ModbusHistory[ModbusHistoryWriteIndex];
  1030. item->tick = tick;
  1031. item->requestLength = requestLength;
  1032. item->responseLength = responseLength;
  1033. (void)memcpy(item->request, request, requestLength);
  1034. if (responseLength > 0U)
  1035. {
  1036. (void)memcpy(item->response, response, responseLength);
  1037. }
  1038. ModbusHistoryWriteIndex++;
  1039. if (ModbusHistoryWriteIndex >= 16U)
  1040. {
  1041. ModbusHistoryWriteIndex = 0U;
  1042. }
  1043. }
  1044. void HistorySaveToRegisters(void)
  1045. {
  1046. uint16_t index;
  1047. if (ModbusHistoryWriteIndex == 0U)
  1048. {
  1049. index = 15U;
  1050. }
  1051. else
  1052. {
  1053. index = ModbusHistoryWriteIndex - 1U;
  1054. }
  1055. (void)memcpy(&ModbusHoldingRegisters[1000U + index * 260U],
  1056. &ModbusHistory[index], sizeof(ModbusHistory[index]));
  1057. }