第二幕:数据推送与播放控制
2026/5/9大约 2 分钟嵌入式开发STM32SPIVS1053
初始化之后,SCI 管控制、SDI 管数据。这一幕实现从打开 WAV 文件到流式播放的完整逻辑,外加进度计算和 seek。
Vs1053 结构体
pub struct Vs1053<'d, BD, TS>
where
BD: BlockDevice,
TS: TimeSource,
{
spi: Spi<'d, Async, Master>,
xcs: Output<'d>,
xdcs: Output<'d>,
rst: Output<'d>,
dreq: ExtiInput<'d, Async>,
volume: u16,
vol_mgr: Controller<BD, TS>,
sd_volume: Volume,
root_dir: Directory,
current_file: Option<embedded_sdmmc::File>,
state: PlaybackState,
byte_rate: u32, // 字节率,seek 和进度都靠它
file_offset: u32,
file_length: u32,
}注意文件系统控制器直接长在播放器里:打开文件、读数据都通过 vol_mgr,这样 play 和 process 就能自包含地操作 SD 卡。
打开文件并解析 WAV 头
pub async fn play(&mut self, filename: &str) -> Result<(), spi::Error> {
self.stop()?;
let file = self.vol_mgr
.open_file_in_dir(
&mut self.sd_volume,
&self.root_dir,
filename,
embedded_sdmmc::Mode::ReadOnly,
)
.await
.map_err(|_| spi::Error::ModeFault)?;
self.file_length = file.length();
self.file_offset = 0;
self.byte_rate = 0;
self.current_file = Some(file);
self.state = PlaybackState::Playing;
// 读前 64 字节,从 WAV 头提取 byte_rate(第 28~31 字节,小端)
let mut header = [0u8; 64];
if let Some(f) = &mut self.current_file {
if let Ok(bytes_read) = self.vol_mgr.read(&self.sd_volume, f, &mut header).await
&& bytes_read >= 44
{
self.byte_rate = u32::from_le_bytes(header[28..32].try_into().unwrap());
}
let _ = f.seek_from_start(0);
}
Ok(())
}byte_rate = sample_rate × channels × bits_per_sample / 8,48 kHz 双声道 16 bit 就是 192000 字节/秒。它是进度条和 seek 换算的核心。
数据推送
每次读 512 字节,再按最多 32 字节切成小块发送,块间等 DREQ:
pub async fn send_data(&mut self, mut data: &[u8]) -> Result<(), spi::Error> {
const MAX_SIZE: usize = 32;
while !data.is_empty() {
self.wait_freq().await;
let chunk = if data.len() > MAX_SIZE {
&data[..MAX_SIZE]
} else {
data
};
self.xdcs.set_low();
self.spi.write(chunk).await?;
self.xdcs.set_high();
data = &data[chunk.len()..];
}
Ok(())
}主循环每轮调用一次 process(),读一块、推一块:
pub async fn process(&mut self) -> Result<(), spi::Error> {
if self.state != PlaybackState::Playing {
return Ok(());
}
let mut buf = [0u8; 512];
let read = if let Some(file) = &mut self.current_file {
Some(self.vol_mgr.read(&self.sd_volume, file, &mut buf).await)
} else {
None
};
match read {
Some(Ok(0)) | None => {
self.stop()?; // EOF
}
Some(Ok(bytes_read)) => {
self.send_data(&buf[..bytes_read]).await?;
self.file_offset = self.file_offset.saturating_add(bytes_read as u32);
}
Some(Err(_)) => {
error!("SD Card Error!");
self.stop()?;
}
}
Ok(())
}流式读取意味着内存里永远只有 512 字节,整首歌不占 SRAM。
播放状态机
pub enum PlaybackState { Stopped, Playing, Paused }Stopped ──play()──▶ Playing ──pause()──▶ Paused
▲ │ │
└──stop()◀─────────┘ continue_play()───┘stop() 不只是关文件,还会向 VS1053 发送 2048 字节全零数据,把解码器 FIFO 清空,防止残留音频串到下一首:
async fn flush_buffer(&mut self) -> Result<(), spi::Error> {
let end_fill = [0u8; 32];
for _ in 0..(2048 / 32) {
self.send_data(&end_fill).await?;
}
Ok(())
}进度与 seek
进度用 file_offset 和 byte_rate 算:
pub fn progress(&self) -> Option<PlaybackProgress> {
if self.byte_rate == 0 || self.file_length <= WAV_HEADER_BYTES {
return None;
}
let data_length = self.file_length - WAV_HEADER_BYTES;
let data_offset = self.file_offset.saturating_sub(WAV_HEADER_BYTES).min(data_length);
let elapsed_seconds = data_offset / self.byte_rate;
let total_seconds = data_length / self.byte_rate;
let percent = ((u64::from(data_offset) * 100) / u64::from(data_length)).min(100) as u8;
Some(PlaybackProgress { elapsed_seconds, total_seconds, percent })
}seek 则反着来:目标秒数 × byte_rate + 44 字节文件头,就是文件偏移:
pub async fn seek_seconds(&mut self, total_seconds: u32) -> Result<(), spi::Error> {
let data_length = self.file_length - WAV_HEADER_BYTES;
let max_seconds = data_length / self.byte_rate;
let total_seconds = total_seconds.min(max_seconds);
let offset = WAV_HEADER_BYTES + total_seconds * self.byte_rate;
let _ = file.seek_from_start(offset);
self.file_offset = offset.min(self.file_length);
self.flush_buffer().await?; // 跳转后清 FIFO,避免旧数据混音
Ok(())
}这套进度/seek 机制是后面“手摇转盘跳进度”的地基:转盘角度累加被换算成秒数,再调用 seek_seconds。
