DMA(Direct Memory Access,直接存储器访问)是一种外设,可以在不需要 CPU 参与的情况下,在存储器与外设之间、存储器与存储器之间自动转运数据。
STM32F103 有 DMA1(7 通道)和 DMA2(5 通道)。每个通道对应不同的外设请求。
DMA 优先级分为 4 级:Low < Medium < High < VeryHigh。当多个通道同时请求时,高优先级通道优先响应。
上图:STM32 DMA 模块框图(来源:STM32入门教程 PPT 第102页)
上图:DMA 内部结构(源地址、目标地址、传输计数器)(来源:STM32入门教程 PPT 第103页)
将源内存区域的数据搬运到目标内存区域。需要使能 DMA_M2M(存储器到存储器模式),此时 DMA 从软件请求触发转为连续工作。
MyDMA.c(标准库)void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; // 源地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 地址自增
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; // 目标地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设到存储器方向
DMA_InitStructure.DMA_BufferSize = Size; // 转运次数
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_Cmd(DMA1_Channel1, DISABLE); // 先失能,由 Transfer 函数启动
}
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE); // 失能以写入传输计数器
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); // 设置转运次数
DMA_Cmd(DMA1_Channel1, ENABLE); // 使能启动
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); // 等待传输完成
DMA_ClearFlag(DMA1_FLAG_TC1);
}
DMA_HandleTypeDef hdma_m2m;
void MyDMA_Init_HAL(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_m2m.Instance = DMA1_Channel1;
hdma_m2m.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_m2m.Init.PeriphInc = DMA_PINC_ENABLE; // 源地址自增
hdma_m2m.Init.MemInc = DMA_MINC_ENABLE; // 目标地址自增
hdma_m2m.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_m2m.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_m2m.Init.Mode = DMA_NORMAL;
hdma_m2m.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&hdma_m2m);
}
void MyDMA_Transfer_HAL(void)
{
HAL_DMA_Start(&hdma_m2m, (uint32_t)DataA, (uint32_t)DataB, 4);
HAL_DMA_PollForTransfer(&hdma_m2m, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
}
// 全局回调
void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma)
{
// 传输完成 — 设置标志位供主循环查询
TransferDone = 1;
}
void MyDMA_Transfer_IT_HAL(void)
{
HAL_DMA_Start_IT(&hdma_m2m, (uint32_t)DataA, (uint32_t)DataB, 4);
}
int main(void)
{
HAL_Init();
MyDMA_Init_HAL((uint32_t)DataA, (uint32_t)DataB, 4);
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
while (1)
{
if (TransferDone)
{
TransferDone = 0;
// 处理转运完成后的数据
}
}
}
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};
int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
while (1) {
DataA[0]++; DataA[1]++; DataA[2]++; DataA[3]++;
// 显示转运前数据
MyDMA_Transfer(); // 启动 DMA 转运
// 显示转运后数据
Delay_ms(1000);
}
}
关键配置项:
DMA_DIR_PeripheralSRC:外设作为源(即使两个都是存储器,M2M 模式下也如此设置)DMA_PeripheralInc / DMA_MemoryInc:使能后源/目的地址自动递增DMA_M2M_Enable:使能存储器到存储器模式DMA_Mode_Normal:正常模式,传输一次后停止ADC 每次转换完成后自动触发 DMA 请求,DMA 将 ADC 数据寄存器(ADC1->DR)的值转运到内存数组。配合 ADC 的连续转换 + 扫描模式,实现多通道自动采集。
DMA_HandleTypeDef hdma_adc;
void AD_Init_HAL(void)
{
// GPIO, ADC 时钟使能 ...(省略,同标准库)
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_adc.Instance = DMA1_Channel1;
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; // ADC 数据寄存器固定
hdma_adc.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增
hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // ADC 16位
hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_adc.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&hdma_adc);
// 连接 DMA 到 ADC
__HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc);
HAL_DMA_Start(&hdma_adc, (uint32_t)&ADC1->DR, (uint32_t)AD_Value, 4);
HAL_ADC_Start_DMA(&hadc, (uint32_t *)AD_Value, 4);
}
uint16_t AD_Value[4];
void AD_Init(void)
{
// ...GPIO + ADC 时钟、引脚初始化...
// 配置规则组 4 个通道(扫描模式)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
// ADC 初始化:连续 + 扫描 + 4 通道
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 4;
// DMA 初始化
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 4;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 外设触发,非 M2M
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE); // ADC 触发 DMA 信号使能
ADC_Cmd(ADC1, ENABLE);
// 校准...
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 触发一次后连续转换
}
| 对比项 | 纯软件多通道 | DMA+ADC |
|---|---|---|
| 触发方式 | 每次软件触发 | ADC 连续转换自动触发 |
| 数据搬运 | CPU 循环读取 DR 寄存器 | DMA 自动将 DR 写入数组 |
| CPU 占用 | 每次转换都占用 | 仅初始化时配置 |
| 通道扩展 | 每次读取一个通道 | 扫描模式一次转换 4 通道 |
| 模式 | 正常模式 | 循环模式,持续采集 |
int main(void)
{
OLED_Init();
AD_Init(); // ADC+DMA 初始化后自动连续采集
while (1) {
OLED_ShowNum(1, 5, AD_Value[0], 4); // 直接读取数组已更新的结果
OLED_ShowNum(2, 5, AD_Value[1], 4);
OLED_ShowNum(3, 5, AD_Value[2], 4);
OLED_ShowNum(4, 5, AD_Value[3], 4);
Delay_ms(100);
}
}