嵌入式系统应用--TFTLCD 显示实验 4 之内存搬运
文章目录
- 1 DMA基本概念
- 1.1 基本介绍
- 1.2 DMA的核心思想
- 1.3 DMA的工作流程
- 1.3.1 初始化阶段
- 1.3.2 传输阶段
- 1.3.3 完成阶段
- 2 DMA的系统架构
- 2.1 DMA控制器组成
- 2.1.1 地址寄存器
- 2.1.2 计数寄存器
- 2.1.3 控制逻辑
- 2.1.4 状态寄存器
- 2.2 总线仲裁机制
- 2.2.1 周期窃取模式(Cycle Stealing)
- 2.2.2 突发传输模式(Burst Mode)
- 2.3 内存映射考虑
- 2.3.1 内存对齐
- 2.3.2 缓存一致性
- 3 STM32中的DMA实现
- 3.1 STM32 DMA架构特点
- 3.1.1 多流多通道架构
- 3.1.2 双缓冲区支持
- 3.1.3 先进的数据传输模式
- 3.2 STM32F4 DMA控制器特性
- 3.2.1 DMA1和DMA2分工
- 3.2.2 流优先级机制
- 3.2.3 FIFO缓冲区
- 3.3 DMA2D图形加速器(存在f429以上芯片)
- 3.3.1 专用图形功能
- 3.3.2 图层操作
- 4 DMA的传输模式
- 4.1 基本传输模式
- 4.1.1 外设到存储器
- 4.1.2 存储器到外设
- 4.1.3 存储器到存储器
- 4.2 高级传输特性
- 4.2.1 循环模式
- 4.2.2 双缓冲区模式
- 4.2.3 链接传输
- 五、DMA在嵌入式系统中的优势
- 5.1 性能优势
- 5.1.1 提高系统吞吐量
- 5.1.2 降低响应延迟
- 5.2 功耗优势
- 5.2.1 降低CPU负载
- 5.2.2 优化的电源管理
- 5.3 系统设计优势
- 5.3.1 简化软件设计
- 5.3.2 提高系统可靠性
- 6 DMA的典型应用场景
- 6.1 图形显示系统
- 6.1.1 帧缓冲区管理
- 6.1.2 图形特效处理
- 6.2 音频处理系统
- 6.2.1 音频数据流
- 6.2.2 实时音频效果
- 6.3 通信接口
- 6.3.1 高速串行通信
- 6.3.2 无线通信
- 6.4 数据采集系统
- 6.4.1 传感器数据采集
- 6.4.2 工业控制
- 7 DMA实战
- 7.1 DMA 框架
- 7.2 设计架构——采用双缓冲核心思路
- 7.3 具体代码实现
- 7.3.1 DMA 的配置
- 7.3.2 传输数据
- 7.3.3 配置缓存
- 8 效果视频
1 DMA基本概念
1.1 基本介绍
DMA(Direct Memory Access,直接内存访问) 是一种计算机系统架构中的重要技术,它允许特定的硬件子系统(外设)直接与主内存进行数据交换,而无需中央处理器(CPU)的介入。这种技术通过专门的DMA控制器来管理数据传输过程,从而解放CPU,使其能够同时执行其他计算任务。
1.2 DMA的核心思想
在没有DMA的传统系统中,数据转移需要CPU全程参与:
外设 → CPU → 内存
或者
内存 → CPU → 外设
这种模式下,CPU需要执行以下步骤:
-
从源地址读取数据
-
将数据暂存在寄存器中
-
将数据写入目标地址
-
更新地址指针
-
检查传输是否完成
// 传统CPU搬运数据方式
void cpu_copy_data(uint8_t *src, uint8_t *dst, uint32_t size) {for(uint32_t i = 0; i < size; i++) {dst[i] = src[i]; // CPU逐个字节复制}// 在此期间CPU被完全占用,不能执行其他任务
}
而使用DMA后,数据传输路径变为:
外设 ↔ DMA控制器 ↔ 内存
CPU仅在传输开始前配置DMA控制器,在传输完成后处理中断即可。
// DMA方式
void dma_copy_data(uint8_t *src, uint8_t *dst, uint32_t size) {DMA_Config(src, dst, size); // 配置DMADMA_Start(); // 启动DMA// CPU立即返回,可以执行其他任务// DMA控制器在后台自动完成数据搬运
}
1.3 DMA的工作流程
DMA的完整工作流程包括以下几个阶段:
1.3.1 初始化阶段
请求阶段:外设向DMA控制器发出传输请求
响应阶段:DMA控制器向CPU申请总线控制权
配置阶段:CPU配置DMA控制器的参数
1.3.2 传输阶段
地址生成:DMA控制器自动生成源地址和目标地址
数据传输:数据在外设和内存之间直接传输
计数更新:传输计数器自动递减
1.3.3 完成阶段
中断产生:传输完成后DMA控制器向CPU发出中断
状态检查:CPU检查传输状态
资源释放:释放总线控制权
2 DMA的系统架构
2.1 DMA控制器组成
典型的DMA控制器包含以下关键组件:
2.1.1 地址寄存器
源地址寄存器:存放数据传输的源起始地址
目标地址寄存器:存放数据传输的目标起始地址
地址增量控制:决定传输后地址是否自动递增
2.1.2 计数寄存器
传输计数器:记录待传输的数据单元数量
自动重装载:在循环模式下自动重新加载初始值
2.1.3 控制逻辑
传输模式控制:决定单次传输或循环传输
数据宽度控制:设置传输的数据位宽(8/16/32位)
优先级控制:管理多个DMA请求的优先级
2.1.4 状态寄存器
传输状态:指示当前传输的进度和状态
错误标志:记录传输过程中出现的错误
2.2 总线仲裁机制
在DMA传输过程中,存在两种总线访问模式:
2.2.1 周期窃取模式(Cycle Stealing)
DMA控制器在CPU不使用总线时"窃取"总线周期
每次只传输一个数据单元
对CPU性能影响较小,但传输效率较低
2.2.2 突发传输模式(Burst Mode)
DMA控制器在获得总线控制权后连续传输多个数据单元
传输效率高,但会暂时阻塞CPU访问内存
适合大数据量传输场景
2.3 内存映射考虑
DMA传输需要考虑内存的物理特性:
2.3.1 内存对齐
DMA控制器通常对内存地址有对齐要求
未对齐的访问可能导致性能下降或传输错误
2.3.2 缓存一致性
在有缓存系统中,需要确保DMA访问的数据与缓存一致性
通常需要手动刷新或无效化缓存
3 STM32中的DMA实现
3.1 STM32 DMA架构特点
STM32微控制器集成了先进的DMA控制器,具有以下特点:
3.1.1 多流多通道架构
DMA流(Stream):STM32F4系列有多个DMA流(DMA1有8个流,DMA2有8个流)
通道(Channel):每个流可以映射到不同的外设通道
灵活配置:允许动态重映射外设到不同的DMA流
3.1.2 双缓冲区支持
支持乒乓缓冲区操作
允许在传输一个缓冲区时准备另一个缓冲区
减少数据传输的延迟
3.1.3 先进的数据传输模式
普通模式:单次传输指定数量的数据
循环模式:自动重复传输,适合周期性数据采集
存储器到存储器:支持内存之间的直接数据传输
3.2 STM32F4 DMA控制器特性
3.2.1 DMA1和DMA2分工
DMA1:主要服务于低速外设(如USART、I2C、SPI等)
DMA2:主要服务于高速外设(如SDIO、以太网、摄像头接口等)
3.2.2 流优先级机制
每个DMA流都有可编程的优先级
支持4个优先级级别:非常低、低、中、高
硬件仲裁解决同时发生的DMA请求
3.2.3 FIFO缓冲区
每个DMA流都有4字的FIFO缓冲区
支持突发传输,提高总线利用率
可配置的FIFO阈值
3.3 DMA2D图形加速器(存在f429以上芯片)
STM32F4系列还包含了专门的DMA2D(DMA 2D)控制器:
3.3.1 专用图形功能
颜色格式转换:支持RGB565、ARGB8888等多种格式转换
Alpha混合:硬件实现的透明度混合操作
图像填充:快速矩形区域填充
图像复制:高效的位图复制操作
3.3.2 图层操作
支持多个图层的合成操作
每像素Alpha值处理
颜色键控(Color Keying)功能
4 DMA的传输模式
4.1 基本传输模式
4.1.1 外设到存储器
数据从外设传输到内存
典型应用:ADC数据采集、串口接收数据
源地址固定,目标地址递增
4.1.2 存储器到外设
数据从内存传输到外设
典型应用:DAC数据输出、串口发送数据
源地址递增,目标地址固定
4.1.3 存储器到存储器
数据在两个内存区域之间传输
典型应用:数据缓冲区复制、图像处理
源地址和目标地址都递增
4.2 高级传输特性
4.2.1 循环模式
传输完成后自动重新初始化
适合连续数据流处理
减少CPU中断开销
4.2.2 双缓冲区模式
自动在两个缓冲区之间切换
实现无停顿的连续数据传输
提高实时数据处理能力
4.2.3 链接传输
支持多个传输任务的自动链接
减少CPU配置开销
适合复杂的数据处理流水线
五、DMA在嵌入式系统中的优势
5.1 性能优势
5.1.1 提高系统吞吐量
并行处理:CPU和DMA可以同时工作
减少总线冲突:优化的传输调度
提高外设利用率:及时的数据传输
5.1.2 降低响应延迟
实时数据传输:无需等待CPU处理
快速中断响应:CPU可以专注于关键任务
确定性的时序:可预测的数据传输时间
5.2 功耗优势
5.2.1 降低CPU负载
CPU可以在DMA传输期间进入低功耗模式
减少动态功耗消耗
延长电池供电设备的续航时间
5.2.2 优化的电源管理
按需激活:只在需要时启用DMA
智能唤醒:DMA完成时唤醒CPU
功耗与性能的平衡
5.3 系统设计优势
5.3.1 简化软件设计
减少数据传输相关的代码复杂度
清晰的任务分离:计算与数据传输解耦
提高代码可维护性
5.3.2 提高系统可靠性
减少软件错误导致的数据损坏
硬件级别的数据完整性检查
可预测的系统行为
6 DMA的典型应用场景
6.1 图形显示系统
6.1.1 帧缓冲区管理
LCD显示数据刷新
图形图层合成
屏幕区域更新
6.1.2 图形特效处理
图像缩放和旋转
颜色空间转换
透明度混合操作
6.2 音频处理系统
6.2.1 音频数据流
ADC采集的音频数据处理
DAC输出的音频数据生成
音频编解码器接口
6.2.2 实时音频效果
数字滤波处理
混音和音量控制
音频数据格式转换
6.3 通信接口
6.3.1 高速串行通信
USB数据传输
以太网数据包处理
CAN总线通信
6.3.2 无线通信
WiFi数据缓冲
蓝牙数据传输
射频模块接口
6.4 数据采集系统
6.4.1 传感器数据采集
多通道ADC数据采集
惯性测量单元数据处理
环境传感器数据读取
6.4.2 工业控制
电机控制数据交换
编码器数据读取
模拟量输入输出
7 DMA实战
7.1 DMA 框架
每个DMA 都有8个通道,8个流。
DMA1:只支持外设到存储器、存储器到外设的传输
DMA2:支持所有传输模式,包括存储器到存储器
7.2 设计架构——采用双缓冲核心思路
因为笔者选用的芯片不带有DMA2D, 不支持图形的基础操作:颜色格式转换、图像混合、填充、拷贝 → 能替代 CPU 做大部分绘制。
CPU 在 Buffer A 渲染图像 → 同时 DMA 把 Buffer B 的内容传输到 LCD。
DMA 传输完成后,缓冲区交换:
CPU 渲染新的数据到 Buffer B。
DMA 把 Buffer A 的数据传输到 LCD。
这样 CPU 渲染和 DMA 传输 流水线并行,避免等待。
时间 →
┌───────────┬───────────┬───────────┬───────────┐
│ │ │ │ │
│ CPU │ 渲染 BufA │ 渲染 BufB │ 渲染 BufA │ 渲染 BufB │
│ │ │ │ │
├───────────┼───────────┼───────────┼───────────┤
│ │ │ │ │
│ DMA │ 传输 BufB │ 传输 BufA │ 传输 BufB │ 传输 BufA │
│ │ │ │ │
└───────────┴───────────┴───────────┴───────────┘
关键点:
- CPU 和 DMA 交错工作,不会抢占同一块缓冲区。
- DMA 传输完成时产生 中断,通知 CPU 可以切换缓冲区。
没有直接把渲染数据直接搬运到屏幕,如果直接搬运到屏幕带来下面的问题:
-
屏幕撕裂(Tearing)问题:上面新的屏幕,下面还是旧的屏幕
-
渲染与显示的时序冲突: LVGL渲染新帧 要占用屏幕, DMA 搬运也占用屏幕,导致总线冲突。
-
适合直接传输的情况,合适没有动画和视频的情况。
7.3 具体代码实现
7.3.1 DMA 的配置
笔者的屏幕是8位,所以采用bytes对齐的方案。 所以速度会非常慢。
/* ========== DMA 初始化 ========== */
int LCD_DMA_Init(void)
{DMA_InitTypeDef DMA_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;/* 使能 DMA2 时钟 */RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);/* 反初始化 stream */DMA_DeInit(LCD_DMA_STREAM);while (DMA_GetCmdStatus(LCD_DMA_STREAM) != DISABLE) { }DMA_StructInit(&DMA_InitStructure);DMA_InitStructure.DMA_Channel = LCD_DMA_CHANNEL;DMA_InitStructure.DMA_PeripheralBaseAddr = 0;DMA_InitStructure.DMA_Memory0BaseAddr =( uint32_t)&(TFTLCD->LCD_DATA); /* 后面启动时设置 */DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;DMA_InitStructure.DMA_BufferSize = 0; /* 后面启动时设置 */DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /* 16-bit */DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;DMA_Init(LCD_DMA_STREAM, &DMA_InitStructure);/* 配置 NVIC 中断 */NVIC_InitStructure.NVIC_IRQChannel = LCD_DMA_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; /* 保证中断优先级适当 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);/* 使能传输完成中断 */DMA_ITConfig(LCD_DMA_STREAM, DMA_IT_TC, ENABLE);return 0;
}INIT_PREV_EXPORT(LCD_DMA_Init);
7.3.2 传输数据
void dma_start_transfer(uint8_t *src, uint32_t len, lv_display_t * disp)
{dma_src_ptr = src;dma_remaining = len;dma_disp = disp;// 启动第一次传输uint32_t this_len = (dma_remaining > MAX_DMA_LEN) ? MAX_DMA_LEN : dma_remaining;DMA_Cmd(LCD_DMA_STREAM, DISABLE);while(LCD_DMA_STREAM->CR & DMA_SxCR_EN); // 等待关闭完成LCD_DMA_STREAM->PAR = (uint32_t)dma_src_ptr;LCD_DMA_STREAM->NDTR = this_len;DMA_ClearFlag(LCD_DMA_STREAM, DMA_FLAG_TCIF0);DMA_Cmd(LCD_DMA_STREAM, ENABLE);dma_src_ptr += this_len;dma_remaining -= this_len;
}
7.3.3 配置缓存
LV_ATTRIBUTE_MEM_ALIGNstatic uint8_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL] __attribute__((at(0x68030000)));LV_ATTRIBUTE_MEM_ALIGNstatic uint8_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL] __attribute__((at(0x6807b000)));lv_display_set_buffers(disp, buf_3_1, buf_3_2, sizeof(buf_3_1), LV_DISPLAY_RENDER_MODE_DIRECT);static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{if(disp_flush_enabled) {/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/LCD_Set_Window(area->x1,area->y1,area->x2,area->y2); uint32_t width = lv_area_get_width(area);uint32_t height = lv_area_get_height(area);uint32_t pixels = width* height;pixels=pixels*2; disp_disable_update(); dma_start_transfer(px_map, pixels, disp_drv);}
}
8 效果视频
4-4 DMA 搬运效果图