第一幕:时钟与中断统一配置
2026/5/9大约 2 分钟嵌入式开发STM32Embassy
所有外设共享同一棵时钟树,所有中断注册在同一个 Irqs 结构体里。这是整合的第一步,也是最容易“各写各的”然后合不上的一步。
时钟树
fn clock_config() -> Stm32Config {
let mut config = Stm32Config::default();
let mut rcc = RccConfig::default();
rcc.hsi = true;
rcc.pll_src = PllSource::HSI;
rcc.pll = Some(Pll {
prediv: PllPreDiv::DIV16, // HSI 16 MHz / 16 = 1 MHz
mul: PllMul::MUL336, // 1 MHz × 336 = 336 MHz
divp: Some(PllPDiv::DIV2), // 336 / 2 = 168 MHz (SYSCLK)
divq: Some(PllQDiv::DIV7), // 336 / 7 = 48 MHz (SDIO)
divr: None,
});
rcc.sys = Sysclk::PLL1_P;
rcc.ahb_pre = AHBPrescaler::DIV1;
rcc.apb1_pre = APBPrescaler::DIV4;
rcc.apb2_pre = APBPrescaler::DIV2;
config.rcc = rcc;
config
}APB1 四分频、APB2 二分频,让挂在两条总线上的外设各自运行在规范频率内。
中断聚合绑定
项目用到的所有中断一次绑完:
bind_interrupts!(struct Irqs {
SDIO => sdmmc::InterruptHandler<peripherals::SDIO>;
DMA2_STREAM3 => dma::InterruptHandler<peripherals::DMA2_CH3>;
DMA2_STREAM6 => dma::InterruptHandler<peripherals::DMA2_CH6>;
DMA1_STREAM2 => dma::InterruptHandler<peripherals::DMA1_CH2>;
DMA1_STREAM3 => dma::InterruptHandler<peripherals::DMA1_CH3>;
DMA1_STREAM4 => dma::InterruptHandler<peripherals::DMA1_CH4>;
DMA1_STREAM7 => dma::InterruptHandler<peripherals::DMA1_CH7>;
I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
EXTI3 => exti::InterruptHandler<interrupt::typelevel::EXTI3>;
EXTI9_5 => exti::InterruptHandler<interrupt::typelevel::EXTI9_5>;
EXTI15_10 => exti::InterruptHandler<interrupt::typelevel::EXTI15_10>;
});| 中断 | 服务谁 |
|---|---|
| SDIO | SD 卡命令/响应 |
| DMA2_CH6 | SDIO 数据块 DMA |
| DMA2_CH3 | SPI1 TX(ST7735 屏幕) |
| DMA1_CH4 / DMA1_CH3 | SPI2 TX/RX(VS1053) |
| DMA1_CH2 / DMA1_CH7 | I2C2 RX/TX(AS5600) |
| I2C2_EV / I2C2_ER | I2C2 事件与错误 |
| EXTI3 | 按键 PB3(上一首) |
| EXTI9_5 | 按键 PB5/PB7/PB9(播放暂停、停止、下一首) |
一个外设一套 DMA 中断,漏绑一个,对应的异步操作就会永久挂起。整合阶段的绝大多数“卡死”都长这样。
注
EXTI15_10 绑了但当前没用到,属于保险;不绑也能编译,绑上也不碍事。
初始化顺序
let p = embassy_stm32::init(clock_config());
info!("boot 168 MHz");init 之后才允许碰外设。时钟配置必须在最前面,否则所有外设的时序都是错的。
