1、DMA运用场景
随着智能化、信息化的不断推进,嵌入式设备的数据处理量也呈现指数级增加,因此对于巨大的数据量处理的情况时,必须采取其它的方式去替CPU减负,以保证嵌入式设备性能。例如SD卡存储器和音视频、网络高速通信等其它情景使用时,如果仅靠CPU去处理,将会消耗大量的系统资源,并且可能不能满足设备实时性的要求,对于嵌入式等一众资源受限的设备中,这是致命的。因此有必要采取一种特殊的方式,使得在执行大量数据处理过程中,CPU依然去执行正常的嵌入式系统任务。
在嵌入式系统中,常用DMA去解决这一问题。DMA(Direct Memory Access),直接存储器访问)是一种特殊的硬件功能,用于数据传输而不需要CPU的干预。DMA主要用于高速数据传输,可以提高系统性能和效率。
①数据传输:DMA可以在外设和存储器之间进行高速数据传输,例如将数据从外设(如传感器、音频设备、网络接口等)直接传输到存储器中,或者从存储器中直接传输到外设中。这样可以减少CPU的负载,并提高数据传输的速度和效率。
②音视频处理:在多媒体应用中,DMA可以用于将音频、视频等数据从外设传输到存储器中进行处理,或者从存储器传输到外设进行播放。通过使用DMA,系统可以实现高质量的音视频数据传输和处理,同时降低对CPU的负担。
③存储器处理:DMA可以用于将存储器中的数据备份到外部存储设备(如硬盘、闪存等),或从外部存储设备中恢复数据到存储器中。这样可以提高数据备份和恢复的速度,并降低对CPU的负载。
④高速通信:DMA可以用于在嵌入式系统中实现高速通信,例如通过网络接口卡(如以太网)、串行接口(如UART、SPI)等传输数据。DMA可以在数据传输时,绕过CPU直接在外设和存储器之间进行传输,提高数据传输的速度和效率。
2、STM32 DMA基础
在STM32F4xx官方参考手册文档中,有对DMA控制器的讲解说明,内容十分的丰富,在这一部分中将其中较为核心基础的内容进行了梳理。
直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。
DMA搬运的三种模式:
①、内存--->内存
②、内存--->外设
③、外设--->内存
DMA1:外设--->内存,内存--->外设
DMA2:外设--->内存,内存--->外设,内存--->内存
DMA2比DMA1多了一个内存到内存的处理功能,因此,如果需要实现内存到内存的DMA搬运模式,必须使用DMA2。
**流:**是数据传输的一条链路,每个DMA控制器有8条独立的数据流,每次传输的最大数据量为65535,如果数据单位为字的话,可以一次传输256KB。
**通道:**每个数据流有8个通道选择,每个通道对应不同的DMA请求。
同一个数据流只能使用一个通道,同一个DMA控制器可以使用多个数据流。
**仲裁器:**仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。
仲裁器优先级管理分为软件优先级管理和硬件优先级管理。多个数据流到来时,仲裁器会分为两个阶段进行仲裁,第一个阶段为软件优先级管理,在其编程时设置数据流的优先级,第二个阶段为硬件阶段,由数据流的硬件编号决定。
**FIFO:**源和目标之间的一个数据中转站。FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址。
一个FIFO为4个字的大小,每个数据流有4字的FIFO,DMA配置为存储器---存储器模式时,FIFO由硬件开启,软件控制无效。且DMA配置为存储器到存储器模式时,不能设置为循环传输。
如图所示可知,DMA1、DMA2控制器挂载在AHB1总线下
由STM32F4xx官方参考手册可知,对于STM32F407系列,其嵌入式SRAM的起始映射的地址从0x2000 0000开始。
由STM32F4xx官方参考手册可知,对于STM32F407系列,其嵌入式FLASH的起始映射的地址从0x0800 0000开始。
3、STM32 编程实现DMA
在这一部分的讲解梳理中,将DMA配置为了内存--->内存 模式,如果是要配置为内存--->外设 或外设--->内存, 修改 DMA结构体的DMA_InitStructure.DMA_DIR参数 即可**。**其它参数的配置的思路大致相同。
cpp
//STM32F407中const修饰的全局存储到FLASH中
const uint32_t src_const_buf[32] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80
};
uint32_t dst_buf[32] = {0};
因为DMA配置结构体中,需要填入外设/内存的地址信息,所以通过打印数组的内存地址,查看数据的存储位置,以确定外设/内存的地址。由上述STM32F4xx的官方参考手册嵌入式FLASH和SRAM部分说明和打印出来的内存地址可知,const修饰的全局变量被存储于嵌入式FLASH中。
DMA数据传输测试,DMA内存到内存模式代码实现效果如下图所示,通过打印的数据也可确定,DMA高速存储成功且数据无误。
实现DMA存储器到存储器,高速数据传输模式的参考代码Demo如下:
cpp
#include "stm32f4xx.h"
#include "stm32f4xx_dma.h"
#include <stdio.h>
//const修饰的全局存储到FLASH中
const uint32_t src_const_buf[32] = {
0xAAAAAAAA,0xBBBBBBBB,0xCCCCCCCC,0xDDDDDDDD,
0xEEEEEEEE,0xFFFFFFFF,0x10000000,0x11111111,
0x22222222,0x33333333,0x44444444,0x55555555,
0x66666666,0x77777777,0x88888888,0x99999999,
0x10000000,0x10000000,0x10000000,0x10000000,
0x11111111,0x11111111,0x11111111,0x11111111,
0x22222222,0x22222222,0x22222222,0x22222222,
0x33333333,0x33333333,0x33333333,0x33333333
};
uint32_t dst_buf[32] = {0};
void DMA_Config(void);
int8_t buf_cmp(uint32_t *pbuf1, uint32_t *pbuf2, int len);
int main(void)
{
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
USART1_Init(115200);
printf("starting...\r\n");
printf("const addr:%X\r\n", (uint32_t)src_const_buf);
printf("addr:%X\r\n", (uint32_t)dst_buf);
DMA_Config();
while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE); //传输完成后,DMA会进行复位
if(buf_cmp((uint32_t *)src_const_buf, dst_buf, 32)==0)
{
printf("DMA传输完成,且数据无误...\r\n");
printf("src:\r\n");
for(int i = 0; i<32; i++)
{
printf("%0X\t", src_const_buf[i]);
}
printf("\r\n");
printf("dst:\r\n");
for(int i = 0; i<32; i++)
{
printf("%0X\t", dst_buf[i]);
}
printf("\r\n");
return 0;
}else{
printf("DMA数据传输故障...\r\n");
return -1;
}
}
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
volatile uint32_t Timeout = 10000;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);//内存到内存的传送,使用DMA2
DMA_DeInit(DMA2_Stream0); //初始化DMA的寄存器到复位状态
while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE); //确保DMA复位完成
//配置DMA流
DMA_InitStructure.DMA_Channel = DMA_Channel_0; //启用DMA通道0
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src_const_buf; //FLASH中的数据地址
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)dst_buf;
//SRAM的数据地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory; //存储器到存储器模式
DMA_InitStructure.DMA_BufferSize = (uint32_t)32;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //FLASH地址自增使能
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //SRAM地址自增
//SRAM地址自增使能
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure); //DMA初始化
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA2_Stream0, ENABLE); //DMA使能
while(DMA_GetCmdStatus(DMA2_Stream0) != ENABLE && (Timeout-->0));
if(Timeout == 0)
{
while(1);
}
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
int8_t buf_cmp(uint32_t *pbuf1, uint32_t *pbuf2, int len)
{
int cnt = 0;
for(int i=0; i<len; i++)
{
if(pbuf1[i] != pbuf2[i])
{
return -1;
}
}
return 0;
}
void DMA2_Stream0_IRQHandler(void)
{
//DMA2通道0数据流传输完成中断
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) == SET)
{
//清除DMA传输完成中断标志位
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
//在此可根据项目需求增加DMA处理完时的操作
}
}