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

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