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_SIZE 在 main.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 个字从源搬到目的;
- 校验一致:绿灯常亮;不一致或出错:绿灯闪烁。
常见问题
-
编译报 BUFFER_SIZE 未定义?
- 它在
main.h(#define BUFFER_SIZE 32)中,确认工程包含该头文件。
-
传输没完成/不进回调?
- 确认
__HAL_RCC_DMA_CLK_ENABLE() 已调用,且 HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn) 已使能。
- 确认
DMA1_Channel1_IRQHandler 在 startup 中正确指向 HAL 的中断处理(库工程默认已连)。
-
数据错位?
- 检查源/目的地址自增与数据宽度是否匹配;外设寄存器做源/目的时要把对应端
Inc 设为 DISABLE。
-
想连续搬运?
- 把
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 处理上一块,实现零等待流式处理。