From f4d6b1baeef99132c715927f43fa18ea4aa7e6df Mon Sep 17 00:00:00 2001 From: Cristian Eigel Date: Wed, 15 Apr 2020 21:44:37 +0200 Subject: [PATCH] Add playlist. Stopping prematurely --- Cargo.toml | 2 +- openocd.gdb | 10 ++-- src/data_reader.rs | 82 ++++++++++++++++++++++++++ src/main.rs | 139 +++++++++++++++++++++++++++++++------------- src/mp3_player.rs | 15 +++-- src/playlist.rs | 71 ++++++++++++++++++++++ src/sound_device.rs | 38 ++++++++---- 7 files changed, 292 insertions(+), 65 deletions(-) create mode 100644 src/playlist.rs diff --git a/Cargo.toml b/Cargo.toml index 2739841..9c40280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,4 @@ debug = true # symbols are nice and they don't increase the size on Flash lto = true # better optimizations [profile.dev] -opt-level = 3 +opt-level = 0 diff --git a/openocd.gdb b/openocd.gdb index 4d72b07..0fdd863 100644 --- a/openocd.gdb +++ b/openocd.gdb @@ -7,13 +7,13 @@ set print asm-demangle on set backtrace limit 32 # detect unhandled exceptions, hard faults and panics -#break DefaultHandler +break DefaultHandler break HardFault -#break rust_begin_unwind +break rust_begin_unwind -break sound_device.rs:194 -#break main.rs:273 -#break minimp3.h:1706 +#break sound_device.rs:171 +#break main.rs:235 +#break main.rs:215 # *try* to stop at the user entry point (it might be gone due to inlining) # # send captured ITM to the file itm.fifo diff --git a/src/data_reader.rs b/src/data_reader.rs index 03b9e21..28655b6 100644 --- a/src/data_reader.rs +++ b/src/data_reader.rs @@ -1,5 +1,7 @@ use crate::hal::hal as embedded_hal; +use crate::playlist::{DirectoryNavigator, Playlist}; use embedded_sdmmc as sdmmc; +use log::error; struct DummyTimeSource {} impl sdmmc::TimeSource for DummyTimeSource { @@ -9,6 +11,7 @@ impl sdmmc::TimeSource for DummyTimeSource { } pub(crate) type FileError = sdmmc::Error; + pub struct DataReader { file: sdmmc::File, file_name: sdmmc::ShortFileName, @@ -34,16 +37,25 @@ impl DataReader { pub fn done(&self) -> bool { self.file.eof() } + pub fn remaining(&self) -> u32 { self.file.left() } + pub fn seek_from_start(&mut self, offset: u32) -> Result<(), ()> { self.file.seek_from_start(offset) } + + pub fn close(self, file_reader: &mut impl FileReader) { + if let Err(e) = file_reader.close_file(self.file) { + error!("Error closing file {}: {:?}", self.file_name, e); + } + } } pub trait FileReader { fn read_data(&mut self, file: &mut sdmmc::File, out: &mut [u8]) -> Result; + fn close_file(&mut self, file: sdmmc::File) -> Result<(), FileError>; } pub struct SdCardReader @@ -90,6 +102,15 @@ where .map_err(|e| FileError::FilenameError(e))?; Ok(DataReader::new(file, file_name)) } + + pub fn open_directory(&mut self, directory_name: &str) -> Result { + let dir = self + .controller + .open_dir(&self.volume, &self.root_dir, directory_name)?; + let dir_name = sdmmc::ShortFileName::create_from_str(directory_name) + .map_err(|e| FileError::FilenameError(e))?; + Ok(Playlist::new(dir, dir_name)) + } } impl FileReader for SdCardReader @@ -101,4 +122,65 @@ where fn read_data(&mut self, file: &mut sdmmc::File, out: &mut [u8]) -> Result { self.controller.read(&self.volume, file, out) } + fn close_file(&mut self, file: sdmmc::File) -> Result<(), FileError> { + self.controller.close_file(&self.volume, file) + } +} + +impl DirectoryNavigator for SdCardReader +where + SPI: embedded_hal::spi::FullDuplex, + CS: embedded_hal::digital::v2::OutputPin, + >::Error: core::fmt::Debug, +{ + fn open_file_read(&mut self, file: &sdmmc::DirEntry) -> Result { + self.controller + .open_dir_entry(&mut self.volume, file.clone(), sdmmc::Mode::ReadOnly) + } + fn next_file( + &mut self, + dir: &sdmmc::Directory, + current_file: Option<&sdmmc::DirEntry>, + extension: &str, + comp: impl Fn(&sdmmc::DirEntry, &sdmmc::DirEntry) -> bool, + ) -> Result, FileError> { + let mut next_dir_entry: Option = None; + self.controller + .iterate_dir(&self.volume, dir, |dir_entry: &sdmmc::DirEntry| { + if dir_entry.attributes.is_hidden() || dir_entry.attributes.is_directory() { + return; + } + if dir_entry.name.extension().is_err() || dir_entry.name.base_name().is_err() { + return; + } + if dir_entry.name.extension().unwrap() != extension { + return; + } + match (current_file, next_dir_entry.as_ref()) { + (Some(current_file), Some(nde)) => { + if comp(current_file, dir_entry) && comp(dir_entry, nde) { + next_dir_entry.replace(dir_entry.clone()); + } + } + (Some(current_file), None) => { + if comp(current_file, dir_entry) { + next_dir_entry.replace(dir_entry.clone()); + } + } + (None, Some(nde)) => { + if comp(dir_entry, nde) { + next_dir_entry.replace(dir_entry.clone()); + } + } + (None, None) => { + next_dir_entry.replace(dir_entry.clone()); + } + }; + })?; + Ok(next_dir_entry) + } + + fn close_dir(&mut self, directory: sdmmc::Directory) { + self.controller.close_dir(&self.volume, directory); + } } diff --git a/src/main.rs b/src/main.rs index c147528..ce87ea8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,10 @@ use panic_itm as _; mod data_reader; mod mp3_player; +mod playlist; mod sound_device; use data_reader::SdCardReader; +use playlist::Playlist; use sound_device::{DmaState, SoundDevice, DMA_LENGTH}; use cortex_m_log::destination; @@ -27,7 +29,6 @@ static mut LOGGER: Option> = None; const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Debug; const MP3_DATA_LENGTH: usize = 12 * 1024; -const INTRO_FILE_NAME: &str = "intro.mp3"; struct CCRamBuffers { pub mp3_decoder_data: mp3::DecoderData, @@ -103,15 +104,21 @@ fn init_spi( spi } +pub struct PlayingResources { + pub sound_device: SoundDevice<'static>, + pub mp3_player: Mp3Player<'static>, + pub card_reader: SdCardReader>>, +} + #[app(device = stm32f3xx_hal::stm32, monotonic = rtfm::cyccnt::CYCCNT, peripherals = true)] const APP: () = { struct Resources { - sound_device: SoundDevice<'static>, - mp3_player: Mp3Player<'static>, - card_reader: SdCardReader>>, + playing_resources: PlayingResources, pa4: gpioa::PA4, + #[init(None)] + current_playlist: Option, } - #[init(spawn=[play_intro])] + #[init(spawn=[start_playlist])] fn init(cx: init::Context) -> init::LateResources { let mut core = cx.core; core.DCB.enable_trace(); @@ -184,33 +191,79 @@ const APP: () = { info!("Init finished"); - cx.spawn.play_intro().expect("To start play_intro"); + cx.spawn.start_playlist("02").expect("To start play_intro"); init::LateResources { - sound_device, - mp3_player, - card_reader, + playing_resources: PlayingResources { + sound_device, + mp3_player, + card_reader, + }, pa4, } } - #[task(resources=[card_reader, sound_device, mp3_player])] - fn play_intro(cx: play_intro::Context) { - let mut card_reader = cx.resources.card_reader; - let mut sound_device = cx.resources.sound_device; - let mut mp3_player = cx.resources.mp3_player; - let play_result = card_reader.lock(|card_reader| { - sound_device.lock(|sound_device| { - mp3_player.lock(|mp3_player| { - card_reader - .open_file(INTRO_FILE_NAME) - .map_err(|e| PlayError::FileError(e)) - .and_then(|song| { + #[task(resources=[playing_resources, current_playlist], spawn=[playlist_next])] + fn start_playlist(mut cx: start_playlist::Context, directory_name: &'static str) { + let currently_playing = cx.resources.current_playlist.is_some(); + let next_playlist = cx.resources.playing_resources.lock(|pr| { + if currently_playing { + pr.sound_device.stop_playing(); + } + pr.card_reader.open_directory(directory_name) + }); + match next_playlist { + Ok(playlist) => { + if cx.resources.current_playlist.replace(playlist).is_none() { + cx.spawn.playlist_next().expect("to spawn playlist next"); + } + } + Err(e) => { + error!("Can not open directory: {}, err: {:?}", directory_name, e); + } + } + } + + #[task(resources=[playing_resources, current_playlist])] + fn playlist_next(mut cx: playlist_next::Context) { + if let Some(playlist) = cx.resources.current_playlist { + let play_result = cx.resources.playing_resources.lock(|pr| { + playlist + .move_next(&mut pr.card_reader) + .map_err(|e| PlayError::from(e)) + .and_then(|song| match song { + None => Ok(None), + Some(song) => { let file_name = song.file_name(); - mp3_player.play_song(song, card_reader, sound_device)?; - Ok(file_name) - }) + pr.mp3_player.play_song( + song, + &mut pr.card_reader, + &mut pr.sound_device, + )?; + Ok(Some(file_name)) + } + }) + }); + match play_result { + Ok(file_name) => info!("Playing {:?}", file_name), + Err(e) => error!("Playlist {} can't be played: {:?}", playlist.name(), e), + } + } + } + + #[task(resources=[playing_resources])] + fn play_intro(mut cx: play_intro::Context) { + let play_result = cx.resources.playing_resources.lock(|pr| { + let card_reader = &mut pr.card_reader; + let sound_device = &mut pr.sound_device; + let mp3_player = &mut pr.mp3_player; + card_reader + .open_file("intro.mp3") + .map_err(|e| PlayError::FileError(e)) + .and_then(|song| { + let file_name = song.file_name(); + mp3_player.play_song(song, card_reader, sound_device)?; + Ok(file_name) }) - }) }); match play_result { Ok(file_name) => info!("Playing {}", file_name), @@ -218,7 +271,7 @@ const APP: () = { } } - #[idle(resources=[sound_device])] + #[idle(resources=[playing_resources])] fn idle(mut cx: idle::Context) -> ! { static mut TICK: Option = None; static mut ALREADY_STOPPED: bool = false; @@ -227,7 +280,10 @@ const APP: () = { loop { if TICK.unwrap().elapsed().as_cycles() > 4_000_000 { *TICK = Some(Instant::now()); - let stopping = cx.resources.sound_device.lock(|sd| sd.stopping); + let stopping = cx + .resources + .playing_resources + .lock(|pr| pr.sound_device.stopping); if stopping && !*ALREADY_STOPPED { info!("Music stopped"); log::logger().flush(); @@ -237,8 +293,9 @@ const APP: () = { } } - #[task(capacity=1, priority=8, resources=[sound_device, mp3_player, card_reader])] + #[task(capacity=1, priority=8, resources=[playing_resources])] fn process_dma_request(cx: process_dma_request::Context, new_state: DmaState) { + let pr = cx.resources.playing_resources; match new_state { DmaState::Unknown => panic!("Unknown dma state"), DmaState::HalfTrigger | DmaState::TriggerComplete => { @@ -247,33 +304,33 @@ const APP: () = { } else { 1 }; - let fil_res = cx.resources.sound_device.fill_pcm_buffer( - index, - cx.resources.mp3_player, - cx.resources.card_reader, - ); + let fil_res = + pr.sound_device + .fill_pcm_buffer(index, &mut pr.mp3_player, &mut pr.card_reader); if fil_res.is_err() { - cx.resources.sound_device.stop_playing(); + pr.sound_device.stop_playing(); } } _ => {} } } - #[task(binds = DMA2_CH3, priority=8, resources=[sound_device], spawn=[process_dma_request])] + #[task(binds = DMA2_CH3, priority=8, resources=[playing_resources], spawn=[process_dma_request])] fn dma2_ch3(cx: dma2_ch3::Context) { - let state = cx.resources.sound_device.dma_interrupt(); + let pr = cx.resources.playing_resources; + let state = pr.sound_device.dma_interrupt(); if cx.spawn.process_dma_request(state).is_err() { error!("Spawn error. Stop playing!"); } } - #[task(binds = TIM7, priority=1, resources=[sound_device])] + #[task(binds = TIM7, priority=1, resources=[playing_resources], spawn=[playlist_next])] fn tim7(mut cx: tim7::Context) { - debug!("Tim7 called"); - cx.resources - .sound_device - .lock(|sound_device| sound_device.playing_stop_timer_interrupt()); + debug!("Tim7 called, now: {:?}", Instant::now()); + cx.resources.playing_resources.lock(|pr| { + pr.sound_device.playing_stop_timer_interrupt(); + }); + cx.spawn.playlist_next().unwrap(); } extern "C" { diff --git a/src/mp3_player.rs b/src/mp3_player.rs index 1190178..9b2900c 100644 --- a/src/mp3_player.rs +++ b/src/mp3_player.rs @@ -12,6 +12,13 @@ pub enum PlayError { FileError(FileError), NotAnMp3, NoValidMp3Frame, + PlaylistFinished, +} + +impl From for PlayError { + fn from(fe: FileError) -> Self { + PlayError::FileError(fe) + } } pub struct Mp3Player<'a> { @@ -65,9 +72,7 @@ impl<'a> Mp3Player<'a> { .as_mut() .expect("prepare_read to have returned"); let buf = &mut self.mp3_data[0..MP3_DETECT_SIZE]; - let bytes_red = data_reader - .read_data(file_reader, buf) - .map_err(|e| PlayError::FileError(e))?; + let bytes_red = data_reader.read_data(file_reader, buf)?; if bytes_red != MP3_DETECT_SIZE || &buf[0..3] != "ID3".as_bytes() || (buf[5] & 0x0f != 0/* only first 4 bits of flags are available */) @@ -130,9 +135,7 @@ impl<'a> Mp3Player<'a> { self.mp3_data.len() ); let out_slice = &mut self.mp3_data[self.write_index..]; - let bytes_red = data_reader - .read_data(file_reader, out_slice) - .map_err(|e| PlayError::FileError(e))?; + let bytes_red = data_reader.read_data(file_reader, out_slice)?; debug!( "Read {}, Remaining {} bytes", bytes_red, diff --git a/src/playlist.rs b/src/playlist.rs new file mode 100644 index 0000000..a25feeb --- /dev/null +++ b/src/playlist.rs @@ -0,0 +1,71 @@ +use crate::data_reader::{DataReader, FileError}; +use embedded_sdmmc as sdmmc; + +pub struct Playlist { + directory: sdmmc::Directory, + directory_name: sdmmc::ShortFileName, + current_file: Option, +} + +impl Playlist { + pub fn new(directory: sdmmc::Directory, directory_name: sdmmc::ShortFileName) -> Self { + Self { + directory, + directory_name, + current_file: None, + } + } + + pub fn get_current_file( + &self, + directory_navigator: &mut impl DirectoryNavigator, + ) -> Result, FileError> { + let current_file = self.current_file.as_ref(); + if current_file.is_none() { + return Ok(None); + } + let current_file = current_file.unwrap(); + let file = directory_navigator.open_file_read(¤t_file)?; + Ok(Some(DataReader::new(file, current_file.name.clone()))) + } + + pub fn move_next( + &mut self, + directory_navigator: &mut impl DirectoryNavigator, + ) -> Result, FileError> { + let comp = |de1: &sdmmc::DirEntry, de2: &sdmmc::DirEntry| { + if let (Ok(den1), Ok(den2)) = (de1.name.base_name(), de2.name.base_name()) { + den1 < den2 + } else { + false + } + }; + self.current_file = directory_navigator.next_file( + &self.directory, + self.current_file.as_ref(), + "MP3", + comp, + )?; + self.get_current_file(directory_navigator) + } + + pub fn name(&self) -> sdmmc::ShortFileName { + return self.directory_name.clone(); + } + + pub fn close(self, directory_navigator: &mut impl DirectoryNavigator) { + directory_navigator.close_dir(self.directory); + } +} + +pub trait DirectoryNavigator { + fn open_file_read(&mut self, file: &sdmmc::DirEntry) -> Result; + fn next_file( + &mut self, + dir: &sdmmc::Directory, + current_file: Option<&sdmmc::DirEntry>, + extension: &str, + comp: impl Fn(&sdmmc::DirEntry, &sdmmc::DirEntry) -> bool, + ) -> Result, FileError>; + fn close_dir(&mut self, dir: sdmmc::Directory); +} diff --git a/src/sound_device.rs b/src/sound_device.rs index 2774420..844fa4f 100644 --- a/src/sound_device.rs +++ b/src/sound_device.rs @@ -2,17 +2,22 @@ use crate::data_reader::FileReader; use crate::mp3_player::{Mp3Player, PlayError}; use cast::{u16, u32}; use log::{debug, error, info}; +use rtfm::cyccnt::Instant; use stm32f3xx_hal::stm32 as stm32f303; use stm32f3xx_hal::stm32::Interrupt; use stm32f3xx_hal::time; pub(crate) const DMA_LENGTH: usize = 2 * (mp3::MAX_SAMPLES_PER_FRAME / 2); -const PLAYING_DONE_DELAY: u16 = 4_000; // ms +const PLAYING_DONE_DELAY: u16 = 2_000; // ms pub fn apb1enr() -> &'static stm32f303::rcc::APB1ENR { unsafe { &(*stm32f303::RCC::ptr()).apb1enr } } +pub fn apb1rstr() -> &'static stm32f303::rcc::APB1RSTR { + unsafe { &(*stm32f303::RCC::ptr()).apb1rstr } +} + fn ahbenr() -> &'static stm32f303::rcc::AHBENR { unsafe { &(*stm32f303::RCC::ptr()).ahbenr } } @@ -66,8 +71,9 @@ impl<'a> SoundDevice<'a> { sysclk_freq: sysclk, stopping: false, }; - obj.init_tim2(apb1enr); - obj.init_tim7(apb1enr); + let apb1rstr = apb1rstr(); + obj.init_tim2(apb1enr, apb1rstr); + obj.init_tim7(apb1enr, apb1rstr); obj.init_dac1(apb1enr); obj.init_dma2(ahbenr); obj @@ -108,11 +114,13 @@ impl<'a> SoundDevice<'a> { if filled < buffer_len / 2 { let filled = (filled + buffer_index * (buffer_len / 2)) as u16; info!( - "Finishing music buffer_index: {}, filled:{}", - buffer_index, filled + "Finishing music buffer_index: {}, filled:{}, now: {:?}", + buffer_index, + filled, + Instant::now() ); self.stop_at_buffer_len = Some(filled); - if buffer_index == 1 { + if buffer_index == 1 || filled == 0 { self.set_dma_stop(); debug!("Set dma stop"); } @@ -168,7 +176,9 @@ impl<'a> SoundDevice<'a> { let stop_index = self.stop_at_buffer_len.expect("To have stop_index set"); self.stopping = true; self.dma2.ch3.cr.modify(|_, w| w.en().disabled()); - self.dma2.ch3.ndtr.write(|w| w.ndt().bits(stop_index)); + if stop_index != 0 { + self.dma2.ch3.ndtr.write(|w| w.ndt().bits(stop_index)); + } self.dma2.ch3.cr.modify(|_, w| { w.circ().disabled() // dma mode is circular }); @@ -176,18 +186,22 @@ impl<'a> SoundDevice<'a> { self.trigger_playing_stop_timer(); } - fn init_tim2(&self, apb1enr: &stm32f303::rcc::APB1ENR) { + fn init_tim2(&self, apb1enr: &stm32f303::rcc::APB1ENR, apb1rstr: &stm32f303::rcc::APB1RSTR) { + apb1rstr.modify(|_, w| w.tim2rst().reset()); + apb1rstr.modify(|_, w| w.tim2rst().clear_bit()); apb1enr.modify(|_, w| w.tim2en().set_bit()); self.tim2.cr2.write(|w| w.mms().update()); } - fn init_tim7(&self, apb1enr: &stm32f303::rcc::APB1ENR) { + fn init_tim7(&self, apb1enr: &stm32f303::rcc::APB1ENR, apb1rstr: &stm32f303::rcc::APB1RSTR) { + apb1rstr.modify(|_, w| w.tim7rst().reset()); + apb1rstr.modify(|_, w| w.tim7rst().clear_bit()); apb1enr.modify(|_, w| w.tim7en().set_bit()); - let psc_ms = u16((self.sysclk_freq.0 / 7200) - 1).unwrap(); - let arr = PLAYING_DONE_DELAY / 7; + self.tim7.cr1.write(|w| w.opm().enabled().cen().disabled()); + let psc_ms = u16((self.sysclk_freq.0 / 72000) - 1).unwrap(); + let arr = PLAYING_DONE_DELAY; self.tim7.psc.write(|w| w.psc().bits(psc_ms)); self.tim7.arr.write(|w| unsafe { w.bits(u32(arr)) }); - self.tim7.cr1.write(|w| w.opm().enabled().cen().clear_bit()); self.tim7.egr.write(|w| w.ug().update()); self.tim7.sr.modify(|_, w| w.uif().clear()); self.tim7.dier.write(|w| w.uie().enabled());