直接内存访问(DMA)

简单解释 直接内存访问(DMA)是一种计算机系统功能,允许外设直接访问系统内存,而无需通过CPU进行数据传输。这种机制可以提高数据传输效率,减少CPU的负担,从而提升系统性能。

DMA基本结构

DMA请求

软件触发

硬件触发

存储器

Flash

SRAM

外设寄存器

起始地址

传输长度

数据是否自增

M2M

传输计数器

自动重装器

存储器影像

类型 起始地址 存储器 作用
ROM 0x0800 0000 Flash 程序存储(这个在之前进行配置linux下的stm32开发环境提及,烧录的起始地址)
ROM 0x1FFF F000 系统存储器 存储bootLoader
ROM 0x1FFF F800 选项字节 一些配置参数
RAM 0x2000 0000 SRAM 临时变量
RAM 0x4000 0000 外设寄存器 外设配置参数
RAM 0xE000 0000 内核外设寄存器 内核外设配置参数

简单来个例子

直接上代码

void DMA_Init (void) {
    __HAL_RCC_DMA1_CLK_ENABLE ();
    dma.Instance = DMA1_Channel1;  // DMA通道实例
    dma.Init.Direction = DMA_MEMORY_TO_MEMORY;  // 数据传输方向:内存到内存
    dma.Init.MemInc = DMA_MINC_ENABLE;  // 存储器地址递增模式 
    dma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;   // 存储器数据宽度
    dma.Init.PeriphInc = DMA_PINC_ENABLE;  // 外设地址递增模式
    dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    // 外设数据宽度
    dma.Init.Mode = DMA_NORMAL;   // 工作模式:正常模式
    dma.Init.Priority = DMA_PRIORITY_HIGH;   // 优先级:高优先级
    HAL_DMA_Init (&dma);
}

简单看一下结构体参数列表

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;   
参数 说明
Instance DMA通道实例
下面为Init结构体参数
Direction 数据传输方向
MemInc 存储器地址递增模式
MemDataAlignment 存储器数据宽度
PeriphInc 外设地址递增模式
PeriphDataAlignment 外设数据宽度
Mode 工作模式
Priority 优先级

下面依次解释:
Instance


DMA通道选择,对于stm32f1系列而言拥有2个DMA通道
查看源码,这里有一个巧妙的地址映射:
首先mcu编程无论是外设还一些其他功能本质是在操纵寄存器,这里采用一个结构体的强制类型转换来实现寄存器的访问,DMA1_Channel1_BASE等于DMA1_BASE+0x08,DMA1_Channel2_BASE等于DMA1_BASE+0x1C,以此类推,这样就可以通过结构体指针来访问对应的寄存器了。

#define DMA1                ((DMA_TypeDef *)DMA1_BASE)
#define DMA2                ((DMA_TypeDef *)DMA2_BASE)
#define DMA1_Channel1       ((DMA_Channel_TypeDef *)DMA1_Channel1_BASE)
#define DMA1_Channel2       ((DMA_Channel_TypeDef *)DMA1_Channel2_BASE)
#define DMA1_Channel3       ((DMA_Channel_TypeDef *)DMA1_Channel3_BASE)
#define DMA1_Channel4       ((DMA_Channel_TypeDef *)DMA1_Channel4_BASE)
#define DMA1_Channel5       ((DMA_Channel_TypeDef *)DMA1_Channel5_BASE)
#define DMA1_Channel6       ((DMA_Channel_TypeDef *)DMA1_Channel6_BASE)
#define DMA1_Channel7       ((DMA_Channel_TypeDef *)DMA1_Channel7_BASE)
#define DMA2_Channel1       ((DMA_Channel_TypeDef *)DMA2_Channel1_BASE)
#define DMA2_Channel2       ((DMA_Channel_TypeDef *)DMA2_Channel2_BASE)
#define DMA2_Channel3       ((DMA_Channel_TypeDef *)DMA2_Channel3_BASE)
#define DMA2_Channel4       ((DMA_Channel_TypeDef *)DMA2_Channel4_BASE)
#define DMA2_Channel5       ((DMA_Channel_TypeDef *)DMA2_Channel5_BASE)
// BASE地址映射
#define DMA1_BASE             (AHBPERIPH_BASE + 0x00000000UL)
#define DMA1_Channel1_BASE    (AHBPERIPH_BASE + 0x00000008UL)
#define DMA1_Channel2_BASE    (AHBPERIPH_BASE + 0x0000001CUL)
#define DMA1_Channel3_BASE    (AHBPERIPH_BASE + 0x00000030UL)
#define DMA1_Channel4_BASE    (AHBPERIPH_BASE + 0x00000044UL)
#define DMA1_Channel5_BASE    (AHBPERIPH_BASE + 0x00000058UL)
#define DMA1_Channel6_BASE    (AHBPERIPH_BASE + 0x0000006CUL)
#define DMA1_Channel7_BASE    (AHBPERIPH_BASE + 0x00000080UL)

Direction


数据传输方向,查看源码 其给出了三种传输方向:外设到内存,内存到外设,内存到内存
其中m to m模式需要注意一下,mcu本身并不存在硬件的电路,所以这种模式是采用软件实现。

