STM32-DMA

DMA概述

DMA为直接存储访问,其作用是代替CPU开进行外设和存储器之间数据的迁移。

之前数据的搬运是靠CPU来实现的,现在有了DMA,就可以通过CPU配置DMA,然后数据搬运就由DMA来实现。

DMA的存在,使得CPU可以去处理自己擅长的计算工作,而把一部分工作交由DMA来进行处理。

之前数据搬运全部由CPU来处理,现在是CPU告诉DMA例如要接收多少数据,但是具体的数据搬运的工作是由DMA自己来完成的。DMA是属于MCU的一个片上外设,不过它的地位要比一般的片上外设高,但是肯定比CPU的地位要低。

宏观理解

我们把上面的图简化一下

第一步是CPU给DMA下发任务。

第二步是由外设向DMA发送请求。

第三步是DMA先对外设进行响应后完成数据交换。

最后一步通知CPU处理完成。

总线共享

这里CPU和DMA共用总线,这里MCU内部会进行协调,分配带宽。这里保证CPU至少有一半带宽。

DMA通道

真正实现数据搬迁的其实是DMA内部的通道。提供的多个通道是并发执行的,不同通道处理的外设不同,数据搬迁方向不同,但是为了实现多个外设的数据搬迁,使用了多通道,并发来进行处理。

通道优先级

这里不是说并发?为什么还有优先级?这是因为优先级高也不是时刻都要搬迁数据。当高优先级空闲或者等待数据,低优先级也可以搬迁数据,这里和CPU处理的差别就很大了。

分为软件优先级和硬件优先级

软件优先级有四个等级,硬件优先级按照管道顺序来拍,先比较软件优先级,软件优先级相等才会比较硬件优先级。

我们可以看到,外设对应哪个通道这个是已经约定好的,然后多个外设接入同一个通道只能选择一个。

这里是决定出哪一个进行发送,但是这里是并发的。

寄存器讲解

拿DMA1举例一共有(2+4*7)个寄存器。2是七个通道共用的寄存器,4是每一个通道都有的单独的寄存器。

这个寄存器用于配置每一个通道,包含模式,软件优先级,存储器数据宽度,外设数据宽度,两个地址递增模式,循环模式,传输方向,三个中断位,一个使能开关。

表示要传输的数据大小。

这两个表示,传输两端的起始地址。

DMA与中断

三个中断标志分别为,出错中断,传输结束中断和传输一半中断。上图是传输一半的例子。DMA在产生中断,其实是内部的通道产生的中断。

cpp 复制代码
  DMA1_Channel1_IRQn          = 11,     /*!< DMA1 Channel 1 global Interrupt                      */ 
  DMA1_Channel2_IRQn          = 12,     /*!< DMA1 Channel 2 global Interrupt                      */ 
  DMA1_Channel3_IRQn          = 13,     /*!< DMA1 Channel 3 global Interrupt                      */ 
  DMA1_Channel4_IRQn          = 14,     /*!< DMA1 Channel 4 global Interrupt                      */ 
  DMA1_Channel5_IRQn          = 15,     /*!< DMA1 Channel 5 global Interrupt                      */ 
  DMA1_Channel6_IRQn          = 16,     /*!< DMA1 Channel 6 global Interrupt                      */ 
  DMA1_Channel7_IRQn          = 17,     /*!< DMA1 Channel 7 global Interrupt                      */ 

但是这里一个通道对应一个中断,是如何区分哪一类型的中断呢,就靠剩余的两个DMA共用的寄存器来控制。

这两个寄存器,通过给每一个通道四位,来控制三种中断和总的是否有中断的表示,下面寄存器控制上面寄存器。

这是整体的流程图

初始化代码

cpp 复制代码
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

首先可以看到的是其对应的句柄结构

