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控制器的核心架构和选择逻辑串起来了。你可以把它当作决策参考。
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控制器的架构和模式选择,是后续所有优化工作的基础。你把这些搞清楚了,后面讲低功耗设计、调试技巧的时候,才能跟得上节奏。