diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-11-19 17:11:05 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-11-19 17:11:05 +0000 |
| commit | 227e073fca97bcbcec42d9705e0a8ef19fc433b5 (patch) | |
| tree | 52e2dd30bbb57c9847bd794281140eb46c1175c5 | |
| parent | e41a5c62688eba8f0c5b40cbbd1a0375d64a01dc (diff) | |
| parent | 99dd5e79db88cf91ee700c00cce87387c6a67d6f (diff) | |
Merge pull request #3280 from elagil/feat_spdifrx_driver
Support for STM32 SPDIFRX
| -rw-r--r-- | embassy-stm32/build.rs | 12 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/spdifrx/mod.rs | 336 | ||||
| -rw-r--r-- | examples/stm32h723/.cargo/config.toml | 8 | ||||
| -rw-r--r-- | examples/stm32h723/Cargo.toml | 69 | ||||
| -rw-r--r-- | examples/stm32h723/build.rs | 35 | ||||
| -rw-r--r-- | examples/stm32h723/memory.x | 106 | ||||
| -rw-r--r-- | examples/stm32h723/src/bin/spdifrx.rs | 165 |
8 files changed, 733 insertions, 0 deletions
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() { | |||
| 1220 | impl_dac_pin!( #peri, #pin_name, #ch); | 1220 | impl_dac_pin!( #peri, #pin_name, #ch); |
| 1221 | }) | 1221 | }) |
| 1222 | } | 1222 | } |
| 1223 | |||
| 1224 | if regs.kind == "spdifrx" { | ||
| 1225 | let peri = format_ident!("{}", p.name); | ||
| 1226 | let pin_name = format_ident!("{}", pin.pin); | ||
| 1227 | let af = pin.af.unwrap_or(0); | ||
| 1228 | let sel: u8 = pin.signal.strip_prefix("IN").unwrap().parse().unwrap(); | ||
| 1229 | |||
| 1230 | g.extend(quote! { | ||
| 1231 | impl_spdifrx_pin!( #peri, #pin_name, #af, #sel); | ||
| 1232 | }) | ||
| 1233 | } | ||
| 1223 | } | 1234 | } |
| 1224 | } | 1235 | } |
| 1225 | } | 1236 | } |
| @@ -1244,6 +1255,7 @@ fn main() { | |||
| 1244 | (("sai", "B"), quote!(crate::sai::Dma<B>)), | 1255 | (("sai", "B"), quote!(crate::sai::Dma<B>)), |
| 1245 | (("spi", "RX"), quote!(crate::spi::RxDma)), | 1256 | (("spi", "RX"), quote!(crate::spi::RxDma)), |
| 1246 | (("spi", "TX"), quote!(crate::spi::TxDma)), | 1257 | (("spi", "TX"), quote!(crate::spi::TxDma)), |
| 1258 | (("spdifrx", "RX"), quote!(crate::spdifrx::Dma)), | ||
| 1247 | (("i2c", "RX"), quote!(crate::i2c::RxDma)), | 1259 | (("i2c", "RX"), quote!(crate::i2c::RxDma)), |
| 1248 | (("i2c", "TX"), quote!(crate::i2c::TxDma)), | 1260 | (("i2c", "TX"), quote!(crate::i2c::TxDma)), |
| 1249 | (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), | 1261 | (("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; | |||
| 107 | pub mod sai; | 107 | pub mod sai; |
| 108 | #[cfg(sdmmc)] | 108 | #[cfg(sdmmc)] |
| 109 | pub mod sdmmc; | 109 | pub mod sdmmc; |
| 110 | #[cfg(spdifrx)] | ||
| 111 | pub mod spdifrx; | ||
| 110 | #[cfg(spi)] | 112 | #[cfg(spi)] |
| 111 | pub mod spi; | 113 | pub mod spi; |
| 112 | #[cfg(tsc)] | 114 | #[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 @@ | |||
| 1 | //! S/PDIF receiver | ||
| 2 | #![macro_use] | ||
| 3 | #![cfg_attr(gpdma, allow(unused))] | ||
| 4 | |||
| 5 | use core::marker::PhantomData; | ||
| 6 | |||
| 7 | use embassy_hal_internal::{into_ref, PeripheralRef}; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | |||
| 10 | use crate::dma::ringbuffer::Error as RingbufferError; | ||
| 11 | pub use crate::dma::word; | ||
| 12 | #[cfg(not(gpdma))] | ||
| 13 | use crate::dma::ReadableRingBuffer; | ||
| 14 | use crate::dma::{Channel, TransferOptions}; | ||
| 15 | use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; | ||
| 16 | use crate::interrupt::typelevel::Interrupt; | ||
| 17 | use crate::pac::spdifrx::Spdifrx as Regs; | ||
| 18 | use crate::rcc::{RccInfo, SealedRccPeripheral}; | ||
| 19 | use crate::{interrupt, peripherals, Peripheral}; | ||
| 20 | |||
| 21 | /// Possible S/PDIF preamble types. | ||
| 22 | #[allow(dead_code)] | ||
| 23 | #[repr(u8)] | ||
| 24 | enum PreambleType { | ||
| 25 | Unused = 0x00, | ||
| 26 | /// The preamble changes to preamble “B” once every 192 frames to identify the start of the block structure used to | ||
| 27 | /// organize the channel status and user information. | ||
| 28 | B = 0x01, | ||
| 29 | /// The first sub-frame (left or “A” channel in stereophonic operation and primary channel in monophonic operation) | ||
| 30 | /// normally starts with preamble “M” | ||
| 31 | M = 0x02, | ||
| 32 | /// The second sub-frame (right or “B” channel in stereophonic operation and secondary channel in monophonic | ||
| 33 | /// operation) always starts with preamble “W”. | ||
| 34 | W = 0x03, | ||
| 35 | } | ||
| 36 | |||
| 37 | macro_rules! new_spdifrx_pin { | ||
| 38 | ($name:ident, $af_type:expr) => {{ | ||
| 39 | let pin = $name.into_ref(); | ||
| 40 | let input_sel = pin.input_sel(); | ||
| 41 | pin.set_as_af(pin.af_num(), $af_type); | ||
| 42 | (Some(pin.map_into()), input_sel) | ||
| 43 | }}; | ||
| 44 | } | ||
| 45 | |||
| 46 | macro_rules! impl_spdifrx_pin { | ||
| 47 | ($inst:ident, $pin:ident, $af:expr, $sel:expr) => { | ||
| 48 | impl crate::spdifrx::InPin<peripherals::$inst> for crate::peripherals::$pin { | ||
| 49 | fn af_num(&self) -> u8 { | ||
| 50 | $af | ||
| 51 | } | ||
| 52 | fn input_sel(&self) -> u8 { | ||
| 53 | $sel | ||
| 54 | } | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Ring-buffered SPDIFRX driver. | ||
| 60 | /// | ||
| 61 | /// Data is read by DMAs and stored in a ring buffer. | ||
| 62 | #[cfg(not(gpdma))] | ||
| 63 | pub struct Spdifrx<'d, T: Instance> { | ||
| 64 | _peri: PeripheralRef<'d, T>, | ||
| 65 | spdifrx_in: Option<PeripheralRef<'d, AnyPin>>, | ||
| 66 | data_ring_buffer: ReadableRingBuffer<'d, u32>, | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Gives the address of the data register. | ||
| 70 | fn dr_address(r: Regs) -> *mut u32 { | ||
| 71 | #[cfg(spdifrx_v1)] | ||
| 72 | let address = r.dr().as_ptr() as _; | ||
| 73 | #[cfg(spdifrx_h7)] | ||
| 74 | let address = r.fmt0_dr().as_ptr() as _; // All fmtx_dr() implementations have the same address. | ||
| 75 | |||
| 76 | return address; | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Gives the address of the channel status register. | ||
| 80 | #[allow(unused)] | ||
| 81 | fn csr_address(r: Regs) -> *mut u32 { | ||
| 82 | r.csr().as_ptr() as _ | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Select the channel for capturing control information. | ||
| 86 | pub enum ControlChannelSelection { | ||
| 87 | /// Capture control info from channel A. | ||
| 88 | A, | ||
| 89 | /// Capture control info from channel B. | ||
| 90 | B, | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Configuration options for the SPDIFRX driver. | ||
| 94 | pub struct Config { | ||
| 95 | /// Select the channel for capturing control information. | ||
| 96 | pub control_channel_selection: ControlChannelSelection, | ||
| 97 | } | ||
| 98 | |||
| 99 | /// S/PDIF errors. | ||
| 100 | #[derive(Debug)] | ||
| 101 | pub enum Error { | ||
| 102 | /// DMA overrun error. | ||
| 103 | RingbufferError(RingbufferError), | ||
| 104 | /// Left/right channel synchronization error. | ||
| 105 | ChannelSyncError, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl From<RingbufferError> for Error { | ||
| 109 | fn from(error: RingbufferError) -> Self { | ||
| 110 | Self::RingbufferError(error) | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | impl Default for Config { | ||
| 115 | fn default() -> Self { | ||
| 116 | Self { | ||
| 117 | control_channel_selection: ControlChannelSelection::A, | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | #[cfg(not(gpdma))] | ||
| 123 | impl<'d, T: Instance> Spdifrx<'d, T> { | ||
| 124 | fn dma_opts() -> TransferOptions { | ||
| 125 | TransferOptions { | ||
| 126 | half_transfer_ir: true, | ||
| 127 | // new_write() and new_read() always use circular mode | ||
| 128 | ..Default::default() | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Create a new `Spdifrx` instance. | ||
| 133 | pub fn new( | ||
| 134 | peri: impl Peripheral<P = T> + 'd, | ||
| 135 | _irq: impl interrupt::typelevel::Binding<T::GlobalInterrupt, GlobalInterruptHandler<T>> + 'd, | ||
| 136 | config: Config, | ||
| 137 | spdifrx_in: impl Peripheral<P = impl InPin<T>> + 'd, | ||
| 138 | data_dma: impl Peripheral<P = impl Channel + Dma<T>> + 'd, | ||
| 139 | data_dma_buf: &'d mut [u32], | ||
| 140 | ) -> Self { | ||
| 141 | let (spdifrx_in, input_sel) = new_spdifrx_pin!(spdifrx_in, AfType::input(Pull::None)); | ||
| 142 | Self::setup(config, input_sel); | ||
| 143 | |||
| 144 | into_ref!(peri, data_dma); | ||
| 145 | |||
| 146 | let regs = T::info().regs; | ||
| 147 | let dr_request = data_dma.request(); | ||
| 148 | let dr_ring_buffer = | ||
| 149 | unsafe { ReadableRingBuffer::new(data_dma, dr_request, dr_address(regs), data_dma_buf, Self::dma_opts()) }; | ||
| 150 | |||
| 151 | Self { | ||
| 152 | _peri: peri, | ||
| 153 | spdifrx_in, | ||
| 154 | data_ring_buffer: dr_ring_buffer, | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | fn setup(config: Config, input_sel: u8) { | ||
| 159 | T::info().rcc.enable_and_reset(); | ||
| 160 | T::GlobalInterrupt::unpend(); | ||
| 161 | unsafe { T::GlobalInterrupt::enable() }; | ||
| 162 | |||
| 163 | let regs = T::info().regs; | ||
| 164 | |||
| 165 | regs.imr().write(|imr| { | ||
| 166 | imr.set_ifeie(true); // Enables interrupts for TERR, SERR, FERR. | ||
| 167 | imr.set_syncdie(true); // Enables SYNCD interrupt. | ||
| 168 | }); | ||
| 169 | |||
| 170 | regs.cr().write(|cr| { | ||
| 171 | cr.set_spdifen(0x00); // Disable SPDIF receiver synchronization. | ||
| 172 | cr.set_rxdmaen(true); // Use RX DMA for data. Enabled on `read`. | ||
| 173 | cr.set_cbdmaen(false); // Do not capture channel info. | ||
| 174 | cr.set_rxsteo(true); // Operate in stereo mode. | ||
| 175 | cr.set_drfmt(0x01); // Data is left-aligned (MSB). | ||
| 176 | |||
| 177 | // Disable all status fields in the data register. | ||
| 178 | // Status can be obtained directly with the status register DMA. | ||
| 179 | cr.set_pmsk(false); // Write parity bit to the data register. FIXME: Add parity check. | ||
| 180 | cr.set_vmsk(false); // Write validity to the data register. | ||
| 181 | cr.set_cumsk(true); // Do not write C and U bits to the data register. | ||
| 182 | cr.set_ptmsk(false); // Write preamble bits to the data register. | ||
| 183 | |||
| 184 | cr.set_chsel(match config.control_channel_selection { | ||
| 185 | ControlChannelSelection::A => false, | ||
| 186 | ControlChannelSelection::B => true, | ||
| 187 | }); // Select channel status source. | ||
| 188 | |||
| 189 | cr.set_nbtr(0x02); // 16 attempts are allowed. | ||
| 190 | cr.set_wfa(true); // Wait for activity before going to synchronization phase. | ||
| 191 | cr.set_insel(input_sel); // Input pin selection. | ||
| 192 | |||
| 193 | #[cfg(stm32h7)] | ||
| 194 | cr.set_cksen(true); // Generate a symbol clock. | ||
| 195 | |||
| 196 | #[cfg(stm32h7)] | ||
| 197 | cr.set_cksbkpen(true); // Generate a backup symbol clock. | ||
| 198 | }); | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Start the SPDIFRX driver. | ||
| 202 | pub fn start(&mut self) { | ||
| 203 | self.data_ring_buffer.start(); | ||
| 204 | |||
| 205 | T::info().regs.cr().modify(|cr| { | ||
| 206 | cr.set_spdifen(0x03); // Enable S/PDIF receiver. | ||
| 207 | }); | ||
| 208 | } | ||
| 209 | |||
| 210 | /// Read from the SPDIFRX data ring buffer. | ||
| 211 | /// | ||
| 212 | /// SPDIFRX is always receiving data in the background. This function pops already-received | ||
| 213 | /// data from the buffer. | ||
| 214 | /// | ||
| 215 | /// If there's less than `data.len()` data in the buffer, this waits until there is. | ||
| 216 | pub async fn read(&mut self, data: &mut [u32]) -> Result<(), Error> { | ||
| 217 | self.data_ring_buffer.read_exact(data).await?; | ||
| 218 | |||
| 219 | let first_preamble = (data[0] >> 4) & 0b11_u32; | ||
| 220 | if (first_preamble as u8) == (PreambleType::W as u8) { | ||
| 221 | trace!("S/PDIF left/right mismatch"); | ||
| 222 | |||
| 223 | // Resynchronize until the first sample is for the left channel. | ||
| 224 | self.data_ring_buffer.clear(); | ||
| 225 | return Err(Error::ChannelSyncError); | ||
| 226 | }; | ||
| 227 | |||
| 228 | for sample in data.as_mut() { | ||
| 229 | if (*sample & (0x0002_u32)) == 0x0001 { | ||
| 230 | // Discard invalid samples, setting them to mute level. | ||
| 231 | *sample = 0; | ||
| 232 | } else { | ||
| 233 | // Discard status information in the lowest byte. | ||
| 234 | *sample &= 0xFFFFFF00; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | Ok(()) | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | #[cfg(not(gpdma))] | ||
| 243 | impl<'d, T: Instance> Drop for Spdifrx<'d, T> { | ||
| 244 | fn drop(&mut self) { | ||
| 245 | T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); | ||
| 246 | self.spdifrx_in.as_ref().map(|x| x.set_as_disconnected()); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | struct State { | ||
| 251 | #[allow(unused)] | ||
| 252 | waker: AtomicWaker, | ||
| 253 | } | ||
| 254 | |||
| 255 | impl State { | ||
| 256 | const fn new() -> Self { | ||
| 257 | Self { | ||
| 258 | waker: AtomicWaker::new(), | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | struct Info { | ||
| 264 | regs: crate::pac::spdifrx::Spdifrx, | ||
| 265 | rcc: RccInfo, | ||
| 266 | } | ||
| 267 | |||
| 268 | peri_trait!( | ||
| 269 | irqs: [GlobalInterrupt], | ||
| 270 | ); | ||
| 271 | |||
| 272 | /// SPIDFRX pin trait | ||
| 273 | pub trait InPin<T: Instance>: crate::gpio::Pin { | ||
| 274 | /// Get the GPIO AF number needed to use this pin. | ||
| 275 | fn af_num(&self) -> u8; | ||
| 276 | /// Get the SPIDFRX INSEL number needed to use this pin. | ||
| 277 | fn input_sel(&self) -> u8; | ||
| 278 | } | ||
| 279 | |||
| 280 | dma_trait!(Dma, Instance); | ||
| 281 | |||
| 282 | /// Global interrupt handler. | ||
| 283 | pub struct GlobalInterruptHandler<T: Instance> { | ||
| 284 | _phantom: PhantomData<T>, | ||
| 285 | } | ||
| 286 | |||
| 287 | impl<T: Instance> interrupt::typelevel::Handler<T::GlobalInterrupt> for GlobalInterruptHandler<T> { | ||
| 288 | unsafe fn on_interrupt() { | ||
| 289 | T::state().waker.wake(); | ||
| 290 | |||
| 291 | let regs = T::info().regs; | ||
| 292 | let sr = regs.sr().read(); | ||
| 293 | |||
| 294 | if sr.serr() || sr.terr() || sr.ferr() { | ||
| 295 | trace!("SPDIFRX error, resync"); | ||
| 296 | |||
| 297 | // Clear errors by disabling SPDIFRX, then reenable. | ||
| 298 | regs.cr().modify(|cr| cr.set_spdifen(0x00)); | ||
| 299 | regs.cr().modify(|cr| cr.set_spdifen(0x03)); | ||
| 300 | } else if sr.syncd() { | ||
| 301 | // Synchronization was successful. | ||
| 302 | trace!("SPDIFRX sync success"); | ||
| 303 | } | ||
| 304 | |||
| 305 | // Clear interrupt flags. | ||
| 306 | regs.ifcr().write(|ifcr| { | ||
| 307 | ifcr.set_perrcf(true); // Clears parity error flag. | ||
| 308 | ifcr.set_ovrcf(true); // Clears overrun error flag. | ||
| 309 | ifcr.set_sbdcf(true); // Clears synchronization block detected flag. | ||
| 310 | ifcr.set_syncdcf(true); // Clears SYNCD from SR (was read above). | ||
| 311 | }); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | foreach_peripheral!( | ||
| 316 | (spdifrx, $inst:ident) => { | ||
| 317 | #[allow(private_interfaces)] | ||
| 318 | impl SealedInstance for peripherals::$inst { | ||
| 319 | fn info() -> &'static Info { | ||
| 320 | static INFO: Info = Info{ | ||
| 321 | regs: crate::pac::$inst, | ||
| 322 | rcc: crate::peripherals::$inst::RCC_INFO, | ||
| 323 | }; | ||
| 324 | &INFO | ||
| 325 | } | ||
| 326 | fn state() -> &'static State { | ||
| 327 | static STATE: State = State::new(); | ||
| 328 | &STATE | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | impl Instance for peripherals::$inst { | ||
| 333 | type GlobalInterrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; | ||
| 334 | } | ||
| 335 | }; | ||
| 336 | ); | ||
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 @@ | |||
| 1 | [target.thumbv7em-none-eabihf] | ||
| 2 | runner = 'probe-rs run --chip STM32H723ZGTx' | ||
| 3 | |||
| 4 | [build] | ||
| 5 | target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) | ||
| 6 | |||
| 7 | [env] | ||
| 8 | 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 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-stm32h7-examples" | ||
| 4 | version = "0.1.0" | ||
| 5 | license = "MIT OR Apache-2.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | # Change stm32h723zg to your chip name, if necessary. | ||
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h723zg", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } | ||
| 10 | embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } | ||
| 11 | embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } | ||
| 12 | embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | ||
| 13 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | ||
| 14 | |||
| 15 | defmt = "0.3" | ||
| 16 | defmt-rtt = "0.4" | ||
| 17 | |||
| 18 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||
| 19 | cortex-m-rt = "0.7.0" | ||
| 20 | embedded-hal = "0.2.6" | ||
| 21 | embedded-hal-1 = { package = "embedded-hal", version = "1.0" } | ||
| 22 | embedded-hal-async = { version = "1.0" } | ||
| 23 | embedded-nal-async = "0.8.0" | ||
| 24 | embedded-io-async = { version = "0.6.1" } | ||
| 25 | panic-probe = { version = "0.3", features = ["print-defmt"] } | ||
| 26 | heapless = { version = "0.8", default-features = false } | ||
| 27 | rand_core = "0.6.3" | ||
| 28 | critical-section = "1.1" | ||
| 29 | static_cell = "2" | ||
| 30 | chrono = { version = "^0.4", default-features = false } | ||
| 31 | grounded = "0.2.0" | ||
| 32 | |||
| 33 | # cargo build/run | ||
| 34 | [profile.dev] | ||
| 35 | codegen-units = 1 | ||
| 36 | debug = 2 | ||
| 37 | debug-assertions = true # <- | ||
| 38 | incremental = false | ||
| 39 | opt-level = 3 # <- | ||
| 40 | overflow-checks = true # <- | ||
| 41 | |||
| 42 | # cargo test | ||
| 43 | [profile.test] | ||
| 44 | codegen-units = 1 | ||
| 45 | debug = 2 | ||
| 46 | debug-assertions = true # <- | ||
| 47 | incremental = false | ||
| 48 | opt-level = 3 # <- | ||
| 49 | overflow-checks = true # <- | ||
| 50 | |||
| 51 | # cargo build/run --release | ||
| 52 | [profile.release] | ||
| 53 | codegen-units = 1 | ||
| 54 | debug = 2 | ||
| 55 | debug-assertions = false # <- | ||
| 56 | incremental = false | ||
| 57 | lto = 'fat' | ||
| 58 | opt-level = 3 # <- | ||
| 59 | overflow-checks = false # <- | ||
| 60 | |||
| 61 | # cargo test --release | ||
| 62 | [profile.bench] | ||
| 63 | codegen-units = 1 | ||
| 64 | debug = 2 | ||
| 65 | debug-assertions = false # <- | ||
| 66 | incremental = false | ||
| 67 | lto = 'fat' | ||
| 68 | opt-level = 3 # <- | ||
| 69 | 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 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 35 | } | ||
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 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* This file is intended for parts in the STM32H723 family. (RM0468) */ | ||
| 4 | /* - FLASH and RAM are mandatory memory sections. */ | ||
| 5 | /* - The sum of all non-FLASH sections must add to 564k total device RAM. */ | ||
| 6 | /* - The FLASH section size must match your device, see table below. */ | ||
| 7 | |||
| 8 | /* FLASH */ | ||
| 9 | /* Select the appropriate FLASH size for your device. */ | ||
| 10 | /* - STM32H730xB 128K */ | ||
| 11 | /* - STM32H723xE/725xE 512K */ | ||
| 12 | /* - STM32H723xG/725xG/733xG/735xG 1M */ | ||
| 13 | FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M | ||
| 14 | |||
| 15 | /* Data TCM */ | ||
| 16 | /* - Two contiguous 64KB RAMs. */ | ||
| 17 | /* - Used for interrupt handlers, stacks and general RAM. */ | ||
| 18 | /* - Zero wait-states. */ | ||
| 19 | /* - The DTCM is taken as the origin of the base ram. (See below.) */ | ||
| 20 | /* This is also where the interrupt table and such will live, */ | ||
| 21 | /* which is required for deterministic performance. */ | ||
| 22 | DTCM : ORIGIN = 0x20000000, LENGTH = 128K | ||
| 23 | |||
| 24 | /* Instruction TCM */ | ||
| 25 | /* - More memory can be assigned to ITCM. See AXI SRAM notes, below. */ | ||
| 26 | /* - Used for latency-critical interrupt handlers etc. */ | ||
| 27 | /* - Zero wait-states. */ | ||
| 28 | ITCM : ORIGIN = 0x00000000, LENGTH = 64K + 0K | ||
| 29 | |||
| 30 | /* AXI SRAM */ | ||
| 31 | /* - AXISRAM is in D1 and accessible by all system masters except BDMA. */ | ||
| 32 | /* - Suitable for application data not stored in DTCM. */ | ||
| 33 | /* - Zero wait-states. */ | ||
| 34 | /* - The 192k of extra shared RAM is fully allotted to the AXI SRAM by default. */ | ||
| 35 | /* As a result: 64k (64k + 0k) for ITCM and 320k (128k + 192k) for AXI SRAM. */ | ||
| 36 | /* This can be re-configured via the TCM_AXI_SHARED[1,0] register when more */ | ||
| 37 | /* ITCM is required. */ | ||
| 38 | AXISRAM : ORIGIN = 0x24000000, LENGTH = 128K + 192K | ||
| 39 | |||
| 40 | /* AHB SRAM */ | ||
| 41 | /* - SRAM1-2 are in D2 and accessible by all system masters except BDMA, LTDC */ | ||
| 42 | /* and SDMMC1. Suitable for use as DMA buffers. */ | ||
| 43 | /* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */ | ||
| 44 | /* buffers, for storing application data in lower-power modes. */ | ||
| 45 | /* - Zero wait-states. */ | ||
| 46 | SRAM1 : ORIGIN = 0x30000000, LENGTH = 16K | ||
| 47 | SRAM2 : ORIGIN = 0x30040000, LENGTH = 16K | ||
| 48 | SRAM4 : ORIGIN = 0x38000000, LENGTH = 16K | ||
| 49 | |||
| 50 | /* Backup SRAM */ | ||
| 51 | /* Used to store data during low-power sleeps. */ | ||
| 52 | BSRAM : ORIGIN = 0x38800000, LENGTH = 4K | ||
| 53 | } | ||
| 54 | |||
| 55 | /* | ||
| 56 | /* Assign the memory regions defined above for use. */ | ||
| 57 | /* | ||
| 58 | |||
| 59 | /* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */ | ||
| 60 | REGION_ALIAS(FLASH, FLASH1); | ||
| 61 | REGION_ALIAS(RAM, DTCM); | ||
| 62 | |||
| 63 | /* The location of the stack can be overridden using the `_stack_start` symbol. */ | ||
| 64 | /* - Set the stack location at the end of RAM, using all remaining space. */ | ||
| 65 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); | ||
| 66 | |||
| 67 | /* The location of the .text section can be overridden using the */ | ||
| 68 | /* `_stext` symbol. By default it will place after .vector_table. */ | ||
| 69 | /* _stext = ORIGIN(FLASH) + 0x40c; */ | ||
| 70 | |||
| 71 | /* Define sections for placing symbols into the extra memory regions above. */ | ||
| 72 | /* This makes them accessible from code. */ | ||
| 73 | /* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */ | ||
| 74 | /* - All other memories connect to a 32-bit wide bus -> align to 4 bytes. */ | ||
| 75 | SECTIONS { | ||
| 76 | .itcm (NOLOAD) : ALIGN(8) { | ||
| 77 | *(.itcm .itcm.*); | ||
| 78 | . = ALIGN(8); | ||
| 79 | } > ITCM | ||
| 80 | |||
| 81 | .axisram (NOLOAD) : ALIGN(8) { | ||
| 82 | *(.axisram .axisram.*); | ||
| 83 | . = ALIGN(8); | ||
| 84 | } > AXISRAM | ||
| 85 | |||
| 86 | .sram1 (NOLOAD) : ALIGN(4) { | ||
| 87 | *(.sram1 .sram1.*); | ||
| 88 | . = ALIGN(4); | ||
| 89 | } > SRAM1 | ||
| 90 | |||
| 91 | .sram2 (NOLOAD) : ALIGN(4) { | ||
| 92 | *(.sram2 .sram2.*); | ||
| 93 | . = ALIGN(4); | ||
| 94 | } > SRAM2 | ||
| 95 | |||
| 96 | .sram4 (NOLOAD) : ALIGN(4) { | ||
| 97 | *(.sram4 .sram4.*); | ||
| 98 | . = ALIGN(4); | ||
| 99 | } > SRAM4 | ||
| 100 | |||
| 101 | .bsram (NOLOAD) : ALIGN(4) { | ||
| 102 | *(.bsram .bsram.*); | ||
| 103 | . = ALIGN(4); | ||
| 104 | } > BSRAM | ||
| 105 | |||
| 106 | }; | ||
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 @@ | |||
| 1 | //! This example receives inputs on SPDIFRX and outputs on SAI4. | ||
| 2 | //! | ||
| 3 | //! Only very few controllers connect the SPDIFRX symbol clock to a SAI peripheral's clock input. | ||
| 4 | //! However, this is necessary for synchronizing the symbol rates and avoiding glitches. | ||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | use defmt::{info, trace}; | ||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_futures::select::{self, select, Either}; | ||
| 11 | use embassy_stm32::spdifrx::{self, Spdifrx}; | ||
| 12 | use embassy_stm32::{bind_interrupts, peripherals, sai}; | ||
| 13 | use grounded::uninit::GroundedArrayCell; | ||
| 14 | use hal::sai::*; | ||
| 15 | use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; | ||
| 16 | |||
| 17 | bind_interrupts!(struct Irqs { | ||
| 18 | SPDIF_RX => spdifrx::GlobalInterruptHandler<peripherals::SPDIFRX1>; | ||
| 19 | }); | ||
| 20 | |||
| 21 | const CHANNEL_COUNT: usize = 2; | ||
| 22 | const BLOCK_LENGTH: usize = 64; | ||
| 23 | const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; | ||
| 24 | const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks | ||
| 25 | |||
| 26 | // DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions | ||
| 27 | #[link_section = ".sram1"] | ||
| 28 | static mut SPDIFRX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit(); | ||
| 29 | |||
| 30 | #[link_section = ".sram4"] | ||
| 31 | static mut SAI_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit(); | ||
| 32 | |||
| 33 | #[embassy_executor::main] | ||
| 34 | async fn main(_spawner: Spawner) { | ||
| 35 | let mut peripheral_config = embassy_stm32::Config::default(); | ||
| 36 | { | ||
| 37 | use embassy_stm32::rcc::*; | ||
| 38 | peripheral_config.rcc.hsi = Some(HSIPrescaler::DIV1); | ||
| 39 | peripheral_config.rcc.pll1 = Some(Pll { | ||
| 40 | source: PllSource::HSI, | ||
| 41 | prediv: PllPreDiv::DIV16, | ||
| 42 | mul: PllMul::MUL200, | ||
| 43 | divp: Some(PllDiv::DIV2), // 400 MHz | ||
| 44 | divq: Some(PllDiv::DIV2), | ||
| 45 | divr: Some(PllDiv::DIV2), | ||
| 46 | }); | ||
| 47 | peripheral_config.rcc.sys = Sysclk::PLL1_P; | ||
| 48 | peripheral_config.rcc.ahb_pre = AHBPrescaler::DIV2; | ||
| 49 | peripheral_config.rcc.apb1_pre = APBPrescaler::DIV2; | ||
| 50 | peripheral_config.rcc.apb2_pre = APBPrescaler::DIV2; | ||
| 51 | peripheral_config.rcc.apb3_pre = APBPrescaler::DIV2; | ||
| 52 | peripheral_config.rcc.apb4_pre = APBPrescaler::DIV2; | ||
| 53 | |||
| 54 | peripheral_config.rcc.mux.spdifrxsel = mux::Spdifrxsel::PLL1_Q; | ||
| 55 | } | ||
| 56 | let mut p = embassy_stm32::init(peripheral_config); | ||
| 57 | |||
| 58 | info!("SPDIFRX to SAI4 bridge"); | ||
| 59 | |||
| 60 | // Use SPDIFRX clock for SAI. | ||
| 61 | // This ensures equal rates of sample production and consumption. | ||
| 62 | let clk_source = embassy_stm32::pac::rcc::vals::Saiasel::_RESERVED_5; | ||
| 63 | embassy_stm32::pac::RCC.d3ccipr().modify(|w| { | ||
| 64 | w.set_sai4asel(clk_source); | ||
| 65 | }); | ||
| 66 | |||
| 67 | let sai_buffer: &mut [u32] = unsafe { | ||
| 68 | SAI_BUFFER.initialize_all_copied(0); | ||
| 69 | let (ptr, len) = SAI_BUFFER.get_ptr_len(); | ||
| 70 | core::slice::from_raw_parts_mut(ptr, len) | ||
| 71 | }; | ||
| 72 | |||
| 73 | let spdifrx_buffer: &mut [u32] = unsafe { | ||
| 74 | SPDIFRX_BUFFER.initialize_all_copied(0); | ||
| 75 | let (ptr, len) = SPDIFRX_BUFFER.get_ptr_len(); | ||
| 76 | core::slice::from_raw_parts_mut(ptr, len) | ||
| 77 | }; | ||
| 78 | |||
| 79 | let mut sai_transmitter = new_sai_transmitter( | ||
| 80 | &mut p.SAI4, | ||
| 81 | &mut p.PD13, | ||
| 82 | &mut p.PC1, | ||
| 83 | &mut p.PD12, | ||
| 84 | &mut p.BDMA_CH0, | ||
| 85 | sai_buffer, | ||
| 86 | ); | ||
| 87 | let mut spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); | ||
| 88 | spdif_receiver.start(); | ||
| 89 | |||
| 90 | let mut renew_sai = false; | ||
| 91 | loop { | ||
| 92 | let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; | ||
| 93 | |||
| 94 | if renew_sai { | ||
| 95 | renew_sai = false; | ||
| 96 | trace!("Renew SAI."); | ||
| 97 | drop(sai_transmitter); | ||
| 98 | sai_transmitter = new_sai_transmitter( | ||
| 99 | &mut p.SAI4, | ||
| 100 | &mut p.PD13, | ||
| 101 | &mut p.PC1, | ||
| 102 | &mut p.PD12, | ||
| 103 | &mut p.BDMA_CH0, | ||
| 104 | sai_buffer, | ||
| 105 | ); | ||
| 106 | } | ||
| 107 | |||
| 108 | match select(spdif_receiver.read(&mut buf), sai_transmitter.wait_write_error()).await { | ||
| 109 | Either::First(spdif_read_result) => match spdif_read_result { | ||
| 110 | Ok(_) => (), | ||
| 111 | Err(spdifrx::Error::RingbufferError(_)) => { | ||
| 112 | trace!("SPDIFRX ringbuffer error. Renew."); | ||
| 113 | drop(spdif_receiver); | ||
| 114 | spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); | ||
| 115 | spdif_receiver.start(); | ||
| 116 | continue; | ||
| 117 | } | ||
| 118 | Err(spdifrx::Error::ChannelSyncError) => { | ||
| 119 | trace!("SPDIFRX channel sync (left/right assignment) error."); | ||
| 120 | continue; | ||
| 121 | } | ||
| 122 | }, | ||
| 123 | Either::Second(_) => { | ||
| 124 | renew_sai = true; | ||
| 125 | continue; | ||
| 126 | } | ||
| 127 | }; | ||
| 128 | |||
| 129 | renew_sai = sai_transmitter.write(&buf).await.is_err(); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | /// Creates a new SPDIFRX instance for receiving sample data. | ||
| 134 | /// | ||
| 135 | /// Used (again) after dropping the SPDIFRX instance, in case of errors (e.g. source disconnect). | ||
| 136 | fn new_spdif_receiver<'d>( | ||
| 137 | spdifrx: &'d mut peripherals::SPDIFRX1, | ||
| 138 | input_pin: &'d mut peripherals::PD7, | ||
| 139 | dma: &'d mut peripherals::DMA2_CH7, | ||
| 140 | buf: &'d mut [u32], | ||
| 141 | ) -> Spdifrx<'d, peripherals::SPDIFRX1> { | ||
| 142 | Spdifrx::new(spdifrx, Irqs, spdifrx::Config::default(), input_pin, dma, buf) | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Creates a new SAI4 instance for transmitting sample data. | ||
| 146 | /// | ||
| 147 | /// Used (again) after dropping the SAI4 instance, in case of errors (e.g. buffer overrun). | ||
| 148 | fn new_sai_transmitter<'d>( | ||
| 149 | sai: &'d mut peripherals::SAI4, | ||
| 150 | sck: &'d mut peripherals::PD13, | ||
| 151 | sd: &'d mut peripherals::PC1, | ||
| 152 | fs: &'d mut peripherals::PD12, | ||
| 153 | dma: &'d mut peripherals::BDMA_CH0, | ||
| 154 | buf: &'d mut [u32], | ||
| 155 | ) -> Sai<'d, peripherals::SAI4, u32> { | ||
| 156 | let mut sai_config = hal::sai::Config::default(); | ||
| 157 | sai_config.slot_count = hal::sai::word::U4(CHANNEL_COUNT as u8); | ||
| 158 | sai_config.slot_enable = 0xFFFF; // All slots | ||
| 159 | sai_config.data_size = sai::DataSize::Data32; | ||
| 160 | sai_config.frame_length = (CHANNEL_COUNT * 32) as u8; | ||
| 161 | sai_config.master_clock_divider = hal::sai::MasterClockDivider::MasterClockDisabled; | ||
| 162 | |||
| 163 | let (sub_block_tx, _) = hal::sai::split_subblocks(sai); | ||
| 164 | Sai::new_asynchronous(sub_block_tx, sck, sd, fs, dma, buf, sai_config) | ||
| 165 | } | ||
