第二幕:主循环与串口交互
2026/5/9大约 2 分钟嵌入式开发STM32Embassy
时钟和中断就绪后,主循环只做两件事:给 VS1053 喂数据、响应串口命令。
启动流程全貌
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
// 1. RCC 时钟配置
// 2. GPIO + USART1 + SPI1
// 3. SDMMC → 识别 SD 卡 → BlockDevice
// 4. FAT32 挂载 → 遍历 .WAV → 构建 playlist
// 5. VS1053 初始化(复位→低速→高速→设音量)
// 6. 进入主循环
}主循环
loop {
// 播放中则推送数据
if player.is_playing() {
player.process().await.unwrap();
}
// 检查串口命令
let mut byte: [u8; 1] = [0u8; 1];
usart.read_exact(&mut byte).unwrap();
match byte.first() {
Some(b'c') => player.continue_play().unwrap(),
Some(b'p') => player.pause().unwrap(),
Some(b's') => player.stop().unwrap(),
Some(b'n') => {
if counter < playlist.len() - 1 {
counter += 1;
player.stop().unwrap();
player.play(playlist[counter].as_str()).await.unwrap();
}
}
Some(b'o') => {
if counter > 0 {
counter -= 1;
player.stop().unwrap();
player.play(playlist[counter].as_str()).await.unwrap();
}
}
_ => error!("Wrong input!"),
}
}| 按键 | 功能 |
|---|---|
c | 继续播放 |
p | 暂停 |
s | 停止 |
n | 下一首 |
o | 上一首 |
通过 minicom、picocom 或 Python 脚本发送单个字符即可控制播放器。
切歌事件链
用户按下 n 时:
串口收到 'n'
→ player.stop(): 关闭当前文件 + flush VS1053 FIFO
→ playlist[counter] 拿到新文件名
→ player.play(song): 打开文件 + 解析 WAV 头 + Playing 状态
→ 下一轮 loop: process() 开始推送数据
→ 声音流出每一步都是异步的——文件系统操作和 SPI 传输不会阻塞其他任务。
内存策略
项目零堆分配:
- 栈数组:
tx_buf[256],rx_buf[256],header[64],buf[512] - heapless 容器:
Vec<String<13>, 128>(播放列表上限 128 首) - 无 allocator,无碎片,编译期确定大小
当前成果与后续方向
已实现
- LED 闪烁(第 1 章)
- SD 卡 + FAT32 + WAV 目录扫描(第 2 章)
- VS1053 播放/暂停/停止/切歌(第 3 章)
- AS5600 磁编码器角度读取(第 4 章)
- 串口交互主循环(本章)
待实现
- PWM + SimpleFOC 电机控制
- SPI 屏幕驱动(显示曲目与状态)
- Click Wheel 物理交互
- 异步多任务重构(Spawner::spawn 分离各模块)
小结
从 #![no_std] 的空文件到一台能通过串口切歌的 WAV 播放器,我们走了五章。时钟树、中断向量表、DMA 对齐、I2C 字节序、SPI 片选握手——嵌软的每个"坑"都是必修课。
Rust + Embassy 的组合证明了嵌入式开发可以不那么痛苦:所有权在编译期消灭数据竞争,异步模型让硬件等待不浪费 CPU,embedded-hal 让驱动跨平台复用。当 WAV 从 VS1053 的 DAC 流出时,那是代码在硅片上的真实回响。
