DMA详解和应用实例

1.头大的常规概念

DMA是Direct Memory Access(直接内存访问)的缩写,它是一种计算机系统的特性,允许某些硬件子系统在不经过中央处理器(CPU)的情况下直接访问主系统内存。使用DMA,CPU可以在传输过程中执行其他操作,而不必占用整个读写周期。这样可以提高数据传输的效率,减少CPU的负担。12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发。对应的使用DMA的类型有,外设->存储器,存储器->外设,存储器->存储器

作者云”:DMA一般都是和外设,存储器搭配使用,而目前市面上主流的开发板配套例程(某火,某原)都是和串口或者ADC一起呈现,当然后续我们也会用上,但本文中我仅单独进行写, STM32在硬件上设计了这么多的DMA通道是煞费苦心的,用好DMA控制器,可以大
大地提高系统的吞吐量。在PC上,将硬盘数据复制到内存,就采用DMA模式,把复制数
据的任务交给DMA来执行,CPU就可以去做别的事情了。在STM32下也是如此,在编程中
不采用DMA模式,全靠CPU去辛苦劳作,是巨大的损失。

2.勉强的结构讲解

虽然学到STM你可以直接学HAL库这些封装程度比较高的库,但是我们想想,有多长时间没有体会到真正恍然大悟感觉,那很奇妙。所以结构应该去了解。

从这个图我们可以看到我们使用DMA需要配置哪些参数,我们学习每个外设最后都要配置对应的结构体,这个图不是官方的图,是江科大的(他们的视频真的不错)

  • DMA_InitTypeDef结构体:用于定义DMA通道的初始化参数,包括以下字段:

    • DMA_PeripheralBaseAddr:外设基地址,用于指定DMA源或目的地址。
    • DMA_MemoryBaseAddr:存储器基地址,用于指定DMA源或目的地址。
    • DMA_DIR:DMA传输方向,可以是DMA_DIR_PeripheralSRC(外设作为源)或DMA_DIR_PeripheralDST(外设作为目的)。
    • DMA_BufferSize:DMA传输的数据量,单位为字节。
    • DMA_PeripheralInc:外设地址寄存器递增模式,可以是DMA_PeripheralInc_Enable(使能)或DMA_PeripheralInc_Disable(禁止)。
    • DMA_MemoryInc:存储器地址寄存器递增模式,可以是DMA_MemoryInc_Enable(使能)或DMA_MemoryInc_Disable(禁止)。
    • DMA_PeripheralDataSize:外设数据宽度,可以是DMA_PeripheralDataSize_Byte(8位)、DMA_PeripheralDataSize_HalfWord(16位)或DMA_PeripheralDataSize_Word(32位)。
    • DMA_MemoryDataSize:存储器数据宽度,可以是DMA_MemoryDataSize_Byte(8位)、DMA_MemoryDataSize_HalfWord(16位)或DMA_MemoryDataSize_Word(32位)。
    • DMA_Mode:DMA传输模式,可以是DMA_Mode_Circular(循环模式)或DMA_Mode_Normal(正常模式)。
    • DMA_Priority:DMA通道优先级,可以是DMA_Priority_VeryHigh(很高)、DMA_Priority_High(高)、DMA_Priority_Medium(中)或DMA_Priority_Low(低)。
    • DMA_M2M:存储器到存储器传输模式,可以是DMA_M2M_Enable(使能)或DMA_M2M_Disable(禁止)。

3.常用函数插入

  • DMA_Init函数:用于根据指定的参数初始化DMA通道,原型如下:

    void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
    

    其中,DMAy_Channelx是要初始化的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。DMA_InitStruct是指向一个DMA_InitTypeDef结构体的指针,用于指定DMA通道的初始化参数。

  • DMA_Cmd函数:用于使能或禁止指定的DMA通道,原型如下:
    void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
    

    其中,DMAy_Channelx是要操作的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。NewState是要设置的DMA通道的新状态,可以是ENABLE(使能)或DISABLE(禁止)。

  • DMA_ITConfig函数:用于配置指定的DMA通道的中断,原型如下

    void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
    

    其中,DMAy_Channelx是要操作的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。DMA_IT是要配置的DMA通道的中断源,可以是以下值的组合:

    * DMA_IT_TC:传输完成中断
    * DMA_IT_HT:半传输中断
    * DMA_IT_TE:传输错误中断
    

    NewState是要设置的DMA通道的中断的新状态,可以是ENABLE(使能)或DISABLE(禁止)。

  • DMA_GetFlagStatus函数:用于检查指定的DMA标志是否被设置,原型如下:
  • FlagStatus DMA_GetFlagStatus(uint32_t DMA_FLAG);

