PUYA DMA — 内存到内存搬运(SRAM→SRAM)(HAL 库)

PUYA PY32F0xx_Firmware V1.5.0 DMA 内存到内存搬运(DMA_SramToSram)示例教学

DMA — 内存到内存搬运(SRAM → SRAM)(HAL 库)

固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/DMA/DMA_SramToSram/Src/main.c


功能简介

DMA(Direct Memory Access,直接存储器访问)允许在不占用 CPU 的情况下让数据在存储器之间或存储器与外设之间自动搬运。CPU 只需“下单”,DMA 控制器在后台搬运,搬完产生中断通知 CPU。

本示例演示最直观的 内存→内存(Memory-to-Memory) 模式:

  • 在源缓冲 aSRC_Const_Buffer[32] 填入 0,1,2,…31
  • 启动 DMA 把 32 个 32 位字 搬到 aDST_Buffer[32]
  • 传输完成中断回调校验数据是否一致;
  • 一致则点亮绿灯,不一致/出错则闪烁报警。

BUFFER_SIZEmain.h 中定义为 32。这是理解“DMA 替 CPU 干活”的最佳入门示例;后面接 ADC/SPI 时都是同样的套路,只是源/目的换成外设寄存器。

硬件准备

  • PY32F030 开发板
  • 板载 LED(观察结果)
  • 无需外接,纯内部 SRAM 搬运

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "main.h"

DMA_HandleTypeDef DmaHandle;
uint32_t aSRC_Const_Buffer[BUFFER_SIZE];  /* 源缓冲 */
uint32_t aDST_Buffer[BUFFER_SIZE];        /* 目的缓冲 */
__IO uint32_t transferCompleteDetected = 0; /* 传输完成标志 */
__IO uint32_t transferErrorDetected    = 0; /* 传输错误标志 */
__IO uint32_t transferFailedDetected   = 0; /* 数据校验失败标志 */

int main(void)
{
  HAL_Init();
  BSP_LED_Init(LED_GREEN);

  /* 准备源数据 0~31 */
  for (uint8_t i = 0; i < BUFFER_SIZE; i++)
    aSRC_Const_Buffer[i] = i;

  APP_DmaConfig();  /* 配置并注册回调、使能中断 */

  /* 启动 DMA 传输(中断方式,CPU 立即返回) */
  if (HAL_DMA_Start_IT(&DmaHandle,
                       (uint32_t)&aSRC_Const_Buffer,  /* 源地址 */
                       (uint32_t)&aDST_Buffer,        /* 目的地址 */
                       BUFFER_SIZE) != HAL_OK)        /* 传输个数(单位=数据宽度) */
  {
    APP_ErrorHandler();
  }

  while (1)
  {
    if (transferFailedDetected == 1 && transferCompleteDetected == 1)
    {
      while (1) { BSP_LED_Toggle(LED_GREEN); HAL_Delay(500); } /* 数据错:快闪 */
    }
    if (transferFailedDetected == 0 && transferCompleteDetected == 1)
    {
      BSP_LED_On(LED_GREEN);   /* 成功:绿灯常亮 */
      while (1) {}
    }
    if (transferErrorDetected == 1)
    {
      BSP_LED_On(LED_GREEN);
      while (1) { BSP_LED_Toggle(LED_GREEN); HAL_Delay(500); } /* 出错:闪烁 */
    }
  }
}

/**
  * @brief  DMA 配置
  */
