• 正文
  • 相关推荐
申请入驻 产业图谱

STM32H7/U5 系列 DMA 图像 90 度旋转实现指南(基于 LAT1416)

18小时前
52
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

嵌入式 UI 开发中,常遇到横屏转竖屏的场景,需将 framebuffer 中的图像逆时针旋转 90 度后显示。传统软件旋转方案通过双重循环实现,但效率低下,占用大量 CPU 算力。本文基于意法半导体 LAT1416 技术文档,详解 STM32H7(MDMA)与 STM32U5(GPDMA)系列芯片借助 DMA 实现图像旋转的核心原理、实操代码及方案对比,助力开发人员释放 CPU 资源,提升系统响应速度。

资料获取:开发经验 | LAT1416 借助 DMA 将内存图像旋转 90 度

1. 核心背景与技术痛点

1.1 需求场景

  • 应用场景:UI 界面从横屏切换为竖屏,需对 RGB565 格式图像(16 位 / 像素,2 字节存储)进行逆时针 90 度旋转;
  • 核心诉求:替代软件旋转方案,通过 DMA 硬件搬运实现旋转,解放 CPU 处理其他事务。

1.2 技术难点

原始图像数据在内存中连续存储(如 W×H 分辨率图像按 “行优先” 排列),但旋转后像素在目标内存中呈 “列优先” 分布,数据地址完全不连续。若直接使用 DMA 传输,无法充分利用 burst 模式提升效率,但仍能通过 DMA 的灵活寻址能力实现旋转,核心优势是脱离 CPU 干预。

2. 两种 DMA 实现方案(分芯片系列)

2.1 STM32H7 系列:MDMA + LinkedList 模式

STM32H7 的 MDMA(Multi-channel DMA)无原生 2D 寻址功能,需通过LinkedList(链表)模式,将旋转操作拆解为多个节点传输任务,每个节点负责将源图像的一行数据转存为目标图像的一列数据。

(1)核心原理

  • 节点数量:需创建与图像高度(HH)相等的链表节点,每个节点处理 1 行→1 列的转换;
  • 寻址逻辑:
    • 源地址:从每行末尾开始(如第 i 行起点为data_src + (WW-1) + i×WW),按半字(2 字节)递减,读取整行像素;
    • 目标地址:从目标列起点开始(如第 i 列起点为data_dst + i),传输后目标地址按HH×2字节偏移(跳至下一列同位置);
  • 数据配置:RGB565 格式为半字(16 位),块传输长度设为 2 字节,每个节点传输WW个块(覆盖整行像素)。

(2)完整实现代码

// 图像参数:WW=图像宽度,HH=图像高度(可根据实际需求修改)
#define WW 5
#define HH 4

// 源/目标图像缓冲区(32字节对齐,满足DMA传输要求)
ALIGN_32BYTES(uint16_t data_src[WW*HH]);  // 源图像:W×H
ALIGN_32BYTES(uint16_t data_dst[HH*WW]);  // 目标图像:H×W(旋转后分辨率反转)
ALIGN_32BYTES(MDMA_LinkNodeTypeDef Xfer_Node[HH]);  // 链表节点(数量=HH)

MDMA_HandleTypeDef MDMA_Handle;
MDMA_LinkNodeConfigTypeDef mdmaLinkNodeConfig;
uint32_t i = 0;

// 初始化每个链表节点
for(i=0; i<HH; i++) {
    // 1. 配置MDMA节点传输参数
    mdmaLinkNodeConfig.Init.Request = MDMA_REQUEST_SW;  // 软件触发传输
    mdmaLinkNodeConfig.Init.TransferTriggerMode = MDMA_FULL_TRANSFER;  // 完整传输触发
    mdmaLinkNodeConfig.Init.Priority = MDMA_PRIORITY_HIGH;  // 高优先级
    mdmaLinkNodeConfig.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;  // 保留小端序
    mdmaLinkNodeConfig.Init.SourceInc = MDMA_SRC_DEC_HALFWORD;  // 源地址按半字递减
    mdmaLinkNodeConfig.Init.DestinationInc = MDMA_DEST_INC_DISABLE;  // 目标地址固定(块传输后偏移)
    mdmaLinkNodeConfig.Init.SourceDataSize = MDMA_SRC_DATASIZE_HALFWORD;  // 源数据:16位(半字)
    mdmaLinkNodeConfig.Init.DestDataSize = MDMA_DEST_DATASIZE_HALFWORD;  // 目标数据:16位(半字)
    mdmaLinkNodeConfig.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;  // 使能数据打包
    mdmaLinkNodeConfig.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;  // 源突发传输:单次
    mdmaLinkNodeConfig.Init.DestBurst = MDMA_DEST_BURST_SINGLE;  // 目标突发传输:单次
    mdmaLinkNodeConfig.Init.BufferTransferLength = 2;  // 每个块传输2字节(1个RGB565像素)
    mdmaLinkNodeConfig.Init.SourceBlockAddressOffset = 0;  // 源块地址无偏移
    mdmaLinkNodeConfig.Init.DestBlockAddressOffset = HH*2;  // 目标块偏移:HH×2字节(跳至下一列)
    
    // 2. 配置源/目标地址
    mdmaLinkNodeConfig.SrcAddress = (uint32_t)(data_src + (WW-1) + i*WW);  // 源地址:当前行末尾
    mdmaLinkNodeConfig.DstAddress = (uint32_t)(data_dst + i);  // 目标地址:当前列起点
    mdmaLinkNodeConfig.BlockCount = WW;  // 块数量=图像宽度(传输整行)
    
    // 3. 创建并添加链表节点
    HAL_MDMA_LinkedList_CreateNode(&(Xfer_Node[i]), &mdmaLinkNodeConfig);
    HAL_MDMA_LinkedList_AddNode(&MDMA_Handle, &(Xfer_Node[i]), 0);
}

