STM32 DMA:高效数据搬运的利器

122 阅读5分钟

引言

在嵌入式开发中,CPU的时间是宝贵资源。DMA技术就像一位不知疲倦的数据搬运工,让CPU从繁琐的数据传输中解放出来,专注于核心逻辑处理。

1. DMA核心概念

1.1 什么是DMA?

DMA(Direct Memory Access)直接存储器存取,是STM32中一个独立于CPU的数据传输模块。它能够在不需要CPU干预的情况下,直接在存储器和外设之间传输数据。

传统方式 vs DMA方式:

  • 传统方式:CPU参与每个数据的搬运,占用大量CPU时间
  • DMA方式:CPU只负责初始化配置,数据传输由DMA控制器完成

1.2 STM32 DMA资源

STM32F103系列提供两个DMA控制器:

  • DMA1:7个通道,支持外设到存储器、存储器到外设的数据传输
  • DMA2:5个通道,额外支持存储器到存储器的数据传输

2. 存储器架构基础

2.1 三种主要存储器类型

存储器类型特点在STM32中的主要用途
Flash非易失性,可重复擦写存储程序代码和常量数据
RAM高速读写,断电丢失数据存储变量和运行时数据
EEPROM非易失性,擦写次数多存储需要修改的用户配置数据

2.2 DMA与存储器的关系

DMA作为数据传输的桥梁,可以在这些存储器之间或存储器与外设之间建立高速数据传输通道:

  • 外设 → 内存:如ADC采集数据到RAM
  • 内存 → 外设:如从RAM发送数据到UART
  • 内存 → 内存:如数据块拷贝

3. DMA工作模式

3.1 基本传输模式

// DMA工作模式配置选项
typedef enum {
    DMA_MODE_NORMAL,     // 正常模式:传输完成即停止
    DMA_MODE_CIRCULAR    // 循环模式:传输完成自动重启
} DMA_Mode_Typedef;

3.2 触发方式

  • 软件触发:通过程序代码启动传输
  • 硬件触发:由外设事件自动触发(如ADC转换完成、UART收到数据)

4. DMA配置详解

4.1 基本配置步骤

void DMA_Config(void)
{
    // 定义DMA初始化结构体
    DMA_InitTypeDef DMA_InitStructure;
    
    // 1. 时钟使能 - DMA属于AHB总线,需要使能对应时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 2. 基本参数配置
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&外设数据寄存器;  // 设置外设数据寄存器地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)内存缓冲区;          // 设置内存缓冲区地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                   // 设置传输方向:外设为数据源
    DMA_InitStructure.DMA_BufferSize = 数据传输数量;                      // 设置要传输的数据数量
    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;                         // 禁用存储器到存储器模式
    
    // 3. 初始化DMA通道
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    // 4. 使能DMA通道
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

4.2 关键参数说明

  • 传输方向:决定数据是从外设到内存,还是从内存到外设
  • 地址递增:指定传输过程中地址是否自动增加
  • 数据宽度:支持字节、半字、全字传输
  • 优先级:当多个DMA通道同时请求时的处理顺序

5. 实战应用:ADC多通道采集

5.1 DMA与ADC的协同工作

// 定义ADC数据缓冲区
uint16_t adc_values[ADC_CHANNEL_COUNT];  // 存储ADC转换结果的数组

// ADC多通道DMA采集配置
void ADC_DMA_Config(void)
{
    // ADC配置结构体
    ADC_InitTypeDef ADC_InitStructure;
    
    // ADC基本配置
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        // 启用扫描模式(多通道)
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 启用连续转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 不使用外部触发
    ADC_Init(ADC1, &ADC_InitStructure);                 // 应用ADC配置
    
    // 启用ADC的DMA请求功能
    ADC_DMACmd(ADC1, ENABLE);
    
    // DMA配置结构体
    DMA_InitTypeDef DMA_InitStructure;
    
    // DMA参数配置
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;      // ADC数据寄存器地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_values;         // 内存缓冲区地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                   // 传输方向:外设到内存
    DMA_InitStructure.DMA_BufferSize = ADC_CHANNEL_COUNT;                // 缓冲区大小(通道数量)
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;     // 外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;              // 内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据:16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          // 内存数据:16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                      // 循环模式(自动重复)
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                  // 高优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                         // 非存储器到存储器模式
    
    // 初始化并启用DMA
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    // 启动ADC转换
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

// DMA传输完成中断服务函数
void DMA1_Channel1_IRQHandler(void)
{
    // 检查传输完成标志
    if(DMA_GetITStatus(DMA1_IT_TC1))  // TC1表示通道1传输完成
    {
        // 清除中断标志
        DMA_ClearITPendingBit(DMA1_IT_TC1);
        
        // 在这里处理ADC数据
        // adc_values数组中已经包含了所有通道的最新数据
        
        // 示例:处理ADC数据
        for(int i = 0; i < ADC_CHANNEL_COUNT; i++)
        {
            // 对每个通道的数据进行处理
            process_adc_data(i, adc_values[i]);
        }
    }
}

6. DMA使用注意事项

6.1 常见问题及解决

  1. 数据传输不完整
    • 检查缓冲器大小设置
    • 确认传输模式配置正确
  2. 数据覆盖问题
    • 合理设置循环模式或正常模式
    • 及时处理完成中断
  3. 性能优化
    • 根据数据特性选择合适的数据宽度
    • 合理设置DMA通道优先级

6.2 最佳实践建议

  • 在数据传输前确保DMA通道已正确配置
  • 使用DMA完成中断来处理数据,避免轮询
  • 对于连续数据流,优先使用循环模式
  • 注意内存对齐,提高传输效率

7. 总结

DMA是STM32中提升系统性能的重要技术,通过合理使用DMA可以:

  • 释放CPU资源:让CPU专注于算法和逻辑处理
  • 提高系统实时性:确保数据及时传输和处理
  • 降低系统功耗:CPU可以在DMA工作时进入低功耗模式 掌握DMA技术是嵌入式开发从入门到进阶的重要一步,建议在实际项目中多加练习,逐步深入理解其工作原理和应用技巧。