title: DMA tags: [STM32, DMA, 数据传输]
Direct Memory Access(直接存储器访问),在不占用 CPU 的情况下在外设和存储器之间搬运数据:
传统方式 (CPU搬运):
CPU: 读取 外设DR → CPU寄存器 → 写入 内存数组
CPU忙 → 无法处理其他任务
DMA方式:
CPU: 配置DMA → 继续运行其他代码
DMA: 自动 外设DR → 内存数组 (不占用CPU)
CPU: DMA传输完成 → 中断通知CPU
DMA基本传输单元:
外设地址寄存器 DMA_CPARx
↓
外设 ←→ DMA 通道 ←→ 存储器
↑
传输方向
DMA1 (STM32F103C8T6):
├─ 通道1: ADC1 / TIM2_CH3 / TIM4_CH1
├─ 通道2: USART3_TX / SPI1_RX / TIM1_CH1
├─ 通道3: USART3_RX / SPI1_TX / TIM1_CH2
├─ 通道4: USART1_TX / SPI2_RX / TIM2_CH4
├─ 通道5: USART1_RX / SPI2_TX / TIM3_CH2
├─ 通道6: USART2_TX / SPI3_RX / TIM3_CH3
└─ 通道7: USART2_RX / SPI3_TX / TIM3_CH4
DMA2: 仅大容量芯片有 (F103RC以上)
内存到内存 (M2M):
源: 数组A 目标: 数组B
DMA直接复制,不经过CPU
外设到内存 (P2M):
源: ADC_DR/USART_DR 目标: 用户数组
典型:ADC多通道扫描
内存到外设 (M2P):
源: 发送缓冲区 目标: USART_DR/SPI_DR
典型:串口发送大批量数据
正常模式 (Normal):
传输 BufferSize 次后停止
需要重新配置 BufferSize 和 Cmd
循环模式 (Circular):
传输完 BufferSize 次后自动重置
地址回到起点,持续传输
适合 ADC 连续扫描 + DMA
typedef struct {
uint32_t DMA_PeripheralBaseAddr; // 外设基地址 (&USART1->DR, &ADC1->DR)
uint32_t DMA_MemoryBaseAddr; // 内存基地址 (uint32_t)&数组
uint32_t DMA_DIR; // DMA_DIR_PeripheralSRC / PeripheralDST
uint32_t DMA_BufferSize; // 传输次数
uint32_t DMA_PeripheralInc; // DMA_PeripheralInc_Enable / Disable
uint32_t DMA_MemoryInc; // DMA_MemoryInc_Enable / Disable
uint32_t DMA_PeripheralDataSize; // Byte / HalfWord / Word
uint32_t DMA_MemoryDataSize; // Byte / HalfWord / Word
uint32_t DMA_Mode; // DMA_Mode_Normal / Circular
uint32_t DMA_Priority; // VeryHigh / High / Medium / Low
uint32_t DMA_M2M; // DMA_M2M_Enable / Disable
} DMA_InitTypeDef;
| 函数 | 描述 |
|---|---|
DMA_Init(DMA_Channelx, &DMA_InitStructure) |
初始化 DMA 通道 |
DMA_Cmd(DMA_Channelx, ENABLE) |
使能 DMA 通道 |
DMA_ITConfig(DMA_Channelx, DMA_IT_TC, ENABLE) |
使能传输完成中断 |
DMA_GetFlagStatus(DMA_Channelx, DMA_FLAG_TCx) |
获取传输完成标志 |
DMA_ClearFlag(DMA_Channelx, DMA_FLAG_TCx) |
清除传输完成标志 |
DMA_GetITStatus(DMA_Channelx, DMA_IT_TC) |
获取传输完成中断状态 |
DMA_ClearITPendingBit(DMA_Channelx, DMA_IT_TC) |
清除传输完成中断标志 |
DMA_SetCurrDataCounter(DMA_Channelx, uint16_t) |
设置剩余传输次数 |
| 通道 | 请求1 | 请求2 | 请求3 | 请求4 | 请求5 |
|---|---|---|---|---|---|
| DMA1_CH1 | ADC1 | TIM2_CH3 | TIM4_CH1 | — | — |
| DMA1_CH2 | USART3_TX | SPI1_RX | TIM1_CH1 | TIM2_UP | TIM3_CH3 |
| DMA1_CH3 | USART3_RX | SPI1_TX | TIM1_CH2 | TIM2_CH1 | TIM3_CH4 |
| DMA1_CH4 | USART1_TX | SPI2_RX | TIM1_CH3 | TIM2_CH2 | TIM4_CH3 |
| DMA1_CH5 | USART1_RX | SPI2_TX | TIM1_CH4 | TIM2_CH4 | — |
| DMA1_CH6 | USART2_TX | SPI3_RX | TIM1_UP | TIM3_CH1 | — |
| DMA1_CH7 | USART2_RX | SPI3_TX | TIM1_TRIG | TIM3_CH2 | — |
#include "stm32f10x.h"
#define BUFFER_SIZE 32
const uint32_t src_buf[BUFFER_SIZE] = {
0, 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
};
uint32_t dst_buf[BUFFER_SIZE] = {0};
void DMA_M2M_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src_buf;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dst_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 内存到内存
DMA_Init(DMA1_Channel6, &DMA_InitStructure); // CH6 可用于 M2M
DMA_Cmd(DMA1_Channel6, ENABLE);
// 等待传输完成
while (DMA_GetFlagStatus(DMA1_Channel6, DMA1_FLAG_TC6) == RESET);
// 此时 dst_buf 内容应与 src_buf 相同
}
int main(void)
{
DMA_M2M_Init();
while (1);
}
#include "stm32f10x.h"
#define ADC_CH_COUNT 4
uint16_t adc_values[ADC_CH_COUNT]; // PA0–PA3
void ADC1_DMA_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// ADC 配置:扫描+连续
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = ADC_CH_COUNT;
ADC_Init(ADC1, &ADC_InitStructure);
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_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_values;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_CH_COUNT;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
ADC_DMACmd(ADC1, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// DMA 自动将 4 通道结果循环写入 adc_values[]
}
// 读取最新值
// adc_values[0]=PA0, [1]=PA1, [2]=PA2, [3]=PA3
// DMA 在 Circular 模式下自动更新
#include "stm32f10x.h"
const uint8_t tx_buffer[] = "Hello DMA!\r\n";
void USART1_DMA_Init(void)
{
// ... USART1 初始化同上节课内容 ...
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = sizeof(tx_buffer) - 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure); // CH4: USART1_TX
DMA_Cmd(DMA1_Channel4, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
// DMA 自动将 tx_buffer 数据发送出去
}
DMA_BufferSize 是传输次数:不是字节数。HalfWord 模式下传输 10 次 = 20 字节,Word 模式下传输 10 次 = 40 字节
外设地址不要配错:外设地址必须使用 (uint32_t)&外设->DR 形式。例:ADC 是 &ADC1->DR,USART 是 &USART1->DR
DMA 通道与硬件请求对应:ADC1 只能使用 DMA1_Channel1。USART1_TX 只能用 DMA1_Channel4。通道选错不会报错但也不会传输
内存到内存(M2M)模式限制:
DMA_PeripheralBaseAddr 指向源地址,DMA_MemoryBaseAddr 指向目标地址DMA_DIR 必须是 DMA_DIR_PeripheralSRC循环模式(Circular)注意:使能循环后传输永不停,不触发正常传输完成中断(只有半传输和错误中断)。想要周期性处理数据时,用半传输中断(DMA_IT_HT)或定时器中断
传输完成中断:正常模式下每传输 BufferSize 次触发一次 TC 中断;使用 DMA_ITConfig(DMA_Channelx, DMA_IT_TC, ENABLE) 使能;中断服务函数中先清除标志再处理数据
DMA 使能顺序:先配置 DMA,再使能外设的 DMA 请求。如果先使能外设再使能 DMA,外设可能在 DMA 未就绪时产生第一次请求
DMA 半传输中断:DMA_IT_HT 在传输到一半时触发,适合双缓冲(乒乓缓冲)场景
ADC 连续模式 + DMA 循环:ADC 设置 ContinuousConvMode=ENABLE、DMA 设 Mode=Circular,可做到全程无 CPU 参与采集。但注意 ADC_CLK 越快,DMA 带宽占用越高
DMA 不会自动停止:正常模式传输完成后自动停止(DMA_Cmd 自动变 DISABLE),再次传输前需重新使能 DMA_Cmd(DMA_Channelx, ENABLE);循环模式不会停止,即使重新配置也需要先 Disable 再 Enable