Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 

923 řádky
28 KiB

  1. /**
  2. * @file main.c
  3. * @brief 基于 uC/OS-II 的 Modbus RTU 从站主程序。
  4. * @version 2.0
  5. * @author zengbingjie
  6. * @date 2026-07-29
  7. * @copyright 版权所有信捷科技股份有限公司
  8. * @note 修改记录:2.0 增加帧隔离、连接状态和可靠收发处理。
  9. */
  10. #include "main.h"
  11. #include "modbus.h"
  12. #include "modbus_backup.h"
  13. #define APP_MODBUS_FUNCTION_INDEX (1U)
  14. #define APP_MODBUS_RTU_TIMER_PHASE_T15 (0U)
  15. #define APP_MODBUS_RTU_TIMER_PHASE_T35 (1U)
  16. static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
  17. static OS_STK AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE];
  18. static OS_STK AppTaskT35Stk[APP_TASK_T35_STK_SIZE];
  19. UART_HandleTypeDef Uart1Handle;
  20. /* 保留真实数据区供 IAR Live Watch 调试;不维护请求、应答或状态镜像副本。 */
  21. volatile uint16_t holdingRegisters[MODBUS_DATA_POINT_COUNT];
  22. volatile uint8_t coilStorage[MODBUS_COIL_STORAGE_SIZE];
  23. volatile uint16_t extendedHoldingRegister;
  24. static MODBUS_SLAVE ModbusSlave;
  25. static OS_EVENT *ModbusFrameSem;
  26. static OS_EVENT *ModbusTxSem;
  27. /* 接收缓冲区由 UART 中断逐字节填充。只有 RTU 定时器置位
  28. * ModbusFrameReady 后,Modbus 任务才读取它,因此解析器不会读到仍在接收中的帧。 */
  29. static uint8_t ModbusRxBuffer[MODBUS_RX_BUF_SIZE];
  30. static uint8_t ModbusTxBuffer[MODBUS_TX_BUF_SIZE];
  31. /* 以下状态由中断和任务共享;更新应保持简短,读取完整帧快照时使用已有的临界区保护。 */
  32. static volatile uint16_t ModbusRxLength;
  33. static volatile uint16_t ModbusFrameLength;
  34. static volatile uint8_t ModbusFrameReady;
  35. static volatile uint8_t ModbusRxOverflow;
  36. static volatile uint8_t ModbusRtuTimerPhase;
  37. static volatile uint8_t ModbusInterCharacterTimeout;
  38. static volatile uint8_t ModbusRxDiscard;
  39. static volatile uint8_t ModbusReceptionNeedsRecovery;
  40. static uint32_t ModbusLastActivityTick;
  41. static uint8_t ModbusHasActivity;
  42. static GPIO_TypeDef *const
  43. AppUserOutputPorts[APP_MODBUS_USER_OUTPUT_COIL_COUNT] =
  44. {
  45. PLC_Q1_PORT,
  46. PLC_Q2_PORT,
  47. PLC_Q3_PORT,
  48. PLC_Q4_PORT,
  49. PLC_Q5_PORT,
  50. PLC_Q6_PORT,
  51. PLC_Q7_PORT
  52. };
  53. static const uint16_t
  54. AppUserOutputPins[APP_MODBUS_USER_OUTPUT_COIL_COUNT] =
  55. {
  56. PLC_Q1_PIN,
  57. PLC_Q2_PIN,
  58. PLC_Q3_PIN,
  59. PLC_Q4_PIN,
  60. PLC_Q5_PIN,
  61. PLC_Q6_PIN,
  62. PLC_Q7_PIN
  63. };
  64. void SystemClock_Config(void);
  65. static void AppTaskStart(void *argument);
  66. static void AppTaskModbus(void *argument);
  67. static void AppTaskT35(void *argument);
  68. static void AppInitSystemTick(void);
  69. static void AppInitGpio(void);
  70. static void AppInitUart1(void);
  71. static void AppInitModbusRtuTimer(void);
  72. static INT8U AppCreateTasks(void);
  73. static HAL_StatusTypeDef AppStartUartReception(void);
  74. static HAL_StatusTypeDef AppRecoverUartReception(void);
  75. static HAL_StatusTypeDef AppRecoverPendingUartReception(void);
  76. static HAL_StatusTypeDef AppEnsureUartReception(void);
  77. static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength);
  78. static void AppStartModbusRtuTimer(void);
  79. static void AppStopModbusRtuTimer(void);
  80. static void AppSetModbusRtuTimer(uint16_t timeoutUs);
  81. static void AppHandleModbusRtuTimer(void);
  82. static uint16_t AppGetReadyFrameLength(void);
  83. static INT8U AppPostModbusFrameSem(void);
  84. static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status);
  85. static uint8_t AppIsModbusRetentionWriteFunction(uint8_t functionCode);
  86. static MODBUS_STATUS AppUpdateLinkStatus(void);
  87. static MODBUS_STATUS AppSyncCoilsToGpio(void);
  88. static void AppProcessFrame(uint16_t rxLength);
  89. /**
  90. * @brief 应用程序入口。
  91. * @return 该函数不返回。
  92. */
  93. int main(void)
  94. {
  95. INT8U osError;
  96. /* 启动调度器前必须先完成硬件初始化。尤其要先初始化 USART1 和 TIM5,
  97. * 这样任务才能安全地启动接收和使用 RTU 帧间隔定时器。 */
  98. HAL_Init();
  99. AppInitSystemTick();
  100. SystemClock_Config();
  101. AppInitGpio();
  102. AppInitUart1();
  103. AppInitModbusRtuTimer();
  104. /* OSStart() 之后由 uC/OS-II 负责任务调度;启动任务会创建下面的
  105. * Modbus 工作任务和定时监测任务。 */
  106. OSInit();
  107. osError = OSTaskCreateExt(
  108. AppTaskStart, NULL,
  109. &AppTaskStartStk[APP_TASK_START_STK_SIZE - 1U],
  110. APP_TASK_START_PRIO, APP_TASK_START_PRIO, &AppTaskStartStk[0],
  111. APP_TASK_START_STK_SIZE, NULL,
  112. OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);
  113. if (osError != OS_ERR_NONE) {
  114. Error_Handler();
  115. }
  116. OSStart();
  117. while (1) {
  118. }
  119. }
  120. /**
  121. * @brief 配置 STM32F407 的 168 MHz 系统时钟。
  122. * @note 使用板载 12 MHz 外部晶振作为 PLL 时钟源。
  123. */
  124. void SystemClock_Config(void)
  125. {
  126. RCC_OscInitTypeDef oscillatorConfig = {0};
  127. RCC_ClkInitTypeDef clockConfig = {0};
  128. __HAL_RCC_PWR_CLK_ENABLE();
  129. __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  130. oscillatorConfig.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  131. oscillatorConfig.HSEState = RCC_HSE_ON;
  132. oscillatorConfig.PLL.PLLState = RCC_PLL_ON;
  133. oscillatorConfig.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  134. oscillatorConfig.PLL.PLLM = 12U;
  135. oscillatorConfig.PLL.PLLN = 336U;
  136. oscillatorConfig.PLL.PLLP = RCC_PLLP_DIV2;
  137. oscillatorConfig.PLL.PLLQ = 7U;
  138. if (HAL_RCC_OscConfig(&oscillatorConfig) != HAL_OK) {
  139. Error_Handler();
  140. }
  141. clockConfig.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
  142. | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  143. clockConfig.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  144. clockConfig.AHBCLKDivider = RCC_SYSCLK_DIV1;
  145. clockConfig.APB1CLKDivider = RCC_HCLK_DIV4;
  146. clockConfig.APB2CLKDivider = RCC_HCLK_DIV2;
  147. if (HAL_RCC_ClockConfig(&clockConfig, FLASH_LATENCY_5) != HAL_OK) {
  148. Error_Handler();
  149. }
  150. }
  151. /**
  152. * @brief 将 SysTick 配置为可调用 uC/OS-II 的内核感知中断优先级。
  153. */
  154. static void AppInitSystemTick(void)
  155. {
  156. HAL_NVIC_SetPriority(SysTick_IRQn, CPU_CFG_KA_IPL_BOUNDARY, 0U);
  157. }
  158. /**
  159. * @brief 初始化 XDM-60T4-E 的连接指示灯和 Q1 至 Q7 输出引脚。
  160. * @note 板卡输出为低电平有效,初始状态全部关闭。
  161. */
  162. static void AppInitGpio(void)
  163. {
  164. GPIO_InitTypeDef gpioConfig = {0};
  165. __HAL_RCC_GPIOE_CLK_ENABLE();
  166. __HAL_RCC_GPIOF_CLK_ENABLE();
  167. __HAL_RCC_GPIOI_CLK_ENABLE();
  168. gpioConfig.Mode = GPIO_MODE_OUTPUT_PP;
  169. gpioConfig.Pull = GPIO_PULLUP;
  170. gpioConfig.Speed = GPIO_SPEED_FREQ_LOW;
  171. gpioConfig.Pin = PLC_Q0_PIN | PLC_Q1_PIN | PLC_Q2_PIN | PLC_Q3_PIN;
  172. HAL_GPIO_Init(GPIOF, &gpioConfig);
  173. gpioConfig.Pin = PLC_Q4_PIN;
  174. HAL_GPIO_Init(GPIOI, &gpioConfig);
  175. gpioConfig.Pin = PLC_Q5_PIN | PLC_Q6_PIN | PLC_Q7_PIN;
  176. HAL_GPIO_Init(GPIOE, &gpioConfig);
  177. HAL_GPIO_WritePin(GPIOF, PLC_Q0_PIN | PLC_Q1_PIN | PLC_Q2_PIN
  178. | PLC_Q3_PIN, GPIO_PIN_SET);
  179. HAL_GPIO_WritePin(GPIOI, PLC_Q4_PIN, GPIO_PIN_SET);
  180. HAL_GPIO_WritePin(GPIOE, PLC_Q5_PIN | PLC_Q6_PIN | PLC_Q7_PIN,
  181. GPIO_PIN_SET);
  182. }
  183. /**
  184. * @brief 初始化 Modbus RTU 使用的 USART1。
  185. */
  186. static void AppInitUart1(void)
  187. {
  188. Uart1Handle.Instance = USART1;
  189. Uart1Handle.Init.BaudRate = APP_MODBUS_BAUD_RATE;
  190. /* 串口参数必须与触摸屏/PLC 主站完全一致。当前工程使用 9 位字长和偶校验
  191. * (HAL 对“8 位数据 + 1 位校验”的配置方式);不能只修改从站而不修改主站。 */
  192. Uart1Handle.Init.WordLength = UART_WORDLENGTH_9B;
  193. Uart1Handle.Init.StopBits = UART_STOPBITS_1;
  194. Uart1Handle.Init.Parity = UART_PARITY_EVEN;
  195. Uart1Handle.Init.Mode = UART_MODE_TX_RX;
  196. Uart1Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  197. Uart1Handle.Init.OverSampling = UART_OVERSAMPLING_16;
  198. if (HAL_UART_Init(&Uart1Handle) != HAL_OK) {
  199. Error_Handler();
  200. }
  201. }
  202. /**
  203. * @brief 将 TIM5 配置为 Modbus RTU 帧间隔单次定时器。
  204. */
  205. static void AppInitModbusRtuTimer(void)
  206. {
  207. __HAL_RCC_TIM5_CLK_ENABLE();
  208. TIM5->CR1 = 0U;
  209. TIM5->PSC = (APP_MODBUS_RTU_TIMER_CLOCK_HZ
  210. / APP_MODBUS_RTU_TIMER_TICK_HZ) - 1U;
  211. TIM5->ARR = APP_MODBUS_RTU_T35_US - 1U;
  212. TIM5->CNT = 0U;
  213. TIM5->EGR = TIM_EGR_UG;
  214. TIM5->SR = 0U;
  215. TIM5->DIER = TIM_DIER_UIE;
  216. HAL_NVIC_SetPriority(TIM5_IRQn, 5U, 0U);
  217. HAL_NVIC_EnableIRQ(TIM5_IRQn);
  218. }
  219. /**
  220. * @brief 创建 Modbus 工作任务与帧间隔检测任务。
  221. * @return uC/OS-II 错误码。
  222. */
  223. static INT8U AppCreateTasks(void)
  224. {
  225. INT8U osError;
  226. osError = OSTaskCreateExt(
  227. AppTaskModbus, NULL,
  228. &AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE - 1U],
  229. APP_TASK_MODBUS_PRIO, APP_TASK_MODBUS_PRIO,
  230. &AppTaskModbusStk[0], APP_TASK_MODBUS_STK_SIZE, NULL,
  231. OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);
  232. if (osError != OS_ERR_NONE) {
  233. return osError;
  234. }
  235. osError = OSTaskCreateExt(
  236. AppTaskT35, NULL, &AppTaskT35Stk[APP_TASK_T35_STK_SIZE - 1U],
  237. APP_TASK_T35_PRIO, APP_TASK_T35_PRIO, &AppTaskT35Stk[0],
  238. APP_TASK_T35_STK_SIZE, NULL,
  239. OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);
  240. return osError;
  241. }
  242. /**
  243. * @brief 启动任务,创建同步对象并启动应用任务。
  244. * @param[in] argument 未使用。
  245. */
  246. static void AppTaskStart(void *argument)
  247. {
  248. INT8U osError;
  249. (void)argument;
  250. ModbusFrameSem = OSSemCreate(0U);
  251. ModbusTxSem = OSSemCreate(0U);
  252. if ((ModbusFrameSem == NULL) || (ModbusTxSem == NULL)) {
  253. Error_Handler();
  254. }
  255. osError = AppCreateTasks();
  256. if (osError != OS_ERR_NONE) {
  257. Error_Handler();
  258. }
  259. OSTaskSuspend(OS_PRIO_SELF);
  260. while (1) {
  261. }
  262. }
  263. /**
  264. * @brief 清空接收状态并启动 USART1 单字节中断接收。
  265. * @return HAL 状态码。
  266. */
  267. static HAL_StatusTypeDef AppStartUartReception(void)
  268. {
  269. OS_CPU_SR cpu_sr = 0U;
  270. HAL_StatusTypeDef halStatus;
  271. /* 新帧必须从干净的软件状态开始。这里使用临界区,避免定时器或 UART 中断
  272. * 看到只清除了一半的状态字段。 */
  273. OS_ENTER_CRITICAL();
  274. AppStopModbusRtuTimer();
  275. ModbusRxLength = 0U;
  276. ModbusFrameLength = 0U;
  277. ModbusFrameReady = 0U;
  278. ModbusRxOverflow = 0U;
  279. ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T15;
  280. ModbusInterCharacterTimeout = 0U;
  281. ModbusRxDiscard = 0U;
  282. OS_EXIT_CRITICAL();
  283. /* 每次只挂接一个字节的接收。接收完成回调负责推进缓冲区并重新挂接下一个
  284. * 字节,使组帧完全由中断驱动且不会阻塞 Modbus 任务。 */
  285. halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0], 1U);
  286. return halStatus;
  287. }
  288. /**
  289. * @brief 在 Modbus 任务上下文中恢复 USART1 单字节中断接收。
  290. * @return HAL 状态码。
  291. */
  292. static HAL_StatusTypeDef AppRecoverUartReception(void)
  293. {
  294. HAL_StatusTypeDef halStatus;
  295. HAL_StatusTypeDef abortStatus;
  296. /* 出错的接收仍由 HAL 持有时,HAL_UART_Receive_IT() 会返回 BUSY。
  297. * 先终止旧接收并清除外设状态,下一次挂接才能从 READY 状态开始。 */
  298. abortStatus = HAL_UART_AbortReceive_IT(&Uart1Handle);
  299. /* STM32 HAL 宏会按外设要求读取 SR 和 DR,以清除奇偶校验、帧格式和溢出错误。
  300. * 该操作必须在中断之外执行。 */
  301. __HAL_UART_CLEAR_PEFLAG(&Uart1Handle);
  302. halStatus = AppStartUartReception();
  303. if ((abortStatus != HAL_OK) || (halStatus != HAL_OK)) {
  304. (void)HAL_UART_AbortReceive_IT(&Uart1Handle);
  305. __HAL_UART_CLEAR_PEFLAG(&Uart1Handle);
  306. halStatus = AppStartUartReception();
  307. }
  308. return halStatus;
  309. }
  310. /**
  311. * @brief 处理一个待执行的 UART 接收恢复请求。
  312. * @return 本次恢复操作返回的 HAL 状态。
  313. * @note 接收已经挂接时 UART 中断仍可能设置待恢复标志。只能清除本次尝试前
  314. * 已取出的请求,不能清除恢复过程中并发产生的新请求。
  315. */
  316. static HAL_StatusTypeDef AppRecoverPendingUartReception(void)
  317. {
  318. HAL_StatusTypeDef halStatus;
  319. halStatus = HAL_OK;
  320. if (ModbusReceptionNeedsRecovery != 0U) {
  321. ModbusReceptionNeedsRecovery = 0U;
  322. halStatus = AppRecoverUartReception();
  323. if (halStatus != HAL_OK) {
  324. ModbusReceptionNeedsRecovery = 1U;
  325. }
  326. }
  327. return halStatus;
  328. }
  329. /**
  330. * @brief 即使 HAL 状态丢失,也保持 UART 接收中断处于挂接状态。
  331. * @return 恢复操作的 HAL 状态;接收已经挂接时返回 HAL_OK。
  332. * @note RS-485 断线时可能完全没有 UART 中断,因此这里的检查可以补充错误回调,
  333. * 同时处理接收被打断后 HAL 停留在 READY/BUSY 状态的情况。
  334. */
  335. static HAL_StatusTypeDef AppEnsureUartReception(void)
  336. {
  337. if (ModbusFrameReady != 0U) {
  338. return HAL_OK;
  339. }
  340. /* 线缆断开可能完全不产生 UART 中断。任务每次唤醒时检查 HAL 状态,可以补充
  341. * ErrorCallback 的处理,即使硬件没有上报错误标志也能重新挂接接收。 */
  342. if ((ModbusReceptionNeedsRecovery != 0U)
  343. || (Uart1Handle.RxState != HAL_UART_STATE_BUSY_RX)) {
  344. ModbusReceptionNeedsRecovery = 1U;
  345. return AppRecoverPendingUartReception();
  346. }
  347. return HAL_OK;
  348. }
  349. /**
  350. * @brief 通过 USART1 发送 Modbus 从站响应帧。
  351. * @param[in] txLength 响应帧长度。
  352. * @return HAL 状态码。
  353. */
  354. static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength)
  355. {
  356. HAL_StatusTypeDef halStatus;
  357. INT8U osError;
  358. if ((txLength == 0U) || (txLength > MODBUS_TX_BUF_SIZE)) {
  359. return HAL_ERROR;
  360. }
  361. /* 发送是异步进行的。任务等待 ModbusTxSem,确保 USART1 发出最后一个字节前
  362. * 不会复用同一个发送缓冲区。 */
  363. halStatus = HAL_UART_Transmit_IT(&Uart1Handle, ModbusTxBuffer, txLength);
  364. if (halStatus != HAL_OK) {
  365. return halStatus;
  366. }
  367. OSSemPend(ModbusTxSem, APP_MODBUS_TX_TIMEOUT_TICKS, &osError);
  368. if (osError != OS_ERR_NONE) {
  369. (void)HAL_UART_AbortTransmit_IT(&Uart1Handle);
  370. return HAL_TIMEOUT;
  371. }
  372. return HAL_OK;
  373. }
  374. /**
  375. * @brief 设置正在运行的 RTU 定时器单次超时时间,单位为微秒。
  376. * @param[in] timeoutUs 单次超时时间,单位为微秒。
  377. */
  378. static void AppSetModbusRtuTimer(uint16_t timeoutUs)
  379. {
  380. /* TIM5 作为单次定时器使用。装载新间隔前先停止、清零并确认状态,
  381. * 否则残留的更新标志可能立即产生错误的帧边界。 */
  382. TIM5->CR1 &= (uint32_t)(~TIM_CR1_CEN);
  383. TIM5->CNT = 0U;
  384. TIM5->ARR = (uint32_t)timeoutUs - 1U;
  385. TIM5->SR = 0U;
  386. TIM5->CR1 |= TIM_CR1_CEN;
  387. }
  388. /**
  389. * @brief 启动 Modbus RTU T1.5/T3.5 定时器的第一阶段。
  390. */
  391. static void AppStartModbusRtuTimer(void)
  392. {
  393. /* 每收到一个字节就重新开始 T1.5。如果线路静默超过 T1.5,当前帧会被标记为
  394. * 无效,随后第二阶段继续等待 T3.5,再决定丢弃帧还是交给任务处理。 */
  395. ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T15;
  396. ModbusInterCharacterTimeout = 0U;
  397. AppSetModbusRtuTimer(APP_MODBUS_RTU_T15_RX_TIMEOUT_US);
  398. }
  399. /**
  400. * @brief 停止并确认 Modbus RTU 帧间隔定时器。
  401. */
  402. static void AppStopModbusRtuTimer(void)
  403. {
  404. TIM5->CR1 &= (uint32_t)(~TIM_CR1_CEN);
  405. TIM5->SR = 0U;
  406. }
  407. /**
  408. * @brief 在中断上下文中处理 T1.5 和 T3.5 单次定时到期事件。
  409. */
  410. static void AppHandleModbusRtuTimer(void)
  411. {
  412. if (ModbusRtuTimerPhase == APP_MODBUS_RTU_TIMER_PHASE_T15) {
  413. /* T1.5 表示帧内出现了字符间隔,此时不能立即解析;还要等待后续 T3.5,
  414. * 用它把这条不完整帧与下一条合法 RTU 帧分隔开。 */
  415. ModbusInterCharacterTimeout = 1U;
  416. ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T35;
  417. AppSetModbusRtuTimer(APP_MODBUS_RTU_T35_AFTER_T15_US);
  418. return;
  419. }
  420. AppStopModbusRtuTimer();
  421. ModbusInterCharacterTimeout = 0U;
  422. if (ModbusRxDiscard != 0U) {
  423. ModbusRxDiscard = 0U;
  424. ModbusRxLength = 0U;
  425. return;
  426. }
  427. if ((ModbusRxLength == 0U) || (ModbusFrameReady != 0U)) {
  428. return;
  429. }
  430. /* T3.5 表示帧结束。通知任务前先保存帧长度,任务随后解析稳定的缓冲区,
  431. * 完成后再重置接收状态。 */
  432. ModbusFrameLength = ModbusRxLength;
  433. ModbusFrameReady = 1U;
  434. (void)HAL_UART_AbortReceive_IT(&Uart1Handle);
  435. (void)AppPostModbusFrameSem();
  436. }
  437. /**
  438. * @brief 原子读取已封存 Modbus 帧的长度。
  439. * @return 完整帧长度;无完整帧时返回 0。
  440. */
  441. static uint16_t AppGetReadyFrameLength(void)
  442. {
  443. OS_CPU_SR cpu_sr = 0U;
  444. uint16_t frameLength;
  445. /* 定时器中断将 ModbusFrameLength 和 ModbusFrameReady 作为一个逻辑事件发布。
  446. * 这里原子地读取快照,避免任务拿到属于另一种缓冲区状态的长度。 */
  447. frameLength = 0U;
  448. OS_ENTER_CRITICAL();
  449. if (ModbusFrameReady != 0U) {
  450. frameLength = ModbusFrameLength;
  451. }
  452. OS_EXIT_CRITICAL();
  453. return frameLength;
  454. }
  455. /**
  456. * @brief 通知 Modbus 任务处理完整帧或刷新通信状态。
  457. * @return uC/OS-II 错误码。
  458. */
  459. static INT8U AppPostModbusFrameSem(void)
  460. {
  461. INT8U osError;
  462. /* RTU 定时器、UART 错误路径和周期链路检查等多个来源共用一个信号量。
  463. * 任务会再次检查帧长度,因此没有完整帧的唤醒也是安全的,并能刷新链路指示。 */
  464. osError = OSSemPost(ModbusFrameSem);
  465. return osError;
  466. }
  467. /**
  468. * @brief 判断协议处理结果是否代表已寻址的有效通信。
  469. * @param[in] status Modbus 协议处理结果。
  470. * @return 1 表示有效通信;0 表示无效通信。
  471. */
  472. static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status)
  473. {
  474. if ((status == MODBUS_STATUS_OK) || (status == MODBUS_STATUS_BROADCAST)
  475. || (status == MODBUS_STATUS_EXCEPTION)) {
  476. return 1U;
  477. }
  478. return 0U;
  479. }
  480. /**
  481. * @brief 判断功能码是否为需要保存到 RTC 的写操作。
  482. * @param[in] functionCode Modbus 功能码。
  483. * @return 写操作返回 1,否则返回 0。
  484. * @note 仅 0x42 修改 0x00010000;标准寄存器和线圈写入不具备掉电保持承诺。
  485. */
  486. static uint8_t AppIsModbusRetentionWriteFunction(uint8_t functionCode)
  487. {
  488. switch (functionCode) {
  489. case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS:
  490. return 1U;
  491. default:
  492. return 0U;
  493. }
  494. }
  495. /**
  496. * @brief 更新连接线圈和 PLC 连接指示灯。
  497. * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。
  498. * @note 仅已寻址的正常响应、广播写或异常响应刷新活动时间;CRC 错误和
  499. * 非本机帧不会误报链路在线。
  500. */
  501. static MODBUS_STATUS AppUpdateLinkStatus(void)
  502. {
  503. MODBUS_STATUS status;
  504. uint32_t currentTick;
  505. uint8_t linkState;
  506. GPIO_PinState pinState;
  507. currentTick = OSTimeGet();
  508. linkState = APP_MODBUS_LINK_DISCONNECTED;
  509. if ((ModbusHasActivity != 0U) &&
  510. ((currentTick - ModbusLastActivityTick)
  511. < APP_MODBUS_LINK_TIMEOUT_TICKS))
  512. {
  513. linkState = APP_MODBUS_LINK_CONNECTED;
  514. }
  515. status = ModbusSetCoil(&ModbusSlave, APP_MODBUS_LINK_STATE_COIL,
  516. linkState);
  517. if (status != MODBUS_STATUS_OK)
  518. {
  519. return status;
  520. }
  521. if (linkState == APP_MODBUS_LINK_CONNECTED)
  522. {
  523. pinState = GPIO_PIN_RESET;
  524. }
  525. else
  526. {
  527. pinState = GPIO_PIN_SET;
  528. }
  529. HAL_GPIO_WritePin(PLC_LINK_LED_PORT, PLC_LINK_LED_PIN, pinState);
  530. return MODBUS_STATUS_OK;
  531. }
  532. /**
  533. * @brief 将线圈 1 至 7 的状态同步到 PLC Q1 至 Q7 输出。
  534. * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。
  535. * @note 板级输出为低有效,因此逻辑线圈置 1 时向 GPIO 写入 GPIO_PIN_RESET。
  536. */
  537. static MODBUS_STATUS AppSyncCoilsToGpio(void)
  538. {
  539. MODBUS_STATUS status;
  540. uint16_t coilOffset;
  541. uint16_t coilAddress;
  542. uint8_t coilValue;
  543. GPIO_PinState pinState;
  544. for (coilOffset = 0U;
  545. coilOffset < APP_MODBUS_USER_OUTPUT_COIL_COUNT;
  546. coilOffset++)
  547. {
  548. coilAddress = coilOffset + APP_MODBUS_FIRST_USER_OUTPUT_COIL;
  549. status = ModbusGetCoil(&ModbusSlave, coilAddress, &coilValue);
  550. if (status != MODBUS_STATUS_OK)
  551. {
  552. return status;
  553. }
  554. pinState = (coilValue != 0U) ? GPIO_PIN_RESET : GPIO_PIN_SET;
  555. HAL_GPIO_WritePin(AppUserOutputPorts[coilOffset],
  556. AppUserOutputPins[coilOffset], pinState);
  557. }
  558. return MODBUS_STATUS_OK;
  559. }
  560. /**
  561. * @brief 处理已封存的 Modbus RTU 请求帧。
  562. * @param[in] rxLength 请求帧长度。
  563. * @note 此函数在 Modbus 任务上下文执行。它负责协议分发、链路活动刷新、
  564. * 线圈 GPIO 同步、0x42 成功后的 RTC 保存和异步 UART 应答启动。
  565. */
  566. static void AppProcessFrame(uint16_t rxLength)
  567. {
  568. MODBUS_STATUS status;
  569. uint16_t txLength;
  570. uint8_t functionCode;
  571. uint8_t isWriteOperation;
  572. txLength = 0U;
  573. functionCode = 0U;
  574. isWriteOperation = 0U;
  575. if (rxLength > APP_MODBUS_FUNCTION_INDEX) {
  576. functionCode = ModbusRxBuffer[APP_MODBUS_FUNCTION_INDEX];
  577. }
  578. if ((rxLength == 0U) || (rxLength > MODBUS_RX_BUF_SIZE))
  579. {
  580. return;
  581. }
  582. if (ModbusRxOverflow != 0U)
  583. {
  584. return;
  585. }
  586. /* 协议解析固定放在任务上下文中执行。这样解析器可以更新线圈/寄存器并构造
  587. * 响应,而不必在 UART 或定时器中断中执行较长的处理。 */
  588. status = ModbusProcessFrame(&ModbusSlave, ModbusRxBuffer, rxLength,
  589. ModbusTxBuffer, MODBUS_TX_BUF_SIZE,
  590. &txLength);
  591. /* 只有 CRC 正确且寻址到本站的帧(包括合法广播写入或已生成的异常响应)才算
  592. * 有效链路活动,线路上的随机干扰不能错误点亮连接指示灯。 */
  593. if (AppIsModbusStatusAccepted(status) != 0U)
  594. {
  595. ModbusLastActivityTick = OSTimeGet();
  596. ModbusHasActivity = 1U;
  597. isWriteOperation = AppIsModbusRetentionWriteFunction(functionCode);
  598. if (((status == MODBUS_STATUS_OK)
  599. || (status == MODBUS_STATUS_BROADCAST))
  600. && (isWriteOperation != 0U))
  601. {
  602. (void)ModbusBackupSave(&ModbusSlave);
  603. }
  604. }
  605. /* 线圈值是协议数据模型;这里是唯一的同步桥,将用户线圈 1..7 映射到板卡
  606. * 低电平有效的 Q1..Q7 输出。 */
  607. if ((status == MODBUS_STATUS_OK)
  608. || (status == MODBUS_STATUS_BROADCAST))
  609. {
  610. (void)AppSyncCoilsToGpio();
  611. }
  612. (void)AppUpdateLinkStatus();
  613. if (txLength > 0U)
  614. {
  615. (void)AppTransmitResponse(txLength);
  616. }
  617. }
  618. /**
  619. * @brief Modbus 协议处理任务。
  620. * @param[in] argument 未使用。
  621. */
  622. static void AppTaskModbus(void *argument)
  623. {
  624. HAL_StatusTypeDef halStatus;
  625. INT8U osError;
  626. uint16_t rxLength;
  627. (void)argument;
  628. /* 先将协议模型绑定到 RAM 数据区,再恢复由 RTC 备份的扩展寄存器,确保第一条
  629. * 请求到达前数据已经准备好。 */
  630. if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT,
  631. holdingRegisters, coilStorage)
  632. != MODBUS_STATUS_OK) {
  633. Error_Handler();
  634. }
  635. if (ModbusConfigureExtendedHoldingRegister(&ModbusSlave,
  636. &extendedHoldingRegister)
  637. != MODBUS_STATUS_OK) {
  638. Error_Handler();
  639. }
  640. if (ModbusBackupRestore(&ModbusSlave) != MODBUS_STATUS_OK) {
  641. Error_Handler();
  642. }
  643. if (AppSyncCoilsToGpio() != MODBUS_STATUS_OK) {
  644. Error_Handler();
  645. }
  646. if (AppUpdateLinkStatus() != MODBUS_STATUS_OK) {
  647. Error_Handler();
  648. }
  649. halStatus = AppRecoverUartReception();
  650. if (halStatus != HAL_OK) {
  651. ModbusReceptionNeedsRecovery = 1U;
  652. }
  653. while (1) {
  654. /* 完整帧解析和 UART 恢复由本任务负责。中断回调只收集字节、标记帧边界或
  655. * 错误,并投递此信号量。 */
  656. halStatus = AppEnsureUartReception();
  657. if (halStatus != HAL_OK) {
  658. OSTimeDly(1U);
  659. continue;
  660. }
  661. OSSemPend(ModbusFrameSem, 0U, &osError);
  662. if (osError != OS_ERR_NONE) {
  663. continue;
  664. }
  665. rxLength = AppGetReadyFrameLength();
  666. if (rxLength > 0U) {
  667. AppProcessFrame(rxLength);
  668. /* 帧定时器已经终止了本次完整帧的接收。正常挂接下一帧时不能再次清除
  669. * SR/DR,因为主站可能在 T3.5 后已经开始发送下一条请求。 */
  670. halStatus = AppStartUartReception();
  671. if (halStatus != HAL_OK) {
  672. ModbusReceptionNeedsRecovery = 1U;
  673. }
  674. }
  675. else {
  676. (void)AppUpdateLinkStatus();
  677. }
  678. }
  679. }
  680. /**
  681. * @brief 定期检测接收静默时间并判断 Modbus RTU 帧结束。
  682. * @param[in] argument 未使用。
  683. */
  684. static void AppTaskT35(void *argument)
  685. {
  686. uint16_t updateElapsedTicks;
  687. (void)argument;
  688. updateElapsedTicks = 0U;
  689. while (1) {
  690. /* 周期唤醒 Modbus 任务,使空闲线路在达到配置
  691. 的超时时间后将连接指示灯恢复为断开状态。 */
  692. OSTimeDly(1U);
  693. updateElapsedTicks += 1U;
  694. if (updateElapsedTicks >= 100U) {
  695. updateElapsedTicks = 0U;
  696. (void)AppPostModbusFrameSem();
  697. }
  698. }
  699. }
  700. /**
  701. * @brief USART1 单字节接收完成回调。
  702. * @param[in] uartHandle 触发回调的串口句柄。
  703. */
  704. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle)
  705. {
  706. HAL_StatusTypeDef halStatus;
  707. if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) {
  708. return;
  709. }
  710. /* 此回调每次只处理一个字节 */
  711. if (ModbusFrameReady != 0U) {
  712. return;
  713. }
  714. /* T1.5 间隔会使当前不完整帧失效。继续接收并丢弃字节直到后续 T3.5 边界,
  715. * 防止迟到的字节被误认为新帧的首字节。 */
  716. if ((ModbusRxDiscard != 0U)
  717. || (ModbusInterCharacterTimeout != 0U)) {
  718. ModbusRxDiscard = 1U;
  719. ModbusRxLength = 0U;
  720. AppStartModbusRtuTimer();
  721. halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0],
  722. 1U);
  723. if (halStatus != HAL_OK) {
  724. ModbusReceptionNeedsRecovery = 1U;
  725. (void)AppPostModbusFrameSem();
  726. }
  727. return;
  728. }
  729. if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) {
  730. ModbusRxOverflow = 1U;
  731. return;
  732. }
  733. /* HAL 已经将当前字节放入 ModbusRxBuffer[ModbusRxLength]。 */
  734. ModbusRxLength++;
  735. AppStartModbusRtuTimer();
  736. if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) {
  737. ModbusRxOverflow = 1U;
  738. return;
  739. }
  740. halStatus = HAL_UART_Receive_IT(&Uart1Handle,
  741. &ModbusRxBuffer[ModbusRxLength], 1U);
  742. if (halStatus != HAL_OK) {
  743. ModbusReceptionNeedsRecovery = 1U;
  744. (void)AppPostModbusFrameSem();
  745. }
  746. }
  747. /**
  748. * @brief USART1 通信错误回调。
  749. * @param[in] uartHandle 触发回调的串口句柄。
  750. * @note 中断仅停止计时、标记当前帧无效并通知任务;接收重挂接由
  751. * AppRecoverPendingUartReception 在任务上下文执行。
  752. */
  753. void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle)
  754. {
  755. if ((uartHandle == NULL) || (uartHandle->Instance != USART1))
  756. {
  757. return;
  758. }
  759. /* UART 错误会使当前整帧失效。中断只记录错误并唤醒任务,不在这里终止或重新
  760. * 进入 HAL 接收流程,从而避免 HAL 重入,并让恢复操作集中在一个上下文中。 */
  761. AppStopModbusRtuTimer();
  762. __HAL_UART_CLEAR_PEFLAG(uartHandle);
  763. ModbusRxOverflow = 1U;
  764. ModbusReceptionNeedsRecovery = 1U;
  765. (void)AppPostModbusFrameSem();
  766. }
  767. /**
  768. * @brief 处理 TIM5 的 Modbus RTU T1.5/T3.5 单次定时到期事件。
  769. * @note 本函数只更新接收状态和投递信号量,不解析帧内容。
  770. */
  771. void AppModbusRtuTimerIrqHandler(void)
  772. {
  773. if ((TIM5->SR & TIM_SR_UIF) == 0U) {
  774. return;
  775. }
  776. TIM5->SR &= (uint32_t)(~TIM_SR_UIF);
  777. AppHandleModbusRtuTimer();
  778. }
  779. /**
  780. * @brief USART1 中断发送完成回调。
  781. * @param[in] uartHandle 触发回调的串口句柄。
  782. */
  783. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *uartHandle)
  784. {
  785. if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) {
  786. return;
  787. }
  788. /* 只有最后一个字节已经移出串口后,才唤醒发送任务。 */
  789. (void)OSSemPost(ModbusTxSem);
  790. }
  791. /**
  792. * @brief 进入不可恢复错误状态。
  793. */
  794. void Error_Handler(void)
  795. {
  796. __disable_irq();
  797. while (1) {
  798. }
  799. }