static void APP_DmaConfig(void)
{
  __HAL_RCC_DMA_CLK_ENABLE();                 /* 开启 DMA 时钟 */

  DmaHandle.Init.Direction           = DMA_MEMORY_TO_MEMORY;  /* 内存到内存 */
  DmaHandle.Init.PeriphInc           = DMA_PINC_ENABLE;       /* 外设(源)地址自增 */
  DmaHandle.Init.MemInc              = DMA_MINC_ENABLE;       /* 内存(目的)地址自增 */
  DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;   /* 源数据宽度 32 位 */
  DmaHandle.Init.MemDataAlignment    = DMA_MDATAALIGN_WORD;   /* 目的数据宽度 32 位 */
  DmaHandle.Init.Mode                = DMA_NORMAL;            /* 普通模式(非循环) */
  DmaHandle.Init.Priority            = DMA_PRIORITY_HIGH;     /* 通道优先级高 */

  DmaHandle.Instance = DMA1_Channel1;          /* 使用 DMA1 通道 1 */
  if (HAL_DMA_Init(&DmaHandle) != HAL_OK) APP_ErrorHandler();

  /* 注册“传输完成”和“传输错误”两个回调 */
  HAL_DMA_RegisterCallback(&DmaHandle, HAL_DMA_XFER_CPLT_CB_ID,  APP_TransferComplete);
  HAL_DMA_RegisterCallback(&DmaHandle, HAL_DMA_XFER_ERROR_CB_ID, APP_TransferError);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);      /* 使能 DMA 中断 */
}

/* 传输完成回调:校验数据 */
static void APP_TransferComplete(DMA_HandleTypeDef *DmaHandle)
{
  for (uint16_t i = 0; i < BUFFER_SIZE; i++)
  {
    if (aDST_Buffer[i] != aSRC_Const_Buffer[i])
    {
      transferFailedDetected = 1;
      break;
    }
  }
  transferCompleteDetected = 1;
}

/* 传输错误回调 */
static void APP_TransferError(DMA_HandleTypeDef *DmaHandle)
{
  transferErrorDetected = 1;
}

代码分段讲解

1. 地址自增与数据宽度

  • PeriphInc / MemInc = ENABLE:搬运时源、目的地址都自动 +1(按数据宽度递增)。若对固定寄存器(如 USART TDR)搬运,应把对应端设为 DISABLE
  • PDATAALIGN_WORD / MDATAALIGN_WORD:每次搬 4 字节。传输个数 BUFFER_SIZE 的单位是“次”,不是字节;32 字 × 4 = 128 字节。

2. 模式 Normal vs Circular

  • DMA_NORMAL:搬完一次即停。
  • DMA_CIRCULAR:搬完自动从头再来,常用于 DAC 波形输出、ADC 连续采样等持续场景。

3. 中断回调

HAL_DMA_Start_IT 启动后 CPU 立刻返回,搬运在后台进行;完成后硬件触发 DMA1_Channel1_IRQn,HAL 在中断里调用你注册的 APP_TransferComplete。这就是 DMA “解放 CPU” 的核心。

实验现象

  • DMA 在后台把 32 个字从源搬到目的;
  • 校验一致:绿灯常亮;不一致或出错:绿灯闪烁。

常见问题

  1. 编译报 BUFFER_SIZE 未定义?

    • 它在 main.h#define BUFFER_SIZE 32)中,确认工程包含该头文件。
  2. 传输没完成/不进回调?

    • 确认 __HAL_RCC_DMA_CLK_ENABLE() 已调用,且 HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn) 已使能。
    • 确认 DMA1_Channel1_IRQHandlerstartup 中正确指向 HAL 的中断处理(库工程默认已连)。
  3. 数据错位?

    • 检查源/目的地址自增与数据宽度是否匹配;外设寄存器做源/目的时要把对应端 Inc 设为 DISABLE
  4. 想连续搬运?

    • Mode 改为 DMA_CIRCULAR,并配合外设(如 ADC/DAC)使用。

小结

  • DMA 四要素:方向、地址自增、数据宽度、模式(普通/循环)
  • 内存↔内存、内存↔外设都用同一套 HAL_DMA_* API,区别只在地址与 Inc 配置。
  • 中断方式让 CPU “下单后就去干别的”,搬完由回调通知,是提升效率的关键。

拓展应用

  • ADC_ContinuousConversion_DMA:ADC 连续采样,DMA 自动把结果搬进数组。
  • SPI/DMA(见 SPI_TwoBoards_FullDuplexMaster_DMA):大批量 SPI 数据后台收发。
  • DMA_CIRCULAR + DAC:播放波形/音频。
  • 双缓冲(Double Buffer):一边 DMA 搬当前块,CPU 处理上一块,实现零等待流式处理。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计