// 启动MDMA链表传输(软件触发)
HAL_MDMA_Start(&MDMA_Handle, mdmaLinkNodeConfig.SrcAddress, mdmaLinkNodeConfig.DstAddress, WW*HH*2);

(3)关键注意事项

  • 缓冲区对齐:源 / 目标缓冲区及链表节点需 32 字节对齐(ALIGN_32BYTES宏),否则 DMA 传输会出错;
  • 内存占用:链表节点数量随图像高度增加而增多(如 1080P 图像需 1080 个节点),会占用额外内存资源。

2.2 STM32U5 系列:GPDMA + 2D 寻址模式

STM32U5 的 GPDMA(General Purpose DMA)支持原生 2D 寻址功能,无需链表模式,直接通过配置地址偏移和重复传输次数,即可实现一行→一列的旋转转换,配置更简洁,且不占用额外内存。

(1)核心原理

  • 2D 寻址优势:通过RepeatCount(重复块传输次数)和地址偏移配置,自动完成 HH 次块传输(覆盖所有行→列转换);
  • 寻址逻辑:
    • 源地址:按半字递增(行优先读取原始图像);
    • 目标地址:从目标图像最后一行的第一列开始,传输后按-(HH×2 + 2)字节偏移(上移一行),块传输完成后按HH×2 + (HH×(WW-1)+1)×2字节偏移(跳至下一列);
  • 触发方式:软件触发,单次配置即可完成整幅图像旋转。

(2)完整实现代码(基于 STM32U585)

// 图像参数:WW=80(宽度),HH=100(高度),RGB565格式
#define WW 80
#define HH 100

// 源图像数据(假设已加载RGB565格式图像)
uint16_t *data_src = (uint16_t *)image_icon_80x100;
// 目标图像缓冲区(32字节对齐)
ALIGN_32BYTES(uint16_t data_dst[HH*WW]);

// GPDMA通道12句柄
DMA_HandleTypeDef handle_GPDMA1_Channel12;
DMA_RepeatBlockConfigTypeDef RepeatBlockConfig;

// 1. 初始化GPDMA通道
handle_GPDMA1_Channel12.Instance = GPDMA1_Channel12;
handle_GPDMA1_Channel12.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;  // 单次突发硬件请求
handle_GPDMA1_Channel12.Init.Request = DMA_REQUEST_SW;  // 软件触发
handle_GPDMA1_Channel12.Init.Direction = DMA_MEMORY_TO_MEMORY;  // 内存到内存传输
handle_GPDMA1_Channel12.Init.SrcInc = DMA_SINC_INCREMENTED;  // 源地址递增
handle_GPDMA1_Channel12.Init.DestInc = DMA_DINC_INCREMENTED;  // 目标地址递增
handle_GPDMA1_Channel12.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;  // 源数据:16位
handle_GPDMA1_Channel12.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;  // 目标数据:16位
handle_GPDMA1_Channel12.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;  // 优先级配置
handle_GPDMA1_Channel12.Init.SrcBurstLength = 1;  // 源突发长度:1
handle_GPDMA1_Channel12.Init.DestBurstLength = 1;  // 目标突发长度:1
handle_GPDMA1_Channel12.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT1;  // 分配传输端口
handle_GPDMA1_Channel12.Init.TransferEventMode = DMA_TCEM_REPEATED_BLOCK_TRANSFER;  // 重复块传输模式
handle_GPDMA1_Channel12.Init.Mode = DMA_NORMAL;  // 正常模式

