From 4d9b983d3cc5090d2998e9b933fe52b24d452fef Mon Sep 17 00:00:00 2001 From: Lizongdi <1210855344@qq.com> Date: Fri, 14 Aug 2026 09:24:55 +0800 Subject: [PATCH] =?UTF-8?q?DMA=E5=BD=93=E5=89=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9A=E4=B8=8D=E8=A7=84=E5=88=99=E6=B3=A2?= =?UTF-8?q?=E5=BD=A2=E7=9A=84485=E5=8F=91=E9=80=81=E4=BC=9A=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E4=B8=8D=E5=8F=AF=E9=80=86=E9=94=99=E8=AF=AF=EF=BC=8C?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E5=88=87=E6=8D=A2=E6=96=B9=E6=A1=88=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bspMCU/bsp_uart.c | 218 +++++++++++++++++++++++++++++------- bspMCU/include/bsp_uart.h | 2 + project/Spoolend/Spoolend.c | 13 ++- 3 files changed, 189 insertions(+), 44 deletions(-) diff --git a/bspMCU/bsp_uart.c b/bspMCU/bsp_uart.c index 55ea372..865ff8b 100644 --- a/bspMCU/bsp_uart.c +++ b/bspMCU/bsp_uart.c @@ -38,6 +38,10 @@ static TUartTab g_ptUartTab[] = { }; #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; @@ -70,17 +74,48 @@ TUartUserData *UART_userdata_init(int _iID, int32_t _uiBaudRate, uint32_t _uiSiz 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 - // 开启串口空闲中断 (IDLE Interrupt) - // 这是核心!DMA 负责搬运,IDLE 负责通知“一帧结束了” - __HAL_UART_ENABLE_IT(ptUartUserData->m_uart, UART_IT_IDLE); + /* 记录 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; + } + } + } - // 启动 DMA 接收 (Circular 模式) - // 注意:这里不需要在中断里重启 DMA,因为 Circular 模式会自动循环 - if (HAL_UART_Receive_DMA(ptUartUserData->m_uart, ptUartUserData->m_dma_rx_buf, ptUartUserData->m_buf_size) != HAL_OK) + /* 用 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(); } @@ -89,49 +124,148 @@ void UART_IT_init(TComCtrl *_ptComCtrl) #endif } -void UART_DMA_RX_IRQHandler(TComCtrl *_ptComCtrl) -{ #ifdef CONFIG_UART_IT_IDLEDMA +/* 共享的 DMA 环形缓冲数据搬运逻辑: + * 根据 DMA 当前写指针与上次处理位置,把新收到的字节送入 rd_ComRecvProc。 */ +static void bsp_uart_process_rx(TComCtrl *_ptComCtrl) +{ TUartUserData *ptUartUserData = (TUartUserData *)_ptComCtrl->m_pUserData; - // 1. 检查是否是空闲中断 (IDLE) - if (__HAL_UART_GET_FLAG(ptUartUserData->m_uart, UART_FLAG_IDLE) != RESET) + 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) { - - // 2. 清除空闲标志 (必须先读 SR 再读 DR,HAL 宏已封装好) - __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; + if (data_len > 0) { - 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 + 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) { - // DMA 指针发生了回绕 (Wrap around) - // 分两段拷贝:[last_pos -> End] 和 [0 -> current_pos] - 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); - } + 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 */ - 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 } diff --git a/bspMCU/include/bsp_uart.h b/bspMCU/include/bsp_uart.h index 794aedc..410b9ca 100644 --- a/bspMCU/include/bsp_uart.h +++ b/bspMCU/include/bsp_uart.h @@ -27,6 +27,7 @@ typedef struct DMA_HandleTypeDef *m_hdma_rx; uint8_t *m_dma_rx_buf; uint16_t m_last_dma_pos; + volatile uint8_t m_rx_error; #else uint8_t m_temp; #endif @@ -36,6 +37,7 @@ extern TUartUserData *UART_userdata_init(int _iID, int32_t _uiBaudRate, uint32_t extern void UART_IT_init(TComCtrl *_ptComCtrl); extern void UART_DMA_RX_IRQHandler(TComCtrl *_ptComCtrl); extern void UART_RX_IRQHandler(TComCtrl *_ptComCtrl); +extern void UART_RX_CheckError(TComCtrl *_ptComCtrl); #endif #endif /* end of include guard: BSP_UART_H */ diff --git a/project/Spoolend/Spoolend.c b/project/Spoolend/Spoolend.c index c4181d1..fc74593 100644 --- a/project/Spoolend/Spoolend.c +++ b/project/Spoolend/Spoolend.c @@ -167,13 +167,13 @@ static void handle_power_on_off(void) void SpoolendInit(void) { /* RS485_1: Modbus 从机 — check/decode 回调由 rd_ComRead 自动调用 */ - TUartUserData *ptUartUserData = UART_userdata_init(1, 115200, 512); + TUartUserData *ptUartUserData = UART_userdata_init(1, 115200, 4096); g_ptRS485_1 = rd_ComCreate(Modbus_Slave_Check, Modbus_Slave_Decode, RS485_1_Send, ptUartUserData->m_buf_size, ptUartUserData); UART_IT_init(g_ptRS485_1); /* RS485_2: Modbus 主机(DMK 驱动器)*/ - TUartUserData *ptUartUserData2 = UART_userdata_init(3, 115200, 512); + TUartUserData *ptUartUserData2 = UART_userdata_init(3, 115200, 4096); g_ptRS485_2 = rd_ComCreate(NULL, NULL, RS485_2_Send, ptUartUserData2->m_buf_size, ptUartUserData2); UART_IT_init(g_ptRS485_2); @@ -203,6 +203,11 @@ void SpoolendTask(void) /* 喂狗 */ HAL_IWDG_Refresh(&hiwdg); + /* 通信错误恢复:DMA 被 HAL 错误路径杀死后,在这里(主循环/线程上下文) + * 检测 CR3.DMAR 位并重启,绝不在中断里重启 DMA,避免死循环。 */ + UART_RX_CheckError(g_ptRS485_1); + UART_RX_CheckError(g_ptRS485_2); + /* 读取 Modbus 从机帧(rd_ComRead 内部自动调用 Modbus_Slave_Decode)*/ __disable_irq(); int i485_1 = rd_ComRead(g_ptRS485_1, pcBuffer1, sizeof(pcBuffer1)); @@ -258,6 +263,10 @@ void SpoolendTask(void) holdingRegisters[10] = 1; } + /* 通信错误恢复(中断里只设标志,重启在这里做,避免中断死循环)*/ + UART_RX_CheckError(g_ptRS485_1); + UART_RX_CheckError(g_ptRS485_2); + /* 发送处理 */ rd_ComSendProc(g_ptRS485_1); rd_ComSendProc(g_ptRS485_2);