嵌入式开发必掌握: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干预即可实现数据传输。

配置

自动传输

自动传输

CPU

DMA控制器

内存

外设

传统方式

CPU读取外设

CPU写入内存

CPU占用100%

DMA方式

DMA自动传输

CPU处理其他任务

CPU占用0%

DMA优势对比

传输方式 CPU占用率 传输速度 适用场景
CPU轮询 100% 较慢 小数据量
中断方式 50-80% 中等 中等数据量
DMA传输 0-10% 最快 大数据量

1.2 STM32 DMA架构

STM32F103 DMA

DMA1
7个通道

DMA2
5个通道

通道1: ADC1

通道2: SPI1_RX

通道3: SPI1_TX

通道4: USART1_TX

通道5: USART1_RX

I2C1

TIM1

通道1-5
外设映射

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;

配置参数详解

DMA配置

传输方向

地址模式

数据宽度

传输模式

优先级

外设→内存

内存→外设

内存→内存

外设地址固定

内存地址递增

8位/16位/32位

单次传输

循环传输

低/中/高/最高

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 双缓冲原理

双缓冲机制

缓冲区A

缓冲区B

DMA写入

CPU处理

CPU处理

DMA写入

工作流程

DMA填充Buffer A

CPU处理Buffer B

切换: DMA填充Buffer B

CPU处理Buffer A

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常见问题与解决方案

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使用场景选择

小数据
<10字节

中等数据
10-100字节

大数据
>100字节

DMA使用决策

数据量大小?

CPU直接处理

中断方式

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 核心要点总结

  1. DMA原理:理解DMA控制器的工作机制和通道映射
  2. 通道配置:正确选择通道、配置方向、数据宽度、优先级
  3. 传输模式:单次模式、循环模式、双缓冲模式的适用场景
  4. 中断处理:传输完成、半传输、错误中断的应用
  5. 性能优化:数据宽度、优先级、循环模式的合理配置

10.2 实战经验总结

  • DMA通道映射要查表确认,避免冲突
  • 数据宽度要保持一致,地址要对齐
  • 循环模式要配合双缓冲,避免数据覆盖
  • 传输完成要等待或使用中断通知
  • 大数据量传输优先使用DMA,小数据量CPU直接处理

投票组件

你对DMA开发的最大困惑是什么?

  1. DMA通道映射复杂,不知道选哪个通道
  2. DMA传输数据错位,不知道哪里配置错误
  3. 双缓冲机制不理解,不知道如何实现
  4. DMA和外设配合不好,传输不正常
  5. 其他问题(请评论区说明)

欢迎在评论区分享你的DMA开发经验和遇到的问题!


互动引导

思考题

  1. 如何设计一个高效的串口DMA收发系统(支持任意长度数据)?
  2. 如何用DMA实现SPI Flash的高速读写?
  3. 多个DMA通道同时工作时,如何避免总线冲突?

实践建议

  1. 先从单次DMA传输开始,理解基本配置
  2. 逐步尝试循环模式和双缓冲
  3. 结合实际外设(串口、ADC、SPI)练习
  4. 使用调试工具观察DMA传输过程
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