diff options
| author | elagil <[email protected]> | 2024-11-18 20:51:23 +0100 |
|---|---|---|
| committer | elagil <[email protected]> | 2024-11-18 20:51:23 +0100 |
| commit | 1ce1e193b71a5addd8c809722e6f4c6e0cb78284 (patch) | |
| tree | f3018798242eca3bb8509a77dc92cbf192c6dcd7 /examples | |
| parent | 62dbdcd45adfa7f3b74b377f0b4ef7eafaef78fd (diff) | |
feat: Add SPDIFRX example
Diffstat (limited to 'examples')
| -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 | 161 |
5 files changed, 379 insertions, 0 deletions
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..deee5ca8d --- /dev/null +++ b/examples/stm32h723/src/bin/spdifrx.rs | |||
| @@ -0,0 +1,161 @@ | |||
| 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_stm32::spdifrx::{self, Spdifrx}; | ||
| 11 | use embassy_stm32::{bind_interrupts, peripherals, sai}; | ||
| 12 | use grounded::uninit::GroundedArrayCell; | ||
| 13 | use hal::sai::*; | ||
| 14 | use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; | ||
| 15 | |||
| 16 | bind_interrupts!(struct Irqs { | ||
| 17 | SPDIF_RX => spdifrx::GlobalInterruptHandler<peripherals::SPDIFRX1>; | ||
| 18 | }); | ||
| 19 | |||
| 20 | const CHANNEL_COUNT: usize = 2; | ||
| 21 | const BLOCK_LENGTH: usize = 64; | ||
| 22 | const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; | ||
| 23 | const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks | ||
| 24 | |||
| 25 | // DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions | ||
| 26 | #[link_section = ".sram1"] | ||
| 27 | static mut SPDIFRX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit(); | ||
| 28 | |||
| 29 | #[link_section = ".sram4"] | ||
| 30 | static mut SAI_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit(); | ||
| 31 | |||
| 32 | #[embassy_executor::main] | ||
| 33 | async fn main(_spawner: Spawner) { | ||
| 34 | let mut peripheral_config = embassy_stm32::Config::default(); | ||
| 35 | { | ||
| 36 | use embassy_stm32::rcc::*; | ||
| 37 | peripheral_config.rcc.hsi = Some(HSIPrescaler::DIV1); | ||
| 38 | peripheral_config.rcc.pll1 = Some(Pll { | ||
| 39 | source: PllSource::HSI, | ||
| 40 | prediv: PllPreDiv::DIV16, | ||
| 41 | mul: PllMul::MUL200, | ||
| 42 | divp: Some(PllDiv::DIV2), // 400 MHz | ||
| 43 | divq: Some(PllDiv::DIV2), | ||
| 44 | divr: Some(PllDiv::DIV2), | ||
| 45 | }); | ||
| 46 | peripheral_config.rcc.sys = Sysclk::PLL1_P; | ||
| 47 | peripheral_config.rcc.ahb_pre = AHBPrescaler::DIV2; | ||
| 48 | peripheral_config.rcc.apb1_pre = APBPrescaler::DIV2; | ||
| 49 | peripheral_config.rcc.apb2_pre = APBPrescaler::DIV2; | ||
| 50 | peripheral_config.rcc.apb3_pre = APBPrescaler::DIV2; | ||
| 51 | peripheral_config.rcc.apb4_pre = APBPrescaler::DIV2; | ||
| 52 | |||
| 53 | peripheral_config.rcc.mux.spdifrxsel = mux::Spdifrxsel::PLL1_Q; | ||
| 54 | } | ||
| 55 | let mut p = embassy_stm32::init(peripheral_config); | ||
| 56 | |||
| 57 | info!("SPDIFRX to SAI4 bridge"); | ||
| 58 | |||
| 59 | // Use SPDIFRX clock for SAI. | ||
| 60 | // This ensures equal rates of sample production and consumption. | ||
| 61 | let clk_source = embassy_stm32::pac::rcc::vals::Saiasel::_RESERVED_5; | ||
| 62 | embassy_stm32::pac::RCC.d3ccipr().modify(|w| { | ||
| 63 | w.set_sai4asel(clk_source); | ||
| 64 | }); | ||
| 65 | |||
| 66 | let sai_buffer: &mut [u32] = unsafe { | ||
| 67 | SAI_BUFFER.initialize_all_copied(0); | ||
| 68 | let (ptr, len) = SAI_BUFFER.get_ptr_len(); | ||
| 69 | core::slice::from_raw_parts_mut(ptr, len) | ||
| 70 | }; | ||
| 71 | |||
| 72 | let spdifrx_buffer: &mut [u32] = unsafe { | ||
| 73 | SPDIFRX_BUFFER.initialize_all_copied(0); | ||
| 74 | let (ptr, len) = SPDIFRX_BUFFER.get_ptr_len(); | ||
| 75 | core::slice::from_raw_parts_mut(ptr, len) | ||
| 76 | }; | ||
| 77 | |||
| 78 | let mut spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); | ||
| 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 | |||
| 88 | spdif_receiver.start(); | ||
| 89 | sai_transmitter.start(); | ||
| 90 | |||
| 91 | loop { | ||
| 92 | let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; | ||
| 93 | |||
| 94 | match spdif_receiver.read_data(&mut buf).await { | ||
| 95 | Ok(_) => (), | ||
| 96 | Err(spdifrx::Error::RingbufferError(_)) => { | ||
| 97 | trace!("SPDIFRX ringbuffer error. Renew."); | ||
| 98 | drop(spdif_receiver); | ||
| 99 | spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); | ||
| 100 | spdif_receiver.start(); | ||
| 101 | continue; | ||
| 102 | } | ||
| 103 | Err(spdifrx::Error::ChannelSyncError) => { | ||
| 104 | trace!("SPDIFRX channel sync (left/right assignment) error."); | ||
| 105 | continue; | ||
| 106 | } | ||
| 107 | Err(spdifrx::Error::SourceSyncError) => { | ||
| 108 | trace!("SPDIFRX source sync error, e.g. disconnect."); | ||
| 109 | continue; | ||
| 110 | } | ||
| 111 | }; | ||
| 112 | |||
| 113 | if sai_transmitter.write(&buf).await.is_err() { | ||
| 114 | trace!("Renew SAI."); | ||
| 115 | drop(sai_transmitter); | ||
| 116 | sai_transmitter = new_sai_transmitter( | ||
| 117 | &mut p.SAI4, | ||
| 118 | &mut p.PD13, | ||
| 119 | &mut p.PC1, | ||
| 120 | &mut p.PD12, | ||
| 121 | &mut p.BDMA_CH0, | ||
| 122 | sai_buffer, | ||
| 123 | ); | ||
| 124 | sai_transmitter.start(); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | /// Creates a new SPDIFRX instance for receiving sample data. | ||
| 130 | /// | ||
| 131 | /// Used (again) after dropping the SPDIFRX instance, in case of errors (e.g. source disconnect). | ||
| 132 | fn new_spdif_receiver<'d>( | ||
| 133 | spdifrx: &'d mut peripherals::SPDIFRX1, | ||
| 134 | input_pin: &'d mut peripherals::PD7, | ||
| 135 | dma: &'d mut peripherals::DMA2_CH7, | ||
| 136 | buf: &'d mut [u32], | ||
| 137 | ) -> Spdifrx<'d, peripherals::SPDIFRX1> { | ||
| 138 | Spdifrx::new_data_only(spdifrx, Irqs, spdifrx::Config::default(), input_pin, dma, buf) | ||
| 139 | } | ||
| 140 | |||
| 141 | /// Creates a new SAI4 instance for transmitting sample data. | ||
| 142 | /// | ||
| 143 | /// Used (again) after dropping the SAI4 instance, in case of errors (e.g. buffer overrun). | ||
| 144 | fn new_sai_transmitter<'d>( | ||
| 145 | sai: &'d mut peripherals::SAI4, | ||
| 146 | sck: &'d mut peripherals::PD13, | ||
| 147 | sd: &'d mut peripherals::PC1, | ||
| 148 | fs: &'d mut peripherals::PD12, | ||
| 149 | dma: &'d mut peripherals::BDMA_CH0, | ||
| 150 | buf: &'d mut [u32], | ||
| 151 | ) -> Sai<'d, peripherals::SAI4, u32> { | ||
| 152 | let mut sai_config = hal::sai::Config::default(); | ||
| 153 | sai_config.slot_count = hal::sai::word::U4(CHANNEL_COUNT as u8); | ||
| 154 | sai_config.slot_enable = 0xFFFF; // All slots | ||
| 155 | sai_config.data_size = sai::DataSize::Data32; | ||
| 156 | sai_config.frame_length = (CHANNEL_COUNT * 32) as u8; | ||
| 157 | sai_config.master_clock_divider = hal::sai::MasterClockDivider::MasterClockDisabled; | ||
| 158 | |||
| 159 | let (sub_block_tx, _) = hal::sai::split_subblocks(sai); | ||
| 160 | Sai::new_asynchronous(sub_block_tx, sck, sd, fs, dma, buf, sai_config) | ||
| 161 | } | ||
