#include "bsp_uart.h" #ifdef USE_UART extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart2_rx; extern DMA_HandleTypeDef hdma_usart3_rx; #ifdef IOC_100PIN extern DMA_HandleTypeDef hdma_uart4_rx; extern DMA_HandleTypeDef hdma_uart5_rx; extern DMA_HandleTypeDef hdma_usart6_rx; extern DMA_HandleTypeDef hdma_uart7_rx; #endif #ifdef CONFIG_UART_IT_IDLEDMA static TUartTab g_ptUartTab[] = { {&hlpuart1, &hdma_usart2_rx}, {&huart1, &hdma_usart1_rx}, {&huart2, &hdma_usart2_rx}, {&huart3, &hdma_usart3_rx}, #ifdef IOC_100PIN {&huart4, &hdma_uart4_rx}, {&huart5, &hdma_uart5_rx}, {&huart6, &hdma_usart6_rx}, {&huart7, &hdma_uart7_rx} #endif }; #else static TUartTab g_ptUartTab[] = { {&hlpuart1, NULL}, {&huart1, NULL}, {&huart2, NULL}, {&huart3, NULL}, #ifdef IOC_100PIN {&huart4, NULL}, {&huart5, NULL}, {&huart6, NULL}, {&huart7, NULL} #endif }; #endif /* HAL 回调只有 huart 指针,这里维护 "UART 实例 -> TComCtrl" 的映射, * 使 bsp_uart.c 不依赖 project 层的全局变量(g_ptRS485_x)。 */ static TComCtrl *s_aptComCtrl[sizeof(g_ptUartTab) / sizeof(g_ptUartTab[0])] = {0}; static void bsp_uart_set_baudrate(UART_HandleTypeDef *_pUartHandle, uint32_t _uiBaudRate) { _pUartHandle->Init.BaudRate = _uiBaudRate; if (HAL_UART_Init(_pUartHandle) != HAL_OK) { Error_Handler(); } } TUartUserData *UART_userdata_init(int _iID, int32_t _uiBaudRate, uint32_t _uiSize) { if (_iID < 0 || _iID >= (sizeof(g_ptUartTab)/sizeof(g_ptUartTab[0]))) return NULL; TUartUserData *ptUartUserData = (TUartUserData *)RD_CALLOC(1, sizeof(TUartUserData)); ptUartUserData->m_buf_size = (_uiSize == 0) ? CONFIG_UART_BUFFER_SIZE : _uiSize; #ifdef CONFIG_UART_IT_IDLEDMA uint8_t *pDMABuf = (uint8_t *)RD_CALLOC(1, ptUartUserData->m_buf_size); ptUartUserData->m_dma_rx_buf = pDMABuf; ptUartUserData->m_last_dma_pos = 0; ptUartUserData->m_hdma_rx = g_ptUartTab[_iID].m_hdma_rx; #endif ptUartUserData->m_uart = g_ptUartTab[_iID].m_uart; if (_uiBaudRate > 0) { bsp_uart_set_baudrate(g_ptUartTab[_iID].m_uart, _uiBaudRate); } return ptUartUserData; } /* 根据 UART 实例反查 TComCtrl(在 HAL 回调中使用) */ static TComCtrl *bsp_uart_find_comctrl(UART_HandleTypeDef *_pUart) { uint32_t i; for (i = 0; i < sizeof(g_ptUartTab) / sizeof(g_ptUartTab[0]); i++) { if (g_ptUartTab[i].m_uart == _pUart) { return s_aptComCtrl[i]; } } return NULL; } void UART_IT_init(TComCtrl *_ptComCtrl) { TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; #ifdef CONFIG_UART_IT_IDLEDMA /* 记录 UART 实例 -> TComCtrl 的映射,供 HAL 回调反查 */ { uint32_t i; for (i = 0; i < sizeof(g_ptUartTab) / sizeof(g_ptUartTab[0]); i++) { if (g_ptUartTab[i].m_uart == ptUartUserData->m_uart) { s_aptComCtrl[i] = _ptComCtrl; break; } } } /* 用 HAL 官方的 "接收直到 IDLE" API 启动循环 DMA 接收。 * 它内部会:启动 Circular DMA + 使能 IDLE 中断 + 设 ReceptionType=TOIDLE。 * * 之后所有事件统一由 HAL 调度,不再手动在 UART 中断里抢 DMA 状态机: * - IDLE/HT/TC 事件 -> HAL_UARTEx_RxEventCallback() * - 通信错误(ORE/FE/NE/PE) -> HAL_UART_ErrorCallback() * 这样 HAL 软件状态机与硬件寄存器状态始终一致,反复插拔 485 线 * 产生的错误由 HAL 负责 abort/重启,不会出现 current_pos 冻结的死锁。 */ if (HAL_UARTEx_ReceiveToIdle_DMA(ptUartUserData->m_uart, ptUartUserData->m_dma_rx_buf, ptUartUserData->m_buf_size) != HAL_OK) { Error_Handler(); } #else HAL_UART_Receive_IT(ptUartUserData->m_uart, &ptUartUserData->m_temp, 1); #endif } #ifdef CONFIG_UART_IT_IDLEDMA /* 共享的 DMA 环形缓冲数据搬运逻辑: * 根据 DMA 当前写指针与上次处理位置,把新收到的字节送入 rd_ComRecvProc。 */ static void bsp_uart_process_rx(TComCtrl *_ptComCtrl) { TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; uint16_t current_pos = ptUartUserData->m_buf_size - __HAL_DMA_GET_COUNTER(ptUartUserData->m_hdma_rx); uint16_t data_len = 0; if (current_pos >= ptUartUserData->m_last_dma_pos) { data_len = current_pos - ptUartUserData->m_last_dma_pos; if (data_len > 0) { rd_ComRecvProc(_ptComCtrl, (const char *)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], data_len); } } else { /* DMA 写指针回绕 (Wrap around) */ uint16_t part1 = ptUartUserData->m_buf_size - ptUartUserData->m_last_dma_pos; uint16_t part2 = current_pos; if (part1 > 0) { rd_ComRecvProc(_ptComCtrl, (const char *)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], part1); } if (part2 > 0) { rd_ComRecvProc(_ptComCtrl, (const char *)&ptUartUserData->m_dma_rx_buf[0], part2); } } ptUartUserData->m_last_dma_pos = current_pos; } #endif /* CONFIG_UART_IT_IDLEDMA */ #ifdef CONFIG_UART_IT_IDLEDMA /* HAL 的 "接收直到 IDLE" 事件回调: * 由 HAL_UART_IRQHandler(USART 中断) 与 HAL_DMA_IRQHandler(DMA 中断) * 统一调度。Circular 模式下 IDLE/HT/TC 均会触发本回调,且不会停止 DMA。 * 这里只处理 IDLE(一帧结束标记)。 */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { TComCtrl *ptComCtrl = bsp_uart_find_comctrl(huart); (void)Size; if (ptComCtrl == NULL) { return; } if (HAL_UARTEx_GetRxEventType(huart) != HAL_UART_RXEVENT_IDLE) { return; /* 只在 IDLE 帧结束时搬运数据 */ } bsp_uart_process_rx(ptComCtrl); } #endif /* CONFIG_UART_IT_IDLEDMA */ #ifdef CONFIG_UART_IT_IDLEDMA /* HAL 通信错误回调: * 反复插拔 485 线会产生 ORE/FE/NE/PE,HAL 检测到后会在本回调被调用前 * 同步完成:UART_EndRxTransfer()(RxState=READY) + HAL_DMA_Abort_IT() * (hdmarx->State=READY, 清 CR3.DMAR)。这里直接走 HAL 状态机重启即可。 */ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { TComCtrl *ptComCtrl = bsp_uart_find_comctrl(huart); TUartUserData *ptUartUserData; if (ptComCtrl == NULL) { return; } ptUartUserData = (TUartUserData *)ptComCtrl->m_pUserData; /* 故意留空:错误恢复完全交给主循环 UART_RX_CheckError()。 * 绝不在中断里重启 DMA——重启会重开 CR3.EIE,插拔噪声立刻又触发 * 错误中断,在中断里形成死循环饿死主循环。这里连标志都不设, * 主循环通过检测 CR3.DMAR 位判断 DMA 是否被 HAL 错误路径杀死。 */ (void)ptUartUserData; } #endif /* CONFIG_UART_IT_IDLEDMA */ #ifdef CONFIG_UART_IT_IDLEDMA /* 在主循环中调用:检测 DMA 是否被 HAL 错误路径杀死(CR3.DMAR 位被清), * 若已死则清错误标志并重启 DMA 接收。此函数运行在线程上下文(非中断), * 即使重启后又立刻产生错误中断,中断里也只是 abort DMA 后返回, * 不会死循环饿死主循环。 */ void UART_RX_CheckError(TComCtrl *_ptComCtrl) { if (_ptComCtrl == NULL) return; TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; if (ptUartUserData == NULL) return; UART_HandleTypeDef *huart = ptUartUserData->m_uart; /* DMA 正常运行时 CR3.DMAR 置位;HAL 错误路径会清掉它并 abort DMA。 */ if ((huart->Instance->CR3 & USART_CR3_DMAR) == 0U) { /* 清掉所有错误/空闲标志,防止重启后立刻又触发 */ __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF | UART_CLEAR_FEF | UART_CLEAR_NEF | UART_CLEAR_PEF | UART_CLEAR_IDLEF); /* 重置 HAL 软件状态,使 ReceiveToIdle_DMA 能成功启动 */ huart->RxState = HAL_UART_STATE_READY; huart->ErrorCode = HAL_UART_ERROR_NONE; ptUartUserData->m_last_dma_pos = 0; (void)HAL_UARTEx_ReceiveToIdle_DMA(huart, ptUartUserData->m_dma_rx_buf, ptUartUserData->m_buf_size); } } #endif /* CONFIG_UART_IT_IDLEDMA */ /* 兼容入口:供仍手动在 UART 中断里调用本函数的旧模块使用。 * 注意:Spoolend 的 USART1/USART3 已改为 HAL_UART_IRQHandler 统一调度, * 不再调用本函数;这里仅保留 IDLE 数据搬运能力,不操作 DMA 状态机。 */ void UART_DMA_RX_IRQHandler(TComCtrl *_ptComCtrl) { #ifdef CONFIG_UART_IT_IDLEDMA TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; UART_HandleTypeDef *huart = ptUartUserData->m_uart; if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(huart); bsp_uart_process_rx(_ptComCtrl); } #endif } void UART_RX_IRQHandler(TComCtrl *_ptComCtrl) { #ifndef CONFIG_UART_IT_IDLEDMA TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; rd_ComRecvProc(_ptComCtrl, (const char*)&ptUartUserData->m_temp, 1); HAL_UART_Receive_IT(ptUartUserData->m_uart, &ptUartUserData->m_temp, 1); #endif } #endif