STM32速成笔记(八)—DMA

简介: 本文介绍了DMA的概念,用途。对于STM32F103ZET6的DMA做出了详细地介绍,给出了DMA配置步骤。最后,以配置DMA搬运ADC转换结果为例,给出了DMA的配置和使用方法。


🎀 文章作者:二土电子
🐸 期待大家一起学习交流!


一、什么是DMA

DMA全程Direct Memory Access,即直接存储器访问。简单来讲,它的功能是把数据从一个地址搬运到另一个地址。通常有三个传输方向,分别是内存到内存,内存到外设和外设到内存。
01b9d64138105467269ad58d9b8a2f84_f3a09acae8a244a28fa65adc5472a752.png

二、DMA有什么作用

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

比如在串口接收或者发送时可以直接利用DMA将接收内容直接搬运到接收数组。或者利用DMA将准备发送的数据搬运到发送的缓冲区。再或者利用DMA把数据搬运到特定的地址,或者从特定的地址利用DMA搬运数据出来。总而言之,在平时的开发过程中,DMA是非常常用的。

三、STM32的DMA

STM32F103ZET6有两个DMA,12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

18842d599a41f8101cdac0eb39c4db7d_2af45d5db0d04f5e81a383d5d7871c30.png

3.1 DMA请求

a7593f13aa569753394efb4cfa14f372_de083d1c93bc4f5d9e3764af058b7d0c.png

如果一个外设想要通过DMA传输数据,必须先给DMA控制器发送DMA请求。DMA控制器收到请求后,会给外设一个应答信号。当外设收到应答信号后,也会给DMA控制器一个应答信号。当DMA控制器收到外设的应答信号后,启动DMA传输。

前面介绍STM32F103ZET6有两个DMA,12个通道,同的 DMA 控制器的通道对应着不同的外设请求。根据中文参考手册,对应关系如下
41ebe8c584b8b7e67ada1435c9ab23c0_d3e848bdb8f447429b31df6cab14855d.png

384c5e4e7873ffae01ecc3b58ad5e4e0_6fb69f0e2dd247e690793fb139d07c60.png

3f16e305df356d47d2e1bac57a4fc190_128e4cbdbaec42c5ba6b2db1c1345a6e.png

1cbbbeb66014957f2bdee99d8c432ca6_47ce447a6900463d859a935fcb3d9dd0.png

3.2 DMA通道

DMA具有12个独立可编程的通道,每个通道对应不同外设的DMA请求。虽然每个通道可以接收多个外设的DMA请求,但是同一时间只能接收一个。
image.png

3.3 仲裁器

当有多个DMA请求时,需要仲裁器来决定响应的先后顺序。仲裁器决定相应顺序的方法有两种

  • 软件判定
    软件中可以通过设置DMA_CCRx寄存器来设置DMA通道的优先级。共有四个优先级可以设置,分别是非常高,高,中和低。
  • 硬件判定
    当遇到两个或者多个相同优先级的DMA通道请求时,仲裁器根据DMA通道的编号来决定响应顺序。DMA通道编号越低,优先级越高。另外,DMA1拥有比DMA2更高的优先级。
    98ba11a3475818da4dc690e90f9e4f58_0b4500c615a94138afada3e128b0480a.png

四、DMA配置