#define DMA_PERIPH_TO_MEMORY         0x00000000U                 /*!< Peripheral to memory direction */
#define DMA_MEMORY_TO_PERIPH         ((uint32_t)DMA_CCR_DIR)     /*!< Memory to peripheral direction */
#define DMA_MEMORY_TO_MEMORY         ((uint32_t)DMA_CCR_MEM2MEM) /*!< Memory to memory direction     */

MemInc MemDataAlignment PeriphInc PeriphDataAlignment


上述是寄存器与外设的地址递增模式和数据宽度,地址递增模式有两种:递增和不递增,数据宽度有三种:字节,半字,字。

#define DMA_PINC_ENABLE              ((uint32_t)DMA_CCR_PINC)   /*!< Peripheral increment mode enable */
#define DMA_PINC_DISABLE             0x00000000U                 /*!< Peripheral increment mode disable */
#define DMA_MINC_ENABLE              ((uint32_t)DMA_CCR_MINC)   /*!< Memory increment mode enable */
#define DMA_MINC_DISABLE             0x00000000U                 /*!< Memory increment mode disable */  
#define DMA_PDATAALIGN_BYTE          0x00000000U                 /*!< Peripheral data alignment: Byte     */
#define DMA_PDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_PSIZE_0)   /*!< Peripheral data alignment: HalfWord */
#define DMA_PDATAALIGN_WORD          ((uint32_t)DMA_CCR_PSIZE_1)   /*!< Peripheral data alignment: Word     */
#define DMA_MDATAALIGN_BYTE          0x00000000U                 /*!< Memory data alignment: Byte     */
#define DMA_MDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_MSIZE_0)   /*!< Memory data alignment: HalfWord */
#define DMA_MDATAALIGN_WORD          ((uint32_t)DMA_CCR_MSIZE_1)   /*!< Memory data alignment: Word     */

Mode


工作模式 分为正常模式与循环模式
注意一点:循环模式在 m to m 模式下不能使用 会跑飞

#define DMA_NORMAL                   0x00000000U                 /*!< Normal mode                  */
#define DMA_CIRCULAR                 ((uint32_t)DMA_CCR_CIRC)    /*!< Circular mode                */

Priority


优先级 分为四个等级:低,中,高,超高

#define DMA_PRIORITY_LOW             0x00000000U                 /*!< Priority level: Low       */
#define DMA_PRIORITY_MEDIUM          ((uint32_t)DMA_CCR_PL_0)   /*!< Priority level: Medium    */
#define DMA_PRIORITY_HIGH            ((uint32_t)DMA_CCR_PL_1)   /*!< Priority level: High      */
#define DMA_PRIORITY_VERY_HIGH       ((uint32_t)DMA_CCR_PL)     /*!<    Priority level: Very High */

数据传输函数
这里需要注意 DMA传输完成后需要手动清除标志位并将状态恢复为READY,否则下次启动DMA传输会失败。

void DMA_Transfer (uint32_t * src, uint32_t * dst, uint32_t size) {
    HAL_DMA_Start (&dma, (uint32_t)src, (uint32_t)dst, size);// 启动DMA传输
    // 等待传输完成
    while (HAL_DMA_GetState (&dma) != HAL_DMA_STATE_READY) {
        // 可以在这里添加超时处理或其他逻辑
        if (__HAL_DMA_GET_FLAG (&dma, __HAL_DMA_GET_TC_FLAG_INDEX (&dma))) {
            // 1. 清除硬件标志位
            __HAL_DMA_CLEAR_FLAG (&dma, __HAL_DMA_GET_TC_FLAG_INDEX (&dma));

            // 2. 手动将状态恢复为 READY (否则下次无法启动)
            dma.State = HAL_DMA_STATE_READY;

            // 3. 解锁 (HAL_DMA_Start 会加锁,这里必须解)
            __HAL_UNLOCK (&dma);

            break; // 跳出循环
        }
    }
    // 传输完成后可以在这里进行其他操作,例如处理传输结果等
}

另其实HAL库也给出了一个轮询传输完成的函数,内部也是通过检查标志位来实现的,使用起来更方便一些。

HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout);

此外还可以采用中断的方式进一步提高性能。

最后结合ADC

有了之前adc 与dma 的铺垫 这里直接上代码
stm32—— 模数转换器(ADC)

  1. 首先在adc初始化中添加DMA句柄绑定
hadc.Init.NbrOfDiscConversion = 1;  //   不连续转换子组大小为 1(每次触发转换一个通道)
    hadc.Init.NbrOfConversion = 1;  // 规则组转换总数 为 1
    // HAL_DMA_Init (&dma);  // 将 ADC 与 DMA 关联
    HAL_ADC_Init (&hadc);  // 初始化 ADC
    hadc.DMA_Handle = &dma;  // 将 DMA 句柄关联到 ADC 句柄
  1. 在DMA初始化中将模式设置为循环模式
dma.Init.Mode = DMA_CIRCULAR ;   // 工作模式:循环模式
  1. 最后在主函数初始化DMA ADC 启动传输
DMA_Init ();
ADC_Init ();
HAL_ADC_Start_DMA (&hadc, &ad_value, 1);
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