/* ============================================================================= * bsp_usart.c - 串口 DMA 接收板级支持包(USART1/3/6) * 职责: * 1. usartX_init() 任务上下文调用:配置 RX 循环 DMA + 空闲中断, * 注册"整帧"回调(帧 = 两次空闲之间的字节) * 2. usartX_idle_isr() 由 stm32f4xx_it.c 的 USER CODE 区调用(需在 * HAL_UART_IRQHandler 之前):读 NDTR 算帧长, * 清 F4 DMA 流事件标志后重启 DMA,递交回调 * 3. usartX_tx_dma_*() TX DMA 发送(缓冲须保持有效直到发送完成) * 数据流: DR16(USART3)/裁判(USART6)/视觉(USART1) --IDLE ISR--> 回调 * 关键细节: * - 采用单缓冲循环 DMA + IDLE 重启(DJI 官方风格),不使用双缓冲 * - F4 手册要求重启 DMA 流前必须清除该流全部事件标志,否则只收到第一帧 * - usart3_init 会把字长改为 9B(CubeMX 生成的 8B+偶校验只有 7 个数据位, * DR16 帧需要 8 数据位+偶校验) * ========================================================================== */ #include "bsp_usart.h" #include "main.h" extern UART_HandleTypeDef huart1; extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_rx; extern UART_HandleTypeDef huart3; extern DMA_HandleTypeDef hdma_usart3_rx; extern UART_HandleTypeDef huart6; extern DMA_HandleTypeDef hdma_usart6_rx; extern DMA_HandleTypeDef hdma_usart6_tx; /* ========================================================================== * 单缓冲循环 DMA + 空闲中断重启接收(DJI 官方风格) * 每路串口接收配置:RX 走循环 DMA,空闲中断里读 NDTR 计算本帧长度, * 复位 NDTR 后继续接收,并通过注册的回调把整帧递交给使用者。 * 回调在中断上下文执行,应尽量短(可拷贝数据/通知任务)。 * ========================================================================== */ #define USART_RX_SLOT_MAX 3 typedef struct { UART_HandleTypeDef * huart; uint8_t * rx_buf; uint16_t buf_num; usart_rx_callback_f callback; } usart_rx_slot_t; static usart_rx_slot_t usart_rx_slots[USART_RX_SLOT_MAX]; /* 诊断计数(空串口事件次数/最近帧长),debug_task 打印 */ volatile uint32_t dbg_usart1_idle = 0; volatile uint32_t dbg_usart3_idle = 0; volatile uint32_t dbg_usart6_idle = 0; volatile uint16_t dbg_usart3_last_len = 0; volatile uint16_t dbg_usart6_last_len = 0; static usart_rx_slot_t * usart_rx_slot_find(UART_HandleTypeDef * huart) { for (uint8_t i = 0; i < USART_RX_SLOT_MAX; i++) if (usart_rx_slots[i].huart == huart) return &usart_rx_slots[i]; return NULL; } static void usart_rx_dma_start(usart_rx_slot_t * slot, DMA_HandleTypeDef * hdma_rx) { __HAL_DMA_DISABLE(hdma_rx); while (hdma_rx->Instance->CR & DMA_SxCR_EN) ; /* F4 手册要求:重新使能流之前必须清除该流全部事件标志, * 否则残留标志会导致流重启后不工作(表现为只收到第一帧) */ { DMA_TypeDef * ctl = (DMA_TypeDef *)((uint32_t)hdma_rx->Instance & ~(0x3FFU)); uint32_t stream_no = ((uint32_t)hdma_rx->Instance - (uint32_t)DMA1_Stream0) / 24U; if (stream_no < 4U) ctl->LIFCR = 0x3FUL << (6U * stream_no); else ctl->HIFCR = 0x3FUL << (6U * (stream_no - 4U)); } hdma_rx->Instance->PAR = (uint32_t) & (slot->huart->Instance->DR); /* 不使用 DMA 半满/全满中断,只靠空闲中断取帧 */ hdma_rx->Instance->CR &= ~(DMA_SxCR_HTIE | DMA_SxCR_TCIE); hdma_rx->Instance->NDTR = slot->buf_num; hdma_rx->Instance->M0AR = (uint32_t)slot->rx_buf; __HAL_DMA_ENABLE(hdma_rx); } static void usart_common_rx_init( UART_HandleTypeDef * huart, DMA_HandleTypeDef * hdma_rx, uint8_t * rx_buf, uint16_t dma_buf_num, usart_rx_callback_f callback) { usart_rx_slot_t * slot = usart_rx_slot_find(huart); if (slot == NULL) { for (uint8_t i = 0; i < USART_RX_SLOT_MAX; i++) if (usart_rx_slots[i].huart == NULL) { slot = &usart_rx_slots[i]; break; } if (slot == NULL) return; } slot->huart = huart; slot->rx_buf = rx_buf; slot->buf_num = dma_buf_num; slot->callback = callback; /* 使能 DMA 接收请求与空闲中断 */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); usart_rx_dma_start(slot, hdma_rx); } /* 空闲中断处理:取帧长,复位 DMA,回调递交(在各 USARTx_IRQHandler 的 USER CODE 区调用) */ static void usart_idle_process(usart_rx_slot_t * slot, DMA_HandleTypeDef * hdma_rx) { if (slot == NULL || slot->callback == NULL) return; __HAL_UART_CLEAR_IDLEFLAG(slot->huart); uint16_t recv_len = slot->buf_num - hdma_rx->Instance->NDTR; usart_rx_dma_start(slot, hdma_rx); if (hdma_rx == &hdma_usart3_rx) dbg_usart3_last_len = recv_len; else if (hdma_rx == &hdma_usart6_rx) dbg_usart6_last_len = recv_len; if (recv_len > 0) slot->callback(slot->rx_buf, recv_len); } /* --- 三路串口的 IDLE 中断入口,由 stm32f4xx_it.c 调用 --- */ void usart1_idle_isr(void) { dbg_usart1_idle++; usart_idle_process(usart_rx_slot_find(&huart1), &hdma_usart1_rx); } void usart3_idle_isr(void) { dbg_usart3_idle++; usart_idle_process(usart_rx_slot_find(&huart3), &hdma_usart3_rx); } void usart6_idle_isr(void) { dbg_usart6_idle++; usart_idle_process(usart_rx_slot_find(&huart6), &hdma_usart6_rx); } int usart_rx_register(UART_HandleTypeDef * huart, usart_rx_callback_f callback) { usart_rx_slot_t * slot = usart_rx_slot_find(huart); if (slot == NULL) return -1; slot->callback = callback; return 0; } /* ========================================================================== * 各串口接收初始化(在任务上下文调用;DMA/NVIC 已由 CubeMX 初始化) * ========================================================================== */ /* 视觉上位机串口(4pin 口 UART2),本工程暂未使用,保留接口 */ void usart1_init(uint8_t * rx_buf, uint16_t dma_buf_num, usart_rx_callback_f callback) { usart_common_rx_init(&huart1, &hdma_usart1_rx, rx_buf, dma_buf_num, callback); } /* DR16 遥控器接收机专用(100000-8E1) * CubeMX 生成的 WordLength=8B 在奇偶校验下只有 7 个数据位, * DR16 帧需要 8 数据位+偶校验,必须改为 9 位字长后重新初始化 */ void usart3_init(uint8_t * rx_buf, uint16_t dma_buf_num, usart_rx_callback_f callback) { CLEAR_BIT(huart3.Instance->CR1, USART_CR1_UE); /* 先关串口再改字长 */ huart3.Init.WordLength = UART_WORDLENGTH_9B; if (HAL_UART_Init(&huart3) != HAL_OK) return; usart_common_rx_init(&huart3, &hdma_usart3_rx, rx_buf, dma_buf_num, callback); } /* 图传链路/裁判系统串口(3pin 口 UART1) */ void usart6_init(uint8_t * rx_buf, uint16_t dma_buf_num, usart_rx_callback_f callback) { usart_common_rx_init(&huart6, &hdma_usart6_rx, rx_buf, dma_buf_num, callback); } /* ========================================================================== * 发送(TX DMA,发送缓冲须保持有效直至传输完成) * ========================================================================== */ void usart1_tx_dma_init(void) { SET_BIT(huart1.Instance->CR3, USART_CR3_DMAT); } void usart1_tx_dma_enable(uint8_t * data, uint16_t len) { __HAL_DMA_DISABLE(&hdma_usart1_tx); while (hdma_usart1_tx.Instance->CR & DMA_SxCR_EN) ; hdma_usart1_tx.Instance->M0AR = (uint32_t)data; hdma_usart1_tx.Instance->NDTR = len; __HAL_DMA_ENABLE(&hdma_usart1_tx); } void usart6_tx_dma_enable(uint8_t * data, uint16_t len) { SET_BIT(huart6.Instance->CR3, USART_CR3_DMAT); __HAL_DMA_DISABLE(&hdma_usart6_tx); while (hdma_usart6_tx.Instance->CR & DMA_SxCR_EN) ; hdma_usart6_tx.Instance->M0AR = (uint32_t)data; hdma_usart6_tx.Instance->NDTR = len; __HAL_DMA_ENABLE(&hdma_usart6_tx); }