STM32学习笔记(7)——DMA直接存储器访问
一、DMA简介
可参考STM32中文参考手册第10章DMA控制器。
直接存储器存取(Direct Memory Access,DMA) 用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
DMA 是干什么用的呢?一般来说,如果没有DMA ,CPU如果想从外设“搬运”数据到存储器(Peripheral to Memory,P2M),或者从一个存储器到另一个存储器(Memory To Memory,M2M),数据必须要经过CPU,这就需要CPU 去亲力亲为。在运行过程中,CPU经常要为这种琐碎的事情被打断,很影响效率。所以,就有了DMA。现在,CPU 就不用那么麻烦了,CPU 只需要“叫”一声DMA,说你帮我去搬一下数据,从这搬到那,DMA 就会代替 CPU 完成这份工作,再也不用 CPU 自己去做了。
在刚学习 STM32 的时候,我们就接触过 STM32 的系统架构,如下图所示。可以看到,STM32 一共有两个DMA控制器:DMA1和DMA2(其中DMA2控制器只存在于大容量和互联型产品中),通过总线矩阵和DMA总线与各个不同的外设连接。这些外设可以向DMA发出DMA请求,由仲裁器根据请求优先级确定响应的顺序(SPI/I2S3、UART4、TIM5、TIM6、TIM7和DAC的DMA请求仅存在于大容量产品和互联型产品)。
二、DMA功能框图
下面图片来自STM32中文参考手册10.2节DMA主要特性。实际上该功能框图是将上面的系统架构图详细化了。为了方便讲解,我们(火哥)将该图分为三个部分讲解。
1. DMA请求
本节参考STM32中文参考手册10.3.7节DMA请求映像。
在 STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中),12个独立的可配置的通道(请求),DMA1有7个通道。DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。
对于DMA1控制器,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。同样,对于DMA2控制器,通过逻辑或输入到DMA1控制器,也只能有一个请求有效。至于当多个请求同时到来的情况,是我们下面要讲的仲裁器。
下面图是DMA1和DMA2的请求映射表:
DMA1:
DMA2:
2. 通道
本节参考STM32中文参考手册10.3.3节DMA通道。
每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据量是可编程的,最大达到65535。包含要传输的数据项数量的寄存器,在每次传输后递减。
每个通道都有3个事件标志,这3个事件标志逻辑或成为一个单独的中断请求(位于DMA 中断状态寄存器(DMA_ISR
)):
- DMA半传输
- DMA传输完成
- DMA传输出错
通过设置DMA_CCRx
寄存器中的PINC
(外设地址)和MINC
(寄存器地址)标志位,外设和存储器的指针在每次传输后可以有选择地完成自动增量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值,增量值取决与所选的数据宽度为1、2或4。递减模式则是每次传输后地址递减。
3. 仲裁器
本节参考STM32中文参考手册10.3.2节仲裁器。
当多个DMA请求来临时,需要仲裁器来分配请求的响应优先级。软件优先级分为4个,可在DMA_CCRx
寄存器中设置:
- 最高优先级(
DMA_Priority_VeryHigh
) - 高优先级(
DMA_Priority_High
) - 中等优先级(
DMA_Priority_Medium
) - 低优先级(
DMA_Priority_Low
)
如果2个请求有相同的软件优先级,则比较硬件优先级:较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4,则通道2的DMA请求先响应,通道4的DMA请求则被挂起。在寄存器DMA_CCRx:PL[1:0]
可以配置。
二、DMA的结构体定义和库函数定义
1. DMA初始化结构体
在头文件stm32f10x_dma.h中定义了DMA初始化结构体:
typedef struct
{
// 1. 解决:数据从哪来、到哪去的问题
uint32_t DMA_PeripheralBaseAddr; /* 外设基地址 */
uint32_t DMA_MemoryBaseAddr; /* 存储器基地址 */
uint32_t DMA_DIR; /* 传输方向 */
// 2. 解决:数据要传多少,传的单位是什么的问题
uint32_t DMA_BufferSize; /* 传输数目 */
uint32_t DMA_PeripheralInc; /* 外设地址是否为增量模式 */
uint32_t DMA_MemoryInc; /* 存储器地址是否为增量模式 */
uint32_t DMA_PeripheralDataSize; /* 外设数据宽度(字节、半字、全字,下同) */
uint32_t DMA_MemoryDataSize; /* 存储器数据宽度 */
// 3. 解决:怎么传输、什么时候传输完成的问题
uint32_t DMA_Mode; /* 选择工作模式:正常传输模式和循环传输模式 */
uint32_t DMA_Priority; /* 配置优先级 */
uint32_t DMA_M2M; /* 是否为M2M */
}DMA_InitTypeDef;
2. DMA库函数
在源文件stm32f10x_dma.c中定义了DMA库函数:
// 有关初始化和复位的函数
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 有关中断的函数
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
// 有关数据的函数
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
// 有关获得状态的函数
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
void DMA_ClearFlag(uint32_t DMAy_FLAG);
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
3. DMA配置流程
DMA_InitTypeDef DMA_InitStructure;
/* 1. 配置DMA时钟(AHB总线)*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 2. 设置外设基地址 */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
/* 3. 设置存储器基地址 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
/* 4. 设置传输方向 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/* 5. 设置传输数目 */
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
/* 6. 设置外设地址访问是否为增量模式 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
/* 7. 设置存储器地址访问是否为增量模式 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/* 8. 设置外设数据宽度 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
/* 9. 设置存储器数据宽度 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
/* 10. 设置工作模式 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
/* 11. 配置DMA优先级 */
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
/* 12. 配置是否为M2M模式 */
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
/* 13. 初始化DMA通道 */
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
/* 14. 清除发送标志 */
DMA_ClearFlag(DMA1_FLAG_TC6);
/* 15. 使能DMA通道 */
DMA_Cmd(DMA1_Channel6, ENABLE);
三、DMA应用简单实例
1. 存储器到存储器(M2M)
- 在FLASH中定义好要传输的数据,在SRAM中定义好用来接收FLASH数据的变量。
- 初始化DMA,主要是配置DMA初始化结构体。
- 编写比较函数。
- 编写main函数。
/* ===== dma.h =====*/
#ifndef __DMA_H
#define __DMA_H
#include "stm32f10x_dma.h"
#define BUFFER_SIZE 32
void M2M_DMA_Config(void);
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength);
#endif /* __DMA_H */
/* ===== dma.c =====*/
#include "dma.h"
#include "usart.h"
// 待传输的数据,存储在FLASH中
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
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};
// 接收数据的变量,存储在SRAM
uint32_t aDST_Buffer[BUFFER_SIZE];
//M2M
void M2M_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
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_M2M = DMA_M2M_Enable;
// 随便找一个通道都行
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC6);
DMA_Cmd(DMA1_Channel6, ENABLE);
}
/* 比较两者数据是否一致 */
uint8_t Buffercmp(const uint32_t* pBuffer,
uint32_t* pBuffer1, uint16_t BufferLength)
{
/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer != *pBuffer1)
{
/* 对应数据源不相等马上退出函数,并返回0 */
return 0;
}
/* 递增两个数据源的地址指针 */
pBuffer++;
pBuffer1++;
}
/* 完成判断并且对应数据相对 */
return 1;
}
/* ===== main.c ===== */
#include "stm32f10x.h"
#include "dma.h"
#include "led.h"
#include "delay.h"
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
int main(void)
{
uint8_t status = 0;
delay_init();
M2M_DMA_Config();
LED_Init();
LED0_ON;
LED1_OFF;
delay_ms(500);
// 检测是否传输完成,若是则继续执行
while( DMA_GetFlagStatus(DMA1_FLAG_TC6) == RESET );
// 比较两者数据是否一致
status = Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
// 根据是否数据一致,两只LED做出相应的动作
if(status == 0)
{
LED0_ON;
LED1_OFF;
}
else
{
LED0_OFF;
LED1_ON;
}
}
2. 存储器到USART串口(M2P)
- 初始化串口(从现有的例程移植过来)
- 配置DMA初始化结构体。
- 编写主函数(开启串口发送DMA请求)。
usart.c 和 usart.h 略,这两个文件的具体内容可见上一次笔记。
/* ===== dma.h ===== */
#ifndef __DMA_H
#define __DMA_H
#include "stm32f10x_dma.h"
#define SENDBUFFER_SIZE 500
void USART_DMA_Config(void);
#endif /* __DMA_H */
/* ===== dma.c ===== */
#include "dma.h"
#include "usart.h"
uint8_t SendBuffer[SENDBUFFER_SIZE];
void USART_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART1_DR_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = SENDBUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 根据上面内容可知,必须是DMA1通道4
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC4);
DMA_Cmd(DMA1_Channel4, ENABLE);
}
/* ===== main.c ===== */
#include "stm32f10x.h"
#include "dma.h"
#include "usart.h"
#include "led.h"
#include "delay.h"
extern uint8_t SendBuffer[SENDBUFFER_SIZE];
int main(void)
{
uint16_t i = 0;
delay_init();
LED_Init();
// 给将要传输的数据赋值(500个p)
for(i = 0; i < SENDBUFFER_SIZE; i++)
{
SendBuffer[i] = 'p';
}
USART_Config();
USART_DMA_Config();
// 注意需要连接usart和dma,并使能
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
while(1)
{
LED1 = !LED1;
delay_ms(500);
}
}