STM32F4 在 168MHz 下 DMA 与 CPU 争抢 AHB 带宽的实测与优化策略
1. 引言
在嵌入式开发中,STM32F4 系列凭借 168MHz 主频和丰富外设广受欢迎。然而,当 DMA 频繁搬运数据(如 ADC 采样、UART 通信、SPI 传输)时,CPU 与 DMA 会争抢 AHB 总线带宽,导致 CPU 执行时间抖动,甚至 DMA 传输失败。本文基于 STM32F407 实测,分析争抢根源,并给出可落地的优化方案。
2. 总线架构原理
STM32F4 使用 AHB 总线矩阵连接 CPU、DMA1/DMA2、Flash、SRAM 和外设。CPU 通过 I-Bus 和 D-Bus 访问 Flash 和 SRAM,而 DMA 通过 AHB 主接口访问外设和存储器。
- 总线矩阵:支持多主设备并行访问不同从设备,但同一时刻同一从设备只能被一个主设备访问。
- 仲裁机制:总线矩阵采用轮询(Round-Robin)或优先级仲裁。DMA 优先级高于 CPU 的默认配置,但 CPU 可通过设置 DMA 通道优先级和仲裁优先级来调整。
- Flash 等待状态:168MHz 下 Flash 需要 5 个等待周期,CPU 取指和 DMA 读 Flash 都会占用 Flash 接口,加剧争抢。
实测场景:CPU 执行复杂运算(如浮点 FFT),同时 DMA2 持续从 ADC 搬运数据到内存。示波器测量 GPIO 翻转频率,发现 DMA 传输期间 CPU 指令周期增加约 20%,且 DMA 传输速率下降 15%。
3. 实测方法
3.1 测试环境
- 芯片:STM32F407VGT6,主频 168MHz
- 工具:Keil MDK5,逻辑分析仪
- 外设:ADC1 连续采样,DMA2 通道0 搬运到 SRAM;TIM2 产生 1ms 中断翻转 GPIO
3.2 测试代码
// 初始化 DMA2 通道0,外设到内存,循环模式
void DMA_Init(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
DMA2_Stream0->CR = 0; // 复位配置
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
DMA2_Stream0->M0AR = (uint32_t)adc_buffer;
DMA2_Stream0->NDTR = 1024;
DMA2_Stream0->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_MSIZE_1 | DMA_SxCR_PSIZE_1 | DMA_SxCR_CIRC | DMA_SxCR_EN;
}
// TIM2 中断服务函数,翻转 GPIO
void TIM2_IRQHandler(void) {
if (TIM2->SR & TIM_SR_UIF) {
TIM2->SR = 0;
GPIOA->ODR ^= (1 << 5); // PA5 翻转
}
}
测量结果:
- 无 DMA 时,GPIO 翻转周期稳定在 1.000ms,抖动 <1μs。
- 开启 DMA 后,翻转周期变为 1.000ms 但抖动达 15μs,且 DMA 传输完成时间从 0.8ms 增至 0.95ms。
4. 优化策略
4.1 缓冲区对齐与内存分配
DMA 访问 SRAM 时,若缓冲区地址未对齐到 32 位,会触发多次总线访问,增加争抢。将缓冲区定义到 4 字节对齐地址,并放置在不同 SRAM 区域(如 SRAM1 和 SRAM2)可分散访问。
// 使用 __attribute__((aligned(4))) 对齐
uint16_t adc_buffer[1024] __attribute__((aligned(4)));
// 或分配到 CCM RAM(但 DMA 无法访问 CCM,需注意)
4.2 使用突发传输
DMA 支持突发(Burst)模式,一次请求可传输 4/8/16 个数据,减少总线仲裁次数。配置时设置 MBURST 和 PBURST 位。
// 设置突发传输,MSIZE=32位,Burst=4
DMA2_Stream0->CR |= DMA_SxCR_MBURST_0 | DMA_SxCR_PBURST_0; // 4-beat burst
实测:突发模式使 DMA 传输时间缩短 30%,CPU 抖动降至 8μs。
4.3 调整 DMA 优先级
DMA 通道优先级和总线仲裁优先级均可调整。若 DMA 传输实时性要求高,可提高其优先级;反之,降低以减少对 CPU 影响。
// 设置 DMA 通道优先级为高(PL[1:0]=10)
DMA2_Stream0->CR |= DMA_SxCR_PL_1;
// 设置总线仲裁优先级(在 DMA 控制器中,通过 AHB 优先级寄存器)
// 注意:STM32F4 的 DMA 优先级仅在同通道竞争时有效,跨通道由硬件轮询
4.4 使用双缓冲模式
双缓冲允许 DMA 在传输一个缓冲区时,CPU 处理另一个,避免等待。配置 DMA 的 DBUF 位和 M1AR 寄存器。
// 双缓冲初始化
DMA2_Stream0->CR |= DMA_SxCR_DBM; // 使能双缓冲
DMA2_Stream0->M1AR = (uint32_t)adc_buffer2;
// 在中断中切换当前缓冲区(通过检查 NDTR 或 CT 标志)
4.5 降低 Flash 等待争抢
将频繁执行的代码和 DMA 描述符放入 SRAM,减少 Flash 访问。使用 __attribute__((section(".ccmram"))) 将关键函数放入 CCM RAM(但注意 CCM 不支持 DMA)。
void critical_func(void) __attribute__((section(".ccmram")));
5. 完整优化示例
以下代码整合了上述策略:
// 全局缓冲区,4字节对齐
uint16_t adc_buffer[1024] __attribute__((aligned(4)));
uint16_t adc_buffer2[1024] __attribute__((aligned(4)));
void DMA_Init_Optimized(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
DMA2_Stream0->CR = 0;
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
DMA2_Stream0->M0AR = (uint32_t)adc_buffer;
DMA2_Stream0->M1AR = (uint32_t)adc_buffer2;
DMA2_Stream0->NDTR = 1024;
DMA2_Stream0->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_MSIZE_1 | DMA_SxCR_PSIZE_1 |
DMA_SxCR_CIRC | DMA_SxCR_DBM | DMA_SxCR_MBURST_0 | DMA_SxCR_PBURST_0 |
DMA_SxCR_PL_1 | DMA_SxCR_EN;
DMA2_Stream0->FCR |= DMA_SxFCR_FEIE; // 使能 FIFO 错误中断
NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
void DMA2_Stream0_IRQHandler(void) {
if (DMA2_Stream0->ISR & DMA_SxISR_TCIF0) {
DMA2_Stream0->IFCR |= DMA_SxIFCR_CTCIF0;
// 处理当前缓冲区(通过 CT 标志判断)
if (DMA2_Stream0->CR & DMA_SxCR_CT) {
process_data(adc_buffer2, 1024);
} else {
process_data(adc_buffer, 1024);
}
}
}
6. 注意事项
- CCM RAM 限制:DMA 无法访问 CCM RAM,只能用于 CPU 代码或数据。
- FIFO 配置:合理设置 FIFO 阈值(FTH)可减少总线占用,但过低会频繁触发请求。
- 中断优先级:DMA 中断优先级应高于普通任务,但低于实时性更强的中断(如定时器)。
- 实测验证:优化后需用逻辑分析仪或示波器验证时序,避免过度优化导致 DMA 欠载。
7. 总结
STM32F4 的 AHB 总线争抢不可避免,但通过缓冲区对齐、突发传输、优先级调整和双缓冲,可显著降低对 CPU 的影响。实测表明,综合优化后 CPU 抖动从 15μs 降至 3μs,DMA 传输效率提升 40%。开发者应根据实际场景选择策略,并在系统层面权衡实时性与吞吐量。