Merge pull request #3280 from elagil/feat_spdifrx_driver
Support for STM32 SPDIFRX
This commit is contained in:
		
						commit
						227e073fca
					
				| @ -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<B>)), | ||||
|         (("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)), | ||||
|  | ||||
| @ -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)] | ||||
|  | ||||
							
								
								
									
										336
									
								
								embassy-stm32/src/spdifrx/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								embassy-stm32/src/spdifrx/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<peripherals::$inst> 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<PeripheralRef<'d, AnyPin>>, | ||||
|     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<RingbufferError> 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<P = T> + 'd, | ||||
|         _irq: impl interrupt::typelevel::Binding<T::GlobalInterrupt, GlobalInterruptHandler<T>> + 'd, | ||||
|         config: Config, | ||||
|         spdifrx_in: impl Peripheral<P = impl InPin<T>> + 'd, | ||||
|         data_dma: impl Peripheral<P = impl Channel + Dma<T>> + '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<T: Instance>: 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<T: Instance> { | ||||
|     _phantom: PhantomData<T>, | ||||
| } | ||||
| 
 | ||||
| impl<T: Instance> interrupt::typelevel::Handler<T::GlobalInterrupt> for GlobalInterruptHandler<T> { | ||||
|     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; | ||||
|         } | ||||
|     }; | ||||
| ); | ||||
							
								
								
									
										8
									
								
								examples/stm32h723/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								examples/stm32h723/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||
							
								
								
									
										69
									
								
								examples/stm32h723/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								examples/stm32h723/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -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 # <- | ||||
							
								
								
									
										35
									
								
								examples/stm32h723/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								examples/stm32h723/build.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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"); | ||||
| } | ||||
							
								
								
									
										106
									
								
								examples/stm32h723/memory.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								examples/stm32h723/memory.x
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| 
 | ||||
| }; | ||||
							
								
								
									
										165
									
								
								examples/stm32h723/src/bin/spdifrx.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								examples/stm32h723/src/bin/spdifrx.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<peripherals::SPDIFRX1>; | ||||
| }); | ||||
| 
 | ||||
| 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<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit(); | ||||
| 
 | ||||
| #[link_section = ".sram4"] | ||||
| static mut SAI_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = 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) | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user