1.前言
在单片机中,任何处理离不开对CPU的申请,可以说CPU要处理一个单片机中大大小小的操作请求。正常来说每一次数据搬运都需要对CPU进行申请,这种操作上的冗余很容易降低CPU的处理效率。所以这类搬运数据的繁琐操作就交给了CPU的小秘书DMA来分担,从而让CPU有时间去处理更为重要的指令进而提高整个系统的运行效率。
2.DMA概述
2.1 DMA的概念
DMA(Direct Memory Access)—直接存储器访问,是单片机的一个外设。DMA用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的CPU 资源可供其它操作使用。通常来说,DMA的传输方向有三个:外设到存储器,存储器到外设,存储器到存储器。
2.2 DMA特性
● 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
● 仅支持 32 位访问的 AHB 从编程接口
● 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
● 每个数据流有单独的四级 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接模式:
— FIFO 模式:可通过软件将阈值级别选取为 FIFO 大小的 1/4、1/2、3/4或是整个FIFO的大小。
— 直接模式:每个 DMA 请求会立即启动对存储器的传输。当在直接模式(禁止 FIFO)下将 DMA 请求配置为以存储器到外设模式传输数据时,DMA 仅会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。
● 通过硬件可以将每个数据流配置为:
— 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
— 也支持在存储器方双缓冲的双缓冲区通道
● 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
● DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
● 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
● 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA 请求
● 要传输的数据项的数目可以由 DMA 控制器或外设管理:
— DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程
— 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
● 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用
● 对源和目标的增量或非增量寻址
● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设 FIFO 大小的一半
● 每个数据流都支持循环缓冲区管理
● 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
2.3 DMA请求映射表
2.4 DMA框图
(1) STM32F407中资源情况:
2个DMA控制器,每个控制器下有8个数据流,每个数据流下有8个通道(同一时间只能选择其中之一)。
(2)仲裁器是DMA的核心,主要用于处理DMA的优先级。
(3)每个通道用于处理不同DMA请求,DMA请求是由外设产生的。
(4)DMA2:可以实现外设到存储器,存储器到外设,存储器到存储器的传输。
DMA1:可以实现外设到存储器,存储器到外设,不能实现存储器到存储器的传输。
3.DMA的使用代码示例
//1:使用接收空闲中断+DMA 0:仅使用接收中断
#define USARTx_RX_BY_IDLE_DMA 1
//DMA2初始化函数
void DMA2_Init(void)
{
//确保USART1初始化
//开启USART1的DMA请求
//开启DMA2时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
if(DMA_GetCmdStatus(DMA2_Stream7) == ENABLE)
{
DMA_Cmd(DMA2_Stream7,DISABLE);
while(DMA_GetCmdStatus(DMA2_Stream7) == ENABLE);
}
if(DMA_GetCmdStatus(DMA2_Stream5) == ENABLE)
{
DMA_Cmd(DMA2_Stream5,DISABLE);
while(DMA_GetCmdStatus(DMA2_Stream5) == ENABLE);
}
//配置DMA2
//USART1--TX
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_BufferSize = sizeof(data);//数据长度
DMA_InitStruct.DMA_Channel = DMA_Channel_4;//DMA通道4
DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;//搬运方向
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Enable;//使能FIFO管道
DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;//管道阈值为上限的1/4(即4字节)
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)data;//存储器基地址
DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器是否突发增量传输
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//数据宽度
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器是否增量寻址
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;//DMA单次搬运
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);//外设基地址
DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设是否突发增量传输
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA2_Stream7, &DMA_InitStruct);
//USART1--RX
DMA_InitStruct.DMA_BufferSize = sizeof(data);//数据长度
DMA_InitStruct.DMA_Channel = DMA_Channel_4;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;//搬运方向
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)data;
DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//数据宽度
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);
DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA2_Stream5, &DMA_InitStruct);
//使能DMA2
DMA_Cmd(DMA2_Stream7, DISABLE);
DMA_Cmd(DMA2_Stream5, ENABLE);
}
//USART1中断初始化函数
void USART1_ITInit(u8 PreemptionPriority, u8 SubPriority)
{
#if USARTx_RX_BY_IDLE_DMA
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
#else
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
#endif
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = PreemptionPriority;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = SubPriority;
NVIC_Init(&NVIC_InitStruct);
}
//USART1中断处理函数
void USART1_IRQHandler(void)
{
#if USARTx_RX_BY_IDLE_DMA
if(USART_GetITStatus(USART1, USART_IT_IDLE) == SET)
{
printf("Receive data = %s", data);
//清除标志位
USART_ReceiveData(USART1);
//关闭USART1_RX的DMA
DMA_Cmd(DMA2_Stream5, DISABLE);
//重置想要接收的数据量
DMA_SetCurrDataCounter(DMA2_Stream5, sizeof(data));
//打开USART1_RX的DMA
DMA_Cmd(DMA2_Stream5, ENABLE);
}
#else
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
USART1_RecString(data);
t = atoi(data);
printf("Receive data = %s\r\n", data);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#endif
}