嵌入式开发必掌握:DMA直接存储访问实战精讲(内存传输+外设交互+双缓冲机制)
·
嵌入式开发必掌握:DMA直接存储访问实战精讲(内存传输+外设交互+双缓冲机制)
标签:嵌入式开发、DMA、STM32、内存传输、外设交互、双缓冲、串口DMA、ADC DMA、底层驱动、性能优化
前言
摘要:DMA(直接存储访问)是解放CPU的利器,是实现高效数据传输的核心技术。本文从DMA工作原理、通道配置、内存到内存传输、外设到内存传输、双缓冲机制到串口DMA收发和ADC DMA采集实战,提供全套可直接量产的驱动代码,同时总结DMA通道冲突、缓冲区对齐、传输完成判断、双缓冲切换等踩坑经验,帮助开发者掌握高效可靠的DMA编程技术。
DMA(Direct Memory Access)是嵌入式系统中实现高效数据传输的核心外设,广泛应用于串口通信、ADC采集、SPI传输、内存拷贝等场景。看似简单的DMA配置,实际开发中却容易遇到通道冲突、数据错位、传输不完整、CPU等待等问题。
文章主要内容:
- DMA工作原理与架构
- DMA通道配置详解
- 内存到内存传输
- 外设到内存传输
- 双缓冲机制实现
- 串口DMA收发实战
- ADC DMA采集实战
一、DMA工作原理与架构
1.1 DMA基本概念
DMA(Direct Memory Access):直接存储访问,无需CPU干预即可实现数据传输。
DMA优势对比:
| 传输方式 | CPU占用率 | 传输速度 | 适用场景 |
|---|---|---|---|
| CPU轮询 | 100% | 较慢 | 小数据量 |
| 中断方式 | 50-80% | 中等 | 中等数据量 |
| DMA传输 | 0-10% | 最快 | 大数据量 |
1.2 STM32 DMA架构
DMA通道映射表(STM32F103):
| DMA1通道 | 外设请求 | 方向 |
|---|---|---|
| 通道1 | ADC1 | 外设→内存 |
| 通道2 | SPI1_RX | 外设→内存 |
| 通道3 | SPI1_TX | 内存→外设 |
| 通道4 | USART1_TX | 内存→外设 |
| 通道5 | USART1_RX | 外设→内存 |
| 通道6 | I2C1_TX | 内存→外设 |
| 通道7 | I2C1_RX | 外设→内存 |
1.3 DMA核心寄存器
typedef struct
{
volatile uint32_t CCR;
volatile uint32_t CNDTR;
volatile uint32_t CPAR;
volatile uint32_t CMAR;
} DMA_Channel_TypeDef;
typedef struct
{
volatile uint32_t ISR;
volatile uint32_t IFCR;
DMA_Channel_TypeDef Channel1;
DMA_Channel_TypeDef Channel2;
DMA_Channel_TypeDef Channel3;
DMA_Channel_TypeDef Channel4;
DMA_Channel_TypeDef Channel5;
DMA_Channel_TypeDef Channel6;
DMA_Channel_TypeDef Channel7;
} DMA_TypeDef;
关键寄存器说明:
| 寄存器 | 作用 | 说明 |
|---|---|---|
| CCR | 通道配置寄存器 | 配置传输方向、模式、优先级等 |
| CNDTR | 数据数量寄存器 | 剩余待传输数据数量 |
| CPAR | 外设地址寄存器 | 外设数据寄存器地址 |
| CMAR | 内存地址寄存器 | 内存缓冲区地址 |
| ISR | 中断状态寄存器 | 传输完成、半传输、错误标志 |
| IFCR | 中断清除寄存器 | 清除中断标志 |
二、DMA通道配置详解
2.1 DMA配置参数
typedef struct
{
uint32_t DMA_PeripheralBaseAddr;
uint32_t DMA_MemoryBaseAddr;
uint32_t DMA_DIR;
uint32_t DMA_BufferSize;
uint32_t DMA_PeripheralInc;
uint32_t DMA_MemoryInc;
uint32_t DMA_PeripheralDataSize;
uint32_t DMA_MemoryDataSize;
uint32_t DMA_Mode;
uint32_t DMA_Priority;
uint32_t DMA_M2M;
} DMA_InitTypeDef;
配置参数详解:
2.2 DMA初始化函数
#include "stm32f10x.h"
void DMA_Channel_Init(DMA_Channel_TypeDef* DMA_Channel,
uint32_t peripheral_addr,
uint32_t memory_addr,
uint16_t buffer_size,
uint32_t direction,
uint32_t data_size,
uint32_t mode,
uint32_t priority)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = peripheral_addr;
DMA_InitStructure.DMA_MemoryBaseAddr = memory_addr;
DMA_InitStructure.DMA_DIR = direction;
DMA_InitStructure.DMA_BufferSize = buffer_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = data_size;
DMA_InitStructure.DMA_MemoryDataSize = data_size;
DMA_InitStructure.DMA_Mode = mode;
DMA_InitStructure.DMA_Priority = priority;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_Channel, &DMA_InitStructure);
}
void DMA_EnableChannel(DMA_Channel_TypeDef* DMA_Channel)
{
DMA_Cmd(DMA_Channel, ENABLE);
}
void DMA_DisableChannel(DMA_Channel_TypeDef* DMA_Channel)
{
DMA_Cmd(DMA_Channel, DISABLE);
}
void DMA_SetBufferSize(DMA_Channel_TypeDef* DMA_Channel, uint16_t size)
{
DMA_Channel->CNDTR = size;
}
uint16_t DMA_GetRemainingData(DMA_Channel_TypeDef* DMA_Channel)
{
return (uint16_t)DMA_Channel->CNDTR;
}
2.3 DMA中断配置
typedef enum
{
DMA_IT_TC = 0x00000002,
DMA_IT_HT = 0x00000004,
DMA_IT_TE = 0x00000008
} DMA_Interrupt_t;
void DMA_EnableInterrupt(DMA_Channel_TypeDef* DMA_Channel, uint32_t interrupt)
{
DMA_ITConfig(DMA_Channel, interrupt, ENABLE);
}
void DMA_DisableInterrupt(DMA_Channel_TypeDef* DMA_Channel, uint32_t interrupt)
{
DMA_ITConfig(DMA_Channel, interrupt, DISABLE);
}
uint8_t DMA_GetFlagStatus(uint32_t flag)
{
return DMA_GetFlagStatus(flag) != RESET;
}
void DMA_ClearFlag(uint32_t flag)
{
DMA_ClearFlag(flag);
}
void DMA_Config_NVIC(uint8_t channel, uint8_t priority)
{
IRQn_Type irq;
switch(channel)
{
case 1: irq = DMA1_Channel1_IRQn; break;
case 2: irq = DMA1_Channel2_IRQn; break;
case 3: irq = DMA1_Channel3_IRQn; break;
case 4: irq = DMA1_Channel4_IRQn; break;
case 5: irq = DMA1_Channel5_IRQn; break;
case 6: irq = DMA1_Channel6_IRQn; break;
case 7: irq = DMA1_Channel7_IRQn; break;
default: return;
}
NVIC_SetPriority(irq, priority);
NVIC_EnableIRQ(irq);
}
三、内存到内存传输
3.1 内存拷贝DMA实现
#define MEMCPY_DMA_CHANNEL DMA1_Channel1
void MemCpy_DMA_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
}
void MemCpy_DMA(void *dest, const void *src, uint32_t size)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(MEMCPY_DMA_CHANNEL);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dest;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
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_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_Init(MEMCPY_DMA_CHANNEL, &DMA_InitStructure);
DMA_Cmd(MEMCPY_DMA_CHANNEL, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
DMA_Cmd(MEMCPY_DMA_CHANNEL, DISABLE);
}
void MemCpy_DMA_Word(void *dest, const void *src, uint32_t word_count)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(MEMCPY_DMA_CHANNEL);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dest;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = word_count;
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(MEMCPY_DMA_CHANNEL, &DMA_InitStructure);
DMA_Cmd(MEMCPY_DMA_CHANNEL, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
DMA_Cmd(MEMCPY_DMA_CHANNEL, DISABLE);
}
3.2 内存填充DMA实现
void MemSet_DMA(void *dest, uint8_t value, uint32_t size)
{
uint8_t *ptr = (uint8_t *)dest;
uint32_t i;
for(i = 0; i < size; i++)
{
ptr[i] = value;
}
}
void MemSet_DMA_Optimized(void *dest, uint8_t value, uint32_t size)
{
uint32_t *ptr32 = (uint32_t *)dest;
uint32_t pattern = (value << 24) | (value << 16) | (value << 8) | value;
uint32_t word_count = size / 4;
uint32_t remaining = size % 4;
if(word_count > 0)
{
DMA_InitTypeDef DMA_InitStructure;
static uint32_t fill_pattern;
fill_pattern = pattern;
DMA_DeInit(MEMCPY_DMA_CHANNEL);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&fill_pattern;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ptr32;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = word_count;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
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(MEMCPY_DMA_CHANNEL, &DMA_InitStructure);
DMA_Cmd(MEMCPY_DMA_CHANNEL, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
DMA_Cmd(MEMCPY_DMA_CHANNEL, DISABLE);
}
if(remaining > 0)
{
uint8_t *ptr8 = (uint8_t *)(ptr32 + word_count);
uint32_t i;
for(i = 0; i < remaining; i++)
{
ptr8[i] = value;
}
}
}
四、外设到内存传输
4.1 ADC DMA传输
#define ADC_DMA_BUFFER_SIZE 100
uint16_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE];
volatile uint8_t adc_dma_complete = 0;
void ADC_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_dma_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUFFER_SIZE;
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_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
NVIC_SetPriority(DMA1_Channel1_IRQn, 5);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1))
{
DMA_ClearITPendingBit(DMA1_IT_TC1);
adc_dma_complete = 1;
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA1_Channel1->CNDTR = ADC_DMA_BUFFER_SIZE;
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
}
float ADC_GetAverageVoltage(void)
{
uint32_t sum = 0;
uint16_t i;
for(i = 0; i < ADC_DMA_BUFFER_SIZE; i++)
{
sum += adc_dma_buffer[i];
}
return (float)sum / ADC_DMA_BUFFER_SIZE * 3.3f / 4095.0f;
}
4.2 SPI DMA传输
#define SPI_DMA_BUFFER_SIZE 256
uint8_t spi_tx_buffer[SPI_DMA_BUFFER_SIZE];
uint8_t spi_rx_buffer[SPI_DMA_BUFFER_SIZE];
volatile uint8_t spi_dma_complete = 0;
void SPI_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)spi_rx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = SPI_DMA_BUFFER_SIZE;
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_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)spi_tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
NVIC_SetPriority(DMA1_Channel3_IRQn, 5);
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
SPI_DeInit(SPI1);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
SPI_Cmd(SPI1, ENABLE);
}
void SPI_DMA_Transfer(uint8_t *tx_data, uint8_t *rx_data, uint16_t size)
{
spi_dma_complete = 0;
DMA_Cmd(DMA1_Channel2, DISABLE);
DMA_Cmd(DMA1_Channel3, DISABLE);
DMA1_Channel2->CMAR = (uint32_t)rx_data;
DMA1_Channel2->CNDTR = size;
DMA1_Channel3->CMAR = (uint32_t)tx_data;
DMA1_Channel3->CNDTR = size;
DMA_Cmd(DMA1_Channel2, ENABLE);
DMA_Cmd(DMA1_Channel3, ENABLE);
}
void DMA1_Channel3_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC3))
{
DMA_ClearITPendingBit(DMA1_IT_TC3);
spi_dma_complete = 1;
}
}
五、双缓冲机制实现
5.1 双缓冲原理
5.2 双缓冲实现
#define DOUBLE_BUFFER_SIZE 256
typedef struct
{
uint8_t buffer_a[DOUBLE_BUFFER_SIZE];
uint8_t buffer_b[DOUBLE_BUFFER_SIZE];
volatile uint8_t active_buffer;
volatile uint8_t data_ready;
volatile uint16_t data_size;
} DoubleBuffer_t;
DoubleBuffer_t double_buffer = {0};
void DoubleBuffer_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)double_buffer.buffer_a;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = DOUBLE_BUFFER_SIZE;
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_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
NVIC_SetPriority(DMA1_Channel5_IRQn, 5);
NVIC_EnableIRQ(DMA1_Channel5_IRQn);
DMA_Cmd(DMA1_Channel5, ENABLE);
double_buffer.active_buffer = 0;
double_buffer.data_ready = 0;
}
void DMA1_Channel5_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC5))
{
DMA_ClearITPendingBit(DMA1_IT_TC5);
double_buffer.data_ready = 1;
double_buffer.data_size = DOUBLE_BUFFER_SIZE - DMA1_Channel5->CNDTR;
DMA_Cmd(DMA1_Channel5, DISABLE);
if(double_buffer.active_buffer == 0)
{
DMA1_Channel5->CMAR = (uint32_t)double_buffer.buffer_b;
double_buffer.active_buffer = 1;
}
else
{
DMA1_Channel5->CMAR = (uint32_t)double_buffer.buffer_a;
double_buffer.active_buffer = 0;
}
DMA1_Channel5->CNDTR = DOUBLE_BUFFER_SIZE;
DMA_Cmd(DMA1_Channel5, ENABLE);
}
}
uint8_t* DoubleBuffer_GetProcessBuffer(void)
{
if(double_buffer.active_buffer == 0)
{
return double_buffer.buffer_b;
}
else
{
return double_buffer.buffer_a;
}
}
void DoubleBuffer_ProcessData(void (*process_func)(uint8_t*, uint16_t))
{
if(double_buffer.data_ready)
{
uint8_t *buffer = DoubleBuffer_GetProcessBuffer();
process_func(buffer, double_buffer.data_size);
double_buffer.data_ready = 0;
}
}
5.3 循环缓冲+双缓冲
#define CIRCULAR_BUFFER_SIZE 512
typedef struct
{
uint8_t buffer[CIRCULAR_BUFFER_SIZE];
volatile uint16_t write_index;
volatile uint16_t read_index;
volatile uint16_t count;
} CircularBuffer_t;
CircularBuffer_t circular_buffer = {0};
void CircularBuffer_Init(void)
{
circular_buffer.write_index = 0;
circular_buffer.read_index = 0;
circular_buffer.count = 0;
}
uint8_t CircularBuffer_Write(uint8_t data)
{
if(circular_buffer.count >= CIRCULAR_BUFFER_SIZE)
{
return 0;
}
circular_buffer.buffer[circular_buffer.write_index] = data;
circular_buffer.write_index++;
if(circular_buffer.write_index >= CIRCULAR_BUFFER_SIZE)
{
circular_buffer.write_index = 0;
}
circular_buffer.count++;
return 1;
}
uint8_t CircularBuffer_Read(uint8_t *data)
{
if(circular_buffer.count == 0)
{
return 0;
}
*data = circular_buffer.buffer[circular_buffer.read_index];
circular_buffer.read_index++;
if(circular_buffer.read_index >= CIRCULAR_BUFFER_SIZE)
{
circular_buffer.read_index = 0;
}
circular_buffer.count--;
return 1;
}
uint16_t CircularBuffer_GetCount(void)
{
return circular_buffer.count;
}
uint8_t CircularBuffer_IsFull(void)
{
return circular_buffer.count >= CIRCULAR_BUFFER_SIZE;
}
uint8_t CircularBuffer_IsEmpty(void)
{
return circular_buffer.count == 0;
}
六、串口DMA收发实战
6.1 串口DMA发送
#define UART_TX_BUFFER_SIZE 256
uint8_t uart_tx_buffer[UART_TX_BUFFER_SIZE];
volatile uint8_t uart_tx_busy = 0;
void UART_DMA_TX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart_tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = UART_TX_BUFFER_SIZE;
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_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
NVIC_SetPriority(DMA1_Channel4_IRQn, 5);
NVIC_EnableIRQ(DMA1_Channel4_IRQn);
USART_DeInit(USART1);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
USART_Cmd(USART1, ENABLE);
}
void UART_DMA_Send(uint8_t *data, uint16_t size)
{
if(uart_tx_busy)
{
return;
}
uart_tx_busy = 1;
memcpy(uart_tx_buffer, data, size);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR = size;
DMA_Cmd(DMA1_Channel4, ENABLE);
}
void DMA1_Channel4_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC4))
{
DMA_ClearITPendingBit(DMA1_IT_TC4);
uart_tx_busy = 0;
}
}
uint8_t UART_IsTxIdle(void)
{
return !uart_tx_busy;
}
6.2 串口DMA接收
#define UART_RX_BUFFER_SIZE 256
uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];
volatile uint16_t uart_rx_count = 0;
void UART_DMA_RX_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart_rx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = UART_RX_BUFFER_SIZE;
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_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
NVIC_SetPriority(USART1_IRQn, 5);
NVIC_EnableIRQ(USART1_IRQn);
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
USART_ReceiveData(USART1);
uart_rx_count = UART_RX_BUFFER_SIZE - DMA1_Channel5->CNDTR;
ProcessReceivedData(uart_rx_buffer, uart_rx_count);
uart_rx_count = 0;
}
}
uint16_t UART_GetReceivedCount(void)
{
return UART_RX_BUFFER_SIZE - DMA1_Channel5->CNDTR;
}
6.3 完整串口DMA收发
typedef struct
{
uint8_t tx_buffer[UART_TX_BUFFER_SIZE];
uint8_t rx_buffer[UART_RX_BUFFER_SIZE];
volatile uint8_t tx_busy;
volatile uint16_t rx_count;
void (*rx_callback)(uint8_t*, uint16_t);
} UART_DMA_t;
UART_DMA_t uart_dma = {0};
void UART_DMA_Init(void)
{
UART_DMA_TX_Init();
UART_DMA_RX_Init();
}
void UART_DMA_SetRxCallback(void (*callback)(uint8_t*, uint16_t))
{
uart_dma.rx_callback = callback;
}
void UART_DMA_SendData(uint8_t *data, uint16_t size)
{
if(size > UART_TX_BUFFER_SIZE)
{
size = UART_TX_BUFFER_SIZE;
}
while(uart_dma.tx_busy);
uart_dma.tx_busy = 1;
memcpy(uart_dma.tx_buffer, data, size);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR = size;
DMA_Cmd(DMA1_Channel4, ENABLE);
}
void ProcessReceivedData(uint8_t *data, uint16_t size)
{
if(uart_dma.rx_callback)
{
uart_dma.rx_callback(data, size);
}
}
int main(void)
{
UART_DMA_Init();
printf("UART DMA Test\r\n");
while(1)
{
if(uart_dma.rx_count > 0)
{
UART_DMA_SendData(uart_dma.rx_buffer, uart_dma.rx_count);
uart_dma.rx_count = 0;
}
}
}
七、ADC DMA采集实战
7.1 多通道ADC DMA采集
#define ADC_CHANNELS 4
#define ADC_SAMPLES 100
typedef struct
{
uint16_t buffer[ADC_CHANNELS * ADC_SAMPLES];
float voltages[ADC_CHANNELS];
volatile uint8_t conversion_complete;
uint32_t sample_count;
} Multi_ADC_DMA_t;
Multi_ADC_DMA_t multi_adc_dma = {0};
void Multi_ADC_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
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);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)multi_adc_dma.buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_CHANNELS * ADC_SAMPLES;
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);
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
NVIC_SetPriority(DMA1_Channel1_IRQn, 5);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DeInit(ADC1);
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_CHANNELS;
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);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1))
{
DMA_ClearITPendingBit(DMA1_IT_TC1);
multi_adc_dma.conversion_complete = 1;
multi_adc_dma.sample_count++;
}
}
void Multi_ADC_ProcessData(void)
{
uint8_t ch;
uint16_t sample;
uint32_t sum[ADC_CHANNELS] = {0};
for(sample = 0; sample < ADC_SAMPLES; sample++)
{
for(ch = 0; ch < ADC_CHANNELS; ch++)
{
uint16_t index = sample * ADC_CHANNELS + ch;
sum[ch] += multi_adc_dma.buffer[index];
}
}
for(ch = 0; ch < ADC_CHANNELS; ch++)
{
multi_adc_dma.voltages[ch] =
(float)sum[ch] / ADC_SAMPLES * 3.3f / 4095.0f;
}
}
void Multi_ADC_PrintVoltages(void)
{
uint8_t ch;
printf("\r\n=== ADC Voltages ===\r\n");
printf("Sample Count: %lu\r\n", multi_adc_dma.sample_count);
for(ch = 0; ch < ADC_CHANNELS; ch++)
{
printf("Channel %d: %.3fV\r\n", ch, multi_adc_dma.voltages[ch]);
}
}
八、DMA开发踩坑总结
8.1 DMA常见问题与解决方案
8.2 踩坑经验汇总
坑点1:DMA通道冲突
// ❌ 错误:同一通道被多个外设使用
// DMA1_Channel4已被USART1_TX占用
DMA_Init(DMA1_Channel4, &DMA_InitStructure); // 冲突!
// ✅ 正确:查阅通道映射表,选择正确通道
// USART1_TX -> DMA1_Channel4
// USART1_RX -> DMA1_Channel5
// SPI1_TX -> DMA1_Channel3
// SPI1_RX -> DMA1_Channel2
坑点2:数据宽度不匹配
// ❌ 错误:外设和内存数据宽度不一致
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 错误!
// ✅ 正确:保持数据宽度一致
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
坑点3:地址未对齐
// ❌ 错误:缓冲区地址未对齐
uint8_t buffer[100]; // 可能未对齐到4字节边界
// ✅ 正确:强制对齐
__attribute__((aligned(4))) uint8_t buffer[100];
// 或者
uint32_t buffer[25]; // 32位数组天然对齐
坑点4:未等待传输完成
// ❌ 错误:启动DMA后立即访问数据
DMA_Cmd(DMA1_Channel1, ENABLE);
ProcessData(buffer); // 数据还未传输完成!
// ✅ 正确:等待传输完成
DMA_Cmd(DMA1_Channel1, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
ProcessData(buffer);
// ✅ 最佳:使用中断通知
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// 在中断中处理数据
坑点5:循环模式缓冲区溢出
// ❌ 错误:循环模式下未及时处理数据
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 数据可能被覆盖!
// ✅ 正确:使用双缓冲或及时处理
// 方案1:双缓冲
if(double_buffer.data_ready)
{
ProcessData(double_buffer.buffer_a);
double_buffer.data_ready = 0;
}
// 方案2:检查剩余数据量
uint16_t remaining = DMA1_Channel1->CNDTR;
if(remaining < last_remaining)
{
uint16_t received = last_remaining - remaining;
ProcessData(buffer, received);
}
last_remaining = remaining;
坑点6:DMA传输计数器未重置
// ❌ 错误:重新启动DMA未重置计数器
DMA_Cmd(DMA1_Channel1, ENABLE);
// 再次启动时,CNDTR可能为0,不传输!
// ✅ 正确:每次启动前重置计数器
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA1_Channel1->CNDTR = buffer_size;
DMA_Cmd(DMA1_Channel1, ENABLE);
8.3 性能优化建议
优化1:选择合适的数据宽度
// 8位数据:每次传输1字节
DMA_PeripheralDataSize_Byte
// 16位数据:每次传输2字节,效率提升2倍
DMA_PeripheralDataSize_HalfWord
// 32位数据:每次传输4字节,效率提升4倍
DMA_PeripheralDataSize_Word
// 示例:ADC数据是12位,使用16位传输
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
优化2:使用循环模式减少CPU干预
// 单次模式:每次传输完成需要重新配置
DMA_Mode_Normal
// 循环模式:自动重新开始,无需CPU干预
DMA_Mode_Circular
// 示例:ADC连续采集使用循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
优化3:优先级配置
// 高优先级DMA通道优先获得总线访问权
DMA_Priority_Low
DMA_Priority_Medium
DMA_Priority_High
DMA_Priority_VeryHigh
// 示例:实时性要求高的外设使用高优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
九、DMA应用设计原则
9.1 DMA使用场景选择
9.2 DMA配置检查清单
配置前检查:
- 确认外设对应的DMA通道
- 确认数据传输方向
- 确认数据宽度和对齐
- 确认缓冲区大小足够
配置时检查:
- 外设地址正确
- 内存地址对齐
- 数据数量正确
- 优先级合理
配置后检查:
- 使能DMA通道
- 使能外设DMA请求
- 配置传输完成中断
- 测试传输功能
9.3 DMA调试技巧
技巧1:使用DMA计数器监控传输进度
uint16_t Get_TransferProgress(void)
{
uint16_t remaining = DMA1_Channel1->CNDTR;
uint16_t total = buffer_size;
uint16_t transferred = total - remaining;
printf("Progress: %d/%d (%.1f%%)\r\n",
transferred, total,
(float)transferred / total * 100.0f);
return transferred;
}
技巧2:使用内存观察窗口验证数据
// 在调试器中观察DMA缓冲区
// Keil: View -> Memory Window
// 输入缓冲区地址: &uart_rx_buffer
// 观察数据是否正确更新
技巧3:使用逻辑分析仪捕获传输时序
// 在DMA传输开始和结束处翻转GPIO
void DMA_Transfer_Start(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 开始标记
DMA_Cmd(DMA1_Channel1, ENABLE);
}
void DMA_Transfer_Complete(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 结束标记
}
十、总结与互动
10.1 核心要点总结
- DMA原理:理解DMA控制器的工作机制和通道映射
- 通道配置:正确选择通道、配置方向、数据宽度、优先级
- 传输模式:单次模式、循环模式、双缓冲模式的适用场景
- 中断处理:传输完成、半传输、错误中断的应用
- 性能优化:数据宽度、优先级、循环模式的合理配置
10.2 实战经验总结
- DMA通道映射要查表确认,避免冲突
- 数据宽度要保持一致,地址要对齐
- 循环模式要配合双缓冲,避免数据覆盖
- 传输完成要等待或使用中断通知
- 大数据量传输优先使用DMA,小数据量CPU直接处理
投票组件
你对DMA开发的最大困惑是什么?
- DMA通道映射复杂,不知道选哪个通道
- DMA传输数据错位,不知道哪里配置错误
- 双缓冲机制不理解,不知道如何实现
- DMA和外设配合不好,传输不正常
- 其他问题(请评论区说明)
欢迎在评论区分享你的DMA开发经验和遇到的问题!
互动引导
思考题:
- 如何设计一个高效的串口DMA收发系统(支持任意长度数据)?
- 如何用DMA实现SPI Flash的高速读写?
- 多个DMA通道同时工作时,如何避免总线冲突?
实践建议:
- 先从单次DMA传输开始,理解基本配置
- 逐步尝试循环模式和双缓冲
- 结合实际外设(串口、ADC、SPI)练习
- 使用调试工具观察DMA传输过程
更多推荐

所有评论(0)