4.代码哐当登场 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						//开启DMA的时钟	
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure;				//定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = ;	//外设基地址,给定形参
DMA_InitStructure.DMA_PeripheralDataSize = ;	//外设数据宽度,选择字节
DMA_InitStructure.DMA_PeripheralInc = ;			//外设地址自增,选择使能
DMA_InitStructure.DMA_MemoryBaseAddr =;			//存储器基地址,给定形参
DMA_InitStructure.DMA_MemoryDataSize = ;		//存储器数据宽度,选择字节
DMA_InitStructure.DMA_MemoryInc = ;		        //存储器地址自增,选择使能
DMA_InitStructure.DMA_DIR = ;					//数据传输方向,选择由外设到存储器
DMA_InitStructure.DMA_BufferSize = ;			//转运的数据大小(转运次数)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;	//模式,选择正常模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;		//存储器到存储器,选择使能
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;	//优先级,选择中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure);	//将结构体变量交给DMA_Init,配置DMA1的通道1	
/*DMA使能*/
DMA_Cmd(DMA1_Channel1, DISABLE);	//这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
#include "stm32f10x.h"

#define BUFFER_SIZE 32 //定义缓冲区大小

uint32_t aSRC_Const_Buffer[BUFFER_SIZE]; //定义源缓冲区
uint32_t aDST_Buffer[BUFFER_SIZE]; //定义目标缓冲区

void RCC_Configuration(void); //时钟配置函数
void GPIO_Configuration(void); //GPIO配置函数
void DMA_Configuration(void); //DMA配置函数
void NVIC_Configuration(void); //中断配置函数
void Error_Handler(void); //错误处理函数

int main(void)
{
  RCC_Configuration(); //配置时钟
  GPIO_Configuration(); //配置GPIO
  DMA_Configuration(); //配置DMA
  NVIC_Configuration(); //配置中断

  /* 填充源缓冲区 */
  for (uint32_t i = 0; i < BUFFER_SIZE; i++)
  {
    aSRC_Const_Buffer[i] = 0x55555555 + i;
  }

  /* 使能DMA通道1 */
  DMA_Cmd(DMA1_Channel1, ENABLE);

  /* 等待传输完成 */
  while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);

  /* 清除传输完成标志 */
  DMA_ClearFlag(DMA1_FLAG_TC1);

  /* 比较源缓冲区和目标缓冲区是否相同 */
  for (uint32_t i = 0; i < BUFFER_SIZE; i++)
  {
    if (aSRC_Const_Buffer[i] != aDST_Buffer[i])
    {
      /* 缓冲区不匹配 */
      Error_Handler();
    }
  }

  /* 缓冲区匹配,程序正常运行 */
  while (1)
  {
    /* 点亮LED */
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
  }
}

/* 时钟配置函数 */
void RCC_Configuration(void)
{
  /* 使能GPIOC时钟 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

  /* 使能DMA1时钟 */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
}

/* GPIO配置函数 */
void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  /* 配置PC13为推挽输出模式 */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* 关闭LED */
  GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}

/* DMA配置函数 */
void DMA_Configuration(void)
{
  DMA_InitTypeDef DMA_InitStructure;

  /* 复位DMA1通道1 */
  DMA_DeInit(DMA1_Channel1);

  /* 配置DMA1通道1的初始化参数 */
  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; //外设数据宽度为32位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //存储器数据宽度为32位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高优先级
  DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器模式
  DMA_Init(DMA1_Channel1, &DMA_InitStructure); //初始化DMA1通道1

  /* 配置DMA1通道1的传输完成中断 */
  DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
}

/* 中断配置函数 */
void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;

  /* 配置NVIC优先级分组为4 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

  /* 配置DMA1通道1中断 */
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn; //DMA1通道1中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级为0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
  NVIC_Init(&NVIC_InitStructure); //初始化NVIC
}

/* DMA1通道1中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
  /* 判断DMA1通道1是否发生传输完成中断 */
  if (DMA_GetITStatus(DMA1_IT_TC1) == SET)
  {
    /* 清除DMA1通道1传输完成中断标志 */
    DMA_ClearITPendingBit(DMA1_IT_TC1);

    /* 关闭LED */
    GPIO_ResetBits(GPIOC, GPIO_Pin_13);
  }
}

/* 错误处理函数 */
void Error_Handler(void)
{
  /* 用户可以在这里添加自己的代码 */
  while(1)
  {
    /* 闪烁LED */
    GPIO_ToggleBits(GPIOC, GPIO_Pin_13);
    Delay(1000); //延时1秒
  }
}

这个代码实例的功能是将一个32字节的源缓冲区的内容复制到一个目标缓冲区,然后比较两个缓冲区是否相同。如果相同,程序正常运行,点亮LED,如果不同,程序进入错误处理函数,闪烁LED。

