diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 71bfb3747..348d48b9b 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1220,6 +1220,17 @@ fn main() { impl_dac_pin!( #peri, #pin_name, #ch); }) } + + if regs.kind == "spdifrx" { + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let af = pin.af.unwrap_or(0); + let sel: u8 = pin.signal.strip_prefix("IN").unwrap().parse().unwrap(); + + g.extend(quote! { + impl_spdifrx_pin!( #peri, #pin_name, #af, #sel); + }) + } } } } @@ -1244,6 +1255,7 @@ fn main() { (("sai", "B"), quote!(crate::sai::Dma)), (("spi", "RX"), quote!(crate::spi::RxDma)), (("spi", "TX"), quote!(crate::spi::TxDma)), + (("spdifrx", "RX"), quote!(crate::spdifrx::Dma)), (("i2c", "RX"), quote!(crate::i2c::RxDma)), (("i2c", "TX"), quote!(crate::i2c::TxDma)), (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 286a18da2..e189351d1 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -107,6 +107,8 @@ pub mod rtc; pub mod sai; #[cfg(sdmmc)] pub mod sdmmc; +#[cfg(spdifrx)] +pub mod spdifrx; #[cfg(spi)] pub mod spi; #[cfg(tsc)] diff --git a/embassy-stm32/src/spdifrx/mod.rs b/embassy-stm32/src/spdifrx/mod.rs new file mode 100644 index 000000000..a205780ad --- /dev/null +++ b/embassy-stm32/src/spdifrx/mod.rs @@ -0,0 +1,336 @@ +//! S/PDIF receiver +#![macro_use] +#![cfg_attr(gpdma, allow(unused))] + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::ringbuffer::Error as RingbufferError; +pub use crate::dma::word; +#[cfg(not(gpdma))] +use crate::dma::ReadableRingBuffer; +use crate::dma::{Channel, TransferOptions}; +use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::spdifrx::Spdifrx as Regs; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::{interrupt, peripherals, Peripheral}; + +/// Possible S/PDIF preamble types. +#[allow(dead_code)] +#[repr(u8)] +enum PreambleType { + Unused = 0x00, + /// The preamble changes to preamble “B” once every 192 frames to identify the start of the block structure used to + /// organize the channel status and user information. + B = 0x01, + /// The first sub-frame (left or “A” channel in stereophonic operation and primary channel in monophonic operation) + /// normally starts with preamble “M” + M = 0x02, + /// The second sub-frame (right or “B” channel in stereophonic operation and secondary channel in monophonic + /// operation) always starts with preamble “W”. + W = 0x03, +} + +macro_rules! new_spdifrx_pin { + ($name:ident, $af_type:expr) => {{ + let pin = $name.into_ref(); + let input_sel = pin.input_sel(); + pin.set_as_af(pin.af_num(), $af_type); + (Some(pin.map_into()), input_sel) + }}; +} + +macro_rules! impl_spdifrx_pin { + ($inst:ident, $pin:ident, $af:expr, $sel:expr) => { + impl crate::spdifrx::InPin for crate::peripherals::$pin { + fn af_num(&self) -> u8 { + $af + } + fn input_sel(&self) -> u8 { + $sel + } + } + }; +} + +/// Ring-buffered SPDIFRX driver. +/// +/// Data is read by DMAs and stored in a ring buffer. +#[cfg(not(gpdma))] +pub struct Spdifrx<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + spdifrx_in: Option>, + data_ring_buffer: ReadableRingBuffer<'d, u32>, +} + +/// Gives the address of the data register. +fn dr_address(r: Regs) -> *mut u32 { + #[cfg(spdifrx_v1)] + let address = r.dr().as_ptr() as _; + #[cfg(spdifrx_h7)] + let address = r.fmt0_dr().as_ptr() as _; // All fmtx_dr() implementations have the same address. + + return address; +} + +/// Gives the address of the channel status register. +#[allow(unused)] +fn csr_address(r: Regs) -> *mut u32 { + r.csr().as_ptr() as _ +} + +/// Select the channel for capturing control information. +pub enum ControlChannelSelection { + /// Capture control info from channel A. + A, + /// Capture control info from channel B. + B, +} + +/// Configuration options for the SPDIFRX driver. +pub struct Config { + /// Select the channel for capturing control information. + pub control_channel_selection: ControlChannelSelection, +} + +/// S/PDIF errors. +#[derive(Debug)] +pub enum Error { + /// DMA overrun error. + RingbufferError(RingbufferError), + /// Left/right channel synchronization error. + ChannelSyncError, +} + +impl From for Error { + fn from(error: RingbufferError) -> Self { + Self::RingbufferError(error) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + control_channel_selection: ControlChannelSelection::A, + } + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Spdifrx<'d, T> { + fn dma_opts() -> TransferOptions { + TransferOptions { + half_transfer_ir: true, + // new_write() and new_read() always use circular mode + ..Default::default() + } + } + + /// Create a new `Spdifrx` instance. + pub fn new( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + spdifrx_in: impl Peripheral

> + 'd, + data_dma: impl Peripheral

> + 'd, + data_dma_buf: &'d mut [u32], + ) -> Self { + let (spdifrx_in, input_sel) = new_spdifrx_pin!(spdifrx_in, AfType::input(Pull::None)); + Self::setup(config, input_sel); + + into_ref!(peri, data_dma); + + let regs = T::info().regs; + let dr_request = data_dma.request(); + let dr_ring_buffer = + unsafe { ReadableRingBuffer::new(data_dma, dr_request, dr_address(regs), data_dma_buf, Self::dma_opts()) }; + + Self { + _peri: peri, + spdifrx_in, + data_ring_buffer: dr_ring_buffer, + } + } + + fn setup(config: Config, input_sel: u8) { + T::info().rcc.enable_and_reset(); + T::GlobalInterrupt::unpend(); + unsafe { T::GlobalInterrupt::enable() }; + + let regs = T::info().regs; + + regs.imr().write(|imr| { + imr.set_ifeie(true); // Enables interrupts for TERR, SERR, FERR. + imr.set_syncdie(true); // Enables SYNCD interrupt. + }); + + regs.cr().write(|cr| { + cr.set_spdifen(0x00); // Disable SPDIF receiver synchronization. + cr.set_rxdmaen(true); // Use RX DMA for data. Enabled on `read`. + cr.set_cbdmaen(false); // Do not capture channel info. + cr.set_rxsteo(true); // Operate in stereo mode. + cr.set_drfmt(0x01); // Data is left-aligned (MSB). + + // Disable all status fields in the data register. + // Status can be obtained directly with the status register DMA. + cr.set_pmsk(false); // Write parity bit to the data register. FIXME: Add parity check. + cr.set_vmsk(false); // Write validity to the data register. + cr.set_cumsk(true); // Do not write C and U bits to the data register. + cr.set_ptmsk(false); // Write preamble bits to the data register. + + cr.set_chsel(match config.control_channel_selection { + ControlChannelSelection::A => false, + ControlChannelSelection::B => true, + }); // Select channel status source. + + cr.set_nbtr(0x02); // 16 attempts are allowed. + cr.set_wfa(true); // Wait for activity before going to synchronization phase. + cr.set_insel(input_sel); // Input pin selection. + + #[cfg(stm32h7)] + cr.set_cksen(true); // Generate a symbol clock. + + #[cfg(stm32h7)] + cr.set_cksbkpen(true); // Generate a backup symbol clock. + }); + } + + /// Start the SPDIFRX driver. + pub fn start(&mut self) { + self.data_ring_buffer.start(); + + T::info().regs.cr().modify(|cr| { + cr.set_spdifen(0x03); // Enable S/PDIF receiver. + }); + } + + /// Read from the SPDIFRX data ring buffer. + /// + /// SPDIFRX is always receiving data in the background. This function pops already-received + /// data from the buffer. + /// + /// If there's less than `data.len()` data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [u32]) -> Result<(), Error> { + self.data_ring_buffer.read_exact(data).await?; + + let first_preamble = (data[0] >> 4) & 0b11_u32; + if (first_preamble as u8) == (PreambleType::W as u8) { + trace!("S/PDIF left/right mismatch"); + + // Resynchronize until the first sample is for the left channel. + self.data_ring_buffer.clear(); + return Err(Error::ChannelSyncError); + }; + + for sample in data.as_mut() { + if (*sample & (0x0002_u32)) == 0x0001 { + // Discard invalid samples, setting them to mute level. + *sample = 0; + } else { + // Discard status information in the lowest byte. + *sample &= 0xFFFFFF00; + } + } + + Ok(()) + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Drop for Spdifrx<'d, T> { + fn drop(&mut self) { + T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); + self.spdifrx_in.as_ref().map(|x| x.set_as_disconnected()); + } +} + +struct State { + #[allow(unused)] + waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +struct Info { + regs: crate::pac::spdifrx::Spdifrx, + rcc: RccInfo, +} + +peri_trait!( + irqs: [GlobalInterrupt], +); + +/// SPIDFRX pin trait +pub trait InPin: crate::gpio::Pin { + /// Get the GPIO AF number needed to use this pin. + fn af_num(&self) -> u8; + /// Get the SPIDFRX INSEL number needed to use this pin. + fn input_sel(&self) -> u8; +} + +dma_trait!(Dma, Instance); + +/// Global interrupt handler. +pub struct GlobalInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for GlobalInterruptHandler { + unsafe fn on_interrupt() { + T::state().waker.wake(); + + let regs = T::info().regs; + let sr = regs.sr().read(); + + if sr.serr() || sr.terr() || sr.ferr() { + trace!("SPDIFRX error, resync"); + + // Clear errors by disabling SPDIFRX, then reenable. + regs.cr().modify(|cr| cr.set_spdifen(0x00)); + regs.cr().modify(|cr| cr.set_spdifen(0x03)); + } else if sr.syncd() { + // Synchronization was successful. + trace!("SPDIFRX sync success"); + } + + // Clear interrupt flags. + regs.ifcr().write(|ifcr| { + ifcr.set_perrcf(true); // Clears parity error flag. + ifcr.set_ovrcf(true); // Clears overrun error flag. + ifcr.set_sbdcf(true); // Clears synchronization block detected flag. + ifcr.set_syncdcf(true); // Clears SYNCD from SR (was read above). + }); + } +} + +foreach_peripheral!( + (spdifrx, $inst:ident) => { + #[allow(private_interfaces)] + impl SealedInstance for peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info{ + regs: crate::pac::$inst, + rcc: crate::peripherals::$inst::RCC_INFO, + }; + &INFO + } + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::$inst { + type GlobalInterrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); diff --git a/examples/stm32h723/.cargo/config.toml b/examples/stm32h723/.cargo/config.toml new file mode 100644 index 000000000..2e53663c5 --- /dev/null +++ b/examples/stm32h723/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H723ZGTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h723/Cargo.toml b/examples/stm32h723/Cargo.toml new file mode 100644 index 000000000..8ebc1051f --- /dev/null +++ b/examples/stm32h723/Cargo.toml @@ -0,0 +1,69 @@ +[package] +edition = "2021" +name = "embassy-stm32h7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h723zg to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h723zg", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h723/build.rs b/examples/stm32h723/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h723/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h723/memory.x b/examples/stm32h723/memory.x new file mode 100644 index 000000000..aa4c00505 --- /dev/null +++ b/examples/stm32h723/memory.x @@ -0,0 +1,106 @@ +MEMORY +{ + /* This file is intended for parts in the STM32H723 family. (RM0468) */ + /* - FLASH and RAM are mandatory memory sections. */ + /* - The sum of all non-FLASH sections must add to 564k total device RAM. */ + /* - The FLASH section size must match your device, see table below. */ + + /* FLASH */ + /* Select the appropriate FLASH size for your device. */ + /* - STM32H730xB 128K */ + /* - STM32H723xE/725xE 512K */ + /* - STM32H723xG/725xG/733xG/735xG 1M */ + FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M + + /* Data TCM */ + /* - Two contiguous 64KB RAMs. */ + /* - Used for interrupt handlers, stacks and general RAM. */ + /* - Zero wait-states. */ + /* - The DTCM is taken as the origin of the base ram. (See below.) */ + /* This is also where the interrupt table and such will live, */ + /* which is required for deterministic performance. */ + DTCM : ORIGIN = 0x20000000, LENGTH = 128K + + /* Instruction TCM */ + /* - More memory can be assigned to ITCM. See AXI SRAM notes, below. */ + /* - Used for latency-critical interrupt handlers etc. */ + /* - Zero wait-states. */ + ITCM : ORIGIN = 0x00000000, LENGTH = 64K + 0K + + /* AXI SRAM */ + /* - AXISRAM is in D1 and accessible by all system masters except BDMA. */ + /* - Suitable for application data not stored in DTCM. */ + /* - Zero wait-states. */ + /* - The 192k of extra shared RAM is fully allotted to the AXI SRAM by default. */ + /* As a result: 64k (64k + 0k) for ITCM and 320k (128k + 192k) for AXI SRAM. */ + /* This can be re-configured via the TCM_AXI_SHARED[1,0] register when more */ + /* ITCM is required. */ + AXISRAM : ORIGIN = 0x24000000, LENGTH = 128K + 192K + + /* AHB SRAM */ + /* - SRAM1-2 are in D2 and accessible by all system masters except BDMA, LTDC */ + /* and SDMMC1. Suitable for use as DMA buffers. */ + /* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */ + /* buffers, for storing application data in lower-power modes. */ + /* - Zero wait-states. */ + SRAM1 : ORIGIN = 0x30000000, LENGTH = 16K + SRAM2 : ORIGIN = 0x30040000, LENGTH = 16K + SRAM4 : ORIGIN = 0x38000000, LENGTH = 16K + + /* Backup SRAM */ + /* Used to store data during low-power sleeps. */ + BSRAM : ORIGIN = 0x38800000, LENGTH = 4K +} + +/* +/* Assign the memory regions defined above for use. */ +/* + +/* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */ +REGION_ALIAS(FLASH, FLASH1); +REGION_ALIAS(RAM, DTCM); + +/* The location of the stack can be overridden using the `_stack_start` symbol. */ +/* - Set the stack location at the end of RAM, using all remaining space. */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +/* The location of the .text section can be overridden using the */ +/* `_stext` symbol. By default it will place after .vector_table. */ +/* _stext = ORIGIN(FLASH) + 0x40c; */ + +/* Define sections for placing symbols into the extra memory regions above. */ +/* This makes them accessible from code. */ +/* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */ +/* - All other memories connect to a 32-bit wide bus -> align to 4 bytes. */ +SECTIONS { + .itcm (NOLOAD) : ALIGN(8) { + *(.itcm .itcm.*); + . = ALIGN(8); + } > ITCM + + .axisram (NOLOAD) : ALIGN(8) { + *(.axisram .axisram.*); + . = ALIGN(8); + } > AXISRAM + + .sram1 (NOLOAD) : ALIGN(4) { + *(.sram1 .sram1.*); + . = ALIGN(4); + } > SRAM1 + + .sram2 (NOLOAD) : ALIGN(4) { + *(.sram2 .sram2.*); + . = ALIGN(4); + } > SRAM2 + + .sram4 (NOLOAD) : ALIGN(4) { + *(.sram4 .sram4.*); + . = ALIGN(4); + } > SRAM4 + + .bsram (NOLOAD) : ALIGN(4) { + *(.bsram .bsram.*); + . = ALIGN(4); + } > BSRAM + +}; diff --git a/examples/stm32h723/src/bin/spdifrx.rs b/examples/stm32h723/src/bin/spdifrx.rs new file mode 100644 index 000000000..69ef5cd07 --- /dev/null +++ b/examples/stm32h723/src/bin/spdifrx.rs @@ -0,0 +1,165 @@ +//! This example receives inputs on SPDIFRX and outputs on SAI4. +//! +//! Only very few controllers connect the SPDIFRX symbol clock to a SAI peripheral's clock input. +//! However, this is necessary for synchronizing the symbol rates and avoiding glitches. +#![no_std] +#![no_main] + +use defmt::{info, trace}; +use embassy_executor::Spawner; +use embassy_futures::select::{self, select, Either}; +use embassy_stm32::spdifrx::{self, Spdifrx}; +use embassy_stm32::{bind_interrupts, peripherals, sai}; +use grounded::uninit::GroundedArrayCell; +use hal::sai::*; +use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPDIF_RX => spdifrx::GlobalInterruptHandler; +}); + +const CHANNEL_COUNT: usize = 2; +const BLOCK_LENGTH: usize = 64; +const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; +const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks + +// DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions +#[link_section = ".sram1"] +static mut SPDIFRX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[link_section = ".sram4"] +static mut SAI_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut peripheral_config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + peripheral_config.rcc.hsi = Some(HSIPrescaler::DIV1); + peripheral_config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV16, + mul: PllMul::MUL200, + divp: Some(PllDiv::DIV2), // 400 MHz + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + peripheral_config.rcc.sys = Sysclk::PLL1_P; + peripheral_config.rcc.ahb_pre = AHBPrescaler::DIV2; + peripheral_config.rcc.apb1_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb2_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb3_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb4_pre = APBPrescaler::DIV2; + + peripheral_config.rcc.mux.spdifrxsel = mux::Spdifrxsel::PLL1_Q; + } + let mut p = embassy_stm32::init(peripheral_config); + + info!("SPDIFRX to SAI4 bridge"); + + // Use SPDIFRX clock for SAI. + // This ensures equal rates of sample production and consumption. + let clk_source = embassy_stm32::pac::rcc::vals::Saiasel::_RESERVED_5; + embassy_stm32::pac::RCC.d3ccipr().modify(|w| { + w.set_sai4asel(clk_source); + }); + + let sai_buffer: &mut [u32] = unsafe { + SAI_BUFFER.initialize_all_copied(0); + let (ptr, len) = SAI_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let spdifrx_buffer: &mut [u32] = unsafe { + SPDIFRX_BUFFER.initialize_all_copied(0); + let (ptr, len) = SPDIFRX_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_transmitter = new_sai_transmitter( + &mut p.SAI4, + &mut p.PD13, + &mut p.PC1, + &mut p.PD12, + &mut p.BDMA_CH0, + sai_buffer, + ); + let mut spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); + spdif_receiver.start(); + + let mut renew_sai = false; + loop { + let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; + + if renew_sai { + renew_sai = false; + trace!("Renew SAI."); + drop(sai_transmitter); + sai_transmitter = new_sai_transmitter( + &mut p.SAI4, + &mut p.PD13, + &mut p.PC1, + &mut p.PD12, + &mut p.BDMA_CH0, + sai_buffer, + ); + } + + match select(spdif_receiver.read(&mut buf), sai_transmitter.wait_write_error()).await { + Either::First(spdif_read_result) => match spdif_read_result { + Ok(_) => (), + Err(spdifrx::Error::RingbufferError(_)) => { + trace!("SPDIFRX ringbuffer error. Renew."); + drop(spdif_receiver); + spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); + spdif_receiver.start(); + continue; + } + Err(spdifrx::Error::ChannelSyncError) => { + trace!("SPDIFRX channel sync (left/right assignment) error."); + continue; + } + }, + Either::Second(_) => { + renew_sai = true; + continue; + } + }; + + renew_sai = sai_transmitter.write(&buf).await.is_err(); + } +} + +/// Creates a new SPDIFRX instance for receiving sample data. +/// +/// Used (again) after dropping the SPDIFRX instance, in case of errors (e.g. source disconnect). +fn new_spdif_receiver<'d>( + spdifrx: &'d mut peripherals::SPDIFRX1, + input_pin: &'d mut peripherals::PD7, + dma: &'d mut peripherals::DMA2_CH7, + buf: &'d mut [u32], +) -> Spdifrx<'d, peripherals::SPDIFRX1> { + Spdifrx::new(spdifrx, Irqs, spdifrx::Config::default(), input_pin, dma, buf) +} + +/// Creates a new SAI4 instance for transmitting sample data. +/// +/// Used (again) after dropping the SAI4 instance, in case of errors (e.g. buffer overrun). +fn new_sai_transmitter<'d>( + sai: &'d mut peripherals::SAI4, + sck: &'d mut peripherals::PD13, + sd: &'d mut peripherals::PC1, + fs: &'d mut peripherals::PD12, + dma: &'d mut peripherals::BDMA_CH0, + buf: &'d mut [u32], +) -> Sai<'d, peripherals::SAI4, u32> { + let mut sai_config = hal::sai::Config::default(); + sai_config.slot_count = hal::sai::word::U4(CHANNEL_COUNT as u8); + sai_config.slot_enable = 0xFFFF; // All slots + sai_config.data_size = sai::DataSize::Data32; + sai_config.frame_length = (CHANNEL_COUNT * 32) as u8; + sai_config.master_clock_divider = hal::sai::MasterClockDivider::MasterClockDisabled; + + let (sub_block_tx, _) = hal::sai::split_subblocks(sai); + Sai::new_asynchronous(sub_block_tx, sck, sd, fs, dma, buf, sai_config) +}