// 初始化GPDMA
if (HAL_DMA_Init(&handle_GPDMA1_Channel12) != HAL_OK) {
    Error_Handler();
}

// 2. 配置2D寻址重复块参数
RepeatBlockConfig.RepeatCount = HH;  // 重复块传输次数=图像高度(HH次)
RepeatBlockConfig.SrcAddrOffset = 0;  // 源地址无偏移
RepeatBlockConfig.DestAddrOffset = -(HH*2 + 2);  // 目标地址偏移:上移一行
RepeatBlockConfig.BlkSrcAddrOffset = 0;  // 块内源地址无偏移
RepeatBlockConfig.BlkDestAddrOffset = HH*2 + (HH*(WW-1) + 1)*2;  // 块传输后跳至下一列

// 配置重复块传输
if (HAL_DMAEx_ConfigRepeatBlock(&handle_GPDMA1_Channel12, &RepeatBlockConfig) != HAL_OK) {
    Error_Handler();
}

// 3. 配置通道属性(非特权通道)
if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel12, DMA_CHANNEL_NPRIV) != HAL_OK) {
    Error_Handler();
}

// 4. 启动GPDMA传输(源地址、目标地址、传输长度=WW×2字节)
HAL_DMA_Start_IT(
    &handle_GPDMA1_Channel12,
    (uint32_t)data_src,
    (uint32_t)(data_dst + (HH*(WW-1))),  // 目标起始地址:最后一行第一列
    WW*2  // 传输长度:一行像素(WW个×2字节)
);

(3)关键注意事项

  • 目标地址计算:起始地址需设为data_dst + HH*(WW-1),对应旋转后第一列的最后一行,确保像素顺序正确;
  • 偏移量配置:DestAddrOffsetBlkDestAddrOffset需根据图像分辨率动态调整,核心是保证列优先存储。

3. 方案对比与扩展建议

3.1 两种 DMA 方案核心差异

对比维度 STM32H7(MDMA + LinkedList) STM32U5(GPDMA + 2D 寻址)
配置复杂度 较高(需创建多个链表节点) 较低(原生 2D 寻址,单次配置)
内存占用 额外占用链表节点内存(随 HH 增加) 无额外内存占用
适用场景 STM32H7 系列无 2D 寻址功能的芯片 STM32U5 系列支持 GPDMA 的芯片
传输效率 中等(链表调度有轻微开销) 较高(原生 2D 寻址,无调度开销)

3.2 进阶优化方案:GPU2D 硬件旋转

若使用的 STM32 芯片内置 GPU2D 模块(如 STM32H7R/S、STM32U59/A/F/G 系列),可直接通过 GPU2D 实现图像旋转,无需 DMA 手动配置:

  • 优势:硬件加速旋转,支持任意角度(如 90°、180°、270°),效率远超 DMA,且完全脱离 CPU 和 DMA 资源;
  • 核心 API:通过HAL_GPU2D_Rotate()函数直接配置旋转角度、源 / 目标图像格式及地址,即可完成操作。

3.3 实用开发建议

  1. 格式适配:本文基于 RGB565 格式(2 字节 / 像素),若使用 RGB888 格式(3 字节 / 像素),需修改SourceDataSizeDestDataSize及地址偏移量(按 3 字节计算);
  2. 地址对齐:DMA 传输对缓冲区地址对齐要求严格(通常 32 字节或 16 字节),务必使用ALIGN_32BYTESALIGN_16BYTES宏;
  3. 调试要点:若旋转后图像错乱,优先检查源 / 目标地址计算、地址递增 / 递减方向、偏移量配置是否正确;
  4. 芯片选型:若新项目需频繁进行图像旋转,优先选择 STM32U5 系列(GPDMA 2D 寻址)或带 GPU2D 的芯片,简化开发并提升效率。

4. 总结

STM32H7 和 STM32U5 系列通过 DMA 均可实现图像 90 度旋转,核心是利用 DMA 的灵活寻址能力解决数据不连续问题:

  • STM32H7 依赖 MDMA 链表模式,虽配置复杂但兼容性强;
  • STM32U5 借助 GPDMA 2D 寻址,配置简洁且内存占用低;
  • 带 GPU2D 的芯片是最优选择,硬件加速旋转效率最高。

实际开发中需根据芯片型号、图像分辨率及系统资源情况选择合适方案,核心目标是在满足旋转需求的同时,最大化释放 CPU 算力,提升系统整体性能。本文代码均基于 LAT1416 文档实测验证,可直接移植到对应芯片项目中,需根据实际图像参数调整WW(宽度)和HH(高度)宏定义。

相关推荐