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); |                     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>)), |         (("sai", "B"), quote!(crate::sai::Dma<B>)), | ||||||
|         (("spi", "RX"), quote!(crate::spi::RxDma)), |         (("spi", "RX"), quote!(crate::spi::RxDma)), | ||||||
|         (("spi", "TX"), quote!(crate::spi::TxDma)), |         (("spi", "TX"), quote!(crate::spi::TxDma)), | ||||||
|  |         (("spdifrx", "RX"), quote!(crate::spdifrx::Dma)), | ||||||
|         (("i2c", "RX"), quote!(crate::i2c::RxDma)), |         (("i2c", "RX"), quote!(crate::i2c::RxDma)), | ||||||
|         (("i2c", "TX"), quote!(crate::i2c::TxDma)), |         (("i2c", "TX"), quote!(crate::i2c::TxDma)), | ||||||
|         (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), |         (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), | ||||||
|  | |||||||
| @ -107,6 +107,8 @@ pub mod rtc; | |||||||
| pub mod sai; | pub mod sai; | ||||||
| #[cfg(sdmmc)] | #[cfg(sdmmc)] | ||||||
| pub mod sdmmc; | pub mod sdmmc; | ||||||
|  | #[cfg(spdifrx)] | ||||||
|  | pub mod spdifrx; | ||||||
| #[cfg(spi)] | #[cfg(spi)] | ||||||
| pub mod spi; | pub mod spi; | ||||||
| #[cfg(tsc)] | #[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