Skip to main content

daisy_embassy/
audio.rs

1use core::convert::Infallible;
2use core::marker::PhantomData;
3
4use crate::codec::{Codec, Pins as CodecPins};
5use defmt::info;
6use embassy_stm32::{self as hal, Peri};
7use grounded::uninit::GroundedArrayCell;
8
9use hal::sai::{self, MasterClockDivider};
10
11// - global constants ---------------------------------------------------------
12
13pub const BLOCK_LENGTH: usize = 32; // 32 samples
14pub const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * 2; //  2 channels
15pub const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; //  2 half-blocks
16
17// - static data --------------------------------------------------------------
18
19//DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions
20#[unsafe(link_section = ".sram1_bss")]
21static TX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit();
22#[unsafe(link_section = ".sram1_bss")]
23static RX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit();
24
25// - types --------------------------------------------------------------------
26
27pub type InterleavedBlock = [u32; HALF_DMA_BUFFER_LENGTH];
28
29/// `AudioPeripherals` is a builder to make `Interface` safely.
30/// It ensures the correct pin mappings and DMA regions for
31/// SAI on every supported Seed revision, preventing invalid peripheral
32/// configurations at compile time.
33/// Use `prepare_interface()` to apply board‐rev-specific SAI setup
34/// and transition into the `Interface<'_, Idle>`. From there you can call `start_interface()` to move to
35/// `Interface<'_, Running>` and begin audio callbacks.
36pub struct AudioPeripherals<'a> {
37    pub codec_pins: CodecPins<'a>,
38    pub sai1: Peri<'a, hal::peripherals::SAI1>,
39    pub i2c2: Peri<'a, hal::peripherals::I2C2>,
40    pub dma1_ch0: Peri<'a, hal::peripherals::DMA1_CH0>,
41    pub dma1_ch1: Peri<'a, hal::peripherals::DMA1_CH1>,
42    pub dma1_ch2: Peri<'a, hal::peripherals::DMA1_CH2>,
43}
44
45impl<'a> AudioPeripherals<'a> {
46    /// Prepares the audio interface.
47    ///
48    /// This method sets up the SAI transmitter and receiver, configures the codec (if necessary),
49    /// allocates DMA buffers, and applies board-specific SAI settings. It returns an `Interface<'a, Idle>`
50    /// in the Idle state, allowing the runtime to decide when to start audio callbacks using `start_interface()`.
51    ///
52    /// # Arguments
53    /// * `audio_config` - Audio configuration parameters such as the sample rate.  
54    ///   You can use `AudioConfig::default()` or `Default::default()` for default settings.
55    ///
56    /// # Notes
57    /// - This method is async because `seed_1_1` requires I2C communication with the WM8731 codec.
58    /// - The board revision is selected via Cargo features (`seed_1_1`, `seed_1_2`).
59    pub async fn prepare_interface(self, audio_config: AudioConfig) -> Interface<'a, Idle> {
60        let tx_buffer: &mut [u32] = unsafe {
61            TX_BUFFER.initialize_all_copied(0);
62            let (ptr, len) = TX_BUFFER.get_ptr_len();
63            core::slice::from_raw_parts_mut(ptr, len)
64        };
65
66        let rx_buffer: &mut [u32] = unsafe {
67            RX_BUFFER.initialize_all_copied(0);
68            let (ptr, len) = RX_BUFFER.get_ptr_len();
69            core::slice::from_raw_parts_mut(ptr, len)
70        };
71
72        Interface {
73            codec: Codec::new(self, audio_config, tx_buffer, rx_buffer).await,
74            _state: PhantomData,
75        }
76    }
77}
78
79pub struct Idle {}
80pub struct Running {}
81pub trait InterfaceState {}
82impl InterfaceState for Idle {}
83impl InterfaceState for Running {}
84
85/// decides when and how you start audio callback at runtime.
86/// It enforces a two-state model:
87///
88/// * **Idle** – peripherals configured but SAI not started.
89/// * **Running** – SAI started, ready to execute audio callbacks.
90///
91/// Transition from Idle to Running by calling `start_interface()`, which performs
92/// codec register writes, waits for codec timing, and starts the SAI receiver and transmitter.
93/// Once Running, invoke `start_callback()` to enter a continuous read→process→write loop. Any SAI errors are returned
94/// to the caller for custom handling.
95///
96/// `Interface<'a, S>` manages the setup and runtime of an SAI-based audio stream.
97/// It drives codec initialization (over I2C if required), configures SAI TX/RX,
98/// and enforces a two-state model:
99///
100/// * **Idle** – peripherals configured but SAI not started.
101/// * **Running** – SAI started, ready to execute audio callbacks.
102///
103/// Transition from Idle to Running by calling `start_interface()`, which performs
104/// codec register writes, waits for codec timing, and starts the SAI receiver and transmitter.
105/// Once Running, invoke `start_callback()` to enter a continuous read→process→write loop. Any SAI errors are returned
106/// to the caller for custom handling.
107///
108/// # Example
109/// ```rust
110/// // 1. Configure peripherals into Idle state
111/// let idle: Interface<Idle> = board
112///     .audio_peripherals
113///     .prepare_interface(Default::default())
114///     .await;
115///
116/// // ... initialize your DSP or other resources ...
117///
118/// // 2. Start interface and transition to Running
119/// let mut audio: Interface<Running> = idle
120///     .start_interface()
121///     .await
122///     .unwrap();
123///
124/// // 3. Audio processing loop with error handling
125/// loop {
126///     // Runs until an SAI error occurs, then returns Err(e)
127///     if let Err(e) = audio
128///         .start_callback(|input, output| {
129///             // process `input` samples into `output` buffer
130///         })
131///         .await
132///     {
133///         // handle SAI error e (be quick to avoid overrun)
134///     }
135///
136///     // ... optionally reset or reinitialize DSP ...
137/// }
138/// ```
139/// # Notes
140/// - Always call `start_interface()` before `start_callback()`.
141/// - Keep callback and error-handling routines short to prevent SAI overruns.
142pub struct Interface<'a, S: InterfaceState> {
143    codec: Codec<'a>,
144    _state: PhantomData<S>,
145}
146
147impl<'a> Interface<'a, Idle> {
148    /// This has to be called before `Interface::start_callback` can be used to ensure proper setup of the interface.
149    /// `Interface::start_callback` should be called immediately afterwards otherwise overruns of the SAI can occur.
150    pub async fn start_interface(mut self) -> Result<Interface<'a, Running>, sai::Error> {
151        self.codec.start().await?;
152        Ok(Interface {
153            codec: self.codec,
154            _state: PhantomData,
155        })
156    }
157
158    // returns (sai_tx, sai_rx, i2c)
159    #[cfg(any(feature = "seed_1_1", feature = "patch_sm"))]
160    pub async fn setup_and_release(
161        self,
162    ) -> Result<
163        (
164            sai::Sai<'a, hal::peripherals::SAI1, u32>,
165            sai::Sai<'a, hal::peripherals::SAI1, u32>,
166            hal::i2c::I2c<'a, hal::mode::Blocking, hal::i2c::Master>,
167        ),
168        sai::Error,
169    > {
170        self.start_interface().await.map(|i| i.codec.release())
171    }
172}
173
174impl Interface<'_, Running> {
175    pub async fn start_callback(
176        &mut self,
177        mut callback: impl FnMut(&[u32], &mut [u32]),
178    ) -> Result<Infallible, sai::Error> {
179        info!("enter audio callback loop");
180        let mut write_buf = [0; HALF_DMA_BUFFER_LENGTH];
181        let mut read_buf = [0; HALF_DMA_BUFFER_LENGTH];
182        loop {
183            self.codec.read(&mut read_buf).await?;
184            callback(&read_buf, &mut write_buf);
185            self.codec.write(&write_buf).await?;
186        }
187    }
188}
189
190impl<S: InterfaceState> Interface<'_, S> {
191    pub fn sai_rx_config(&self) -> &sai::Config {
192        &self.codec.sai_rx_config
193    }
194
195    pub fn sai_tx_config(&self) -> &sai::Config {
196        &self.codec.sai_tx_config
197    }
198}
199#[derive(Clone, Copy)]
200pub enum Fs {
201    Fs8000,
202    Fs32000,
203    Fs44100,
204    Fs48000,
205    Fs88200,
206    Fs96000,
207}
208const CLOCK_RATIO: u32 = 256; //Not yet support oversampling.
209impl Fs {
210    pub fn into_clock_divider(self) -> MasterClockDivider {
211        let fs = match self {
212            Fs::Fs8000 => 8000,
213            Fs::Fs32000 => 32000,
214            Fs::Fs44100 => 44100,
215            Fs::Fs48000 => 48000,
216            Fs::Fs88200 => 88200,
217            Fs::Fs96000 => 96000,
218        };
219        let kernel_clock = hal::rcc::frequency::<hal::peripherals::SAI1>().0;
220        let mclk_div = (kernel_clock / (fs * CLOCK_RATIO)) as u8;
221        mclk_div_from_u8(mclk_div)
222    }
223}
224
225pub struct AudioConfig {
226    pub fs: Fs,
227}
228
229impl Default for AudioConfig {
230    fn default() -> Self {
231        AudioConfig { fs: Fs::Fs48000 }
232    }
233}
234
235//================================================
236
237const fn mclk_div_from_u8(v: u8) -> MasterClockDivider {
238    match v {
239        1 => MasterClockDivider::DIV1,
240        2 => MasterClockDivider::DIV2,
241        3 => MasterClockDivider::DIV3,
242        4 => MasterClockDivider::DIV4,
243        5 => MasterClockDivider::DIV5,
244        6 => MasterClockDivider::DIV6,
245        7 => MasterClockDivider::DIV7,
246        8 => MasterClockDivider::DIV8,
247        9 => MasterClockDivider::DIV9,
248        10 => MasterClockDivider::DIV10,
249        11 => MasterClockDivider::DIV11,
250        12 => MasterClockDivider::DIV12,
251        13 => MasterClockDivider::DIV13,
252        14 => MasterClockDivider::DIV14,
253        15 => MasterClockDivider::DIV15,
254        16 => MasterClockDivider::DIV16,
255        17 => MasterClockDivider::DIV17,
256        18 => MasterClockDivider::DIV18,
257        19 => MasterClockDivider::DIV19,
258        20 => MasterClockDivider::DIV20,
259        21 => MasterClockDivider::DIV21,
260        22 => MasterClockDivider::DIV22,
261        23 => MasterClockDivider::DIV23,
262        24 => MasterClockDivider::DIV24,
263        25 => MasterClockDivider::DIV25,
264        26 => MasterClockDivider::DIV26,
265        27 => MasterClockDivider::DIV27,
266        28 => MasterClockDivider::DIV28,
267        29 => MasterClockDivider::DIV29,
268        30 => MasterClockDivider::DIV30,
269        31 => MasterClockDivider::DIV31,
270        32 => MasterClockDivider::DIV32,
271        33 => MasterClockDivider::DIV33,
272        34 => MasterClockDivider::DIV34,
273        35 => MasterClockDivider::DIV35,
274        36 => MasterClockDivider::DIV36,
275        37 => MasterClockDivider::DIV37,
276        38 => MasterClockDivider::DIV38,
277        39 => MasterClockDivider::DIV39,
278        40 => MasterClockDivider::DIV40,
279        41 => MasterClockDivider::DIV41,
280        42 => MasterClockDivider::DIV42,
281        43 => MasterClockDivider::DIV43,
282        44 => MasterClockDivider::DIV44,
283        45 => MasterClockDivider::DIV45,
284        46 => MasterClockDivider::DIV46,
285        47 => MasterClockDivider::DIV47,
286        48 => MasterClockDivider::DIV48,
287        49 => MasterClockDivider::DIV49,
288        50 => MasterClockDivider::DIV50,
289        51 => MasterClockDivider::DIV51,
290        52 => MasterClockDivider::DIV52,
291        53 => MasterClockDivider::DIV53,
292        54 => MasterClockDivider::DIV54,
293        55 => MasterClockDivider::DIV55,
294        56 => MasterClockDivider::DIV56,
295        57 => MasterClockDivider::DIV57,
296        58 => MasterClockDivider::DIV58,
297        59 => MasterClockDivider::DIV59,
298        60 => MasterClockDivider::DIV60,
299        61 => MasterClockDivider::DIV61,
300        62 => MasterClockDivider::DIV62,
301        63 => MasterClockDivider::DIV63,
302        _ => panic!(),
303    }
304}