|
|
@ -38,6 +38,10 @@ static TUartTab g_ptUartTab[] = { |
|
|
}; |
|
|
}; |
|
|
#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) |
|
|
static void bsp_uart_set_baudrate(UART_HandleTypeDef *_pUartHandle, uint32_t _uiBaudRate) |
|
|
{ |
|
|
{ |
|
|
_pUartHandle->Init.BaudRate = _uiBaudRate; |
|
|
_pUartHandle->Init.BaudRate = _uiBaudRate; |
|
|
@ -70,17 +74,48 @@ TUartUserData *UART_userdata_init(int _iID, int32_t _uiBaudRate, uint32_t _uiSiz |
|
|
return ptUartUserData; |
|
|
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) |
|
|
void UART_IT_init(TComCtrl *_ptComCtrl) |
|
|
{ |
|
|
{ |
|
|
TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; |
|
|
TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; |
|
|
#ifdef CONFIG_UART_IT_IDLEDMA |
|
|
#ifdef CONFIG_UART_IT_IDLEDMA |
|
|
// 开启串口空闲中断 (IDLE Interrupt)
|
|
|
/* 记录 UART 实例 -> TComCtrl 的映射,供 HAL 回调反查 */ |
|
|
// 这是核心!DMA 负责搬运,IDLE 负责通知“一帧结束了”
|
|
|
{ |
|
|
__HAL_UART_ENABLE_IT(ptUartUserData->m_uart, UART_IT_IDLE); |
|
|
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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 启动 DMA 接收 (Circular 模式)
|
|
|
/* 用 HAL 官方的 "接收直到 IDLE" API 启动循环 DMA 接收。
|
|
|
// 注意:这里不需要在中断里重启 DMA,因为 Circular 模式会自动循环
|
|
|
* 它内部会:启动 Circular DMA + 使能 IDLE 中断 + 设 ReceptionType=TOIDLE。 |
|
|
if (HAL_UART_Receive_DMA(ptUartUserData->m_uart, ptUartUserData->m_dma_rx_buf, ptUartUserData->m_buf_size) != HAL_OK) |
|
|
* |
|
|
|
|
|
* 之后所有事件统一由 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(); |
|
|
Error_Handler(); |
|
|
} |
|
|
} |
|
|
@ -89,49 +124,148 @@ void UART_IT_init(TComCtrl *_ptComCtrl) |
|
|
#endif |
|
|
#endif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void UART_DMA_RX_IRQHandler(TComCtrl *_ptComCtrl) |
|
|
|
|
|
{ |
|
|
|
|
|
#ifdef CONFIG_UART_IT_IDLEDMA |
|
|
#ifdef CONFIG_UART_IT_IDLEDMA |
|
|
|
|
|
/* 共享的 DMA 环形缓冲数据搬运逻辑:
|
|
|
|
|
|
* 根据 DMA 当前写指针与上次处理位置,把新收到的字节送入 rd_ComRecvProc。 */ |
|
|
|
|
|
static void bsp_uart_process_rx(TComCtrl *_ptComCtrl) |
|
|
|
|
|
{ |
|
|
TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; |
|
|
TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; |
|
|
// 1. 检查是否是空闲中断 (IDLE)
|
|
|
uint16_t current_pos = ptUartUserData->m_buf_size |
|
|
if (__HAL_UART_GET_FLAG(ptUartUserData->m_uart, UART_FLAG_IDLE) != RESET) |
|
|
- __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; |
|
|
// 2. 清除空闲标志 (必须先读 SR 再读 DR,HAL 宏已封装好)
|
|
|
if (data_len > 0) |
|
|
__HAL_UART_CLEAR_IDLEFLAG(ptUartUserData->m_uart); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 计算本次接收了多少数据
|
|
|
|
|
|
// 公式:总长度 - DMA 当前剩余未传输的数量
|
|
|
|
|
|
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; |
|
|
rd_ComRecvProc(_ptComCtrl, |
|
|
if (data_len > 0) |
|
|
(const char *)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], |
|
|
{ |
|
|
data_len); |
|
|
rd_ComRecvProc(_ptComCtrl, (const char*)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], data_len); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
|
|
|
/* DMA 写指针回绕 (Wrap around) */ |
|
|
|
|
|
uint16_t part1 = ptUartUserData->m_buf_size - ptUartUserData->m_last_dma_pos; |
|
|
|
|
|
uint16_t part2 = current_pos; |
|
|
|
|
|
|
|
|
|
|
|
if (part1 > 0) |
|
|
{ |
|
|
{ |
|
|
// DMA 指针发生了回绕 (Wrap around)
|
|
|
rd_ComRecvProc(_ptComCtrl, |
|
|
// 分两段拷贝:[last_pos -> End] 和 [0 -> current_pos]
|
|
|
(const char *)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], |
|
|
uint16_t part1 = ptUartUserData->m_buf_size - ptUartUserData->m_last_dma_pos; |
|
|
part1); |
|
|
uint16_t part2 = current_pos; |
|
|
} |
|
|
|
|
|
if (part2 > 0) |
|
|
if (part1 > 0) |
|
|
{ |
|
|
{ |
|
|
rd_ComRecvProc(_ptComCtrl, |
|
|
rd_ComRecvProc(_ptComCtrl, (const char*)&ptUartUserData->m_dma_rx_buf[ptUartUserData->m_last_dma_pos], part1); |
|
|
(const char *)&ptUartUserData->m_dma_rx_buf[0], |
|
|
} |
|
|
part2); |
|
|
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 */ |
|
|
|
|
|
|
|
|
ptUartUserData->m_last_dma_pos = current_pos; // 更新位置
|
|
|
#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 |
|
|
#endif |
|
|
} |
|
|
} |
|
|
|