STM32 DMA控制器架构:DMA1与DMA2的区别、通道映射关系、FIFO与直接模式选择

好,咱们进入第二章。这一章我打算把DMA控制器的家底给你翻一翻。很多同学用了很久DMA,但DMA1和DMA2到底有啥区别?通道怎么映射?FIFO和直接模式什么时候选?说实话,这些细节才是决定你项目成败的关键。

我记得刚带团队那会儿,有个同事做音频采集,DMA传输老是丢数据。折腾了两天,最后发现是DMA2的FIFO阈值没配对。嗯,这种坑,咱们今天一次性讲清楚。

2.1 DMA1与DMA2:双核架构的差异

STM32的DMA控制器,说白了就是两个独立的数据搬运工。DMA1和DMA2,它们挂在不同的总线矩阵上,服务的对象也不一样。

对比项 DMA1 DMA2
总线接口 挂载在AHB1上 挂载在AHB2上
通道数量 7个通道 5个通道
主要服务外设 SPI、I2C、USART、ADC等低速外设 定时器、DAC、SDIO、以太网等高速外设
内存访问能力 可访问Flash、SRAM、外设寄存器 可访问Flash、SRAM、外设寄存器
FIFO支持 部分通道支持 全部通道支持

你可能会问,为什么要有两个?其实这是ST的设计智慧。DMA2离高速外设更近,总线带宽更高。我在做电机控制项目时,把定时器触发的DMA请求都分配给了DMA2,效果很明显。

核心原则:高速外设优先使用DMA2,低速外设分配给DMA1。这样可以避免总线冲突,提升系统吞吐量。

2.2 通道映射关系:谁跟谁配对

每个DMA通道都有固定的外设请求映射。这个映射关系,在STM32参考手册里有一张大表。我建议你把它打印出来贴在工位上。

以STM32F4为例,咱们看看几个常用的映射:

DMA控制器 通道 外设请求 典型应用
DMA1 通道0 SPI3_RX、I2C1_RX SPI从设备接收
DMA1 通道1 SPI3_TX、I2C1_TX SPI从设备发送
DMA1 通道2 USART2_RX、SPI1_RX 串口接收
DMA1 通道3 USART2_TX、SPI1_TX 串口发送
DMA2 通道0 TIM1_CH0、TIM8_UP 定时器触发采集
DMA2 通道1 DAC1、TIM2_UP DAC波形输出

我曾经踩过的坑:有一次做多路ADC采集,我把ADC1和ADC2的DMA请求都配到了DMA1的通道0上。结果两个外设同时请求时,通道冲突导致数据错位。后来改成ADC1用DMA1通道0,ADC2用DMA2通道0,问题就解决了。

2.3 FIFO与直接模式:两种传输哲学

DMA传输有两种模式:FIFO模式和直接模式。这两种模式的选择,直接影响你的数据完整性和实时性。

2.3.1 直接模式

直接模式,就是数据来了就传,不经过FIFO缓冲。它的特点是:

  • 延迟低:数据从外设到内存,几乎零等待
  • 占用总线频繁:每次传输都要占用AHB总线
  • 适合小数据量:比如单个字节的串口收发

2.3.2 FIFO模式

FIFO模式,数据先存到内部FIFO,攒够了再一次性传输。它的优势:

  • 减少总线占用:一次突发传输多个数据
  • 数据完整性好:FIFO可以缓冲数据抖动
  • 适合大数据块:比如音频、图像数据
特性 直接模式 FIFO模式
延迟 低 中等(取决于阈值)
总线占用 频繁 突发式
数据完整性 一般 好
适用场景 实时性要求高、数据量小 数据量大、允许一定延迟
功耗影响 较高(频繁唤醒总线) 较低(集中传输)

我的选择习惯:做低功耗项目时,我倾向于用FIFO模式。比如一个温湿度传感器,每秒采集一次数据,用FIFO模式可以把多次采集的数据打包传输,CPU和总线大部分时间都在睡觉,功耗自然就降下来了。