这个代码实例的步骤如下:

  • 包含所需的头文件,定义缓冲区大小,声明源缓冲区和目标缓冲区,声明配置函数和错误处理函数。
  • 在main函数中,调用配置函数,配置时钟、GPIO、DMA和中断,填充源缓冲区的内容,使能DMA通道1,等待传输完成,清除传输完成标志,比较源缓冲区和目标缓冲区的内容,根据结果执行不同的操作。
  • 定义一个时钟配置函数,使能GPIOC时钟和DMA1时钟。
  • 定义一个GPIO配置函数,配置PC13为推挽输出模式,关闭LED。
  • 定义一个DMA配置函数,复位DMA1通道1,配置DMA1通道1的初始化参数,配置DMA1通道1的传输完成中断。
  • 定义一个中断配置函数,配置NVIC优先级分组为4,配置DMA1通道1中断。
  • 定义一个DMA1通道1中断服务函数,判断DMA1通道1是否发生传输完成中断,清除传输完成中断标志,关闭LED。
  • 定义一个错误处理函数,用户可以在这里添加自己的代码,闪烁LED。

  5.总结

  • 注意DMA的传输方向:DMA的传输方向是指数据是从外设到存储器,还是从存储器到外设,还是从存储器到存储器。根据不同的传输方向,需要配置相应的源地址和目的地址,以及DMA请求的类型。如果传输方向配置错误,会导致数据传输失败或混乱。
  • 注意DMA的数据宽度:DMA的数据宽度是指外设和存储器的数据位数,可以是8位、16位或32位。根据不同的数据宽度,需要配置相应的数据对齐方式,以及源地址和目的地址的增量模式。如果数据宽度配置错误,会导致数据传输不完整或错误。
  • 注意DMA的传输模式:DMA的传输模式是指DMA是以循环模式还是正常模式进行数据传输。循环模式是指当传输完成后,DMA会自动重新开始传输,直到停止或中断。正常模式是指当传输完成后,DMA会自动停止传输,等待下一次启动。根据不同的传输模式,需要配置相应的传输数量和中断事件。如果传输模式配置错误,会导致数据传输过多或过少,或者无法停止或启动。
  • 注意DMA的优先级:DMA的优先级是指当多个DMA通道同时有传输请求时,哪个通道优先进行传输。DMA的优先级分为四级,分别是很高、高、中和低。根据不同的优先级,需要配置相应的控制寄存器。如果优先级配置错误,会导致数据传输的延迟或冲突。
  • 注意DMA的服务范围:DMA的服务范围是指DMA可以访问的外设和存储器的范围。不同的DMA控制器和通道有不同的服务范围,需要根据芯片的总线架构和功能框图来确定。如果服务范围配置错误,会导致数据传输无法进行或异常。
  • 注意DMA的缓存问题:DMA的缓存问题是指当使用带有缓存的内存区域作为DMA的源端或目的端时,可能会出现数据不一致或不更新的问题。这是因为CPU和DMA访问内存的方式不同,CPU可能会使用缓存来加速访问,而DMA直接访问实际的存储器。这样会导致缓存和存储器之间的数据不同步,或者缓存的数据被DMA覆盖。为了解决这个问题,需要在适当的时机清除或失效缓存,或者使用透写或共享的缓存策略,或者禁用缓存。

  • 34
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
DMA(Direct Memory Access,直接内存访问)是一种不需要CPU干预就能够进行数据传输的技术,它可以将数据从外设(如ADC、DAC、USART等)直接传输到内存(或者从内存传输到外设),从而减少CPU的负担。在STM32中,DMA控制器与多个外设和内存之间进行数据传输,可以大大提高系统的效率。 STM32的DMA功能主要有以下几个方面: 1. 支持多种数据传输方式,如单次传输、循环传输、内存到内存传输等。 2. 支持多种数据宽度,如8位、16位、32位。 3. 支持多个通道,不同的通道可以同时传输不同的数据。 4. 支持多种数据传输优先级,可以控制DMA传输的优先级,从而保证数据传输的实时性。 5. 支持DMA中断和传输完成中断,可以在数据传输完成后自动触发中断,从而进行下一步的操作。 在STM32中,DMA的使用主要包括以下几个步骤: 1. 配置DMA通道,包括数据宽度、传输方式、传输方向等。 2. 配置DMA源地址和目的地址,可以是外设地址或者内存地址。 3. 配置DMA传输数据长度,即需要传输的数据量。 4. 配置DMA传输优先级和触发方式,可以选择软件触发或者硬件触发。 5. 开启DMA传输,等待传输完成或者触发中断。 在使用DMA的过程中,需要注意以下几个问题: 1. DMA的传输速度比CPU快,因此需要注意是否会出现数据溢出的情况。 2. DMA传输的数据长度需要精确控制,否则可能会出现数据错误的情况。 3. DMA的通道数量有限,需要根据实际需要进行合理分配。 4. DMA传输过程中需要注意数据的正确性和实时性,以避免出现数据错误或者延迟的情况。 总之,STM32的DMA功能可以大大提高系统的效率,是嵌入式系统开发中不可或缺的重要技术。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值