第2章 STM32F407 DMA控制器架构:双AHB总线与数据流映射
DMA控制器,说白了就是CPU的「数据搬运工」。CPU告诉它「把这里的数据搬到那里」,它就能自己干完,CPU可以腾出手来做更重要的事。但很多初学者一上来就对着参考手册发懵——什么双AHB、数据流、通道映射,看得头大。
别急,这一章我带你把这些概念一个一个拆开。搞懂了架构,后面调优才有底气。
2.1 双AHB总线架构:为什么是两条路?
STM32F407的DMA控制器挂在两条AHB总线上:AHB1和AHB2。这不是设计者闲得慌,而是有讲究的。
我打个比方:一条路只能单向通行,两条路就能双向同时跑。DMA1和DMA2各占一条总线,它们可以同时进行数据传输,互不干扰。
关键点:双AHB总线意味着DMA1和DMA2可以并行工作。如果你的系统里同时有ADC采集和SPI通信,让它们分别走不同的DMA控制器,吞吐量直接翻倍。
我在一个数据采集项目里就吃过亏。一开始把所有外设都挂在DMA1上,结果ADC和USART抢带宽,采集到的数据偶尔丢包。后来把USART挪到DMA2,问题立刻解决。嗯,这就是双总线的价值。
2.2 DMA1与DMA2的区别:不只是编号不同
很多人以为DMA1和DMA2就是两个一样的复制品。其实不然。它们能访问的外设范围不一样。
| 特性 | DMA1 | DMA2 |
|---|---|---|
| 挂载总线 | AHB1 | AHB2 |
| 可访问外设 | ADC1/2/3、SPI/I2S、USART、I2C、定时器等 | DCMI(摄像头接口)、SDIO、SPI/I2S、USART、I2C、定时器等 |
| 特有外设 | ADC1/2/3 | DCMI、SDIO |
| 数据流数量 | 8个 | 8个 |
| 通道数量/流 | 8个 | 8个 |
你看,DMA2能访问DCMI和SDIO,DMA1不行。反过来,ADC的三个模块只能走DMA1。所以设计时得先查清楚:你的外设挂在哪个DMA上?
我的习惯:先把所有要用DMA的外设列出来,对照参考手册的DMA请求映射表,看看它们能不能分到不同的DMA控制器上。能分开就尽量分开,这是最基础的性能优化。
2.3 8个数据流与每个流8个通道的映射关系
这是DMA架构里最容易搞混的部分。我尽量讲清楚。
每个DMA控制器有8个数据流(Stream 0~7)。每个数据流有8个通道(Channel 0~7)。但注意:一个数据流同一时间只能使用一个通道。
你想想看,这就像一条高速公路有8条车道(数据流),每条车道可以走8种不同的车(通道),但同一时间一条车道上只能跑一种车。
那通道是干什么用的?通道决定了这个数据流能响应哪个外设的请求。
举个例子:
- 数据流0的通道0:对应ADC1
- 数据流0的通道1:对应SPI1_RX
- 数据流0的通道2:对应USART1_TX
- ……以此类推
你配置DMA时,需要做两件事:
- 选数据流:决定用哪条「车道」
- 选通道:告诉这条车道要服务哪个外设
我曾经踩过的坑:两个外设共用一个数据流的不同通道,以为能同时工作。结果发现数据流同一时间只能处理一个通道的请求,另一个外设只能排队等。后来我把它们分到不同的数据流上,问题才解决。
2.4 数据流与通道映射表(以DMA1为例)
下面这张表是DMA1的部分映射关系。完整版在参考手册的Table 45里,我这里只挑几个常用的给你看。
| 数据流 | 通道0 | 通道1 | 通道2 | 通道3 | 通道4 | 通道5 | 通道6 | 通道7 |
|---|---|---|---|---|---|---|---|---|
| Stream 0 | ADC1 | SPI1_RX | USART1_TX | I2C1_TX | 定时器2_UP | 定时器3_CH1 | — | — |
| Stream 1 | ADC2 | SPI1_TX | USART1_RX | I2C1_RX | 定时器2_CH1 | 定时器3_CH2 | — | — |
| Stream 2 | ADC3 | SPI2_RX | USART2_TX | I2C2_TX | 定时器2_CH2 | 定时器3_CH3 | — | — |
| Stream 3 | — | SPI2_TX | USART2_RX | I2C2_RX | 定时器2_CH3 | 定时器3_CH4 | — | — |
看到没?同一个外设(比如USART1)的TX和RX,分别对应不同的数据流和通道。这样设计是为了让收发可以同时进行,互不阻塞。
2.5 数据流优先级与仲裁
当多个数据流同时请求传输时,谁先谁后?DMA控制器有一套优先级仲裁机制。
优先级分四级:
- 非常高(Very High)
- 高(High)
- 中(Medium)
- 低(Low)
每个数据流都可以独立设置优先级。如果两个数据流优先级相同,那就看编号——编号小的优先(Stream 0 > Stream 1)。
实战建议:把对实时性要求高的外设(比如音频I2S、摄像头DCMI)设为高优先级。把后台数据记录(比如SDIO写卡)设为低优先级。别把所有流都设成「非常高」,那样优先级等于没设。
2.6 架构总览图
下面这张SVG图把整个DMA控制器的架构串起来了。你对照着看,思路会更清晰。
2.7 配置DMA的步骤总结
搞清楚了架构,配置DMA其实就四步:
- 查映射表:确定你的外设对应哪个DMA控制器、哪个数据流、哪个通道
- 开时钟:使能对应DMA控制器的时钟(RCC_AHB1ENR)
- 配参数:设置数据流的方向、地址、数据宽度、优先级、FIFO等
- 使能:先配好外设,再使能DMA数据流
一个小技巧:我习惯在代码里用宏定义把数据流和通道的映射关系写清楚。比如 #define DMA_STREAM_ADC1 DMA1_Stream0,这样半年后回来看代码,不用再翻手册。