feat(vd960DBN): UART2 RX DMA 循环接收 — 根治打印关中断丢帧 (2026-08-17)

遗留项'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 仍充裕
This commit is contained in:
wangfq
2026-08-17 15:59:16 +08:00
parent f274d70fbd
commit fed4335947
2 changed files with 102 additions and 23 deletions
@@ -16,6 +16,84 @@ void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART2_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){ void uart_init(void){
GPIO_InitTypeDef GPIO_InitStructure = {0}; GPIO_InitTypeDef GPIO_InitStructure = {0};
USART_InitTypeDef USART_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_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure); USART_Init(USART2, &USART_InitStructure);
// USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); /* RXNE 中断由 uart2_dma_init 关闭 (DMA 接管, 2026-08-17) */
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
@@ -55,6 +132,8 @@ void uart_init(void){
USART_Cmd(USART2, ENABLE); USART_Cmd(USART2, ENABLE);
uart2_dma_init(); /* DMA 循环接收接管 (2026-08-17) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
@@ -90,27 +169,9 @@ void USART1_IRQHandler(void)
*/ */
void USART2_IRQHandler(void) void USART2_IRQHandler(void)
{ {
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) /* 2026-08-17: RXNE 中断已关 (DMA 接管), 本 handler 不再触发。
{ 保留空函数 + IDLE 中断注释: 若后续需要帧边界辅助, 在此加
uint8_t _dat = USART_ReceiveData(USART2); USART_IT_IDLE 处理 (读 SR + 读 DR 清标志, 置标志由主循环消费)。 */
// 喂给帧解析器
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;
}
}
} }
@@ -151,6 +212,8 @@ void uart_srv(void)
uint8_t i; uint8_t i;
uint8_t _report_flag = 0; uint8_t _report_flag = 0;
uart2_dma_poll(); /* DMA 环形缓冲 → 帧解析 (2026-08-17) */
// 检查命令超时 // 检查命令超时
lup_cmd_check_timeout(); lup_cmd_check_timeout();
+16
View File
@@ -52,10 +52,26 @@
- [ ] 待回归:TCP JSON 命令交互(RECE_BUF_LEN 减小后接收缓冲 1024 ≥ MSS 帧 740B,理论无影响) - [ ] 待回归:TCP JSON 命令交互(RECE_BUF_LEN 减小后接收缓冲 1024 ≥ MSS 帧 740B,理论无影响)
- [ ] 待回归:快照区 3s 后初始化(`INIT: snap ok`)、断网快照落盘、BLE 0x28/0x29/0x2A - [ ] 待回归:快照区 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 跑飞重启,诊断价值) - BOOT_CNT 打印保留(区分复位 vs 跑飞重启,诊断价值)
- 快照区延后 3s 保留(非根因,但作为启动早期 SPI 减压无害) - 快照区延后 3s 保留(非根因,但作为启动早期 SPI 减压无害)
- (可选)g_report_cfg 1400B 结构体审查、WCHNET Mem_Heap 7.2KB 库配置——收益有限暂不动 - (可选)g_report_cfg 1400B 结构体审查、WCHNET Mem_Heap 7.2KB 库配置——收益有限暂不动
- [ ] UART2 DMA 板级验证:Loop MCU 数据正常解析(无 checksum fail)、
粘帧/半帧/长阻塞(SPI 擦除时)不丢帧
--- ---