From fed4335947297d61ce64ab8074f15e5edae6e52e Mon Sep 17 00:00:00 2001 From: wangfq Date: Mon, 17 Aug 2026 15:59:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(vd960DBN):=20UART2=20RX=20DMA=20=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E6=8E=A5=E6=94=B6=20=E2=80=94=20=E6=A0=B9=E6=B2=BB?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E5=85=B3=E4=B8=AD=E6=96=AD=E4=B8=A2=E5=B8=A7?= =?UTF-8?q?=20(2026-08-17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 遗留项'UART2 偶发丢帧(1-3分钟一次 checksum fail)'落地: - 根因: PRINT 临界区(关中断~7.4ms)屏蔽 USART2 RXNE 中断 → 丢字节 - 方案: DMA1_Ch6 循环模式硬件收字节(不依赖CPU中断), 主循环轮询消费 - uart2_dma_init(): Ch6 循环模式 + 512B 环形缓冲(aligned(4)) + 关 RXNE - uart2_dma_poll(): 主循环读 DMA_GetCurrDataCounter 批量喂 lup_feed_byte (状态机移入主循环无竞争); 溢出保护(未消费>256B重置+丢帧计数) - uart_init() 末尾调 uart2_dma_init(); USART2_IRQHandler 清空留 IDLE 注释 - 资源: DMA1 全空闲(WCHNET 独立 ETH DMA/BLE 不用 DMA1) → Ch6 独占 - .bss +512B(缓冲), 栈 6.3KB→5.8KB 仍充裕 --- .../OnlyUpdateApp_Peripheral/APP/usart_biz.c | 109 ++++++++++++++---- vd960DBN/docs/devlog.md | 16 +++ 2 files changed, 102 insertions(+), 23 deletions(-) diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c index 20db4d6..5925f6b 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c @@ -16,6 +16,84 @@ void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +/*==================== UART2 RX DMA (2026-08-17 方案A) ==================== + * 根因: PRINT 临界区(关中断 ~7.4ms)期间 USART2 RXNE 中断被屏蔽 → 丢字节 → checksum fail + * 方案: DMA1_Ch6 循环模式硬件收字节 (不依赖 CPU 中断), 主循环轮询消费 + * - 512B 环形缓冲: 能装 ~7 帧 (70B), 覆盖主循环长阻塞窗口 (SPI 擦除 45ms) + * - 无新增中断 (关 RXNE, DMA 硬件接管) → 无优先级冲突; WCHNET 用独立 ETH DMA, + * BLE 栈不用 DMA1 → Ch6 独占无冲突 + * - lup_feed_byte 状态机移入主循环 (原中断上下文) → 无中断竞争更安全 + *======================================================================*/ +#define UART2_DMA_BUF_LEN 512 +static uint8_t uart2_dma_buf[UART2_DMA_BUF_LEN] __attribute__((aligned(4))); +static uint16_t uart2_dma_last = 0; /* 主循环消费位置 (DMA 写指针由硬件维护) */ +static uint32_t uart2_dma_drop = 0; /* 溢出丢弃计数 (主循环消费不及时) */ + +void uart2_dma_init(void) +{ + DMA_InitTypeDef DMA_InitStructure; + + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); + + DMA_DeInit(DMA1_Channel6); + DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DATAR); + DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart2_dma_buf; + DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; + DMA_InitStructure.DMA_BufferSize = UART2_DMA_BUF_LEN; + DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; + DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; + DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; + DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; + DMA_InitStructure.DMA_Priority = DMA_Priority_High; + DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; + DMA_Init(DMA1_Channel6, &DMA_InitStructure); + + /* DMA 接管 USART2 RX: 硬件收字节, 打印关中断不丢 */ + USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); + DMA_Cmd(DMA1_Channel6, ENABLE); + + /* 关闭 RXNE 中断 (DMA 接管后逐字节中断不再需要) */ + USART_ITConfig(USART2, USART_IT_RXNE, DISABLE); + + uart2_dma_last = 0; +} + +/* 主循环每轮调用: 消费 DMA 环形缓冲新字节 → 批量喂 lup_feed_byte 状态机 */ +void uart2_dma_poll(void) +{ + uint16_t cur = (uint16_t)(UART2_DMA_BUF_LEN - DMA_GetCurrDataCounter(DMA1_Channel6)); + uint16_t n = (uint16_t)((cur + UART2_DMA_BUF_LEN - uart2_dma_last) & (UART2_DMA_BUF_LEN - 1)); + + if (n == 0) return; + + /* 溢出保护: 未消费 > 半缓冲 (256B) → 消费太慢被 DMA 覆盖, 重置解析器丢帧计数 */ + if (n > (UART2_DMA_BUF_LEN / 2)) { + uart2_dma_drop++; + lup_frame_reset(); + uart2_dma_last = cur; + return; + } + + while (n--) { + uint8_t b = uart2_dma_buf[uart2_dma_last]; + uart2_dma_last = (uint16_t)((uart2_dma_last + 1) & (UART2_DMA_BUF_LEN - 1)); + + if (lup_feed_byte(b)) { + /* 帧接收完成 → 复制到 g_pkg_uart_2 (与中断版同逻辑, 主循环无竞争) */ + const uint8_t *frame = lup_frame_data(); + uint16_t frame_len = lup_frame_len(); + if (frame_len <= BUFF_STACK_SIZE) { + memcpy(g_pkg_uart_2.pkg, frame, frame_len); + g_pkg_uart_2.offset = frame_len; + g_pkg_uart_2.flag = 1; + g_pkg_uart_2.tick = 0; + } + lup_frame_reset(); + } + } +} + void uart_init(void){ GPIO_InitTypeDef GPIO_InitStructure = {0}; USART_InitTypeDef USART_InitStructure = {0}; @@ -44,8 +122,7 @@ void uart_init(void){ USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART2, &USART_InitStructure); - // USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); - USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); + /* RXNE 中断由 uart2_dma_init 关闭 (DMA 接管, 2026-08-17) */ NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; @@ -55,6 +132,8 @@ void uart_init(void){ USART_Cmd(USART2, ENABLE); + uart2_dma_init(); /* DMA 循环接收接管 (2026-08-17) */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; @@ -90,27 +169,9 @@ void USART1_IRQHandler(void) */ void USART2_IRQHandler(void) { - if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) - { - uint8_t _dat = USART_ReceiveData(USART2); - - // 喂给帧解析器 - if (lup_feed_byte(_dat)) { - // 帧接收完成,复制到 g_pkg_uart_2 - const uint8_t *frame = lup_frame_data(); - uint16_t frame_len = lup_frame_len(); - if (frame_len <= BUFF_STACK_SIZE) { - memcpy(g_pkg_uart_2.pkg, frame, frame_len); - g_pkg_uart_2.offset = frame_len; - g_pkg_uart_2.flag = 1; - g_pkg_uart_2.tick = 0; - } - lup_frame_reset(); - } else if (g_lup_parser.state != LUP_FRAME_STATE_IDLE) { - // 正在接收帧中,tick 归零 - g_pkg_uart_2.tick = 0; - } - } + /* 2026-08-17: RXNE 中断已关 (DMA 接管), 本 handler 不再触发。 + 保留空函数 + IDLE 中断注释: 若后续需要帧边界辅助, 在此加 + USART_IT_IDLE 处理 (读 SR + 读 DR 清标志, 置标志由主循环消费)。 */ } @@ -151,6 +212,8 @@ void uart_srv(void) uint8_t i; uint8_t _report_flag = 0; + uart2_dma_poll(); /* DMA 环形缓冲 → 帧解析 (2026-08-17) */ + // 检查命令超时 lup_cmd_check_timeout(); diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 1729cda..e96f475 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -52,10 +52,26 @@ - [ ] 待回归:TCP JSON 命令交互(RECE_BUF_LEN 减小后接收缓冲 1024 ≥ MSS 帧 740B,理论无影响) - [ ] 待回归:快照区 3s 后初始化(`INIT: snap ok`)、断网快照落盘、BLE 0x28/0x29/0x2A +### UART2 RX DMA 改造(2026-08-17 晚,方案A 实施) +遗留项"UART2 偶发丢帧"(1-3 分钟一次 checksum fail)落地修复: +- 根因:PRINT 临界区(关中断 ~7.4ms)期间 USART2 RXNE 中断被屏蔽 → 丢字节 → 粘帧 +- 方案:DMA1_Ch6 循环模式硬件收字节(不依赖 CPU 中断),主循环轮询消费 +- `usart_biz.c` 改动: + - 新增 `uart2_dma_init()`:Ch6 循环模式 + 512B 环形缓冲(`aligned(4)`)+ 关 RXNE 中断 + - 新增 `uart2_dma_poll()`:主循环每轮读 `DMA_GetCurrDataCounter` 算新字节, + 批量喂 `lup_feed_byte`(状态机移入主循环,无中断竞争);溢出保护(未消费>256B + 重置+丢帧计数) + - `uart_init()` 末尾调 `uart2_dma_init()`;`USART2_IRQHandler` 清空(RXNE 已关, + 保留 IDLE 注释备用) +- 资源:DMA1 全空闲(WCHNET 用独立 ETH DMA、BLE 栈不用 DMA1)→ Ch6 独占无冲突 +- 注意:.bss +512B(缓冲),栈 6.3KB → 5.8KB 仍充裕 + ### 遗留 - BOOT_CNT 打印保留(区分复位 vs 跑飞重启,诊断价值) - 快照区延后 3s 保留(非根因,但作为启动早期 SPI 减压无害) - (可选)g_report_cfg 1400B 结构体审查、WCHNET Mem_Heap 7.2KB 库配置——收益有限暂不动 +- [ ] UART2 DMA 板级验证:Loop MCU 数据正常解析(无 checksum fail)、 + 粘帧/半帧/长阻塞(SPI 擦除时)不丢帧 ---