3、STM32 DMA控制器:架构、通道映射与传输配置
各位同学,今天我们来啃一块硬骨头——STM32的DMA控制器。说实话,我刚接触STM32时,DMA这块看了好几遍才真正搞明白。它不像GPIO那样直观,但一旦用好了,你的音频系统性能能提升一个档次。
3.1 DMA架构:数据搬运的“高速公路”
STM32的DMA,说白了就是一个独立于CPU的数据搬运工。它不需要CPU干预,就能在内存和外设之间、内存和内存之间搬数据。我习惯把它想象成一条高速公路——CPU是开小车的,DMA是开卡车的,各走各的道,互不干扰。
STM32的DMA架构分两种:DMA1和DMA2。DMA1有7个通道,DMA2有5个通道。嗯,这里要注意,DMA2通常挂在AHB总线上,带宽更高,适合高速外设。我在做音频项目时,音频数据流都走DMA2,省心不少。
核心要点:DMA控制器通过AHB总线矩阵访问存储器,外设通过APB总线发起DMA请求。这个架构决定了数据传输的路径和速度。
3.2 DMA通道与请求映射:谁跟谁配对?
每个DMA通道可以响应多个外设的请求,但同一时间只能服务一个。你想想看,这就像一条车道,虽然可以走多种车,但一次只能过一辆。
以STM32F4为例,DMA2的通道0可以响应ADC1、TIM5_CH1等外设请求。具体映射关系,我建议你直接查参考手册的DMA请求映射表。我曾经因为搞错了映射关系,调试了整整一个下午——ADC数据死活不更新,最后发现是通道配错了。
| DMA通道 | 外设请求(部分) | 适用场景 |
|---|---|---|
| DMA1_CH1 | ADC1、TIM2_CH3 | 低速数据采集 |
| DMA2_CH0 | ADC1、TIM5_CH1 | 高速ADC采样 |
| DMA2_CH3 | SPI1_RX、I2S3_EXT | 音频I2S接收 |
| DMA2_CH4 | SPI1_TX、I2S3_EXT | 音频I2S发送 |
我的经验:音频项目优先用DMA2的通道3和通道4,分别对应I2S的接收和发送。这两个通道的优先级可以设高一些,避免音频数据断流。
3.3 DMA传输配置流程:一步步来,别急
配置DMA传输,我总结了一个“五步法”。你按这个顺序来,基本不会出错。
- 使能DMA时钟——RCC_AHB1PeriphClockCmd(),别忘了这一步,我见过有人卡在这半小时。
- 初始化DMA通道——设置源地址、目的地址、传输方向、数据宽度等。
- 配置传输模式——普通模式还是循环模式?音频一般用循环模式。
- 设置优先级——低、中、高、最高,根据实时性要求选。
- 使能DMA通道——最后一步,开启传输。
来看一个实际的代码片段,配置DMA2_CH4用于I2S音频发送:
void Audio_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 1. 使能DMA2时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
// 2. 配置DMA2通道4
DMA_InitStructure.DMA_Channel = DMA_Channel_0; // I2S3对应通道0
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI3->DR;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)audio_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = AUDIO_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_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream4, &DMA_InitStructure);
// 3. 使能DMA传输完成中断
DMA_ITConfig(DMA2_Stream4, DMA_IT_TC, ENABLE);
// 4. 使能DMA流
DMA_Cmd(DMA2_Stream4, ENABLE);
}
避坑指南:我曾经在配置DMA时,忘记设置DMA_BufferSize,结果数据只传输了一次就停了。还有,使能DMA前一定要确保所有配置都完成,否则可能产生意外中断。
3.4 FIFO与突发传输:提升效率的“加速器”
FIFO是DMA内部的一个小缓存,默认是4个字深。它的作用是什么?说白了就是缓冲——当外设还没准备好时,数据先暂存在FIFO里,等外设准备好了再发出去。
突发传输(Burst Transfer)则是另一种加速手段。它允许DMA一次传输多个数据,而不是一个一个地搬。我习惯把突发传输比作“批发”,普通传输是“零售”——批发当然比零售快。
FIFO和突发传输配合使用,效果更佳。当FIFO达到一定阈值(比如半满或全满)时,触发一次突发传输。这样既减少了总线占用次数,又提高了传输效率。
实战建议:在音频应用中,我通常开启FIFO模式,阈值设为半满。突发传输设成4拍或8拍。这样既能保证音频数据的连续性,又不会因为突发太长而影响其他外设的响应。
嗯,这里有个细节要注意:FIFO阈值和突发长度要匹配。如果阈值设得太低,突发还没开始就满了,反而降低效率。我一般遵循“阈值 ≥ 突发长度”的原则。
最后说一句,DMA配置看似复杂,但多练几次就熟了。你想想看,一旦DMA跑起来,CPU就能腾出手来做更重要的处理——比如音频算法、UI响应。这才是嵌入式系统设计的精髓所在。