第 4 章:串口驱动进阶——GPDMA + Idle 中断实现变长数据流接收

在第 3 章,我们通过 RIF 拿到了 UART7 的"所有权"。现在,我们要实现一个工业级的串口驱动。

为什么不用简单的 HAL_UART_Receive_IT?因为在多核通讯或读取 IMU 数据时,数据长度往往是不固定的。频繁的字节中断会拖累 M33 的实时性,而简单的 DMA 传输又无法感知数据何时结束。

本章实战方案: 利用 STM32MP257 全新的 GPDMA (General Purpose DMA) 结合 UART 的 Idle Line(空闲总线)检测,实现一个"零拷贝、非阻塞、变长接收"的串口框架。


4.1 核心原理:为什么是 GPDMA + Idle?

  1. GPDMA:MP257 的 DMA 架构与传统 STM32 不同,它是基于通道索引和链接列表的,效率更高。

  2. Idle Line:当串口线路上超过一个字节的时间没有新数据时,硬件会自动触发 IDLE 中断。

  3. 组合拳:DMA 负责默默地把数据搬运到 RAM,IDLE 中断负责大喊一声:"报告 M33,这阵子数据传完了,请处理!"


4.2 实战:配置 GPDMA 接收通道

在 MP257 中,GPDMA 必须先进行 RIF 配置(如第 3 章所述),然后初始化。

UART_HandleTypeDef huart7;

DMA_HandleTypeDef hdma_uart7_rx;

void MX_UART7_DMA_Init(void) {

__HAL_RCC_GPDMA1_CLK_ENABLE();

// 配置 GPDMA1 Channel 0

hdma_uart7_rx.Instance = GPDMA1_Channel0;

hdma_uart7_rx.Init.Request = GPDMA1_REQUEST_UART7_RX; // 请求源

hdma_uart7_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;

hdma_uart7_rx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;

hdma_uart7_rx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;

hdma_uart7_rx.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;

hdma_uart7_rx.Init.Mode = DMA_NORMAL; // 建议用 Normal 模式,每次 IDLE 后重置

HAL_DMA_Init(&hdma_uart7_rx);

__HAL_LINKDMA(&huart7, hdmarx, hdma_uart7_rx);

}

4.3 实战:UART7 的初始化与 IDLE 中断开启

void MX_UART7_Init(void) {

huart7.Instance = UART7;

huart7.Init.BaudRate = 115200;

huart7.Init.WordLength = UART_WORDLENGTH_8B;

huart7.Init.StopBits = UART_STOPBITS_1;

huart7.Init.Parity = UART_PARITY_NONE;

huart7.Init.Mode = UART_MODE_TX_RX;

huart7.Init.HwFlowCtl = UART_HWCONTROL_NONE;

if (HAL_UART_Init(&huart7) != HAL_OK) {

Error_Handler();

}

// 重点:开启 IDLE 空闲中断

__HAL_UART_ENABLE_IT(&huart7, UART_IT_IDLE);

// 启动 DMA 接收(预设一个足够大的缓冲区)

HAL_UART_Receive_DMA(&huart7, rx_buffer, RX_BUF_SIZE);

}

4.4 深度实战:中断服务函数 (ISR) 的逻辑实现

这是整章的灵魂。在 stm32mp2xx_it.c 中,我们需要处理 UART7_IRQHandler

#define RX_BUF_SIZE 512

uint8_t rx_buffer[RX_BUF_SIZE];

void UART7_IRQHandler(void) {

// 检查是否是 IDLE 中断

if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_IDLE)) {

__HAL_UART_CLEAR_IDLEFLAG(&huart7); // 必须手动清除

// 核心逻辑:计算接收了多少字节

// 剩余长度 = RX_BUF_SIZE - DMA 当前传输剩余值

uint32_t remaining = __HAL_DMA_GET_COUNTER(&hdma_uart7_rx);

uint32_t length = RX_BUF_SIZE - remaining;

// 停止当前 DMA 传输,处理数据

HAL_UART_DMAStop(&huart7);

// --- 处理接收到的数据 ---

// 例如:Process_Data(rx_buffer, length);

// 此处打印收到的字节数(实战中建议使用无锁队列转发)

printf("Received %ld bytes via DMA+IDLE\r\n", length);

// 再次开启 DMA,准备下一次接收

HAL_UART_Receive_DMA(&huart7, rx_buffer, RX_BUF_SIZE);

}

HAL_UART_IRQHandler(&huart7);

}

4.5 性能优化:如何做到"不丢包"?

在工业实战中,如果在处理 Process_Data 时串口又有新数据进来,DMA 还没开启,就会丢包。

进阶技巧:双缓冲区 (Double Buffering)

  1. 定义两个缓冲区 Buf_ABuf_B

  2. DMA 指向 Buf_A

  3. IDLE 中断触发后,立即将 DMA 指向 Buf_B

  4. M33 在后台慢慢处理 Buf_A 的内容。 这样两个核/两个任务之间就形成了一个简单的乒乓切换,极大地提高了吞吐量。


4.6 部署与验证

  1. 接线:连接开发板的调试 Type-C(ST-Link 虚拟串口)。

  2. 发送测试:使用串口助手(如 Mobaxterm)发送一串很长的、长度随机的字符串。

  3. 结果:观察 M33 串口输出,它应该能精准报出每一帧数据的长度,并原样打印或处理。


4.7 避坑指南 (Debug Tips)

  • 陷阱 1:DMA 传输过快 。如果波特率极高且数据连续,IDLE 中断可能来不及触发。此时应结合 DMA 半完成中断 (HT) 来处理。

  • 陷阱 2:RIF 权限不够。再次检查 GPDMA 对应的通道是否已经在 RIMC 中划归给 M33。

  • 陷阱 3:Cache 一致性。如果你开启了 M33 的 Data Cache,DMA 把数据搬到 RAM 后,CPU 读取的可能是 Cache 里的旧数据。

    • 解决 :在处理数据前调用 SCB_InvalidateDCache_by_Addr(rx_buffer, RX_BUF_SIZE);

总结: 我们现在拥有了一个高效的串口"进出口"。M33 已经可以稳定地与外界(如 PC 或未来的 Linux 侧应用)交换数据。

相关推荐
liuyunshengsir1 天前
linux 下新增用户后无法使用TAB补全功能的最佳解决方法
linux·运维·服务器
书生执笔画浮沉1 天前
rpmrebuild
linux·centos·rpm
ZhengEnCi1 天前
01c-LSTM与GRU门控机制详解
人工智能
科技林总1 天前
自然语言处理任务分类
人工智能·自然语言处理
谈思汽车1 天前
当 AI 走进工厂与家庭:谁来保护AIoT 的“最后一米”?
人工智能·物联网·智能家居·健康医疗
IT_陈寒1 天前
React状态更新那点事儿,我掉坑里爬了半天
前端·人工智能·后端
Raink老师1 天前
【AI面试临阵磨枪】LLM 推理优化技术:量化、蒸馏、稀疏注意力、vLLM、TGI 核心思想。
人工智能·ai 面试
异方辰电子1 天前
8.原理图为什么看不到具体的电路(比如STM32的晶振等)
stm32·单片机·嵌入式硬件
睡觉吧狗命最最最重要1 天前
java开发的最优转型路径
人工智能
_小雨林1 天前
(UPDATING)LLM微调之实战,SFTTrainer官方案例、LoRA/QloRA微调案例、Unsloth、分布式训练、LLaMA Factory
人工智能·深度学习