cpp 复制代码
typedef struct __DMA_HandleTypeDef
{
  DMA_Channel_TypeDef        *Instance;                                                    /*!< Register base address                  */
  
  DMA_InitTypeDef            Init;                                                         /*!< DMA communication parameters           */
  
  HAL_LockTypeDef            Lock;                                                         /*!< DMA locking object                     */
  
  __IO HAL_DMA_StateTypeDef  State;                                                        /*!< DMA transfer state                     */
  
  void                       *Parent;                                                      /*!< Parent object state                    */
  
  void                       (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*!< DMA transfer complete callback         */
  
  void                       (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback    */
  
  void                       (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);    /*!< DMA transfer error callback            */

  void                       (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);    /*!< DMA transfer abort callback            */
  
  __IO uint32_t              ErrorCode;                                                    /*!< DMA Error code                         */

  DMA_TypeDef                *DmaBaseAddress;                                             /*!< DMA Channel Base Address               */
  
  uint32_t                   ChannelIndex;                                                /*!< DMA Channel Index                      */

} DMA_HandleTypeDef;   

句柄中包含的就是这个通道地址,和初始化寄存器的数据,和指向哪个DMA的这些信息。

cpp 复制代码
typedef struct
{
  __IO uint32_t CCR;
  __IO uint32_t CNDTR;
  __IO uint32_t CPAR;
  __IO uint32_t CMAR;
} DMA_Channel_TypeDef;

typedef struct
{
  __IO uint32_t ISR;
  __IO uint32_t IFCR;
} DMA_TypeDef;

这样可以找到具体通道的寄存器,也可以找到DMA共用的寄存器。

cpp 复制代码
typedef struct
{
  uint32_t Direction;                 /*!< Specifies if the data will be transferred from memory to peripheral, 
                                           from memory to memory or from peripheral to memory.
                                           This parameter can be a value of @ref DMA_Data_transfer_direction */

  uint32_t PeriphInc;                 /*!< Specifies whether the Peripheral address register should be incremented or not.
                                           This parameter can be a value of @ref DMA_Peripheral_incremented_mode */

  uint32_t MemInc;                    /*!< Specifies whether the memory address register should be incremented or not.
                                           This parameter can be a value of @ref DMA_Memory_incremented_mode */

  uint32_t PeriphDataAlignment;       /*!< Specifies the Peripheral data width.
                                           This parameter can be a value of @ref DMA_Peripheral_data_size */

  uint32_t MemDataAlignment;          /*!< Specifies the Memory data width.
                                           This parameter can be a value of @ref DMA_Memory_data_size */

  uint32_t Mode;                      /*!< Specifies the operation mode of the DMAy Channelx.
                                           This parameter can be a value of @ref DMA_mode
                                           @note The circular buffer mode cannot be used if the memory-to-memory
                                                 data transfer is configured on the selected Channel */

  uint32_t Priority;                  /*!< Specifies the software priority for the DMAy Channelx.
                                           This parameter can be a value of @ref DMA_Priority_level */
} DMA_InitTypeDef;

这里包含的就是我们要给寄存器配置的值。

cpp 复制代码
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

我们可以看到在初始化函数这里,打开的是NVIC的中断,但是具体DMA的配置在哪里呢。

cpp 复制代码
    /* USART1 DMA Init */
    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA1_Channel5;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);

    /* USART1_TX Init */
    hdma_usart1_tx.Instance = DMA1_Channel4;
    hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_tx.Init.Mode = DMA_NORMAL;
    hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(huart,hdmatx,hdma_usart1_tx);

这里便配置了DMA中通道的寄存器。本质就是向这些寄存器中配置数据。

这里就是对CCR寄存器进行配置。

这里确定了哪个DMA,然后准备好指向共用寄存器的数组下标。

这里注意通道一个占20字节,但是只有四个寄存器,所以空四个出来,共用寄存器放在DMA最前面。

cpp 复制代码
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__)               \
                        do{                                                      \
                              (__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); \
                              (__DMA_HANDLE__).Parent = (__HANDLE__);             \
                          } while(0U)

这个作用是把DMA和外设建立起链接。

相关推荐
云泽8082 小时前
STM32 USART 详解(三):硬件架构、内部寄存器与物理层收发原理
stm32·嵌入式硬件·硬件架构
天空'之城2 小时前
单片机基础核心知识点汇总(三十三)
数据库·单片机·嵌入式硬件
数字新视界4 小时前
机房动环监控的智能化管理与安全提升探索
嵌入式硬件·物联网·数据中心·数据中心基础设施管理·dcim管理系统
@嵌入式扫地僧5 小时前
TFLite Micro STM32/ESP32 开箱即用推理骨架 + INT8 量化脚本实战:零调试跑通端侧 AI 推理完整步骤
人工智能·嵌入式硬件·tinyml·端侧ai·tflite micro
自小吃多5 小时前
高云FPGA ModelSim中的仿真
笔记·嵌入式硬件·fpga开发
单片机仿真设计6 小时前
基于 STM32 的家庭环境监测与火灾报警灭火系统(Proteus 仿真)
stm32·单片机·嵌入式硬件·proteus·毕设
深圳市恒锐丰科技杨生6 小时前
EG4321 锂电池 CC‑CV 充电管理控制器|屹晶 EGmicro
嵌入式硬件·硬件工程
辰域电子6 小时前
STM32项目开源:智能万年历/智能闹钟设计(代码+原理图+仿真)
stm32·单片机·嵌入式硬件·开源·proteus
一条破秋裤7 小时前
STM32F103 RCC 第一层复习:时钟树、外设时钟与重复测试
stm32·单片机·嵌入式硬件