STM32学习心得二十八:DMA基本原理及相关实验

记录一下,方便以后翻阅~
主要内容:
1) DMA基本原理;
2) 相关寄存器及库函数介绍;
3) 相关实验代码解读。
实验功能:系统启动后,通过按键KEY0控制串口1以DMA方式发送数据,按下KEY0,就开始DMA传送,同时,串口调试助手可以收到DMA发送的内容。
官方资料:《STM32中文参考手册V10》第10章——DMA控制器
1. DMA(Direct MemoryAccess-直接存储器访问)基本原理
1.1 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。
DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
简要总结:提高数据传输的速度,为CPU减负。
1.2 STM32最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA1有7个通道。DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。
还有一个仲裁起来协调各个DMA请求的优先权。
1.3 DMA框图如下所示
在这里插入图片描述
DMA1有7个通道,DMA2有5个通道,仲裁器来处理优先级,DMA1和DMA2的请求来自APB1和APB2的外设。DMA总线用来访问存储器。
1.4 DMA特点
1.4.1 每个通道都直接连接专用的硬件DMA请求,都支持软件触发,这些通过软件来配置;
1.4.2 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求0优先于请求1,依此类推) ;
1.4.3 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
1.4.4 支持循环的缓冲器管理;
1.4.5 每个通道都有3个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
1.4.6 外设和存储器,存储器和外设的传输 ,存储器和存储器间的传输;
1.4.7 闪存、SRAM、外设的SRAM、APB1 APB2和AHB外设均可作为访问的源和目标;
1.4.8 可编程的数据传输数目:最大为65536。
2. DMA控制器
在这里插入图片描述
在这里插入图片描述
3. DMA处理
在这里插入图片描述
针对DMA_CCRx寄存器,位4,数据传输方向:
在这里插入图片描述
4. 仲裁器
在这里插入图片描述
针对DMA_CCRx寄存器,位[12~13],PL通道优先级:
在这里插入图片描述
5. 通道
在这里插入图片描述
6. 指针增量
在这里插入图片描述
7. 循环模式
在这里插入图片描述
在这里插入图片描述
8. 通道传输数据量
在这里插入图片描述
9. 中断
在这里插入图片描述
10. 通道配置过程
在这里插入图片描述
11. DMA配置参数
1.1 通道;
1.2 优先级;
1.3 数据传输方向;
1.4 存储器/外设,数据宽度;
1.5 存储器/外设,地址是否增量;
1.6 循环模式;
1.7 数据传输量。
12. 相关库函数
12.1 官方库函数为stm32f10x_dma.c和tm32f10x_dma.h;
12.2 常用库函数

1)     void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
2)     void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
3)     void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
4)     void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
5)     uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
6)     FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
7)     void DMA_ClearFlag(uint32_t DMAy_FLAG);
8)     ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
9)     void DMA_ClearITPendingBit(uint32_t DMAy_IT);

12.3 常用的外设DMA使能库函数

1)    void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
2)    void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
3)    void DAC_DMACmd(uint32_t DAC_Channel, FunctionalState NewState);
4)    void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
5)    void SDIO_DMACmd(FunctionalState NewState);
6)    void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
7)    void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase, uint16_t TIM_DMABurstLength);
8)    void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState);

12.4 void DMA_Init(DMA_CHx, &DMA_InitStructure)函数第二个入口参数结构体解读

typedef struct
{
uint32_t DMA_PeripheralBaseAddr;  //外设基地址//
uint32_t DMA_MemoryBaseAddr;      //存储器基地址//
uint32_t DMA_DIR;                 //数据传输方向//
uint32_t DMA_BufferSize;          //通道传输数据量//
uint32_t DMA_PeripheralInc;       //外设增量模式//
uint32_t DMA_MemoryInc;           //存储器增量模式//
uint32_t DMA_PeripheralDataSize;  //外设数据宽度//
uint32_t DMA_MemoryDataSize;      //存储器数据宽度//
uint32_t DMA_Mode;                //模式:是否循环//
uint32_t DMA_Priority;            //优先级//
uint32_t DMA_M2M;                 //是否存储器到存储器方式//     
}DMA_InitTypeDef;

13. DMA程序配置过程
13.1 使能DMA时钟:

RCC_AHBPeriphClockCmd();

13.2 初始化DMA通道参数:

DMA_Init();

13.3 使能串口DMA发送,串口DMA使能函数:

USART_DMACmd();

13.4 使能DMA1通道,启动传输:

DMA_Cmd();

13.5 查询DMA传输状态:

DMA_GetFlagStatus();

13.6 获取/设置通道当前剩余数据量:

DMA_GetCurrDataCounter();
DMA_SetCurrDataCounter();

14. 相关实验代码解读
14.1 dma.h头文件代码解读

#ifndef __DMA_H
#define __DMA_H    
#include "sys.h"  
//申明两个函数//
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);  //配置DMA1_CHx,4个入口参数//
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);                              //使能DMA1_CHx//     
#endif

14.2 dma.c文件代码解读

