STM32 DMA数据传输机制详解
DMA(直接存储器访问)是一种高效的数据传输技术,允许外设与内存之间或内存与内存之间在无需中央处理器干预的情况下进行高速数据交换,从而显著降低CPU负载。
STM32系列微控制器中的DMA模块提供12个可独立配置的通道,其中DMA1拥有7个通道,DMA2有5个。每个通道支持软件触发和特定硬件事件触发。通常情况下,内存到内存的数据传输由软件启动,而外设到内存的传输则通过硬件信号触发。
以STM32F103C8T6为例,其具备完整的DMA1功能,可用于实现如数据搬运、模拟信号采集等高效率任务。
存储器映射与总线架构
系统中包含主控单元(如Cortex-M3内核)与多个从属设备。总线矩阵连接这些组件:左侧为发起访问的主动单元,右侧为被访问的被动单元。寄存器作为软硬件之间的桥梁,既可被程序读写,又能通过位控制影响外设行为。
DMA核心工作原理
当配置好传输参数后,需满足以下三个条件才能开始传输:
- 传输计数器大于零;
- 触发源发出有效信号;
- DMA通道已使能。
若启用中断功能,还需通过DMA_ITConfig开启中断,并在NVIC中注册对应中断向量。传输完成后,传输计数器自动归零。若需重新启动,必须先关闭DMA,更新计数器,再重新开启。
常见应用场景
1. 内存间数据复制
将源地址数据批量复制至目标地址,适用于图像处理、缓冲区管理等场景。
2. ADC多通道扫描 + DMA自动采集
启动一次转换后,ADC按顺序对多个通道采样,每次结果存入数据寄存器(ADC_DR)。借助DMA,可在每个通道完成转换后自动将结果搬运至指定内存区域。由于启用了循环模式(Circular Mode),系统将持续运行,实现连续采样。
实现代码示例
【基础内存搬运】
#include "MyDMA.h"
uint16_t dma_buffer_size;
void MyDMA_Config(uint32_t src_addr, uint32_t dst_addr, uint16_t size)
{
dma_buffer_size = size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef dma_init_struct;
dma_init_struct.DMA_PeripheralBaseAddr = src_addr;
dma_init_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
dma_init_struct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
dma_init_struct.DMA_MemoryBaseAddr = dst_addr;
dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_init_struct.DMA_Mode = DMA_Mode_Normal;
dma_init_struct.DMA_BufferSize = size;
dma_init_struct.DMA_DIR = DMA_DIR_PeripheralSRC;
dma_init_struct.DMA_M2M = DMA_M2M_Enable;
dma_init_struct.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel1, &dma_init_struct);
DMA_Cmd(DMA1_Channel1, DISABLE);
}
void Start_DMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, dma_buffer_size);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
【ADC多通道连续采集 + DMA】
#include "Bsp_ADC.h"
uint16_t adc_results[4] = {0};
void ADC_Configuration(void)
{
// 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 设置ADC时钟分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置GPIO为模拟输入
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AIN;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init_struct);
// 配置规则组通道
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);
// 配置DMA通道
DMA_InitTypeDef dma_config;
dma_config.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
dma_config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
dma_config.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma_config.DMA_MemoryBaseAddr = (uint32_t)adc_results;
dma_config.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
dma_config.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_config.DMA_Mode = DMA_Mode_Circular;
dma_config.DMA_BufferSize = 4;
dma_config.DMA_DIR = DMA_DIR_PeripheralSRC;
dma_config.DMA_M2M = DMA_M2M_Disable;
dma_config.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel1, &dma_config);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 初始化ADC
ADC_InitTypeDef adc_config;
adc_config.ADC_Mode = ADC_Mode_Independent;
adc_config.ADC_DataAlign = ADC_DataAlign_Right;
adc_config.ADC_NbrOfChannel = 4;
adc_config.ADC_ScanConvMode = ENABLE;
adc_config.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc_config.ADC_ContinuousConvMode = ENABLE;
ADC_Init(ADC1, &adc_config);
// 校准ADC
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
// 启动转换
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发首次转换
}
该方案实现了四个模拟通道的持续采样,所有数据通过DMA自动流入数组 adc_results[],无需频繁轮询或中断干预,极大提升了系统响应速度与实时性。