4.1 DMA配置步骤

  • 使能DMA时钟
  • 初始化DMA通道,包括配置通道,外设和内存地址,传输数据量等
  • 使能外设DMA功能
  • 开启DMA通道传输
  • 查询DMA通道状态

    4.2 DMA结构体成员

  • DMA_PeripheralBaseAddr:外设地址,外设地址,通过DMA_CPAR寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口DMA 传输,那么外设基地址为串口接收/发送数据存储器USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。
  • DMA_Memory0BaseAddr:存储器地址,通过DMA_CMAR寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放DMA传输数据的内存地址。比如我们定义一个u32类型数组,直接写数组首地址(直接使用数组名)即可,在DMA传输的时候就可以发送数组数据,或者把数组用来接收其他数据。
  • DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存储器到存储器。通过设定DMA_CCR寄存器的DIR[1:0]位的值决定。
  • DMA_BufferSize:用来设置一次传输数据的大小,通过DMA_CNDTR寄存器设置。
  • DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过DMA_CCR寄存器的PINC位设置,如果设置为递增,那么下一次传输的时候地址加1。通常外设只有一个数据寄存器,所以一般不会使能该位,即配置为DMA_PeripheralInc_Disable。
  • DMA_MemoryInc:用来设置内存地址是否递增,通过DMA_CCR寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为DMA_MemoryInc_Enable。
  • DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的PSIZE[1:0]位设置。
  • DMA_Mode:DMA传输模式选择,可选择一次传输或者循环传输,通过DMA_CCR寄存器的CIRC位来设定。比如我们要从内存(存储器)中传输64个字节到串口,如果设置为循环传输,那么它会在64个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。
  • DMA_Priority:用来设置DMA通道的优先级,有低,中,高,超高四种级别,可通过DMA_CCR寄存器的PL[1:0]位来设定。DMA优先级只有在多个DMA数据流同时使用时才有意义。
  • DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

    五、DMA配置程序

    这里以配置DMA,将ADC采集到的数据搬运到内存中的某一个数组中为例,讲解一下DMA的配置和使用方法。

    5.1 ADC1初始化程序

    ADC使用TIM4的通道4触发,具体配置可见本系列另一篇文章STM32速成笔记—ADC。这里在之前配置的基础上需要开启ADC的DMA传输,在初始化ADC时加上下面的程序
ADC_DMACmd(ADC1,ENABLE);   // 使能ADC的DMA传输

ADC初始化程序如下

/*
 *==============================================================================
 *函数名称:ADC1_Init
 *函数功能:初始化ADCx
 *输入参数:无
 *返回值:无
 *备  注:TIM4通道4触发AD转换,使能了DMA
 *==============================================================================
 */
void ADC1_Init(void)
{
   
   
    // 结构体定义
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;

    // 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);

    // 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    // 规则通道配置
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);

    // GPIO配置
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;   //ADC1通道1
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;   // 模拟输入
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);

    // ADC参数配置
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;   // 独立模式
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;   // 非扫描模式    
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;   // 关闭连续转换
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;   // TIM2通道2触发
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;   // 右对齐    
    ADC_InitStructure.ADC_NbrOfChannel = 1;   // 1个转换在规则序列中 也就是只转换规则序列1 
    ADC_Init(ADC1, &ADC_InitStructure);   // ADC初始化

    // 使能外部触发
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    ADC_DMACmd(ADC1,ENABLE);   // 使能ADC的DMA传输
    ADC_Cmd(ADC1, ENABLE);   // 开启AD转换器

    // ADC校准
    ADC_ResetCalibration(ADC1);   // 重置指定的ADC的校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC1));   // 获取ADC重置校准寄存器的状态

    ADC_StartCalibration(ADC1);   // 开始指定ADC的校准状态
    while(ADC_GetCalibrationStatus(ADC1));   // 获取指定ADC的校准程序

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);   // 使能或者失能指定的ADC的软件转换启动功能
}

5.2 DMA初始化程序

由上面的介绍可知,ADC1是DMA1的通道1,我们配置一下DMA1的通道1,使能传输完成中断。

/*
 *==============================================================================
 *函数名称:DMA1_Init
 *函数功能:DMA1初始化
 *输入参数:souAddr:数据源地址;desAddr:数据目的地址
 *返回值:无
 *备  注:数据传输宽度为16位,外设到内存,循环传输,使能了传输完成中断
 *==============================================================================
 */
void DMA1_Init (u32 souAddr,u32 desAddr)
{
   
   
    // 结构体定义
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 使能DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

    //DMA1初始化
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = souAddr;   // 数据源地址
    DMA_InitStructure.DMA_MemoryBaseAddr = desAddr;   // 目的地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   //   传输方向(外设到内存)
    DMA_InitStructure.DMA_BufferSize = 128;   // 一次传输数据大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 外设地址不自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 内存地址自增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;   // 外设数据宽度选择
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 内存数据宽度选择
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   // DMA模式:循环传输
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;   // 优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   // 禁止内存到内存的传输
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);   // 配置DMA1

    // 使能传输完成中断
    DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);

    // NVIC配置
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 使能DMA1通道1
    DMA_Cmd(DMA1_Channel1,ENABLE);
}

// DMA1中断服务函数
void  DMA1_Channel1_IRQHandler(void)
{
   
   
    if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
    {
   
   
        DMA_Cmd(DMA1_Channel1,DISABLE);
        while (1)
        {
   
   }
    }
    // 清除中断标志位
    DMA_ClearITPendingBit(DMA1_IT_TC1);
}

