第 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_bufferRX_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 侧应用)交换数据。

相关推荐
道可云4 小时前
淄博电机模具领军企业如何用AI提质增效?道可云携手精密制造专精特新,探索工业大脑赋能新路径
人工智能·制造
cxr8284 小时前
大语言模型上下文缓存命中率测试全场景清单
人工智能·python·算法·缓存·语言模型·自然语言处理·llm
yingyuecom4 小时前
映悦AI复刻爆款更新深度评测:资产锁定如何治好AI短剧的‘变脸症’
人工智能·gpt·chatgpt·prompt·aigc
caimouse4 小时前
ReactOS BootCD 光盘布局完整深度分析
单片机·reactos
有书Show4 小时前
市场部与IT“破壁”实战:打通数据流,让AI在品牌推广中真正落地
人工智能
OceanBase数据库官方博客4 小时前
OceanBase DataStudio:让 AI数据生产从拼装走向一体化(技术解析与
人工智能·oceanbase
科技圈观察4 小时前
2026巡检无人机十大品牌怎么选?续航、避障、行业适配度三维筛选法
人工智能
苏州邦恩精密4 小时前
蔡司3D扫描仪厂家如何应用于航空航天制造
大数据·数据库·人工智能·3d·自动化·制造
学术小白人4 小时前
国内外学术体系与论文等级区分—— 从 SCI / SSCI / EI 到北大核心 / CSSCI / CSCD 全面解析
大数据·人工智能·神经网络·数据分析·论文
fpcc4 小时前
Linux 7.1介绍
linux·运维