stm32的dma数组格式(11-玩转STM32之直接存储器访问)
在上一章节有用到DMA控制器,在这边文章进行一个说明,看完这章可以回头看看10 玩转STM32之通用同步异步收发器(USART)将会有一定的了解。什么是DMA,DMA的作用又是什么以及控制方式是什么,本章将会对这方面进行讲解。
1、什么是DMADMA就是 直接存储器访问,英文的全名是Direct Memory Access。
2、DMA的作用他的作用就是为MCU减轻负担的。
这里说一下具体是怎么样的。1- DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。2-DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。根据下图可以直观的知道DMA的控制方式。DMA有三种控制方式,图1是一个总的控制,图二是三种不同的控制。
图21-外设-->存储器
2-存储器-->外设
3-存储器-->存储器
3、对于STM32F429来说:
① STM32F429最多有2个DMA控制器(DMA1和DMA2 );② 2个DMA控制器总共有16个数据流(每个控制器8个);③每个DMA控制器都用于管理一个或者多个外设的存储器访问请求;④每个数据流总共可以有多达8个通道(或请求),每个通道都有一个仲裁器,用于处理DMA请求间的优先级。
DMA的主要特性11.1.1 结构
STM32F4全系列的DMA结构如下图所示:
从图中可以看出,每个DMA有两个AHB主总线,一个是用来访问存储器,一个是用来进行外设访问的。这里说下主要的,要想实现存储器到存储器之间的传输,AHB外设也必须支持访问存储器,所以在这里只有DMA2可以实现。
11.2 DMA控制器功能11.2.1 DMA 事务DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。每个 DMA 传输包含三项操作:
①数据源:通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。②输出目的地:通过DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元。 ③数据量:DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
1.传输模式源传输和目标传输在整个4GB区域(地址在0X00000000和0XFFFFFFFF之间)都可以寻址外设和存储器。传输方向使用DMA_SxCR中的DIR[1:0]位进行配置,有3种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。DMA传输模式如下所示。
内部结构体:
2.可编程的数据量
源和目标数据的数据宽度可以通过DMA_SxCR的PSIZE和MSIZE位(可以是8位,16位、32位)编程。
3.指针增量通过设置DMA_SxCR中的PINC和MINC标志位,外设和存储器的指针在每次传输后可以有选择地完成自动增量。通过单个寄存器访问外设源或目标数据时,一般需要禁止通增模式。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值,增量值取决于所选的数据宽度(1、2或4)。第一个传输的地址是存放在DMA SxMOAR和DMA SxPAR中的地址。在传输过程中,这些寄存器保持它们初始的数值,软件不能改变和读出当前正在传输的地址。当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作。要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA SxNDTR中重新写入传输数目。在循环模式下,最后一次传输结束时,DMA_SxNDTR的内容会自动地被重新加载为其初始数值,内部的当前存储器和外设地址寄存器也被重新加载为DMA_SxMOAR和DMA_SxPAR设定的初始基地址。
11.2.2 数据流11.2.3 通道
DMA映射关系:
11.2.4 仲裁
仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8 个DMA数据流请求管理,并启动外设/存储器访问序列。优先级管理分为两个阶段: ①软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别: — 非常高优先级 — 高优先级 — 中优先级 — 低优先级 ②硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4。
11.2.5 循环模式
循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。可以使用 DMA_SxCR寄存器中的 CIRC 位使能此特性。
当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。
11.2.6 单次和突发模式
DMA控制器可以产生单次传输或4个、8个和16个节拍(8位、16位或32位,由PSIZE和MSIZE决定)的突发传输。
例如,如果将PSIZE和MSIZE都设置为01(将数据传输的宽度设置为16位),那么启动一次4个节拍的突发传输,DMA控制器会再突发传输4×16位(8字节)的数据,而且在这一突发传输过程(4个半字传输过程)中,不释放对总线的控制权。
11.2.7 FIFO①FIFO 用于在源数据传输到目标之前临时存储这些数据。②FIFO的大小是4*4字节,传输的过程为:源地址→AHB主端口x→FIFO→AHB主端口y→目的地址。是否使用FIFO门限,可以区别FIFO模式和Direct模式。③FIFO常用于DMA控制器和Memories之间的缓冲。Memory-to-Memory,是必须使用FIFO模式的。④FIFO模式和Direct模式的区别:FIFO模式——从源地址来的数据先放在FIFO中,达到门限后,再根据目的地址的数据宽度送出;Direct模式——数据放在FIFO,有DMA请求就送走,和门限值无关。不可突发传输
11.2.8 DMA中断对于每个 DMA 数据流,可在发生以下事件时产生中断: ①达到半传输 ②传输完成 ③传输错误④FIFO 错误(上溢、下溢或 FIFO 级别错误) ⑤直接模式错误
11.2.9 DMA数据流配置过程
(1)复位数据流使能位EN,并检测是否设置成功。(2)设置外设端寄存器地址(3)设置存储器地址(4)设置DMA传输数据量(5)选择数据流使用的通道(6)设置数据流优先级(7)配置FIFO的使用情况(8)配置数据传输方向、外设和存储器增量/固定模式、单独/突发事务、外设和存储器数据宽度、循 环模式、双缓冲区模式、传输完成一半/全部完成,和/或DMA_SxCR中错误的中断。(9)通过将DMA_SxCR中的EN位置1激活数据流。如果使用到了外设的话,还需要使能相应外设的DMA请求功能。
11.3 DMA典型应用步骤及常用库函数11.3.1 DMA典型应用步骤以配置串口1的发送为DMA方式为例
> /**************先将串口功能配置好!****************/
> ① 使能DMA时钟
RCC_AHB1PeriphClockCmd();
② 复位DMA配置,并检测何时EN位变为0,可以对其进行初始化
DMA_DeInit();
③ 初始化DMA通道参数
DMA_Init();
④ 使能DMA流
DMA_Cmd();
⑤ 查询DMA的EN位,确保数据流就绪,可以配置
DMA_GetCmdStatus();
⑥ 使能串口DMA发送,串口DMA使能函数
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
DMA配置涉及到的一下参数:优先级数据传输方向存储器/外设 数据宽度存储器/外设 地址是否增量循环模式数据传输量
10.3.2 常用库函数1、通用函数
/*stm32f4xx_dma.c
stm32f4xx_dma.h*/
①/*初始化DMA工作方式 */
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);
②/*使能或禁止DMA数据流 */
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState);
③/*设置DMA传输数据量 */
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);
④/*获取当前DMA数据量 */
uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
⑤/*获取DMA数据流是否能用 */
FunctionalState DMA_GetCmdStatus(DMA_Stream_TypeDef* DMAy_Streamx);
⑥/*清除DMA数据流相应标志位 */
void DMA_ClearFlag(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_FLAG);
⑦/*设置DMA数据流中断 */
void DMA_ITConfig(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT, FunctionalState NewState);
⑧/*获取DMA数据流中断标志 */
ITStatus DMA_GetITStatus(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT);
⑨/*清除DMA数据流中断标志 */
void DMA_ClearITPendingBit(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT);
2、常用的外设DMA使能库函数
①void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq,FunctionalState NewState);
②void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
③void DAC_DMACmd(uint32_t DAC_Channel, FunctionalState NewState);
④void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
⑤void SDIO_DMACmd(FunctionalState NewState);
⑥void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
⑦void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase,uint16_t TIM_DMABurstLength)
void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource,FunctionalState NewState);
使用DMA实现两块存储器之间数据搬移。
要想实现存储器与存储器之间的数据DMA传输,只能使用DMA2控制器的数据流0(DMA2_Stream0)。
1.编程要点
(1)使能DMA2的工作时钟。(2)复位DMA2_Stream0,并判断何时复位成功。配置DMA前,需要先禁止DMA。如果上次DMA操作没有结束,需要进行判断等待。(3)根据需求,初始化DMA2_Stream0。(4)使能DMA2_Stream0。(5)判断DMA2_Stream0是否使能成功
2.主程序
const uint32_t SRC_Buffer[16]= {
0x11111111, 0x22222222, 0x33333333, 0x44444444,
0x55555555, 0x66666666, 0x77777777, 0x88888888,
0x99999999, 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc,
0xdddddddd, 0xeeeeeeee, 0xffffffff, 0x5a5a5a5a }; //DMA传输源存储块
uint32_t DST_Buffer[16]; //DMA传输目标存储块
int main(void)
{
DMA_Config(); //DMA初始化,并启动DMA传输
/*等待DMA传输完成*/
while(DMA_GetFlagStatus(DMA_STREAM,DMA_FLAG_TCIF0)==DISABLE);
/*比对DMA传输结束后,源存储单元和目标存储单元内容是否一致*/
if(memcmp(SRC_Buffer, DST_Buffer, 16)==0) //完全一致,使用memcmp需要包含string.h
{
//传输成功,没有数据错误
}
else //传输过程中有数据出错
{
//出错处理
}
while (1);
}
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/*-------------------第1步--------------------*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //使能DMA2时钟
/*-------------------第2步--------------------*/
DMA_DeInit(DMA2_Stream0); //复位,禁止DMA2_Stream0
while (DMA_GetCmdStatus(DMA2_Stream0)!= DISABLE); //确保DMA数据流禁止成功
/*-------------------第3步--------------------*/
/*开始初始化DMA2_Stream0*/
DMA_InitStructure.DMA_Channel=DMA_Channel_0; //选择通道
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t) SRC_Buffer; //数据源地址
DMA_InitStructure.DMA_Memory0BaseAddr=(uint32_t)DST_Buffer; //目标地址
DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToMemory; //存储器到存储器模式
DMA_InitStructure.DMA_BufferSize =16;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//使能外设端自动递增功能
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable; //使能存储器端自动递增功能
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word; //32位宽度
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Word; //32位宽度
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal; //正常模式,不循环,只传输一次
DMA_InitStructure.DMA_Priority=DMA_Priority_High; //设置DMA2_Stream优先级为高
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable; //禁用FIFO模式
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full; //此时这一参数无用
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single; //单次模式
DMA_InitStructure.DMA_PeripheralBurst=DMA_MemoryBurst_Single; //单次模式
DMA_Init(DMA2_Stream0, &DMA_InitStructure); //完成DMA2_Stream0配置
/*-------------------第4步--------------------*/
DMA_Cmd(DMA2_Stream0, ENABLE); //使能DMA2_Stream0,启动DMA数据传输
/*-------------------第5步--------------------*/
while(DMA_GetCmdStatus(DMA2_Stream0) != ENABLE) //检测DMA数据流是否有效
{
}
}
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com