定义一个存储AD转换结果的数组,初始化时,程序如下

u16 gAdcAdValue[128];   // 存储AD值

DMA1_Init((u32)(&ADC1->DR),(u32)&gAdcAdValue);   // DMA1初始化

中断服务函数中将存储标志位置1表示存储完成

u8 gDmaAdcSaveFlag = 0;   // ADC数据存储标志位

// DMA1中断服务函数
void  DMA1_Channel1_IRQHandler(void)
{
   
   
    if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
    {
   
   
        gDmaAdcSaveFlag = 1;   // 存储标志位置1,表示存储完成
    }
    // 清除中断标志位
    DMA_ClearITPendingBit(DMA1_IT_TC1);
}

上面的配置就可以实现ADC采集,DMA将采集结果搬运到内存中的一个数组里面。

相关文章
|
2月前
|
存储 芯片
STM32 cubemx配置USART DMA传输
STM32 cubemx配置USART DMA传输
37 0
|
4月前
|
存储 物联网 芯片
STM32速成笔记(十四)—串口IAP
本文介绍了什么是IAP,IAP有什么作用,如何实现IAP。最后,给出了IAP的实现程序。
83 0
STM32速成笔记(十四)—串口IAP
|
4月前
|
芯片 内存技术
STM32速成笔记(十三)—低功耗模式
本文介绍了三种STM32低功耗模式的进入和退出方法,针对待机唤醒给出了程序设计。
108 0
STM32速成笔记(十三)—低功耗模式
|
4月前
|
存储 芯片 内存技术
STM32速成笔记(十二)—Flash闪存
本文简单介绍了什么是Flash。针对STM32F1的Flash做了详细介绍,介绍了操作Flash的步骤,并且给出了程序设计。最后,介绍了一些注意事项。
29 0
STM32速成笔记(十二)—Flash闪存
|
4月前
|
存储 芯片
STM32速成笔记(十一)—EEPROM(AT24C02)
本文详细介绍了什么是AT24C02,介绍了它的引脚,读/写时序,给出了应用实例和详细的程序设计。最后,简单介绍了AT24C02的应用场景。
128 0
STM32速成笔记(十一)—EEPROM(AT24C02)
|
4月前
STM32速成笔记(十)—IWDG
本文详细介绍了什么是IWDG,STM32的IWDG特性,框图和配置步骤。此外,给出了STM32的IWDG配置程序。通过一个简单的应用实例,展示了IWDG的配置和使用方法。
26 0
STM32速成笔记(十)—IWDG
|
4月前
|
API
STM32速成笔记(九)—RTC
本文详细介绍了RTC模块,介绍了STM32的RTC的特性,框图,配置步骤,并给出了详细的程序设计。最后,针对实际使用时可能遇到的问题给出了解决方法以及程序。
46 0
STM32速成笔记(九)—RTC
|
4月前
|
存储 传感器
STM32速成笔记(七)—ADC
本文介绍了ADC的概念,用途,针对STM32的ADC做出了详细介绍,给出了配置步骤,配置程序。通过一个简单的小项目展示了ADC的配置和使用方法。此外,还针对如何利用定时器触发AD转换,如何采集交流信号,如何计算交流信号有效值进行了介绍,并给出了程序设计。
70 0
STM32速成笔记(七)—ADC
|
4月前
STM32速成笔记(六)—定时器
本文介绍了定时器的概念,作用。针对STM32F1的通用定时器做了详细介绍。此外,介绍了PWM的概念,用途以及STM32F1的PWM,给出了PWM频率的计算方法。最后通过介绍利用定时器的更新中断和PWM这两种方法实现呼吸灯,展示了定时器和PWM的配置步骤,并给出了详细的程序设计。另外,介绍了利用定时器实现按键长短按的检测方法。
97 0
STM32速成笔记(六)—定时器
|
4月前
|
芯片
STM32速成笔记(五)—串口通信
本文介绍了串口通信的概念,用途以及一些相关概念。介绍了如何进行printf重定向,如何根据接收到的特定信息,执行特定操作。此外,本文以通过上位机发送特殊指令控制LED亮灭的小项目,给出了详细的配置方法和程序设计。
107 0
STM32速成笔记(五)—串口通信

相关产品

  • 云迁移中心