第二幕:主循环与任务编排
2026/8/4大约 2 分钟嵌入式开发STM32Embassy
时钟和中断就绪后,启动流程分两段:先派任务,再初始化外设,最后进主循环。
派任务
spawner.spawn(simple_foc::as5600::as5600_task(as5600_i2c).unwrap());
spawner.spawn(
simple_foc::motor::motor_safety_task(p.TIM1, p.PE9, p.PE11, p.PE13, p.PE14).unwrap(),
);
spawner.spawn(
input::button_task(
ExtiInput::new(p.PB9, p.EXTI9, Pull::Up, Irqs),
ButtonEvent::Next,
).unwrap(),
);
spawner.spawn(
input::button_task(
ExtiInput::new(p.PB7, p.EXTI7, Pull::Up, Irqs),
ButtonEvent::Stop,
).unwrap(),
);
spawner.spawn(
input::button_task(
ExtiInput::new(p.PB5, p.EXTI5, Pull::Up, Irqs),
ButtonEvent::PlayPause,
).unwrap(),
);
spawner.spawn(
input::button_task(
ExtiInput::new(p.PB3, p.EXTI3, Pull::Up, Irqs),
ButtonEvent::Prev,
).unwrap(),
);四个按键任务同一个 button_task 模板,pool_size = 4 让任务池允许四个实例。每个任务等下降沿 → 40 ms 消抖 → 低电平确认后往通道发事件;通道满就直接丢,播放器不缺按键。
初始化外设
派完任务,按依赖顺序初始化:
- 屏幕背光和
PlayerDisplay,先显示Booting...; - SDIO 循环重试识别 SD 卡(失败显示
SD CARD / Insert FAT card); - 挂载 FAT32,打开根目录,扫描 WAV 构建播放列表(失败显示
FAT ERROR,空列表显示NO WAV); - 初始化 VS1053(复位 → 低速配寄存器 → 切 8 MHz),失败显示
VS1053 / Init failed; - 设音量 100%,播放第一首。
致命错误统一进 idle_forever(),屏幕停在一张错误页上:
async fn idle_forever() -> ! {
loop {
Timer::after_millis(20).await;
}
}主循环
主循环每轮做四件事:喂 VS1053、处理按键、转盘联动、刷新屏幕:
loop {
// 1. 播放中则推数据;EOF 自动下一首
if player.is_playing() {
match player.process().await {
Ok(()) => {
if !player.is_playing() {
current = next_index(current, playlist.len());
play_current(&mut player, &playlist, current, screen.as_mut()).await;
turntable.sync_motor(player.is_playing());
}
}
Err(_) => { /* 显示 PLAYBACK 错误,stop */ }
}
}
// 2. 按键事件
if let Some(event) = input::try_event() {
match event {
ButtonEvent::PlayPause => { /* 播放/暂停/继续 */ }
ButtonEvent::Prev => { /* 上一首 */ }
ButtonEvent::Next => { /* 下一首 */ }
ButtonEvent::Stop => { /* 停止 */ }
}
turntable.sync_motor(player.is_playing());
}
// 3. 转盘联动(堵转暂停、手摇跳转、松手恢复)
turntable.sync_motor(player.is_playing());
if player.is_playing() && turntable.should_pause_for_stall() { /* 暂停 */ }
if player.is_paused() {
if let Some(target) = turntable.poll_jog_seek(player.progress()) {
player.seek_seconds(target).await;
}
if turntable.should_resume_after_jog() { /* 继续播放 */ }
} else {
turntable.reset_jog();
}
// 4. 每 500 ms 刷新屏幕进度
if player.is_playing() && last_progress_refresh.elapsed().as_millis() >= 500 {
show_current(/* ... */);
last_progress_refresh = Instant::now();
}
if !player.is_playing() {
Timer::after_millis(5).await;
}
}按键映射
| 按键 | 引脚 | 事件 |
|---|---|---|
| PB3 | EXTI3 | 上一首 |
| PB5 | EXTI5 | 播放/暂停 |
| PB7 | EXTI7 | 停止 |
| PB9 | EXTI9 | 下一首 |
切歌流程固定是:stop() 关文件并清 FIFO → 更新 current → play_current() 打开新文件 → 同步电机状态 → 重置进度刷新计时。
显示联动
屏幕由 show_boot / show_error / show_track 三个界面组成,播放页每 500 ms 刷新一次,显示:
- 状态栏:
PLAYING/PAUSED/STOPPED(颜色不同); - 曲目编号
3/5、文件名; - 进度条和
mm:ss / mm:ss时间。
底部固定画着四个按键提示:*:STOP、#:PLAY、<:PRV、>:NXT,和实物按键一一对应。
注
主循环没有串口命令、没有蓝牙遥控——交互全部走物理按键和转盘。想改成遥控器,把 input::try_event() 的源头换成串口或无线通道就行,主循环不用动。