#include "dma.h"
//申明一个结构体//
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;                   //保存DMA每次数据传送的长度//
//编写MYDMA_Config函数,4个入口参数,包括:DMA_CHx:DMA通道CHx;cpar:外设地址;cmar:存储器地址;cndtr:数据传输量// 
//该函数固定的部分包括:从存储器->外设模式;8位数据宽度;存储器增量模式(外设地址不增量)//
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
 //第一步,使能DMA传输//
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
 DMA_DeInit(DMA_CHx);                                       //将DMA的通道1寄存器重设为缺省值//
 DMA1_MEM_LEN=cndtr;
 //第二步,初始化DMA_Init函数//
 DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;           //DMA外设基地址,其中一个入口参数//
 DMA_InitStructure.DMA_MemoryBaseAddr = cmar;               //DMA内存基地址,其中一个入口参数//
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;         //数据传输方向,从内存读取发送到外设,设为固定//
 DMA_InitStructure.DMA_BufferSize = cndtr;                  //DMA通道的DMA缓存的大小,其中一个入口参数//
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;         //外设地址寄存器不变,设为固定//
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                  //存储器增量模式,设为固定//
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位,设为固定//
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;          //数据宽度为8位,设为固定//
 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                            //不执行循环模式,设为固定//
 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                    //中优先级,随便设,设为固定// 
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                             //非存储器到存储器模式,设为固定//
 DMA_Init(DMA_CHx, &DMA_InitStructure);    
} 
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
 DMA_Cmd(DMA_CHx, DISABLE );                           //关闭USART1 TX DMA1 所指示的通道//      
 DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);         //DMA通道的DMA缓存的大小//
 DMA_Cmd(DMA_CHx, ENABLE);                             //使能USART1 TX DMA1 所指示的通道// 
} 

14.3 main.c文件代码解读

#include "led.h"
#include "key.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"  
#include "dma.h"
#define SEND_BUF_SIZE 8200 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK WarShip STM32F1 DMA 串口实验"};
int main(void)
 {  
 u16 i;
 u8 t=0;
 u8 j,mask=0;
 // float pro=0;                                 //进度,用不到//
 delay_init();                                   //延时函数初始化//   
 uart_init(115200);                              //串口初始化为115200//
 LED_Init();                                     //初始化与LED连接的硬件接口//
 KEY_Init();                                     //按键初始化//  
 //DMA1通道4,外设为串口1,针对USART_DR寄存器,存储器为SendBuff,长度SEND_BUF_SIZE//  
 MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);
 //显示提示信息 
 j=sizeof(TEXT_TO_SEND);           //计算"ALIENTEK WarShip STM32F1 DMA 串口实验"这段字符串的字节长度,远小于8200//
 //填充数据到SendBuff//
 for(i=0;i<SEND_BUF_SIZE;i++)      //当0≤i<8200时,执行//  
    {
  if(t>=j)                         //当t≥j时,执行//
  {
   if(mask)                  
   {
    SendBuff[i]=0x0a;              //如果mask=1,则SendBuff[i]=0x0a,换行的意思//
    t=0;
   }else 
   {
    SendBuff[i]=0x0d;              //如果mask=0,则SendBuff[i]=0x0d,回车的意思//
    mask++;
   } 
  }else                            //当t<j时//
  {
   mask=0;
   SendBuff[i]=TEXT_TO_SEND[t];    //复制TEXT_TO_SEND语句//
   printf("\r\nDMA DATA:%d\r\n",SendBuff[i]); 
   t++;
   }        
    }     
 i=0;                              //i至0//
 while(1)
 {
  t=KEY_Scan(0);
  if(t==KEY0_PRES)                                    //如果KEY0按下,执行//
  {
   printf("\r\nDMA DATA:\r\n");      
   USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);       //使能串口1的DMA发送//      
   MYDMA_Enable(DMA1_Channel4);                       //开始一次DMA传输//   
      //实际应用中,传输数据期间,可以执行另外的任务//
      while(1)
      {
    if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET)       //判断通道4传输是否完成//
    {
     LED1=1;
     DMA_ClearFlag(DMA1_FLAG_TC4);                    //清除通道4传输完成标志//
     break;                                           //推出当前循环//
      }
     //pro=DMA_GetCurrDataCounter(DMA1_Channel4);     //得到当前还剩余多少个数据//
     //pro=1-pro/SEND_BUF_SIZE;                       //得到百分比//   
     //pro*=100;                                      //扩大100倍//
        LED1=!LED1;                                 
      }       
  }
  i++;
  delay_ms(10);
  if(i==20)
  {
   LED0=!LED0;                                       //提示系统正在运行 
   i=0;
  }     
 }
}

实验结果
按一次KEY0键,一次导入如下字符串数据至串口调试助手上:
在这里插入图片描述
旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数
7)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读
8)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器
9)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法
10)复习串口通信相关知识,可参考STM32学习心得十四:串口通信相关知识及配置方法

猜你喜欢

转载自blog.csdn.net/Leisure_ksj/article/details/106476089