2.4 FIFO阈值配置的艺术

FIFO模式有个关键参数:阈值。它决定了FIFO存到多少数据才开始传输。阈值有四个等级:1/4、1/2、3/4和满。

怎么选?我一般这样判断:

  • 1/4阈值:适合对实时性有要求的场景,比如音频播放
  • 1/2阈值:平衡方案,大多数情况用这个
  • 3/4或满阈值:适合大数据块传输,比如文件读写

举个例子,做音频播放时,我通常把FIFO阈值设为1/4。这样FIFO里只要有4个字节就开始传输,保证音频数据连续不断流。而做SD卡数据写入时,我会用满阈值,一次传输16个字节,效率更高。

2.5 知识结构图

下面这张图,把DMA控制器的核心架构和选择逻辑串起来了。你可以把它当作决策参考。

STM32 DMA控制器架构与模式选择 STM32 DMA控制器架构与模式选择 DMA1 控制器 DMA2 控制器 低速外设组 SPI1/2/3 I2C1/2/3 USART1/2/3 ADC1/2/3 高速外设组 TIM1/2/3/4/5 DAC1/2 SDIO 以太网MAC 传输模式选择 直接模式 FIFO模式 小数据量 → 直接模式 大数据块 → FIFO模式 低功耗需求 → FIFO模式 核心原则 高速外设 → DMA2 低速外设 → DMA1

2.6 实战配置示例

说了这么多理论,咱们来点实际的。下面是一个配置DMA2通道0进行定时器触发ADC采集的代码片段:

// 配置DMA2通道0,用于定时器触发ADC采集
void DMA2_Channel0_Config(void)
{
    // 1. 使能DMA2时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    
    // 2. 等待DMA2就绪
    while(!(RCC->AHB1ENR & RCC_AHB1ENR_DMA2EN));
    
    // 3. 配置通道0
    DMA2_Stream0->CR = 0;  // 先复位配置
    
    // 4. 设置传输方向:外设到内存
    DMA2_Stream0->CR |= DMA_SxCR_DIR_0;  // 外设到内存
    
    // 5. 使能FIFO模式,阈值设为1/2
    DMA2_Stream0->FCR |= DMA_SxFCR_FTH_0;  // 1/2 FIFO阈值
    
    // 6. 设置外设地址(ADC数据寄存器)
    DMA2_Stream0->PAR = (uint32_t)&(ADC1->DR);
    
    // 7. 设置内存地址
    DMA2_Stream0->M0AR = (uint32_t)adc_buffer;
    
    // 8. 设置传输数据量
    DMA2_Stream0->NDTR = 100;  // 采集100个点
    
    // 9. 使能传输完成中断
    DMA2_Stream0->CR |= DMA_SxCR_TCIE;
    
    // 10. 使能DMA流
    DMA2_Stream0->CR |= DMA_SxCR_EN;
}

小技巧:配置DMA时,一定要先复位CR寄存器,再逐位设置。我见过有人直接对CR做位运算,结果因为默认值不对导致传输异常。安全第一,先清零再赋值。

2.7 避坑指南

最后,分享几个我实际项目中遇到的坑:

  • 通道冲突:两个外设不能同时使用同一个DMA通道。如果必须同时传输,要么分时复用,要么换到另一个DMA控制器上。
  • FIFO溢出:FIFO阈值设得太高,外设数据产生速度又很快,FIFO满了还没开始传输,数据就丢了。解决办法是降低阈值或提高DMA优先级。
  • 总线死锁:DMA和CPU同时访问同一块内存区域,可能导致总线仲裁死锁。我的做法是让DMA访问专用的内存缓冲区,CPU处理完再拷贝。

嗯,这一章的内容就到这里。DMA控制器的架构和模式选择,是后续所有优化工作的基础。你把这些搞清楚了,后面讲低功耗设计、调试技巧的时候,才能跟得上节奏。