diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.github/workflows/build_examples.yml b/.github/workflows/build_examples.yml new file mode 100644 index 0000000..65ea546 --- /dev/null +++ b/.github/workflows/build_examples.yml @@ -0,0 +1,48 @@ +name: Build LibDaisy Examples + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install target + run: rustup target add thumbv7em-none-eabihf + + - name: Cache cargo registry + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Build examples + run: | + excluded_examples="sdmmc usb_midi" # List of examples to exclude + for example in $(ls examples/*.rs); do + example_name=$(basename $example .rs) + if [[ ! " $excluded_examples " =~ " $example_name " ]]; then + cargo build --example $example_name + else + echo "Skipping $example_name" + fi + done \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bafd44..0918412 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,4 +33,4 @@ jobs: with: use-cross: true command: build - args: --verbose --release --examples --target thumbv7em-none-eabihf --features ${{ matrix.logger }} + args: --verbose --release --target thumbv7em-none-eabihf --features ${{ matrix.logger }} diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 73f596c..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,22 +0,0 @@ -on: - push: - branches: [staging, trying, main] - pull_request: - -name: Clippy - -jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - components: clippy - toolchain: stable - target: thumbv7em-none-eabihf - override: true - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --examples --target thumbv7em-none-eabihf diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1d4a4c6..ce7c7c3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -20,4 +20,4 @@ jobs: with: use-cross: true command: build - args: --verbose --release --examples --target thumbv7em-none-eabihf + args: --verbose --release --target thumbv7em-none-eabihf diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 990acdd..0000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -group_imports = "StdExternalCrate" -imports_granularity = "Crate" -reorder_impl_items = true -trailing_semicolon = false -wrap_comments = true diff --git a/Cargo.toml b/Cargo.toml index 59a030c..be2a12d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ description = "Hardware Abstraction Layer implementation for Daisy boards" keywords = ["cortex-m", "stm32h7xx", "stm32h750", "hal", "daisy"] readme = "README.md" name = "libdaisy" -version = "0.1.0" +version = "0.2.0" license = "MIT" repository = "https://github.com/mtthw-meyer/libdaisy-rust.git" documentation = "https://docs.rs/libdaisy" @@ -14,28 +14,45 @@ exclude = [".gitignore"] [dependencies] cfg-if = "1" cortex-m = "0.7" -cortex-m-log = { version = "0.8", features = ["itm", "semihosting", "log-integration"], optional = true } -cortex-m-rtic = "1" -cortex-m-semihosting = { version = "0.5", optional = true } +cortex-m-log = { version = "0.8", features = [ + "itm", + "semihosting", + "log-integration", +], optional = true } +cortex-m-semihosting = { version = "0.5", optional = true } debouncr = "0.2.2" -lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } +lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } log = "0.4" micromath = "2" panic-halt = "0.2.0" -panic-itm = { version = "0.4", optional = true } -panic-rtt-target = { version = "0.1.2", features = ["cortex-m"], optional = true } -panic-semihosting = { version = "0.6", optional = true } -rtt-target = { version = "0.4.0", optional = true } +panic-itm = { version = "0.4", optional = true } +panic-rtt-target = { version = "0.1.3", optional = true } +panic-semihosting = { version = "0.6", optional = true } +rtt-target = { version = "0.5.0", optional = true } stm32-fmc = "0.3.0" -stm32h7xx-hal = { version = "0.14.0", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } +stm32h7xx-hal = { version = "0.16.0", features = [ + "stm32h750v", + "rt", + "fmc", + "xspi", + "sdmmc", + "sdmmc-fatfs", + "usb_hs", +] } +embedded-hal = "0.2" [features] default = [] -log-itm = ["panic-itm", "lazy_static", "cortex-m-log"] +log-itm = ["dep:panic-itm", "dep:lazy_static", "dep:cortex-m-log"] log-none = [] -log-rtt = ["rtt-target", "panic-rtt-target"] -log-semihosting = ["panic-semihosting", "lazy_static", "cortex-m-log", "cortex-m-semihosting"] +log-rtt = ["dep:rtt-target", "dep:panic-rtt-target"] +log-semihosting = [ + "dep:lazy_static", + "dep:cortex-m-log", + "dep:cortex-m-semihosting", +] +panic-semihosting = ["dep:panic-semihosting"] # this lets you use `cargo fix`! #[[bin]] @@ -44,18 +61,19 @@ log-semihosting = ["panic-semihosting", "lazy_static", "cortex-m-log", "cortex-m #bench = false [profile.dev] -codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size in flash incremental = false -opt-level = "s" # optimize for binary size +opt-level = "s" # optimize for binary size [profile.release] codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash -lto = true # better optimizations -opt-level = "s" # optimize for binary size +debug = true # symbols are nice and they don't increase the size in flash +lto = true # better optimizations +opt-level = "s" # optimize for binary size -[dev_dependencies] +[dev-dependencies] +cortex-m-rtic = "1" embedded-sdmmc = "0.4" libm = "0.2" num_enum = { version = "0.5", default-features = false } diff --git a/README.md b/README.md index e98d898..fb45505 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ One of: ```bash cargo embed --features log-rtt --example passthru ``` +**note** You will need to specify the board IE `--chip stm32h750v` for the daisy seed, or create an `Embed.toml` that specifies the chip. ## Build Examples diff --git a/examples/blinky.rs b/examples/blinky.rs index e180f79..01c866b 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -8,8 +8,7 @@ )] mod app { // Includes a panic handler and optional logging facilities - use libdaisy::logger; - use libdaisy::{gpio, system}; + use libdaisy::{gpio, logger, system}; use log::info; use stm32h7xx_hal::{stm32, time::MilliSeconds, timer::Timer}; @@ -25,18 +24,26 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); ( Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/button.rs b/examples/button.rs new file mode 100644 index 0000000..128376b --- /dev/null +++ b/examples/button.rs @@ -0,0 +1,56 @@ +#![deny(warnings)] +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true)] +mod app { + use libdaisy::{ + gpio, + hal::gpio::{Edge, ExtiPin}, + logger, system, + }; + use log::info; + + #[shared] + struct Shared {} + + #[local] + struct Local { + button: gpio::SeedButton, + led: gpio::SeedLed, + } + + #[init] + fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { + logger::init(); + let mut core = ctx.core; + let mut device = ctx.device; + let ccdr = system::SeedSeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_seed_system_init!(core, device, ccdr); + info!("Startup done!"); + + // Button + system.gpio.button.make_interrupt_source(&mut device.SYSCFG); + system + .gpio + .button + .trigger_on_edge(&mut device.EXTI, Edge::Rising); + system.gpio.button.enable_interrupt(&mut device.EXTI); + + ( + Shared {}, + Local { + button: system.gpio.button, + led: system.gpio.led, + }, + init::Monotonics(), + ) + } + + #[task(binds = EXTI3, local = [button, led])] + fn button_click(ctx: button_click::Context) { + ctx.local.button.clear_interrupt_pending_bit(); + ctx.local.led.toggle(); + } +} diff --git a/examples/delay.rs b/examples/delay.rs index 151c4db..bb75a70 100644 --- a/examples/delay.rs +++ b/examples/delay.rs @@ -7,7 +7,10 @@ peripherals = true, )] mod app { - use libdaisy::{audio, logger, system}; + use libdaisy::{ + audio::{self, AudioBuffer}, + logger, system, + }; use log::info; #[shared] @@ -16,14 +19,18 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, + buffer: AudioBuffer, sdram: &'static mut [f32], } #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let system = system::System::init(ctx.core, ctx.device); + + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; info!("Startup done!"); diff --git a/examples/flash.rs b/examples/flash.rs index 228f6f9..feaef54 100644 --- a/examples/flash.rs +++ b/examples/flash.rs @@ -26,12 +26,21 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); + + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); let mut flash = system.flash; @@ -79,7 +88,7 @@ mod app { Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/hid_blinky.rs b/examples/hid_blinky.rs index 0373248..8fc2fd3 100644 --- a/examples/hid_blinky.rs +++ b/examples/hid_blinky.rs @@ -27,11 +27,22 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); + info!("Startup done!"); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(1).into_rate()); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); + + timer2.set_freq(MilliSeconds::from_ticks(1).into_rate()); let mut led1 = hid::Led::new(system.gpio.led, false, 1000); led1.set_brightness(0.5); @@ -55,7 +66,7 @@ mod app { led1, adc1, control1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/knob.rs b/examples/knob.rs index 7129f01..97f6186 100644 --- a/examples/knob.rs +++ b/examples/knob.rs @@ -11,6 +11,7 @@ mod app { use libdaisy::logger; use libdaisy::{gpio::*, hid, prelude::*, system}; use log::info; + use stm32h7xx_hal::time::MilliSeconds; use stm32h7xx_hal::{adc, stm32, timer::Timer}; #[shared] @@ -27,12 +28,25 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); + info!("Startup done!"); + + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); let duty_cycle = 50; let resolution = 20; - system.timer2.set_freq((duty_cycle * resolution).Hz()); + timer2.set_freq((duty_cycle * resolution).Hz()); let daisy28 = system .gpio @@ -62,7 +76,7 @@ mod app { led1, adc1, control1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/passthru.rs b/examples/passthru.rs index a5cec48..01b98e8 100644 --- a/examples/passthru.rs +++ b/examples/passthru.rs @@ -6,7 +6,9 @@ peripherals = true, )] mod app { - use libdaisy::{audio, logger, system}; + const BLOCK_SIZE: usize = 128; + use libdaisy::logger; + use libdaisy::{audio, system}; use log::info; #[shared] @@ -15,22 +17,26 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, } #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let system = system::System::init(ctx.core, ctx.device); - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; - info!("Startup done!"); + // Latest changes here. This approach allows you to + // access peripherals and resources that were simply + // moved out of the function in the previous implementation. + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_seed_system_init!(core, device, ccdr, BLOCK_SIZE); + + info!("Startup done!!"); ( Shared {}, Local { audio: system.audio, - buffer, }, init::Monotonics(), ) @@ -46,17 +52,10 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [audio, buffer], priority = 8)] + #[task(binds = DMA1_STR1, local = [audio], priority = 8)] fn audio_handler(ctx: audio_handler::Context) { let audio = ctx.local.audio; - let buffer = ctx.local.buffer; - - if audio.get_stereo(buffer) { - for (left, right) in buffer { - audio.push_stereo((*left, *right)).unwrap(); - } - } else { - info!("Error reading data!"); - } + + audio.for_each(|left, right| (left, right)); } } diff --git a/examples/sdram.rs b/examples/sdram.rs index 1f46c7e..4bf3fd6 100644 --- a/examples/sdram.rs +++ b/examples/sdram.rs @@ -26,11 +26,20 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); let sdram = system.sdram; @@ -72,7 +81,7 @@ mod app { Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/sine.rs b/examples/sine.rs index e3478a8..13d5871 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -7,14 +7,17 @@ peripherals = true )] mod app { - use libdaisy::{audio, logger, system}; + use libdaisy::{ + audio::{self, AudioBuffer}, + gpio::SeedLed, + logger, system, + }; use libm; use log::info; - use stm32h7xx_hal::time::MilliSeconds; pub struct AudioRate { pub audio: audio::Audio, - pub buffer: audio::AudioBuffer, + buffer: AudioBuffer, } #[shared] @@ -25,19 +28,18 @@ mod app { ar: AudioRate, phase: f32, pitch: f32, + led: SeedLed, } #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); - - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); - - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; // audio ring buffer + let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; ( Shared {}, @@ -48,6 +50,7 @@ mod app { }, phase: 0.0, pitch: 440.0, + led: system.gpio.led, }, init::Monotonics(), ) @@ -61,12 +64,13 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [ar, phase, pitch], shared = [], priority = 8)] + #[task(binds = DMA1_STR1, local = [ar, phase, pitch, led], shared = [], priority = 8)] fn audio_handler(ctx: audio_handler::Context) { let audio = &mut ctx.local.ar.audio; let buffer = &mut ctx.local.ar.buffer; let phase = ctx.local.phase; let pitch = ctx.local.pitch; + let led = ctx.local.led; audio.get_stereo(buffer); for _ in 0..buffer.len() { @@ -78,6 +82,7 @@ mod app { if *pitch > 10_000.0 { *pitch = 440.0; + led.toggle(); } *pitch += 0.1; diff --git a/examples/switch.rs b/examples/switch.rs index 771cdf3..591784d 100644 --- a/examples/switch.rs +++ b/examples/switch.rs @@ -26,7 +26,17 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); let daisy28 = system .gpio @@ -35,9 +45,7 @@ mod app { .expect("Failed to get pin daisy28!") .into_pull_up_input(); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(1).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(1).into_rate()); // Switch rate is determined by timer freq let mut switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); @@ -49,7 +57,7 @@ mod app { Local { seed_led: system.gpio.led, switch1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/toggle.rs b/examples/toggle.rs index 327e9fa..90e2aa7 100644 --- a/examples/toggle.rs +++ b/examples/toggle.rs @@ -10,6 +10,7 @@ mod app { use libdaisy::logger; use libdaisy::{gpio::*, hid, prelude::*, system}; use log::info; + use stm32h7xx_hal::time::MilliSeconds; use stm32h7xx_hal::{stm32, time::Hertz, timer::Timer}; #[shared] @@ -25,7 +26,17 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); let daisy28 = system .gpio @@ -34,7 +45,7 @@ mod app { .expect("Failed to get pin daisy28!") .into_pull_up_input(); - system.timer2.set_freq(Hertz::from_raw(100)); + timer2.set_freq(Hertz::from_raw(100)); let switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); @@ -43,7 +54,7 @@ mod app { Local { seed_led: system.gpio.led, switch1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/usb_midi.rs b/examples/usb_midi.rs index 7fd65f6..ea27997 100644 --- a/examples/usb_midi.rs +++ b/examples/usb_midi.rs @@ -56,14 +56,6 @@ mod app { let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run"); ccdr.peripheral.kernel_usb_clk_mux(UsbClkSel::Hsi48); - /* - unsafe { - let pwr = &*stm32::PWR::ptr(); - pwr.cr3.modify(|_, w| w.usbregen().set_bit()); - while pwr.cr3.read().usb33rdy().bit_is_clear() {} - } - */ - let mut timer2 = device.TIM2.timer( MilliSeconds::from_ticks(200).into_rate(), ccdr.peripheral.TIM2, @@ -79,6 +71,7 @@ mod app { let gpio = gpio::GPIO::init( gpioc.pc7, + gpiog.pg3, Some(gpiob.pb12), Some(gpioc.pc11), Some(gpioc.pc10), @@ -110,6 +103,8 @@ mod app { Some(gpioa.pa2), Some(gpiob.pb14), Some(gpiob.pb15), + None, + None, ); let (pin_dm, pin_dp) = { (gpioa.pa11.into_alternate(), gpioa.pa12.into_alternate()) }; @@ -195,7 +190,7 @@ mod app { let led = &mut local.seed_led; if !usb_dev.poll(&mut [midi]) { - return + return; } let mut buffer = [0; 64]; diff --git a/examples/volume.rs b/examples/volume.rs index f1edccc..57f2bab 100644 --- a/examples/volume.rs +++ b/examples/volume.rs @@ -10,9 +10,10 @@ mod app { //use rtic::cyccnt::U32Ext; + use audio::AudioBuffer; use libdaisy::{audio, gpio::*, hid, logger, prelude::*, system, MILICYCLES}; use log::info; - use stm32h7xx_hal::{adc, stm32, timer::Timer}; + use stm32h7xx_hal::{adc, stm32, time::MilliSeconds, timer::Timer}; #[shared] struct Shared { @@ -22,7 +23,6 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, adc1: adc::Adc, timer2: Timer, } @@ -30,8 +30,10 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Enable adc1"); let mut adc1 = system.adc1.enable(); @@ -49,13 +51,19 @@ mod app { // Transform linear input into logarithmic control1.set_transform(|x| x * x); + let timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + ( Shared { control1 }, Local { audio: system.audio, - buffer, adc1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) @@ -71,21 +79,16 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [audio, buffer], shared = [control1], priority = 8)] + #[task(binds = DMA1_STR1, local = [audio], shared = [control1], priority = 8)] fn audio_handler(mut ctx: audio_handler::Context) { - let audio_handler::LocalResources { audio, buffer } = ctx.local; - - if audio.get_stereo(buffer) { - for (left, right) in buffer { - ctx.shared.control1.lock(|c| { - let volume = c.get_value(); - info!("{}", volume); - *left *= volume; - *right *= volume; - audio.push_stereo((*left, *right)).unwrap(); - }); - } - } + let audio = ctx.local.audio; + + ctx.shared.control1.lock(|c| { + let volume = c.get_value(); + info!("{}", volume); + + audio.for_each(|left, right| (left * volume, right * volume)); + }); } #[task(binds = TIM2, local = [timer2, adc1], shared = [control1])] diff --git a/memory.x b/memory.x index ed7ccb1..cb330d6 100644 --- a/memory.x +++ b/memory.x @@ -7,6 +7,8 @@ MEMORY SRAM (RWX) : ORIGIN = 0x24000000, LENGTH = 512K RAM_D2 (RWX) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (RWX) : ORIGIN = 0x38000000, LENGTH = 64K + BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 32 + BACKUP_SRAM2 (RWX) : ORIGIN = 0x38800020, LENGTH = 2016 ITCMRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 64K SDRAM (RWX) : ORIGIN = 0xc0000000, LENGTH = 64M QSPIFLASH (RX): ORIGIN = 0x90000000, LENGTH = 8M @@ -46,5 +48,19 @@ SECTIONS PROVIDE(__sdram_end = _esdram); } > SDRAM + .boot_info (NOLOAD) : + { + . = ALIGN(4); + *(.boot_info) + *(.boot_info*) + . = ALIGN(4); + } > BACKUP_SRAM + .crash_info (NOLOAD) : + { + . = ALIGN(4); + *(.crash_info) + *(.crash_info*) + . = ALIGN(4); + } > BACKUP_SRAM2 } diff --git a/src/audio.rs b/src/audio.rs index 9c22dbe..c77f725 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,12 +1,23 @@ //! Audio module. Handles audio startup and I/O. //! As well as converting between the S24 input and f32 for processing. -use cortex_m::{asm, prelude::_embedded_hal_blocking_i2c_Write}; -use log::info; + +// This is a temporary measure for the TX and RX BUFFER +// items. More properly, they should be `UnsafeSyncCell<[0; DMA_BUFFER_SIZE]>`, +// but this type is unstable. +#![allow(static_mut_refs)] + +use core::convert::Infallible; +use cortex_m::prelude::_embedded_hal_blocking_i2c_Write; +use embedded_hal::blocking::i2c::WriteRead; +use log::{error, info}; use stm32h7xx_hal::{ dma, gpio::{gpiob, gpioe, gpioh, Analog}, + hal::blocking::delay::DelayMs, i2c::*, - pac, rcc, + pac, + prelude::*, + rcc, rcc::rec, sai, sai::*, @@ -44,7 +55,7 @@ type DmaInputStream = dma::Transfer< dma::dma::Stream1, sai::dma::ChannelB, dma::MemoryToPeripheral, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], dma::DBTransfer, >; @@ -52,14 +63,41 @@ type DmaOutputStream = dma::Transfer< dma::dma::Stream0, sai::dma::ChannelA, dma::PeripheralToMemory, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], + dma::DBTransfer, +>; + +type DmaInputStreamS2dfm = dma::Transfer< + dma::dma::Stream1, + sai::dma::ChannelA, + dma::MemoryToPeripheral, + &'static mut [u32], + dma::DBTransfer, +>; + +type DmaOutputStreamS2dfm = dma::Transfer< + dma::dma::Stream0, + sai::dma::ChannelB, + dma::PeripheralToMemory, + &'static mut [u32], dma::DBTransfer, >; +pub enum AudioStream { + Normal { + input: DmaInputStream, + output: DmaOutputStream, + }, + S2dfm { + input: DmaInputStreamS2dfm, + output: DmaOutputStreamS2dfm, + }, +} + type StereoIteratorHandle = fn(StereoIterator, &mut Output); #[derive(Debug, Copy, Clone, PartialEq)] -struct S24(i32); +pub struct S24(i32); impl From for S24 { fn from(x: i32) -> S24 { @@ -87,20 +125,13 @@ impl From for u32 { impl From for S24 { fn from(x: f32) -> S24 { - let x = if x <= FBIPMIN { - FBIPMIN - } else if x >= FBIPMAX { - FBIPMAX - } else { - x - }; - S24((x * F32_TO_S24_SCALE) as i32) + S24((x.clamp(FBIPMIN, FBIPMAX) * F32_TO_S24_SCALE) as i32) } } impl From for f32 { fn from(x: S24) -> f32 { - ((x.0 ^ S24_SIGN) - S24_SIGN) as f32 * S24_TO_F32_SCALE + ((x.0 << 8) >> 8) as f32 * S24_TO_F32_SCALE } } @@ -109,14 +140,14 @@ pub struct Audio { sai: sai::Sai, input: Input, output: Output, - input_stream: DmaInputStream, - output_stream: DmaOutputStream, + pub audio_stream: AudioStream, + max_transfer_size: usize, } impl Audio { - /// Setup audio handler + /// Setup audio handler for the Seed. #[allow(clippy::too_many_arguments)] - pub fn new( + pub fn seed( dma1_stream0: dma::dma::StreamX, dma1_stream1: dma::dma::StreamX, sai1_d: stm32::SAI1, @@ -136,14 +167,295 @@ impl Audio { i2c_sda: gpiob::PB11, clocks: &rcc::CoreClocks, + board_version: crate::system::seed::Version, + delay: &mut impl DelayMs, + + block_size: usize, + sample_rate: Hertz, + ) -> Self { + match board_version { + crate::system::seed::Version::Seed1_1 => { + let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) + .fifo_enable(false); + let mut output_stream = dma::Transfer::init( + dma1_stream0, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, + rx_buffer, + None, + dma_config, + ); + + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut input_stream = dma::Transfer::init( + dma1_stream1, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, + tx_buffer, + None, + dma_config, + ); + + info!("Set up SAI..."); + let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); + let master_config = I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(true); + let slave_config = I2SChanConfig::new(I2SDir::Tx) + .set_sync_type(I2SSync::Internal) + .set_frame_sync_active_high(true); + + let pins_a = ( + sai_mclk_a.into_alternate(), + sai_sck_a.into_alternate(), + sai_fs_a.into_alternate(), + sai_sd_a.into_alternate(), + Some(sai_sd_b.into_alternate()), + ); + + // Hand off to audio module + let mut sai = sai1_d.i2s_ch_a( + pins_a, + sample_rate, + I2SDataSize::BITS_24, + sai1_rec, + clocks, + I2sUsers::new(master_config).add_slave(slave_config), + ); + + info!("Setting up WM8731 Audio Codec..."); + let i2c2_pins = ( + i2c_scl.into_alternate_open_drain(), + i2c_sda.into_alternate_open_drain(), + ); + + let mut i2c = crate::i2c::I2cTimeout::new(i2c2_d.i2c( + i2c2_pins, + Hertz::from_raw(100_000), + i2c2_p, + clocks, + )); + + let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high + + // Go through configuration setup + for (register, value) in REGISTER_CONFIG { + let register: u8 = (*register).into(); + let value: u8 = *value; + let byte1: u8 = + ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); + let byte2: u8 = value; + let bytes = [byte1, byte2]; + + i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); + + delay.delay_ms(1); + } + + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelA); + }); + sai.enable(); + output_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); + }); + + let max_transfer_size = block_size * 2; + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, + ); + + info!( + "Setup up Audio DMA: input: {:?}, output: {:?}", + &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 + ); + + Audio { + sai, + audio_stream: AudioStream::Normal { + input: input_stream, + output: output_stream, + }, + input, + output, + max_transfer_size, + } + } + crate::system::seed::Version::Seed2DFM + | crate::system::seed::Version::Seed + | crate::system::seed::Version::Seed3 => { + let dma_buffer_size = block_size * 2 * 2; + + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) + .fifo_enable(false); + let mut output_stream = dma::Transfer::init( + dma1_stream0, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, + rx_buffer, + None, + dma_config, + ); + + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut input_stream = dma::Transfer::init( + dma1_stream1, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, + tx_buffer, + None, + dma_config, + ); + + info!("Setup up SAI..."); + let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); + let master_config = I2SChanConfig::new(I2SDir::Tx).set_frame_sync_active_high(true); + let slave_config = I2SChanConfig::new(I2SDir::Rx) + .set_sync_type(I2SSync::Internal) + .set_frame_sync_active_high(true); + + let pins_a = ( + sai_mclk_a.into_alternate(), + sai_sck_a.into_alternate(), + sai_fs_a.into_alternate(), + sai_sd_a.into_alternate(), + Some(sai_sd_b.into_alternate()), + ); + + // Hand off to audio module + let mut sai = sai1_d.i2s_ch_a( + pins_a, + sample_rate, + I2SDataSize::BITS_24, + sai1_rec, + clocks, + I2sUsers::new(master_config).add_slave(slave_config), + ); + + match board_version { + crate::system::seed::Version::Seed => { + info!("Setting up AK4556/PCM3060 Audio CODEC..."); + let mut ak_reset = i2c_sda + .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); + delay.delay_ms(1); + ak_reset.set_low(); + delay.delay_ms(1); + ak_reset.set_high(); + } + crate::system::seed::Version::Seed2DFM + | crate::system::seed::Version::Seed3 => { + // Set deemphasis low + info!("Setting up PCM3060 Audio CODEC..."); + i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); + } + _ => unreachable!(), + } + + // There is no need to wait in this configuration. + output_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelA); + }); + + // Not sure why exactly, but enabling SAI before the input DMA stream + // will properly order the input channels. + info!("Audio started!"); + sai.enable(); + + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); + }); + + let max_transfer_size = block_size * 2; + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, + ); + + info!( + "Setup up Audio DMA: input: {:?}, output: {:?}", + &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 + ); + + Audio { + sai, + audio_stream: AudioStream::S2dfm { + input: input_stream, + output: output_stream, + }, + input, + output, + max_transfer_size, + } + } + } + } + + /// Initializes audio for the Daisy Patch Submodule. + #[allow(clippy::too_many_arguments)] + pub fn patch_sm( + dma1_stream0: dma::dma::StreamX, + dma1_stream1: dma::dma::StreamX, + sai1_d: stm32::SAI1, + sai1_p: rcc::rec::Sai1, + i2c2_d: stm32::I2C2, + i2c2_p: rcc::rec::I2c2, + + sai_mclk_a: gpioe::PE2, + sai_sd_b: gpioe::PE3, + sai_fs_a: gpioe::PE4, + sai_sck_a: gpioe::PE5, + sai_sd_a: gpioe::PE6, + + i2c_scl: gpiob::PB10, + i2c_sda: gpiob::PB11, + + clocks: &rcc::CoreClocks, + delay: &mut impl DelayMs, + block_size: usize, + sample_rate: Hertz, ) -> Self { - let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; + let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma::dma::DmaConfig::default() .priority(dma::config::Priority::High) .memory_increment(true) .peripheral_increment(false) .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) .fifo_enable(false); + let mut output_stream = dma::Transfer::init( dma1_stream0, unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, @@ -152,10 +464,12 @@ impl Audio { dma_config, ); - let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma_config .transfer_complete_interrupt(true) .half_transfer_interrupt(true); + let mut input_stream = dma::Transfer::init( dma1_stream1, unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, @@ -164,12 +478,13 @@ impl Audio { dma_config, ); - info!("Setup up SAI..."); + info!("Set up SAI..."); let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); - let master_config = I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(false); - let slave_config = I2SChanConfig::new(I2SDir::Tx) - .set_sync_type(I2SSync::Internal) - .set_frame_sync_active_high(false); + let master_config = + sai::I2SChanConfig::new(sai::I2SDir::Rx).set_frame_sync_active_high(true); + let slave_config = sai::I2SChanConfig::new(sai::I2SDir::Tx) + .set_sync_type(sai::I2SSync::Internal) + .set_frame_sync_active_high(true); let pins_a = ( sai_mclk_a.into_alternate(), @@ -179,102 +494,174 @@ impl Audio { Some(sai_sd_b.into_alternate()), ); - // Hand off to audio module let mut sai = sai1_d.i2s_ch_a( pins_a, - crate::AUDIO_SAMPLE_HZ, - I2SDataSize::BITS_24, + sample_rate, + sai::I2SDataSize::BITS_24, sai1_rec, clocks, - I2sUsers::new(master_config).add_slave(slave_config), + sai::I2sUsers::new(master_config).add_slave(slave_config), ); - info!("Setup up WM8731 Audio Codec..."); + info!("Setting up PCM3060 Audio Codec..."); let i2c2_pins = ( i2c_scl.into_alternate_open_drain(), i2c_sda.into_alternate_open_drain(), ); - let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); + let mut i2c = crate::i2c::I2cTimeout::new(i2c2_d.i2c( + i2c2_pins, + Hertz::from_raw(100_000), + i2c2_p, + clocks, + )); + if let Err(e) = pcm3060_init(&mut i2c, delay) { + error!("PCM3060 codec did not respond: {e:?}"); + } - let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high + const DEV_ADDR: u8 = 0x46; - // Go through configuration setup - for (register, value) in REGISTER_CONFIG { - let register: u8 = (*register).into(); - let value: u8 = *value; - let byte1: u8 = ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); - let byte2: u8 = value; - let bytes = [byte1, byte2]; + const REG_SYS_CTRL: u8 = 0x40; + const REG_DAC_CTRL1: u8 = 0x43; + const REG_ADC_CTRL1: u8 = 0x48; - i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); + const MASK_MRST: u8 = 0x80; + const MASK_SRST: u8 = 0x40; + const MASK_ADC_PSV: u8 = 0x20; + const MASK_DAC_PSV: u8 = 0x10; + const MASK_FMT: u8 = 0x01; - // wait ~10us - asm::delay(5_000); + type CodecI2c = crate::i2c::I2cTimeout; + + fn read_reg(reg_addr: u8, i2c: &mut CodecI2c) -> Result { + let mut buffer = [0u8; 1]; + i2c.write_read(DEV_ADDR, &[reg_addr], &mut buffer)?; + Ok(buffer[0]) + } + + fn write_reg(reg_addr: u8, val: u8, i2c: &mut CodecI2c) -> Result<(), crate::i2c::Error> { + i2c.write(DEV_ADDR, &[reg_addr, val]) + } + + // POWER-ON RESET and EXTERNAL RESET Sequence + // Default Format spec for now: I2S 24-bit MSB aligned (FMT1/2[1:0] = 01) + + // Slave address: 0b100011Nx + // N = 0 (GND on hardware) + // x = R/W (not passed to address param) + fn pcm3060_init( + i2c: &mut CodecI2c, + delay: &mut impl DelayMs, + ) -> Result<(), crate::i2c::Error> { + // MSRT + let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !MASK_MRST; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + delay.delay_ms(4); + + // SRST + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !MASK_SRST; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + delay.delay_ms(4); + + // ADC/DAC Format set to 24-bit LJ + let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c)?; + let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c)?; + dac_ctrl |= MASK_FMT; + adc_ctrl |= MASK_FMT; + write_reg(REG_DAC_CTRL1, dac_ctrl, i2c)?; + write_reg(REG_ADC_CTRL1, adc_ctrl, i2c)?; + + // Disable Powersave for ADC/DAC + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !(MASK_ADC_PSV | MASK_DAC_PSV); + write_reg(REG_SYS_CTRL, sys_ctrl, i2c) } info!("Start audio stream..."); input_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelA); }); - - output_stream.start(|sai1_rb| { + sai.enable(); + output_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelB); - - // wait until sai1's fifo starts to receive data - info!("Sai1 fifo waiting to receive data."); - while sai1_rb.chb().sr.read().flvl().is_empty() {} - info!("Audio started!"); - sai.enable(); - sai.try_send(0, 0).unwrap(); }); - let input = Input::new(unsafe { &mut RX_BUFFER }); - let output = Output::new(unsafe { &mut TX_BUFFER }); - - info!( - "Setup up Audio DMA: input: {:?}, output: {:?}", - &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 + let max_transfer_size = block_size * 2; + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, ); Audio { sai, - input_stream, - output_stream, + audio_stream: AudioStream::Normal { + input: input_stream, + output: output_stream, + }, input, output, + max_transfer_size, } } /// Check interrupts and set indexes for I/O fn read(&mut self) -> bool { // Check interrupt(s) - if self.input_stream.get_half_transfer_flag() { - self.input_stream.clear_half_transfer_interrupt(); - self.input.set_index(0); - self.output.set_index(0); - true - } else if self.input_stream.get_transfer_complete_flag() { - self.input_stream.clear_transfer_complete_interrupt(); - self.input.set_index(MAX_TRANSFER_SIZE); - self.output.set_index(MAX_TRANSFER_SIZE); - true - } else { - false + match &mut self.audio_stream { + AudioStream::Normal { input, output } => { + input.clear_interrupts(); + + if output.get_half_transfer_flag() { + output.clear_half_transfer_interrupt(); + self.input.set_index(0); + self.output.set_index(0); + true + } else if output.get_transfer_complete_flag() { + output.clear_transfer_complete_interrupt(); + self.input.set_index(self.max_transfer_size); + self.output.set_index(self.max_transfer_size); + true + } else { + false + } + } + AudioStream::S2dfm { output, input } => { + input.clear_interrupts(); + + if output.get_half_transfer_flag() { + output.clear_half_transfer_interrupt(); + self.input.set_index(0); + self.output.set_index(0); + true + } else if output.get_transfer_complete_flag() { + output.clear_transfer_complete_interrupt(); + self.input.set_index(self.max_transfer_size); + self.output.set_index(self.max_transfer_size); + true + } else { + false + } + } } } - /// Directly pass received audio to output without any processing. - pub fn passthru(&mut self) { - // Copy data - if self.read() { - let mut index = 0; - let mut out_index = self.output.index; - while index < MAX_TRANSFER_SIZE { - self.output.buffer[out_index] = self.input.buffer[index + self.input.index]; - self.output.buffer[out_index + 1] = self.input.buffer[index + self.input.index + 1]; - index += 2; - out_index += 2; + /// Check interrupts without clearing flags. + pub fn peek_read(&mut self) -> bool { + // Check interrupt(s) + match &mut self.audio_stream { + AudioStream::Normal { input, output } => { + input.clear_interrupts(); + output.get_half_transfer_flag() || output.get_transfer_complete_flag() + } + AudioStream::S2dfm { output, input } => { + input.clear_interrupts(); + output.get_half_transfer_flag() || output.get_transfer_complete_flag() } } } @@ -283,7 +670,7 @@ impl Audio { pub fn get_stereo(&mut self, buffer: &mut AudioBuffer) -> bool { if self.read() { for (i, (left, right)) in StereoIterator::new( - &self.input.buffer[self.input.index..self.input.index + MAX_TRANSFER_SIZE], + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], ) .enumerate() { @@ -298,12 +685,106 @@ impl Audio { fn get_stereo_iter(&mut self) -> Option { if self.read() { return Some(StereoIterator::new( - &self.input.buffer[self.input.index..MAX_TRANSFER_SIZE], - )) + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], + )); } None } + /// Process audio frame-by-frame. + #[inline] + pub fn for_each(&mut self, mut process: F) + where + F: FnMut(f32, f32) -> (f32, f32), + { + self.try_for_each::<_, Infallible>(|left, right| Ok(process(left, right))) + .unwrap() + } + + /// Process audio frame-by-frame. + /// + /// If the process closure returns an error, + /// it's bubbled up to the callsite of this method. + pub fn try_for_each(&mut self, mut process: F) -> Result<(), E> + where + F: FnMut(f32, f32) -> Result<(f32, f32), E>, + { + if self.read() { + let input = self.input.buffer + [self.input.index..self.input.index + self.max_transfer_size] + .chunks_exact(2); + + let output = self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size] + .chunks_exact_mut(2); + + for (input, output) in input.zip(output) { + let (left, right) = + process(S24(input[0] as i32).into(), S24(input[1] as i32).into())?; + output[0] = S24::from(left).into(); + output[1] = S24::from(right).into(); + } + } + + Ok(()) + } + + /// Process audio block. + #[inline] + pub fn process_block(&mut self, mut process: F) + where + F: FnMut(&[u32], &mut [S24]), + { + self.try_process_block::<_, Infallible>(|input, output| Ok(process(input, output))) + .unwrap() + } + + /// Process audio audio block. + /// + /// If the process closure returns an error, + /// it's bubbled up to the callsite of this method. + pub fn try_process_block(&mut self, mut process: F) -> Result<(), E> + where + F: FnMut(&[u32], &mut [S24]) -> Result<(), E>, + { + if self.read() { + process( + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], + // # Safety + // + // S24 and u32 are both represented with 32 bits. + // + // Anything written to this output should be properly clamped using the S24 + // wrapper. + unsafe { + core::mem::transmute::<&mut [u32], &mut [S24]>( + &mut self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size], + ) + }, + )?; + } + + Ok(()) + } + + pub fn passthrough(&mut self) { + if self.read() { + let input = self.input.buffer + [self.input.index..self.input.index + self.max_transfer_size] + .chunks_exact(2); + + let output = self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size] + .chunks_exact_mut(2); + + for (input, output) in input.zip(output) { + output[0] = input[0]; + output[1] = input[1]; + } + } + } + /// Push data to the DMA buffer for output /// Call this once per sample per call to [get_stereo()](Audio#get_stereo) #[allow(clippy::result_unit_err)] @@ -315,12 +796,17 @@ impl Audio { struct Input { index: usize, buffer: &'static DmaBuffer, + max_transfer_size: usize, } impl Input { /// Create a new Input from a DmaBuffer - fn new(buffer: &'static DmaBuffer) -> Self { - Self { index: 0, buffer } + fn new(buffer: &'static DmaBuffer, max_transfer_size: usize) -> Self { + Self { + index: 0, + buffer, + max_transfer_size, + } } fn set_index(&mut self, index: usize) { @@ -336,12 +822,17 @@ impl Input { struct Output { index: usize, buffer: &'static mut DmaBuffer, + max_transfer_size: usize, } impl Output { /// Create a new Input from a DmaBuffer - fn new(buffer: &'static mut DmaBuffer) -> Self { - Self { index: 0, buffer } + fn new(buffer: &'static mut DmaBuffer, max_transfer_size: usize) -> Self { + Self { + index: 0, + buffer, + max_transfer_size, + } } fn set_index(&mut self, index: usize) { @@ -349,11 +840,11 @@ impl Output { } pub fn push(&mut self, data: (f32, f32)) -> Result<(), ()> { - if self.index < (MAX_TRANSFER_SIZE * 2) { + if self.index < (self.max_transfer_size * 2) { self.buffer[self.index] = S24::from(data.0).into(); self.buffer[self.index + 1] = S24::from(data.1).into(); self.index += 2; - return Ok(()) + return Ok(()); } Err(()) } diff --git a/src/bootloader.rs b/src/bootloader.rs new file mode 100644 index 0000000..37798f3 --- /dev/null +++ b/src/bootloader.rs @@ -0,0 +1,360 @@ +//! Types and functions for interfacing with the Daisy bootloader. + +use core::mem::MaybeUninit; +use stm32h7xx_hal::pac::{CPUID, SCB}; + +#[link_section = ".boot_info"] +static mut BOOT_INFO: MaybeUninit<[u32; 4]> = MaybeUninit::uninit(); + +unsafe fn get_info() -> [u32; 4] { + let info_ptr = core::ptr::addr_of!(BOOT_INFO); + unsafe { core::ptr::read_volatile(info_ptr).assume_init() } +} + +unsafe fn set_info(info: [u32; 4]) { + let info_ptr = core::ptr::addr_of_mut!(BOOT_INFO); + unsafe { + core::ptr::write_volatile(info_ptr, MaybeUninit::new(info)); + } +} + +// These should probably be defined in the linker, +// but this'll do for now. +pub const D1_AXIFLASH_BASE: u32 = 0x0800_0000; +pub const D1_ITCMRAM_BASE: u32 = 0x0000_0000; +pub const D1_DTCMRAM_BASE: u32 = 0x2000_0000; +pub const D1_AXISRAM_BASE: u32 = 0x2400_0000; +pub const D2_AXISRAM_BASE: u32 = 0x3000_0000; +pub const D3_SRAM_BASE: u32 = 0x3800_0000; +pub const SDRAM_BASE: u32 = 0xC000_0000; +pub const QSPI_BASE: u32 = 0x9000_0000; + +pub const INTERNAL_FLASH_SIZE: u32 = 0x20000; +pub const ITCMRAM_SIZE: u32 = 0x10000; +pub const DTCMRAM_SIZE: u32 = 0x20000; +pub const SRAM_D1_SIZE: u32 = 0x80000; +pub const SRAM_D2_SIZE: u32 = 0x48000; +pub const SRAM_D3_SIZE: u32 = 0x10000; +pub const SDRAM_SIZE: u32 = 0x4000000; +pub const QSPI_SIZE: u32 = 0x800000; + +const D1_AXIFLASH_END: u32 = D1_AXIFLASH_BASE + INTERNAL_FLASH_SIZE - 1; +const D1_ITCMRAM_END: u32 = D1_ITCMRAM_BASE + ITCMRAM_SIZE - 1; +const D1_DTCMRAM_END: u32 = D1_DTCMRAM_BASE + DTCMRAM_SIZE - 1; +const D1_AXISRAM_END: u32 = D1_AXISRAM_BASE + SRAM_D1_SIZE - 1; +const D2_AXISRAM_END: u32 = D2_AXISRAM_BASE + SRAM_D2_SIZE - 1; +const D3_SRAM_END: u32 = D3_SRAM_BASE + SRAM_D3_SIZE - 1; +const SDRAM_END: u32 = SDRAM_BASE + SDRAM_SIZE - 1; +const QSPI_END: u32 = QSPI_BASE + QSPI_SIZE - 1; + +/// Describes a region in memory. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemoryRegion { + InternalFlash, + ItcmRam, + DtcmRam, + SramD1, + SramD2, + SramD3, + SdRam, + Qspi, +} + +impl MemoryRegion { + /// Return the memory region containing this address, if any. + pub fn from_address(address: u32) -> Option { + match address { + D1_AXIFLASH_BASE..=D1_AXIFLASH_END => Some(MemoryRegion::InternalFlash), + D1_ITCMRAM_BASE..=D1_ITCMRAM_END => Some(MemoryRegion::ItcmRam), + D1_DTCMRAM_BASE..=D1_DTCMRAM_END => Some(MemoryRegion::DtcmRam), + D1_AXISRAM_BASE..=D1_AXISRAM_END => Some(MemoryRegion::SramD1), + D2_AXISRAM_BASE..=D2_AXISRAM_END => Some(MemoryRegion::SramD2), + D3_SRAM_BASE..=D3_SRAM_END => Some(MemoryRegion::SramD3), + SDRAM_BASE..=SDRAM_END => Some(MemoryRegion::SdRam), + QSPI_BASE..=QSPI_END => Some(MemoryRegion::Qspi), + _ => None, + } + } + + /// Return the memory region occupied by the vector table, + /// which is a proxy for determining the execution location of the + /// current program. + pub fn from_vtable(scb: &SCB) -> Option { + let address = scb.vtor.read(); + + Self::from_address(address) + } +} + +/// Describes how the Daisy bootloader should behave. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DaisyBootType { + /// This causes the bootloader to immediately jump to + /// the target application, assuming its address has been loaded in. + /// + /// This should not be set by any code outside the bootloader. + Jump, + /// Jump immediately to the target application. + SkipTimeout, + /// Wait in the bootloader indefinitely. + InfiniteTimeout, + /// The target application encountered an unrecoverable error. + /// + /// This can allow the bootloader to break otherwise infinite + /// boot-reset loops. By default, the bootloader will + /// stop jumping back to the application if this has been + /// set five times. + Panic, +} + +impl DaisyBootType { + /// Read from backup SRAM to get the boot type, if any. + pub fn from_backup() -> Option { + let info = unsafe { get_info() }; + + let boot_word = info[0]; + + Self::from_bits(boot_word) + } + + fn to_bits(self) -> u32 { + match self { + Self::Jump => 0xDEADBEEF, + Self::SkipTimeout => 0x5AFEB007, + Self::InfiniteTimeout => 0xB0074EFA, + // You gotta be careful what you eat. + Self::Panic => 0x8BADF00D, + } + } + + fn from_bits(bits: u32) -> Option { + match bits { + 0xDEADBEEF => Some(Self::Jump), + 0x5AFEB007 => Some(Self::SkipTimeout), + 0xB0074EFA => Some(Self::InfiniteTimeout), + 0x8BADF00D => Some(Self::Panic), + _ => None, + } + } + + /// Write the boot type to backup SRAM. + /// + /// # Safety + /// + /// Writing this value sets bits in backup SRAM directly, + /// the XOR mutable aliasing is not guaranteed. + unsafe fn write(&self) { + let mut info = unsafe { get_info() }; + info[0] = self.to_bits(); + unsafe { + set_info(info); + } + } + + /// Write an invalid bit pattern to backup SRAM, effectively clearing the boot type. + /// + /// # Safety + /// + /// Writing this value sets bits in backup SRAM directly, + /// the XOR mutable aliasing is not guaranteed. + pub unsafe fn clear() { + let mut info = unsafe { get_info() }; + info[0] = 0; + unsafe { + set_info(info); + } + } +} + +/// The bootloader version. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Version { + /// Any version below v6. + LessThanV6, + /// Exactly v6. + V6, + /// v6.1 or above. + V6_1, +} + +impl Version { + /// Read from backup SRAM to get the version, if any. + pub fn from_backup() -> Option { + let info = unsafe { get_info() }; + let version_word = info[2]; + + Self::from_bits(version_word) + } + + fn from_bits(bits: u32) -> Option { + match bits { + 0 => Some(Self::LessThanV6), + 1 => None, + 2 => Some(Self::V6), + _ => Some(Self::V6_1), + } + } + + fn to_bits(self) -> u32 { + match self { + Self::LessThanV6 => 0, + Self::V6 => 2, + Self::V6_1 => 3, + } + } + + /// Write the version to backup SRAM. + unsafe fn write(&self) { + let mut info = unsafe { get_info() }; + info[2] = self.to_bits(); + unsafe { + set_info(info); + } + } +} + +/// Get the target application entry point, if present. +/// +/// This will only return `Some` when the boot type is `Jump`. +pub fn application_address() -> Option { + let boot_type = DaisyBootType::from_backup(); + + match boot_type { + Some(DaisyBootType::Jump) => { + let info = unsafe { get_info() }; + Some(info[1]) + } + _ => None, + } +} + +/// Set the target application entry point. +/// +/// This also sets the boot type to `Jump`. +/// +/// # Safety +/// +/// Writing this value sets bits in backup SRAM directly, +/// the XOR mutable aliasing is not guaranteed. +pub unsafe fn set_application_address(address: u32) { + let boot_type = DaisyBootType::Jump; + unsafe { boot_type.write() }; + + let mut info = unsafe { get_info() }; + info[1] = address; + unsafe { + set_info(info); + } +} + +/// A counter for how many times an application has +/// reset itself in a panicked state. +pub fn panic_count() -> u32 { + let info = unsafe { get_info() }; + info[3] +} + +/// Set the panic count. +/// +/// # Safety +/// +/// Writing this value sets bits in backup SRAM directly, +/// the XOR mutable aliasing is not guaranteed. +pub unsafe fn set_panic_count(value: u32) { + let mut info = unsafe { get_info() }; + info[3] = value; + + unsafe { + set_info(info); + } +} + +/// Describes which bootloader the device will reset to. +#[derive(Debug)] +pub enum BootType { + /// The built-in ST Micro system bootloader. + Stm(crate::gpio::SeedButton), + /// The daisy bootloader with its associated boot type. + Daisy(DaisyBootType), +} + +/// Reset to the provided bootloader. +/// +/// # Panics +/// +/// Panics if the Daisy bootloader is selected but +/// the program is running from internal flash, meaning +/// no Daisy bootloader can be present. +pub fn reset_to_bootloader(boot_type: BootType) -> ! { + reset_to_bootloader_with(boot_type, || ()) +} + +/// Reset to the provided bootloader, running the provided +/// closure after data caching is disabled. +/// +/// # Panics +/// +/// Panics if the Daisy bootloader is selected but +/// the program is running from internal flash, meaning +/// no Daisy bootloader can be present. +pub fn reset_to_bootloader_with(boot_type: BootType, f: F) -> ! { + // # SAFETY + // + // SCB and CPUID are zero-sized types. They are passed around + // as a means of maintaining correctness. + // + // However, since we're resetting the device here (or panicking), + // requiring that we pass them in can be a little onerous. + // + // TODO: In the event of a panic, invalid configurations may + // be observed. There's no guarantee that the panic handler + // will restore the device to a sound configuration. + // + // This should probably be split out into an unsafe reset function. + let mut scb: SCB = unsafe { core::mem::transmute(()) }; + let mut cpuid = unsafe { core::mem::transmute::<(), CPUID>(()) }; + scb.disable_dcache(&mut cpuid); + + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + f(); + + match boot_type { + BootType::Stm(boot_pin) => { + let mut pin = boot_pin.into_push_pull_output(); + pin.set_high(); + + // Allow capacitor to charge. + use crate::delay::DelayMs; + let mut delay = crate::delay::CycleDelay::new(); + delay.delay_ms(10u8); + } + BootType::Daisy(daisy_type) => { + let region = MemoryRegion::from_vtable(&scb); + + if matches!(region, Some(MemoryRegion::InternalFlash)) { + panic!("Attempted to jump to non-existent Daisy bootloader"); + } + + unsafe { + // write twice just in case + daisy_type.write(); + daisy_type.write(); + } + } + } + + system_reset() +} + +/// Reset the device. +/// +/// This should behave exactly like pressing the RESET button. +pub fn system_reset() -> ! { + // Clear all interrupts. + let ptr = unsafe { &*stm32h7xx_hal::pac::RCC::PTR }; + ptr.cier.reset(); + + SCB::sys_reset() +} diff --git a/src/delay.rs b/src/delay.rs new file mode 100644 index 0000000..c3be752 --- /dev/null +++ b/src/delay.rs @@ -0,0 +1,67 @@ +//! Simple, cycle-based delay. + +use cortex_m::asm::delay as delay_cycles; +pub use embedded_hal::blocking::delay::{DelayMs, DelayUs}; + +/// A type that uses CPU cycles as a delay source. +/// +/// [CycleDelay] is guaranteed to block for +/// at least as many cycles as requested, but it +/// makes no claims about accuracy. Servicing +/// interrupts can make the delay extend much longer. +/// +/// For accurate timings, use a hardware timer. +/// +/// Delay methods are provided via `embedded-hal`'s [DelayMs] +/// and [DelayUs] traits. +/// +/// ``` +/// use libdaisy::delay::{CycleDelay, DelayMs}; +/// +/// let mut delay = CycleDelay::new(); +/// // You may need to specify the literal's +/// // type to help Rust resolve the trait. +/// delay.delay_ms(10u8); +/// ``` +#[derive(Default)] +pub struct CycleDelay; + +impl CycleDelay { + /// Construct a new [CycleDelay]. + pub fn new() -> Self { + CycleDelay + } + + /// Block for `cycles` processor cycles. + pub fn delay_cycles(&mut self, cycles: u32) { + delay_cycles(cycles); + } + + /// Block for approximately `cycles` nanoseconds. + /// + /// This is a rough approximation with a resolution + /// of about four nanoseconds. + pub fn delay_ns(&mut self, ns: u32) { + delay_cycles(ns / 4); + } +} + +macro_rules! impl_delay { + ($ty:path) => { + impl DelayMs<$ty> for CycleDelay { + fn delay_ms(&mut self, ms: $ty) { + self.delay_cycles((ms as u32).saturating_mul(crate::MILICYCLES)); + } + } + + impl DelayUs<$ty> for CycleDelay { + fn delay_us(&mut self, us: $ty) { + self.delay_cycles((us as u32).saturating_mul(crate::MICROCYCLES)); + } + } + }; +} + +impl_delay!(u8); +impl_delay!(u16); +impl_delay!(u32); diff --git a/src/devices/encoder.rs b/src/devices/encoder.rs new file mode 100644 index 0000000..2667c26 --- /dev/null +++ b/src/devices/encoder.rs @@ -0,0 +1,103 @@ +use embedded_hal::digital::v2::InputPin; + +/// A quadrature encoder driver. +/// +/// This struct reads the quadrature input via polling. +pub struct Encoder { + a: A, + b: B, + decoder: QuadratureDecoder, +} + +impl Encoder +where + A: InputPin, + B: InputPin, + ::Error: core::fmt::Debug, + ::Error: core::fmt::Debug, +{ + pub const fn new(a: A, b: B) -> Self { + Self { + a, + b, + decoder: QuadratureDecoder::new(), + } + } + + /// Update the encoder. + /// + /// `milliseconds` should be a monotonically increasing count. + /// This allows the debouncing implementation to limit updates + /// to the desired rate. + pub fn update(&mut self, milliseconds: u32) { + self.decoder.update( + self.a.is_high().unwrap(), + self.b.is_high().unwrap(), + milliseconds, + ); + } + + /// Get the magnitude of the last encoder turn, if any. + pub fn increment(&self) -> Option { + self.decoder.increment() + } +} + +#[derive(Debug, Clone)] +pub struct QuadratureDecoder { + last_update: u32, + a: u8, + b: u8, + increment: i32, +} + +impl Default for QuadratureDecoder { + fn default() -> Self { + Self::new() + } +} + +/// A quadrature encoder decoder. +/// +/// This is useful when your quadrature encoder data +/// lines are not connected directly to Daisy pins. +impl QuadratureDecoder { + pub const fn new() -> Self { + Self { + last_update: 0, + a: 0, + b: 0, + increment: 0, + } + } + + /// Update the decoder with the current state of `a` and `b`. + /// + /// `milliseconds` should be a monotonically increasing count. + /// This allows the debouncing implementation to limit updates + /// to the desired rate. + pub fn update(&mut self, a: bool, b: bool, milliseconds: u32) { + self.increment = 0; + + if milliseconds.saturating_sub(self.last_update) >= 1 { + self.last_update = milliseconds; + + self.a = (self.a << 1) | a as u8; + self.b = (self.b << 1) | b as u8; + + if (self.a & 0x03) == 0x02 && (self.b & 0x03) == 0x00 { + self.increment = 1; + } else if (self.b & 0x03) == 0x02 && (self.a & 0x03) == 0x00 { + self.increment = -1; + } + } + } + + /// Get the magnitude of the last encoder turn, if any. + pub fn increment(&self) -> Option { + match self.increment { + 0 => None, + i => Some(i), + } + } +} diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs new file mode 100644 index 0000000..abed2fc --- /dev/null +++ b/src/devices/expander_pca9555.rs @@ -0,0 +1,105 @@ +use crate::hal; +use hal::i2c::I2c; +use hal::prelude::*; +use stm32h7xx_hal::prelude::{ + _embedded_hal_blocking_i2c_Read as Read, _embedded_hal_blocking_i2c_Write as Write, + _embedded_hal_blocking_i2c_WriteRead as WriteRead, +}; + +/// Data corresponding to a single PCA9555 I/O Expander +/// +/// In the future we can add: +/// - chained-device support (similar to pca9685) +/// - output configuration +/// - INT pin support for non-polling behavior +/// +/// For now this is a minimal driver for providing access +/// to 16 additional inptus. +pub struct Pca9555 { + i2c: I2c, + address: u8, + states: [bool; 16], +} + +#[derive(Debug)] +pub enum I2cError { + Write(W), + WriteRead(WR), +} + +#[expect(type_alias_bounds)] +pub type WriteReadError = + I2cError<::Error, ::Error>; + +impl Pca9555 +where + I2c: Write + WriteRead + Read, +{ + pub fn new(address: u8, mut bus: I2c) -> Self { + let base_address = 0x20; + let dev_addr = base_address | (address & 0x07); + let mut dev = Pca9555 { + i2c: bus, + address: dev_addr, + states: Default::default(), + }; + + // Configuration + // Default config appears to be: + // configuration = input + // polarity = not-inverted + // So for our initial implementation.. no config is necessary + + // Any attempts to write here will cause an infinite loop during if device is not + // present and serial callback hasn't been started. So maybe we don't do this here yet. + + // This was here to test why device wasn't sending ACK (but it wasn't on the PCB ...) + // Will remove these comments, and finish driver once I have appropriate HW + // crate::delay::CycleDelay::new().delay_ms(30u8); + // let _ = dev + // .i2c + // .write(dev.address, &[Register::ConfigurationPort0 as u8, 0xff]); + + // Fill internal data with current states on hardware. + // dev.update(); + + dev + } + + /// Polls the device, reading both of it's input registers to fill the + /// internal state data + // pub fn update(&mut self) -> Result<(), WriteReadError> { + pub fn update(&mut self) { + // Register pairs flip automatically after reads. + // So we can basically just send the `InputPort0` command, + // and read two data bytes back every time we want new data + let mut raw_states = [0u8; 2]; + let _ = self + .i2c + .write_read(self.address, &[Register::InputPort0 as u8], &mut raw_states); + + let raw_union: u16 = (raw_states[0] as u16) | ((raw_states[1] as u16) << 8); + for i in 0..16 { + self.states[i] = (raw_union & (1 << i)) != 0; + } + } + + /// Returns the current state recorded for a particular pin + pub fn get_state(&self, index: usize) -> bool { + let idx = index.min(15); + self.states[idx] + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Register { + InputPort0 = 0x00, + InputPort1 = 0x01, + OutputPort0 = 0x02, + OutputPort1 = 0x03, + PolarityInversion0 = 0x04, + PolarityInversion1 = 0x05, + ConfigurationPort0 = 0x06, + ConfigurationPort1 = 0x07, +} diff --git a/src/devices/led_driver_pca9685.rs b/src/devices/led_driver_pca9685.rs new file mode 100644 index 0000000..ad00ae5 --- /dev/null +++ b/src/devices/led_driver_pca9685.rs @@ -0,0 +1,188 @@ +#![allow(dead_code)] + +use cortex_m::peripheral::DWT; +use embedded_hal::blocking::delay::DelayMs; +use embedded_hal::blocking::i2c::Write; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct LedRegister { + pub on: u16, + pub off: u16, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Pca9685TransmitBuffer { + pub register_addr: u8, + pub leds: [LedRegister; 16], +} + +impl Pca9685TransmitBuffer { + pub const SIZE: usize = 1 + 16 * 4; + + pub const fn new() -> Self { + Self { + register_addr: PCA9685_LED0, + leds: [LedRegister { on: 0, off: 0 }; 16], + } + } +} + +impl Default for Pca9685TransmitBuffer { + fn default() -> Self { + Self { + register_addr: PCA9685_LED0, + leds: [LedRegister { on: 0, off: 0 }; 16], + } + } +} + +pub type DmaBuffer = [Pca9685TransmitBuffer; N]; + +const RETRY_MS: u32 = 1000; + +pub struct LedDriverPca9685 { + i2c: I, + draw_buffer: Option<&'static mut [u8]>, + transmit_buffer: Option<&'static mut [u8]>, + addresses: [u8; NUM_DRIVERS], + /// Whether each driver has been initialized and has kept responding. + ready: [bool; NUM_DRIVERS], + /// DWT cycle count after which unready drivers are tried again. + retry_at: u32, +} + +// Constants +const PCA9685_I2C_BASE_ADDRESS: u8 = 0b01000000; +const PCA9685_MODE1: u8 = 0x00; +const PCA9685_MODE2: u8 = 0x01; +const PRE_SCALE_MODE: u8 = 0xFE; +const PCA9685_LED0: u8 = 0x06; + +impl LedDriverPca9685 +where + I: Write, +{ + /// `i2c` must give up on a dead bus (see [`crate::i2c::I2cTimeout`]); the + /// HAL's own blocking I2C spins forever when no driver is connected. + pub fn new( + mut i2c: I, + addresses: [u8; NUM_DRIVERS], + dma_buffer_a: &'static mut DmaBuffer, + dma_buffer_b: &'static mut DmaBuffer, + ) -> Self { + let mut ready = [false; NUM_DRIVERS]; + for (ready, &addr) in ready.iter_mut().zip(&addresses) { + *ready = Self::init_driver(&mut i2c, addr, 20).is_ok(); + } + + let len = NUM_DRIVERS * Pca9685TransmitBuffer::SIZE; + + // Initialize buffers + for buf in dma_buffer_a.iter_mut() { + buf.register_addr = PCA9685_LED0; + } + for buf in dma_buffer_b.iter_mut() { + buf.register_addr = PCA9685_LED0; + } + + let buffer_a_u8 = + unsafe { core::slice::from_raw_parts_mut(dma_buffer_a.as_mut_ptr() as *mut u8, len) }; + let buffer_b_u8 = + unsafe { core::slice::from_raw_parts_mut(dma_buffer_b.as_mut_ptr() as *mut u8, len) }; + + Self { + i2c, + draw_buffer: Some(buffer_b_u8), + transmit_buffer: Some(buffer_a_u8), + addresses, + ready, + retry_at: DWT::cycle_count().wrapping_add(RETRY_MS * crate::MILICYCLES), + } + } + + /// Wake one driver and set it up, stopping at the first write it doesn't + /// acknowledge. + fn init_driver(i2c: &mut I, addr: u8, settle_ms: u8) -> Result<(), I::Error> { + let address = PCA9685_I2C_BASE_ADDRESS | addr; + let mut delay = crate::delay::CycleDelay::new(); + i2c.write(address, &[PCA9685_MODE1, 0x00])?; + delay.delay_ms(settle_ms); + i2c.write(address, &[PCA9685_MODE1, 0x00])?; + delay.delay_ms(settle_ms); + i2c.write(address, &[PCA9685_MODE1, 0b00100000])?; // Auto increment + delay.delay_ms(settle_ms); + // There are a few configurations in this register we may want to expose later. + i2c.write(address, &[PCA9685_MODE2, 0b00000110]) // OE hi-z, odrv=1 + } + + pub fn set_led(&mut self, led_index: usize, brightness: f32) { + let driver_idx = led_index / 16; + let led_idx_in_driver = led_index % 16; + + if driver_idx >= NUM_DRIVERS { + return; + } + + if let Some(buffer) = &mut self.draw_buffer { + let offset = driver_idx * Pca9685TransmitBuffer::SIZE + 1 + led_idx_in_driver * 4; + + let val = (brightness * 4095.0) as u16; + let on = 0; + let off = if val > 4095 { 4095 } else { val }; + + buffer[offset] = (on & 0xFF) as u8; + buffer[offset + 1] = (on >> 8) as u8; + buffer[offset + 2] = (off & 0xFF) as u8; + buffer[offset + 3] = (off >> 8) as u8; + } + } + + pub fn swap_buffers_and_transmit(&mut self) { + // Swap buffers + let draw = self.draw_buffer.take().unwrap(); + let transmit = self.transmit_buffer.take().unwrap(); + self.draw_buffer = Some(transmit); + self.transmit_buffer = Some(draw); + + // Start transmission + self.transmit(); + } + + fn transmit(&mut self) { + let now = DWT::cycle_count(); + // Signed distance, so the counter's wrap is harmless. + let retry = (now.wrapping_sub(self.retry_at) as i32) >= 0; + if retry { + self.retry_at = now.wrapping_add(RETRY_MS * crate::MILICYCLES); + } + + for driver_idx in 0..NUM_DRIVERS { + if !self.ready[driver_idx] { + // The oscillator needs 500us after waking; this runs in the + // main loop, so no longer than that. + if !retry + || Self::init_driver(&mut self.i2c, self.addresses[driver_idx], 1).is_err() + { + continue; + } + self.ready[driver_idx] = true; + } + + let address = PCA9685_I2C_BASE_ADDRESS | self.addresses[driver_idx]; + + let buffer = self.transmit_buffer.as_mut().unwrap(); + let start = driver_idx * Pca9685TransmitBuffer::SIZE; + let end = start + Pca9685TransmitBuffer::SIZE; + let bytes = &buffer[start..end]; + if self.i2c.write(address, bytes).is_err() { + self.ready[driver_idx] = false; + } + } + } + + pub fn poll(&mut self) { + // No-op for blocking + } +} diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..e71d384 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,6 @@ +pub mod encoder; +pub mod expander_pca9555; +pub mod led_driver_pca9685; +pub mod mpr121; +pub mod multiplexer; +pub mod shift_register; diff --git a/src/devices/mpr121.rs b/src/devices/mpr121.rs new file mode 100644 index 0000000..11b2e49 --- /dev/null +++ b/src/devices/mpr121.rs @@ -0,0 +1,249 @@ +use cortex_m::prelude::_embedded_hal_blocking_delay_DelayMs; +use stm32h7xx_hal::prelude::{ + _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, +}; + +pub struct Mpr121 { + address: u8, + thresholds: [Mpr121Thresholds; 12], + states: TouchStates, + analog_states: [f32; 12], +} + +#[derive(Debug)] +pub enum I2cError { + Write(W), + WriteRead(WR), +} + +#[expect(type_alias_bounds)] +pub type WriteReadError = + I2cError<::Error, ::Error>; + +impl Mpr121 { + pub fn new( + address: u8, + touch_threshold: u8, + release_threshold: u8, + bus: &mut W, + ) -> Result> { + let mpr = Mpr121 { + address, + thresholds: [Mpr121Thresholds { + touch: touch_threshold, + release: release_threshold, + }; 12], + states: Default::default(), + analog_states: [0.0; 12], + }; + + mpr.write(bus, Register::SoftReset, 0x63)?; + crate::delay::CycleDelay::new().delay_ms(1u8); + + mpr.write(bus, Register::Ecr, 0x0)?; + + mpr.write(bus, Register::Mhdr, 0x01)?; + mpr.write(bus, Register::Nhdr, 0x01)?; + mpr.write(bus, Register::Nclr, 0x0E)?; + mpr.write(bus, Register::Fdlr, 0x00)?; + + mpr.write(bus, Register::Mhdf, 0x01)?; + mpr.write(bus, Register::Nhdf, 0x05)?; + mpr.write(bus, Register::Nclf, 0x01)?; + mpr.write(bus, Register::Fdlf, 0x00)?; + + mpr.write(bus, Register::Nhdt, 0x00)?; + mpr.write(bus, Register::Nclt, 0x00)?; + mpr.write(bus, Register::Fdlt, 0x00)?; + + mpr.write(bus, Register::Debounce, 0)?; + mpr.write(bus, Register::Config1, 0x10)?; // default, 16uA charge current + mpr.write(bus, Register::Config2, 0x20)?; // 0.5uS encoding, 1ms period + + let ecr_settings = 0x80 + 12; // 5 bits for baseline tracking & proximity disabled + X + // amount of electrodes running (12) + mpr.write(bus, Register::Ecr, ecr_settings)?; // start with above ECR setting + + mpr.write_thresholds(bus)?; + + Ok(mpr) + } + + fn write( + &self, + bus: &mut W, + register: Register, + data: u8, + ) -> Result<(), WriteReadError> { + self.write_raw(bus, register as u8, data) + } + + fn write_raw( + &self, + bus: &mut W, + register: u8, + data: u8, + ) -> Result<(), WriteReadError> { + // first get the current set value of the MPR121_ECR register + let ecr_reg = Register::Ecr as u8; + let mut ecr_backup = 0; + + bus.write_read( + self.address, + &[ecr_reg], + core::slice::from_mut(&mut ecr_backup), + ) + .map_err(I2cError::WriteRead)?; + + // MPR121 must be put in Stop Mode to write to most registers + let stop_required = !((register == ecr_reg) || (0x73..=0x7A).contains(®ister)); + + if stop_required { + // clear this register to set stop mode + bus.write(self.address, &[ecr_reg, 0x00]) + .map_err(I2cError::Write)?; + } + + bus.write(self.address, &[register, data]) + .map_err(I2cError::Write)?; + + if stop_required { + // write back the previous set ECR settings + bus.write(self.address, &[ecr_reg, ecr_backup]) + .map_err(I2cError::Write)?; + } + + Ok(()) + } + + fn read( + &self, + bus: &mut W, + register: Register, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + self.read_raw(bus, register as u8, buffer) + } + + fn read_raw( + &self, + bus: &mut W, + register: u8, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + bus.write_read(self.address, &[register], buffer) + .map_err(I2cError::WriteRead) + } + + pub fn write_thresholds( + &self, + bus: &mut W, + ) -> Result<(), WriteReadError> { + for (i, Mpr121Thresholds { touch, release }) in self.thresholds.iter().enumerate() { + let touch_register = Register::TouchTh0 as usize + i * 2; + let release_register = Register::ReleaseTh0 as usize + i * 2; + + self.write_raw(bus, touch_register as u8, *touch)?; + self.write_raw(bus, release_register as u8, *release)?; + } + + Ok(()) + } + + pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { + let mut buffer = [0u8; 2]; + self.read(bus, Register::TouchStatusL, &mut buffer)?; + + self.states = TouchStates(u16::from_le_bytes(buffer)); + let u10_scale = 1.0 / (1 << 10) as f32; + + for i in 0..self.analog_states.len() { + let register = Register::FiltData0L as usize + i * 2; + self.read_raw(bus, register as u8, &mut buffer)?; + + // the data returned is a 10-bit unsigned value + self.analog_states[i] = u16::from_le_bytes(buffer) as f32 * u10_scale; + } + + Ok(()) + } + + pub fn states(&self) -> TouchStates { + self.states + } + + pub fn analog_states(&self) -> [f32; 12] { + self.analog_states + } +} + +#[derive(Default, Clone, Copy)] +pub struct TouchStates(u16); + +impl TouchStates { + pub fn is_touched(&self, channel: u8) -> bool { + ((self.0 >> channel) & 1) > 0 + } + + pub fn raw(&self) -> u16 { + self.0 + } +} + +#[derive(Clone, Copy)] +pub struct Mpr121Thresholds { + pub touch: u8, + pub release: u8, +} + +impl Default for Mpr121Thresholds { + fn default() -> Self { + Mpr121Thresholds { + touch: 12, + release: 6, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Register { + TouchStatusL = 0x00, + TouchStatusH = 0x01, + FiltData0L = 0x04, + FiltData0H = 0x05, + Baseline0 = 0x1E, + Mhdr = 0x2B, + Nhdr = 0x2C, + Nclr = 0x2D, + Fdlr = 0x2E, + Mhdf = 0x2F, + Nhdf = 0x30, + Nclf = 0x31, + Fdlf = 0x32, + Nhdt = 0x33, + Nclt = 0x34, + Fdlt = 0x35, + + TouchTh0 = 0x41, + ReleaseTh0 = 0x42, + Debounce = 0x5B, + Config1 = 0x5C, + Config2 = 0x5D, + ChargeCurr0 = 0x5F, + ChargeTime1 = 0x6C, + Ecr = 0x5E, + AutoConfig0 = 0x7B, + AutoConfig1 = 0x7C, + UpLimit = 0x7D, + LowLimit = 0x7E, + TargetLimit = 0x7F, + + GpioDir = 0x76, + GpioEn = 0x77, + GpioSet = 0x78, + GpioClr = 0x79, + GpioToggle = 0x7A, + + SoftReset = 0x80, +} diff --git a/src/devices/multiplexer.rs b/src/devices/multiplexer.rs new file mode 100644 index 0000000..4062f82 --- /dev/null +++ b/src/devices/multiplexer.rs @@ -0,0 +1,60 @@ +use embedded_hal::digital::v2::OutputPin; + +pub struct Multiplexer { + analog_pin: A, + digital_pins: D, + channels: [u32; LENGTH], + index: u8, +} + +impl Multiplexer { + pub fn analog_pin(&mut self) -> &mut A { + &mut self.analog_pin + } + + pub fn channels(&self) -> &[u32] { + &self.channels + } + + pub fn update(&mut self, value: u32) { + self.channels[self.index as usize] = value; + } +} + +macro_rules! impl_multi { + ($count:literal, $new:ident, $($ty:ident),*) => { + impl Multiplexer + where + $($ty: OutputPin),* + { + /// Construct a new [Multiplexer]. + /// + /// `digital_pins` must be a tuple of one to three output pins. + pub fn $new(analog_pin: A, digital_pins: ($($ty,)*)) -> Self { + Self { + analog_pin, + digital_pins, + channels: [const { 0 }; $count], + index: 0, + } + } + + /// Tick the multiplexer pins by one. + #[allow(unused_assignments, non_snake_case)] + pub fn tick(&mut self) { + self.index = (self.index + 1) % $count; + let ($($ty,)*) = &mut self.digital_pins; + let mut mask = self.index; + + $( + let _ = $ty.set_state(((mask & 1) > 0).into()); + mask >>= 1; + )* + } + } + }; +} + +impl_multi!(2, new_1, D0); +impl_multi!(4, new_2, D0, D1); +impl_multi!(8, new_3, D0, D1, D2); diff --git a/src/devices/shift_register.rs b/src/devices/shift_register.rs new file mode 100644 index 0000000..bbc6f52 --- /dev/null +++ b/src/devices/shift_register.rs @@ -0,0 +1,134 @@ +use embedded_hal::digital::v2::{InputPin, OutputPin}; + +/// A trait for reading and updating shift registers. +/// +/// This is implemented for [`ShiftRegister4021`] for up to +/// 8 parallel connections. +pub trait ShiftRegister { + /// Update the shift register object, reading each bit sequentially + /// in a blocking fashion. + fn update(&mut self); + + /// Read `input` from the given `parallel` chain. + /// + /// # Panics + /// + /// Panics if `inptut` or `parallel` are out of range. + fn read_parallel(&self, input: usize, parallel: usize) -> bool; + + /// Read `input` from the first parallel group. + /// + /// # Panics + /// + /// Panics if `inptut` is out of range. + fn read(&self, input: usize) -> bool { + self.read_parallel(input, 0) + } +} + +/// A 4021 shift register meta-struct. +/// +/// This struct permits configurations with both series and parallel +/// configurations. Note that `PARALLEL` will need to match the +/// number of data pins for the update implementation to work. +pub struct ShiftRegister4021 { + clock: C, + latch: L, + data: D, + delay: u32, + bits: [[u8; SERIES]; PARALLEL], +} + +impl + ShiftRegister4021 +{ + /// Construct a new [`ShiftRegister4021`]. + /// + /// `clock` and `latch` are expected to take single pins, + /// while `data` expects a tuple of up to 8 pins. + pub fn new(clock: C, latch: L, data: D, initial_state: bool) -> Self { + /// We'll use an arithmetic shift to fill the byte + let initial_state = ((initial_state as i8) << 7); + let initial_state = (initial_state >> 7) as u8; + + Self { + clock, + latch, + data, + delay: 1000, + bits: [[initial_state; SERIES]; PARALLEL], + } + } + + /// Set the delay time between clock edges in nanoseconds. + pub fn set_delay(&mut self, delay_ns: u32) { + self.delay = delay_ns; + } + + fn parallel(&self) -> usize { + PARALLEL + } +} + +macro_rules! impl_sr { + ($count:literal, $($ty:ident),*) => { + impl ShiftRegister for ShiftRegister4021 + where + T: OutputPin, + U: OutputPin, + ::Error: core::fmt::Debug, + ::Error: core::fmt::Debug, + $($ty: InputPin,)* + $(<$ty as InputPin>::Error: core::fmt::Debug,)* + { + #[allow(non_snake_case)] + fn update(&mut self) { + let mut delay = crate::delay::CycleDelay::new(); + + self.clock.set_low().unwrap(); + self.latch.set_high().unwrap(); + + delay.delay_ns(self.delay); + + self.latch.set_low().unwrap(); + + let total = self.bits[0].len(); + let ($($ty,)*) = &self.data; + for series in 0..total { + for bit in 0..8 { + self.clock.set_low().unwrap(); + delay.delay_ns(self.delay); + + let new_data: [u8; $count] = [ + $($ty.is_high().unwrap() as u8),* + ]; + + for (parallel, data) in new_data.into_iter().enumerate() { + self.bits[parallel][series] &= !(1 << bit); + self.bits[parallel][series] |= data << bit; + } + + self.clock.set_high().unwrap(); + delay.delay_ns(self.delay); + } + } + } + + fn read_parallel(&self, bit: usize, parallel: usize) -> bool { + let byte_address = bit / 8; + let bit_address = bit % 8; + + ((self.bits[parallel][byte_address] >> bit_address) & 1) > 0 + } + } + }; +} + +impl_sr!(1, A); +impl_sr!(2, A, B); +impl_sr!(3, A, B, C); +impl_sr!(4, A, B, C, D); +impl_sr!(5, A, B, C, D, E); +impl_sr!(6, A, B, C, D, E, F); +impl_sr!(7, A, B, C, D, E, F, G); +impl_sr!(8, A, B, C, D, E, F, G, H); diff --git a/src/flash.rs b/src/flash.rs index ace5517..a0a4e2e 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -29,7 +29,7 @@ pub enum FlashErase { #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum FlashState { Idle, - Programming { address: u32, chunk: u32 }, + Programming { address: u32, chunk: usize }, Erasing(FlashErase), } @@ -37,6 +37,8 @@ enum FlashState { pub struct Flash { qspi: stm32h7xx_hal::xspi::Qspi, state: FlashState, + page_buffer: [u8; 256], + page_len: usize, } /* @@ -83,7 +85,7 @@ impl Flash { fn wait_write(&mut self) -> FlashResult<()> { loop { if self.write_complete()? { - return Ok(()) + return Ok(()); } } } @@ -167,6 +169,8 @@ impl Flash { let mut flash = Flash { qspi, state: FlashState::Idle, + page_buffer: [0u8; 256], + page_len: 0, }; //enable quad @@ -189,14 +193,56 @@ impl Flash { flash } + pub fn begin_erase(&mut self, op: FlashErase) -> NBFlashResult<()> { + match self.state { + FlashState::Idle => { + self.enable_write()?; + self.wait(); + match op { + FlashErase::Chip => self.write_command(0x60), + FlashErase::Sector4K(a) => self.qspi.write_extended( + QspiWord::U8(0xD7), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + FlashErase::Block32K(a) => self.qspi.write_extended( + QspiWord::U8(0x52), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + FlashErase::Block64K(a) => self.qspi.write_extended( + QspiWord::U8(0xD8), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + }?; + self.state = FlashState::Erasing(op); + Ok(()) + } + _ => Err(nbError::WouldBlock), + } + } + + pub fn poll_erase(&mut self) -> NBFlashResult<()> { + if self.write_complete()? { + self.state = FlashState::Idle; + Ok(()) + } else { + Err(nbError::WouldBlock) + } + } + /// Erase all or some of the chip. /// /// Remarks: /// - Erasing sets all the bits in the given area to `1`. /// - The memory array of the IS25LP064A/032A is organized into uniform 4 /// Kbyte sectors or - /// 32/64 Kbyte uniform blocks (a block consists of eight/sixteen adjacent - /// sectors respectively). + /// 32/64 Kbyte uniform blocks (a block consists of eight/sixteen adjacent + /// sectors respectively). pub fn erase(&mut self, op: FlashErase) -> NBFlashResult<()> { match self.state { FlashState::Erasing(e) => { @@ -266,17 +312,17 @@ impl Flash { /// to a 1. /// - The starting byte can be anywhere within the page (256 byte chunk). /// When the end of the - /// page is reached, the address will wrap around to the beginning of the - /// same page. If the data to be programmed are less than a full page, - /// the data of all other bytes on the same page will remain unchanged. + /// page is reached, the address will wrap around to the beginning of the + /// same page. If the data to be programmed are less than a full page, + /// the data of all other bytes on the same page will remain unchanged. pub fn program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { - let prog = |flash: &mut Self, chunk_index: u32| -> NBFlashResult<()> { - if let Some(chunk) = data.chunks(32).nth(chunk_index as usize) { + let prog = |flash: &mut Self, chunk_index: usize| -> NBFlashResult<()> { + if let Some(chunk) = data.chunks(32).nth(chunk_index) { flash.enable_write()?; flash.wait(); flash.qspi.write_extended( QspiWord::U8(0x02), - QspiWord::U24(address + chunk_index * 32), + QspiWord::U24(address + chunk_index as u32 * 32), QspiWord::None, chunk, )?; @@ -306,4 +352,61 @@ impl Flash { _ => panic!("invalid state for programming"), } } + + pub fn begin_program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { + for (src, dest) in data.iter().zip(self.page_buffer.iter_mut()) { + *dest = *src; + } + self.page_len = data.len().min(256); + + match self.state { + FlashState::Idle => { + self.enable_write()?; + self.wait(); + self.qspi.write_extended( + QspiWord::U8(0x02), + QspiWord::U24(address), + QspiWord::None, + &self.page_buffer[..self.page_len.min(32)], + )?; + self.state = FlashState::Programming { address, chunk: 1 }; + + Ok(()) + } + _ => Err(nbError::WouldBlock), + } + } + + pub fn poll_program(&mut self) -> NBFlashResult<()> { + match self.state { + FlashState::Programming { address, chunk } => { + if !self.write_complete()? { + return Err(nbError::WouldBlock); + } + + let offset = chunk * 32; + + if offset >= self.page_len { + self.state = FlashState::Idle; + return Ok(()); + } + + self.enable_write()?; + self.wait(); + self.qspi.write_extended( + QspiWord::U8(0x02), + QspiWord::U24(address + offset as u32), + QspiWord::None, + &self.page_buffer[offset..(offset + 32).min(self.page_len)], + )?; + self.state = FlashState::Programming { + address, + chunk: chunk + 1, + }; + + Err(nbError::WouldBlock) + } + _ => Ok(()), + } + } } diff --git a/src/gpio.rs b/src/gpio.rs index 74ae32c..5939bc7 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -11,22 +11,24 @@ pub use gpio::{ }, gpioc::{ PC0 as Daisy15, PC1 as Daisy20, PC10 as Daisy2, PC11 as Daisy1, PC12 as Daisy6, - PC4 as Daisy21, PC8 as Daisy4, PC9 as Daisy3, + PC2 as Daisy31, PC3 as Daisy32, PC4 as Daisy21, PC8 as Daisy4, PC9 as Daisy3, }, gpiod::{PD11 as Daisy26, PD2 as Daisy5}, gpiog::{PG10 as Daisy7, PG11 as Daisy8, PG9 as Daisy27}, }; use stm32h7xx_hal::{ gpio, - gpio::{gpioc::PC7, Alternate, Analog, Output, PushPull}, + gpio::{gpioc::PC7, gpiog::PG3, Alternate, Analog, Input, Output, PushPull}, }; pub type SeedLed = PC7>; +pub type SeedButton = PG3; /// GPIO struct for holding Daisy GPIO pins #[allow(clippy::upper_case_acronyms)] -pub struct GPIO { +pub struct SeedGPIO { pub led: SeedLed, + pub button: SeedButton, pub daisy0: Option>, pub daisy1: Option>, pub daisy2: Option>, @@ -58,13 +60,16 @@ pub struct GPIO { pub daisy28: Option>, pub daisy29: Option>, pub daisy30: Option>, + pub daisy31: Option>, + pub daisy32: Option>, } -impl GPIO { +impl SeedGPIO { /// Initialize the GPIOs #[allow(clippy::too_many_arguments)] pub fn init( seed_led: gpio::gpioc::PC7, + seed_button: gpio::gpiog::PG3, daisy0: Option>, daisy1: Option>, daisy2: Option>, @@ -96,11 +101,15 @@ impl GPIO { daisy28: Option>, daisy29: Option>, daisy30: Option>, - ) -> GPIO { + daisy31: Option>, + daisy32: Option>, + ) -> SeedGPIO { let led = seed_led.into_push_pull_output(); + let button = seed_button.into_floating_input(); Self { led, + button, daisy0, daisy1, daisy2, @@ -132,6 +141,130 @@ impl GPIO { daisy28, daisy29, daisy30, + daisy31, + daisy32, + } + } +} + +pub type PatchSmLed = PC7>; +pub type PatchSmButton = PG3; + +/// GPIO struct for holding Daisy GPIO pins +#[allow(clippy::upper_case_acronyms)] +pub struct PatchSmGPIO { + pub led: Option, + pub button: PatchSmButton, + pub a2: Option>, + pub a3: Option>, + pub a8: Option>, + pub a9: Option>, + pub b5: Option>, + pub b6: Option>, + pub b7: Option>, + pub b8: Option>, + pub b9: Option>, + pub b10: Option>, + pub c1: Option>, + pub c2: Option>, + pub c3: Option>, + pub c4: Option>, + pub c5: Option>, + // NOTE: C6-C9 are mismatched from the datasheet STM32 pin connections + // This is to match the documented "CVx" numbering that is used everywhere + pub c6: Option>, + pub c7: Option>, + pub c8: Option>, + pub c9: Option>, + pub c10: Option>, + pub d1: Option>>, + pub d2: Option>, + pub d3: Option>, + pub d4: Option>, + pub d5: Option>, + pub d6: Option>, + pub d7: Option>, + pub d8: Option>, + pub d9: Option>, + pub d10: Option>, +} + +impl PatchSmGPIO { + /// Initialize the GPIOs + #[allow(clippy::too_many_arguments)] + pub fn init( + patch_sm_led: gpio::gpioc::PC7, + patch_sm_button: gpio::gpiog::PG3, + a2: Option>, + a3: Option>, + a8: Option>, + a9: Option>, + b5: Option>, + b6: Option>, + b7: Option>, + b8: Option>, + b9: Option>, + b10: Option>, + c1: Option>, + c2: Option>, + c3: Option>, + c4: Option>, + c5: Option>, + // Physical pin arguments stay in "physical" order to make this obvious, + // and easier to confirm w/ datasheet.. + c6_phys: Option>, + c7_phys: Option>, + c8_phys: Option>, + c9_phys: Option>, + c10: Option>, + d1: Option>>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + ) -> PatchSmGPIO { + let led = patch_sm_led.into_push_pull_output(); + let button = patch_sm_button.into_floating_input(); + + Self { + led: Some(led), + button, + a2, + a3, + a8, + a9, + b5, + b6, + b7, + b8, + b9, + b10, + c1, + c2, + c3, + c4, + c5, + // Remap to match expected CV ordering in datasheet, frontpanels, etc. + c6: c9_phys, + c7: c8_phys, + c8: c6_phys, + c9: c7_phys, + c10, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, } } } diff --git a/src/hid.rs b/src/hid.rs index b7cff1c..3d7b6f8 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -47,7 +47,10 @@ where pub fn new(pin: T, switch_type: SwitchType) -> Self { Self { pin, - state: debounce_4(false), + state: debounce_4(match switch_type { + SwitchType::PullUp => true, + SwitchType::PullDown => false, + }), falling: false, rising: false, switch_type, @@ -151,7 +154,7 @@ where /// If the switch is being held pub fn is_held(&self) -> bool { if let Some(held_threshold) = self.held_threshold { - return self.falling && self.held_counter >= held_threshold + return self.falling && self.held_counter >= held_threshold; } false } @@ -211,7 +214,7 @@ impl AnalogControl { /// /// ```rust /// if let Ok(data) = adc1.read(control1.get_pin()) { - /// control1.update(data); + /// control1.update(data); /// } /// ``` pub fn update(&mut self, value: u32) { @@ -273,13 +276,8 @@ where /// Set the brightness of the LED from 0.0 to 1.0. pub fn set_brightness(&mut self, value: f32) { - let value = if value > 1.0 { - 1.0 - } else if value < 0.0 { - 0.0 - } else { - value - }; + let value = value.clamp(0., 1.); + match self.invert { // Bias for slower transitions in the low brightness range // TODO configurable? diff --git a/src/i2c.rs b/src/i2c.rs new file mode 100644 index 0000000..015fc8a --- /dev/null +++ b/src/i2c.rs @@ -0,0 +1,245 @@ +//! Blocking I2C with timeouts. +//! +//! The HAL's blocking transactions can spin forever in certain circumstances. +//! This provides the same functionality with hardcoded 2ms timeouts. + +use crate::hal; +use core::ops::Deref; +use cortex_m::peripheral::DWT; +use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; +use hal::i2c::I2c; +use hal::pac::i2c1::{isr, RegisterBlock}; + +/// How long a single step of a transaction may take before giving up. +pub const DEFAULT_TIMEOUT_US: u32 = 2_000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + /// The addressed device or a data byte was not acknowledged. + NotAcknowledge, + /// A misplaced START or STOP was observed. + Bus, + /// Another master won the bus. + Arbitration, + /// A step of the transaction never completed. The peripheral has been reset. + Timeout, +} + +/// An I2C master whose transactions always return. +pub struct I2cTimeout { + i2c: I2C, + timeout_cycles: u32, +} + +impl I2cTimeout +where + I2C: Deref, +{ + /// Take over a peripheral the HAL has already clocked and timed, e.g. + /// `I2cTimeout::new(device.I2C1.i2c(pins, freq, rec, &clocks))`. + pub fn new(i2c: I2c) -> Self + where + I2c: Free, + { + Self::with_timeout_us(i2c, DEFAULT_TIMEOUT_US) + } + + pub fn with_timeout_us(i2c: I2c, timeout_us: u32) -> Self + where + I2c: Free, + { + Self { + i2c: i2c.free_peripheral(), + timeout_cycles: timeout_us.saturating_mul(crate::MICROCYCLES), + } + } + + fn deadline(&self) -> u32 { + DWT::cycle_count().wrapping_add(self.timeout_cycles) + } + + fn expired(deadline: u32) -> bool { + // Signed distance, so the counter's wrap (~9 s at 480 MHz) is harmless. + (DWT::cycle_count().wrapping_sub(deadline) as i32) >= 0 + } + + /// Clear PE: the state machine and flags reset and SCL/SDA are released. + /// PE must stay low for at least three APB cycles. + fn reset(&mut self) { + self.i2c.cr1.modify(|_, w| w.pe().clear_bit()); + for _ in 0..3 { + let _ = self.i2c.cr1.read(); + } + self.i2c.cr1.modify(|_, w| w.pe().set_bit()); + } + + /// Spin until `done`, an error flag, or the deadline. + fn wait(&mut self, done: impl Fn(&isr::R) -> bool) -> Result<(), Error> { + let deadline = self.deadline(); + loop { + let isr = self.i2c.isr.read(); + + if done(&isr) { + return Ok(()); + } else if isr.berr().is_error() { + self.i2c.icr.write(|w| w.berrcf().set_bit()); + return Err(Error::Bus); + } else if isr.arlo().is_lost() { + self.i2c.icr.write(|w| w.arlocf().set_bit()); + return Err(Error::Arbitration); + } else if isr.nackf().bit_is_set() { + // The hardware sends STOP on its own after a NACK. + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + self.flush_txdr(); + return Err(Error::NotAcknowledge); + } else if Self::expired(deadline) { + self.reset(); + return Err(Error::Timeout); + } + } + } + + fn flush_txdr(&mut self) { + if self.i2c.isr.read().txis().bit_is_set() { + self.i2c.txdr.write(|w| w.txdata().bits(0)); + } + if self.i2c.isr.read().txe().is_not_empty() { + self.i2c.isr.write(|w| w.txe().set_bit()); + } + } + + fn start(&mut self, addr: u8, length: usize, read: bool, autoend: bool) -> Result<(), Error> { + // a previous address phase may still be active for up to half a + // bus cycle + let deadline = self.deadline(); + while self.i2c.cr2.read().start().bit_is_set() { + if Self::expired(deadline) { + self.reset(); + return Err(Error::Timeout); + } + } + + self.i2c.cr2.write(|w| { + let w = w + .sadd() + .bits(u16::from(addr) << 1) + .add10() + .clear_bit() + .nbytes() + .bits(length as u8) + .start() + .set_bit() + .autoend() + .bit(autoend); + match read { + true => w.rd_wrn().read(), + false => w.rd_wrn().write(), + } + }); + + Ok(()) + } + + fn send(&mut self, bytes: &[u8]) -> Result<(), Error> { + for byte in bytes { + // START (or the previous byte) was acknowledged. + self.wait(|isr| isr.txis().is_empty())?; + self.i2c.txdr.write(|w| w.txdata().bits(*byte)); + } + Ok(()) + } + + fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + for byte in buffer { + self.wait(|isr| isr.rxne().is_not_empty())?; + *byte = self.i2c.rxdr.read().rxdata().bits(); + } + Ok(()) + } +} + +impl Write for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + assert!(!bytes.is_empty() && bytes.len() < 256); + + self.start(addr, bytes.len(), false, false)?; + self.send(bytes)?; + self.wait(|isr| isr.tc().is_complete())?; + self.i2c.cr2.write(|w| w.stop().set_bit()); + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +impl WriteRead for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + assert!(!bytes.is_empty() && bytes.len() < 256); + assert!(!buffer.is_empty() && buffer.len() < 256); + + self.start(addr, bytes.len(), false, false)?; + self.send(bytes)?; + self.wait(|isr| isr.tc().is_complete())?; + // Repeated START; the previous address phase is over, so no wait. + self.i2c.cr2.write(|w| { + w.sadd() + .bits(u16::from(addr) << 1 | 1) + .add10() + .clear_bit() + .rd_wrn() + .read() + .nbytes() + .bits(buffer.len() as u8) + .start() + .set_bit() + .autoend() + .set_bit() + }); + self.receive(buffer)?; + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +impl Read for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + assert!(!buffer.is_empty() && buffer.len() < 256); + + self.start(addr, buffer.len(), true, true)?; + self.receive(buffer)?; + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +/// The HAL's `I2c::free` is per-instance rather than generic. +pub trait Free { + fn free_peripheral(self) -> I2C; +} + +macro_rules! free { + ($($I2CX:ident),+) => { + $( + impl Free for I2c { + fn free_peripheral(self) -> hal::pac::$I2CX { + self.free().0 + } + } + )+ + }; +} + +free!(I2C1, I2C2, I2C3, I2C4); diff --git a/src/lib.rs b/src/lib.rs index 5c2704c..08156b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,30 @@ #![no_std] #![allow(dead_code)] -// #[macro_use(singleton)] -// extern crate cortex_m; - -use cortex_m::asm::delay as delay_cycles; use stm32h7xx_hal::time::Hertz; pub const MILLI: u32 = 1_000; -pub const AUDIO_FRAME_RATE_HZ: u32 = 1_000; -pub const AUDIO_BLOCK_SIZE: u16 = 48; -pub const AUDIO_SAMPLE_RATE: usize = 48_000; -pub const AUDIO_SAMPLE_HZ: Hertz = Hertz::from_raw(48_000); +pub const MICRO: u32 = 1_000_000; pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32); pub const MILICYCLES: u32 = CLOCK_RATE_HZ.raw() / MILLI; +pub const MICROCYCLES: u32 = CLOCK_RATE_HZ.raw() / MICRO; pub type FrameTimer = stm32h7xx_hal::timer::Timer; pub use stm32h7xx_hal as hal; pub mod audio; +pub mod bootloader; +pub mod delay; +pub mod devices; pub mod flash; pub mod gpio; pub mod hid; +pub mod i2c; pub mod logger; pub mod mpu; pub mod prelude; pub mod sdmmc; pub mod sdram; pub mod system; - -// Delay for ms, note if interrupts are active delay time will extend -pub fn delay_ms(ms: u32) { - delay_cycles(ms * MILICYCLES); -} diff --git a/src/logger.rs b/src/logger.rs index 38d752d..eef9896 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -62,6 +62,7 @@ cfg_if::cfg_if! { } } else if #[cfg(any(feature = "log-semihosting"))] { + #[cfg(feature = "panic-semihosting")] use panic_semihosting as _; use lazy_static::lazy_static; @@ -71,10 +72,10 @@ cfg_if::cfg_if! { use cortex_m_log::printer::semihosting; use cortex_m_log::printer::semihosting::Semihosting; use cortex_m_log::modes::InterruptOk; - use cortex_m_semihosting::hio::HStdout; + use cortex_m_semihosting::hio::HostStream; lazy_static! { - static ref LOGGER: Logger> = Logger { + static ref LOGGER: Logger> = Logger { level: LevelFilter::Info, inner: semihosting::InterruptOk::<_>::stdout().expect("Get Semihosting stdout"), }; diff --git a/src/mpu.rs b/src/mpu.rs index 66ee759..4dd3608 100644 --- a/src/mpu.rs +++ b/src/mpu.rs @@ -1,6 +1,5 @@ -//!Configure MPU +//! Configure MPU -/// /// Based on example from: /// https://github.com/richardeoin/stm32h7-fmc/blob/master/examples/stm32h747i-disco.rs /// @@ -10,7 +9,7 @@ /// /// Function will panic if `size` is not a power of 2. Function /// will panic if `size` is not at least 32 bytes. - +/// /// Refer to ARMĀ®v7-M Architecture Reference Manual ARM DDI 0403 /// Version E.b Section B3.5 use log::info; @@ -50,7 +49,7 @@ pub fn enable(mpu: &mut cortex_m::peripheral::MPU, scb: &mut cortex_m::periphera fn log2minus1(sz: u32) -> u32 { for x in 5..=31 { if sz == (1 << x) { - return x - 1 + return x - 1; } } panic!("Unknown memory region size!"); diff --git a/src/system.rs b/src/system.rs deleted file mode 100644 index 7c0184d..0000000 --- a/src/system.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! Contains setup for Daisy board hardware. -#![allow(dead_code)] - -use log::info; -use stm32h7xx_hal::{ - adc, - delay::Delay, - dma, - prelude::*, - rcc, stm32, - stm32::TIM2, - time::{Hertz, MegaHertz, MilliSeconds}, - timer::{Event, Timer}, -}; - -use crate::{audio::Audio, *}; - -const START_OF_DRAM2: u32 = 0x30000000; -const DMA_MEM_SIZE: usize = 32 * 1024; - -const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16); -const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200); -const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200); - -// PCLKx -const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4); -// 49_152_344 -// PLL1 -const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; -const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); -const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); -// PLL2 -const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); -const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? -const PLL2_R_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 4); // No divder given, what's the default? - -const PLL3_P_HZ: Hertz = Hertz::from_raw(AUDIO_SAMPLE_HZ.raw() * 257); -const PLL3_Q_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); -const PLL3_R_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); - -pub struct System { - pub gpio: crate::gpio::GPIO, - pub audio: audio::Audio, - pub exti: stm32::EXTI, - pub syscfg: stm32::SYSCFG, - pub adc1: adc::Adc, - pub adc2: adc::Adc, - pub timer2: Timer, - pub sdram: &'static mut [f32], - pub flash: crate::flash::Flash, -} - -impl System { - /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { - // Power - let pwr = pwr.constrain(); - let vos = pwr.vos0(syscfg).freeze(); - - rcc.constrain() - .use_hse(HSE_CLOCK_MHZ.convert()) - .sys_ck(CLOCK_RATE_HZ) - .pclk1(PCLK_HZ) // DMA clock - // PLL1 - .pll1_strategy(rcc::PllConfigStrategy::Iterative) - .pll1_p_ck(PLL1_P_HZ) - .pll1_q_ck(PLL1_Q_HZ) - .pll1_r_ck(PLL1_R_HZ) - // PLL2 - .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input - // .pll2_q_ck(PLL2_Q_HZ) - // .pll2_r_ck(PLL2_R_HZ) - // PLL3 - .pll3_strategy(rcc::PllConfigStrategy::Fractional) - .pll3_p_ck(PLL3_P_HZ) // used for SAI1 - .pll3_q_ck(PLL3_Q_HZ) - .pll3_r_ck(PLL3_R_HZ) - .freeze(vos, syscfg) - } - - /// Setup cache - pub fn init_cache( - scb: &mut cortex_m::peripheral::SCB, - cpuid: &mut cortex_m::peripheral::CPUID, - ) { - scb.enable_icache(); - scb.enable_dcache(cpuid); - } - - /// Enable debug - pub fn init_debug(dcb: &mut cortex_m::peripheral::DCB, dwt: &mut cortex_m::peripheral::DWT) { - dcb.enable_trace(); - cortex_m::peripheral::DWT::unlock(); - dwt.enable_cycle_counter(); - } - - /// Batteries included initialization - pub fn init(mut core: rtic::export::Peripherals, device: stm32::Peripherals) -> System { - info!("Starting system init"); - let ccdr = Self::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - - info!("Setup up DMA RAM in DRAM2..."); - crate::mpu::init_dma( - &mut core.MPU, - &mut core.SCB, - START_OF_DRAM2 as *mut u32, - DMA_MEM_SIZE, - ); - - // log_clocks(&ccdr); - let mut delay = Delay::new(core.SYST, ccdr.clocks); - // Setup ADCs - let (adc1, adc2) = adc::adc12( - device.ADC1, - device.ADC2, - 4.MHz(), - &mut delay, - ccdr.peripheral.ADC12, - &ccdr.clocks, - ); - - Self::init_debug(&mut core.DCB, &mut core.DWT); - - // Timers - let mut timer2 = device.TIM2.timer( - MilliSeconds::from_ticks(100).into_rate(), - ccdr.peripheral.TIM2, - &ccdr.clocks, - ); - timer2.listen(Event::TimeOut); - - // let mut timer3 = device - // .TIM3 - // .timer(1.ms(), ccdr.peripheral.TIM3, &mut ccdr.clocks); - // timer3.listen(Event::TimeOut); - - info!("Setting up GPIOs..."); - let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); - let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); - let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); - let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); - let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE); - let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF); - let gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); - let gpioh = device.GPIOH.split(ccdr.peripheral.GPIOH); - let gpioi = device.GPIOI.split(ccdr.peripheral.GPIOI); - - // Configure SDRAM - info!("Setting up SDRAM..."); - let sdram = crate::sdram::Sdram::new( - device.FMC, - ccdr.peripheral.FMC, - &ccdr.clocks, - &mut delay, - &mut core.SCB, - &mut core.MPU, - gpiod.pd0, - gpiod.pd1, - gpiod.pd8, - gpiod.pd9, - gpiod.pd10, - gpiod.pd14, - gpiod.pd15, - gpioe.pe0, - gpioe.pe1, - gpioe.pe7, - gpioe.pe8, - gpioe.pe9, - gpioe.pe10, - gpioe.pe11, - gpioe.pe12, - gpioe.pe13, - gpioe.pe14, - gpioe.pe15, - gpiof.pf0, - gpiof.pf1, - gpiof.pf2, - gpiof.pf3, - gpiof.pf4, - gpiof.pf5, - gpiof.pf11, - gpiof.pf12, - gpiof.pf13, - gpiof.pf14, - gpiof.pf15, - gpiog.pg0, - gpiog.pg1, - gpiog.pg2, - gpiog.pg4, - gpiog.pg5, - gpiog.pg8, - gpiog.pg15, - gpioh.ph2, - gpioh.ph3, - gpioh.ph5, - gpioh.ph8, - gpioh.ph9, - gpioh.ph10, - gpioh.ph11, - gpioh.ph12, - gpioh.ph13, - gpioh.ph14, - gpioh.ph15, - gpioi.pi0, - gpioi.pi1, - gpioi.pi2, - gpioi.pi3, - gpioi.pi4, - gpioi.pi5, - gpioi.pi6, - gpioi.pi7, - gpioi.pi9, - gpioi.pi10, - ) - .into(); - - let dma1_streams = dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1); - - info!("Setup up Audio..."); - let audio = Audio::new( - dma1_streams.0, - dma1_streams.1, - device.SAI1, - ccdr.peripheral.SAI1, - device.I2C2, - ccdr.peripheral.I2C2, - gpioe.pe2, - gpioe.pe3, - gpioe.pe4, - gpioe.pe5, - gpioe.pe6, - gpioh.ph4, - gpiob.pb11, - &ccdr.clocks, - ); - - // Setup GPIOs - let gpio = crate::gpio::GPIO::init( - gpioc.pc7, - Some(gpiob.pb12), - Some(gpioc.pc11), - Some(gpioc.pc10), - Some(gpioc.pc9), - Some(gpioc.pc8), - Some(gpiod.pd2), - Some(gpioc.pc12), - Some(gpiog.pg10), - Some(gpiog.pg11), - Some(gpiob.pb4), - Some(gpiob.pb5), - Some(gpiob.pb8), - Some(gpiob.pb9), - Some(gpiob.pb6), - Some(gpiob.pb7), - Some(gpioc.pc0), - Some(gpioa.pa3), - Some(gpiob.pb1), - Some(gpioa.pa7), - Some(gpioa.pa6), - Some(gpioc.pc1), - Some(gpioc.pc4), - Some(gpioa.pa5), - Some(gpioa.pa4), - Some(gpioa.pa1), - Some(gpioa.pa0), - Some(gpiod.pd11), - Some(gpiog.pg9), - Some(gpioa.pa2), - Some(gpiob.pb14), - Some(gpiob.pb15), - ); - - // Setup cache - Self::init_cache(&mut core.SCB, &mut core.CPUID); - - info!("System init done!"); - - //setup flash - let flash = crate::flash::Flash::new( - device.QUADSPI, - ccdr.peripheral.QSPI, - &ccdr.clocks, - gpiof.pf6, - gpiof.pf7, - gpiof.pf8, - gpiof.pf9, - gpiof.pf10, - gpiog.pg6, - ); - - System { - gpio, - audio, - exti: device.EXTI, - syscfg: device.SYSCFG, - adc1, - adc2, - timer2, - sdram, - flash, - } - } -} - -fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { - info!("Core {}", ccdr.clocks.c_ck()); - info!("hclk {}", ccdr.clocks.hclk()); - info!("pclk1 {}", ccdr.clocks.pclk1()); - info!("pclk2 {}", ccdr.clocks.pclk2()); - info!("pclk3 {}", ccdr.clocks.pclk2()); - info!("pclk4 {}", ccdr.clocks.pclk4()); - info!( - "PLL1\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll1_p_ck(), - ccdr.clocks.pll1_q_ck(), - ccdr.clocks.pll1_r_ck() - ); - info!( - "PLL2\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll2_p_ck(), - ccdr.clocks.pll2_q_ck(), - ccdr.clocks.pll2_r_ck() - ); - info!( - "PLL3\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll3_p_ck(), - ccdr.clocks.pll3_q_ck(), - ccdr.clocks.pll3_r_ck() - ); -} diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..00bb358 --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,184 @@ +//! Contains setup for Daisy board hardware. +#![allow(dead_code)] + +use hal::rcc::CoreClocks; +use log::info; +use stm32h7xx_hal::{ + gpio, + prelude::*, + rcc, stm32, + time::{Hertz, MegaHertz}, +}; + +use crate::*; + +pub mod patch_sm; +pub mod seed; + +pub use patch_sm::{MinimalPatchSmSystem, PatchSmSystem}; +pub use seed::{MinimalSeedSystem, SeedSystem}; + +pub enum Board { + Seed(seed::Version), + PatchSm, +} + +const START_OF_DRAM2: u32 = 0x30000000; +const DMA_MEM_SIZE: usize = 32 * 1024; + +const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16); +const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200); +const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200); + +// PCLKx +const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4); +// 49_152_344 +// PLL1 +const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; +const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); +const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); +// PLL2 +const PLL2_P_HZ: Hertz = Hertz::from_raw(40_000_000); +const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? +const PLL2_R_HZ: Hertz = Hertz::from_raw(200_000_000); + +fn init_clocks( + mut pwr: stm32::PWR, + mut rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, +) -> rcc::Ccdr { + // Power + initialize_backup_sram(&mut pwr, &mut rcc); + let pwr = pwr.constrain(); + let vos = pwr.vos0(syscfg).freeze(); + + let PLL3_P_HZ = Hertz::from_raw(audio_sample_rate.raw() * 257); + let PLL3_Q_HZ = Hertz::from_raw(PLL3_P_HZ.raw()); + let PLL3_R_HZ = Hertz::from_raw(PLL3_P_HZ.raw()); + + rcc.constrain() + .use_hse(HSE_CLOCK_MHZ.convert()) + .sys_ck(CLOCK_RATE_HZ) + .pclk1(PCLK_HZ) // DMA clock + // PLL1 + .pll1_strategy(rcc::PllConfigStrategy::Iterative) + .pll1_p_ck(PLL1_P_HZ) + .pll1_q_ck(PLL1_Q_HZ) + .pll1_r_ck(PLL1_R_HZ) + // PLL2 + .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input + // .pll2_q_ck(PLL2_Q_HZ) + .pll2_r_ck(PLL2_R_HZ) + // PLL3 + .pll3_strategy(rcc::PllConfigStrategy::Fractional) + .pll3_p_ck(PLL3_P_HZ) // used for SAI1 + .pll3_q_ck(PLL3_Q_HZ) + .pll3_r_ck(PLL3_R_HZ) + .freeze(vos, syscfg) +} + +pub fn initialize_backup_sram(pwr: &mut stm32::PWR, rcc: &mut stm32::RCC) { + pwr.cr1.modify(|_, w| w.dbp().set_bit()); + pwr.cr2.modify(|_, w| w.bren().set_bit()); + + loop { + if pwr.cr1.read().dbp().bit_is_set() { + break; + } + } + + rcc.ahb4enr.modify(|_, w| w.bkpramen().set_bit()); + // read it back + let _bit = rcc.ahb4enr.read().bkpramen().bit(); +} + +pub struct InternalUsbPins { + pub dp: gpio::gpioa::PA12, + pub dm: gpio::gpioa::PA11, +} + +/// All peripherals and other resources required for the system +pub struct SystemResources<'a> { + pub clocks: &'a CoreClocks, + pub adc1: stm32::ADC1, + pub adc2: stm32::ADC2, + pub adc12_rec: rcc::rec::Adc12, + pub dac: stm32::DAC, + pub dac12_rec: rcc::rec::Dac12, + pub syst: stm32::SYST, + pub mpu: &'a mut stm32::MPU, + pub scb: &'a mut stm32::SCB, + pub dcb: &'a mut stm32::DCB, + pub dwt: &'a mut stm32::DWT, + pub fmc: stm32::FMC, + pub fmc_rec: rcc::rec::Fmc, + pub i2c2: stm32::I2C2, + pub i2c2_rec: rcc::rec::I2c2, + pub cpuid: &'a mut cortex_m::peripheral::CPUID, + pub qspi: stm32::QUADSPI, + pub qspi_rec: rcc::rec::Qspi, + + pub sai1: stm32::SAI1, + pub sai1_rec: rcc::rec::Sai1, + + pub gpioa: stm32::GPIOA, + pub gpioa_rec: rcc::rec::Gpioa, + + pub gpiob: stm32::GPIOB, + pub gpiob_rec: rcc::rec::Gpiob, + + pub gpioc: stm32::GPIOC, + pub gpioc_rec: rcc::rec::Gpioc, + + pub gpiod: stm32::GPIOD, + pub gpiod_rec: rcc::rec::Gpiod, + + pub gpioe: stm32::GPIOE, + pub gpioe_rec: rcc::rec::Gpioe, + + pub gpiof: stm32::GPIOF, + pub gpiof_rec: rcc::rec::Gpiof, + + pub gpiog: stm32::GPIOG, + pub gpiog_rec: rcc::rec::Gpiog, + + pub gpioh: stm32::GPIOH, + pub gpioh_rec: rcc::rec::Gpioh, + + pub gpioi: stm32::GPIOI, + pub gpioi_rec: rcc::rec::Gpioi, + + pub dma1: stm32::DMA1, + pub dma1_rec: rcc::rec::Dma1, + + pub block_size: usize, + pub sample_rate: Hertz, +} + +fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { + info!("Core {}", ccdr.clocks.c_ck()); + info!("hclk {}", ccdr.clocks.hclk()); + info!("pclk1 {}", ccdr.clocks.pclk1()); + info!("pclk2 {}", ccdr.clocks.pclk2()); + info!("pclk3 {}", ccdr.clocks.pclk2()); + info!("pclk4 {}", ccdr.clocks.pclk4()); + info!( + "PLL1\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll1_p_ck(), + ccdr.clocks.pll1_q_ck(), + ccdr.clocks.pll1_r_ck() + ); + info!( + "PLL2\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll2_p_ck(), + ccdr.clocks.pll2_q_ck(), + ccdr.clocks.pll2_r_ck() + ); + info!( + "PLL3\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll3_p_ck(), + ccdr.clocks.pll3_q_ck(), + ccdr.clocks.pll3_r_ck() + ); +} diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs new file mode 100644 index 0000000..7223fa3 --- /dev/null +++ b/src/system/patch_sm.rs @@ -0,0 +1,480 @@ +use log::info; +use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; + +use crate::{audio::Audio, *}; + +use super::*; + +pub struct PatchSmSystem { + pub gpio: crate::gpio::PatchSmGPIO, + pub audio: audio::Audio, + pub adc1: adc::Adc, + pub adc2: adc::Adc, + pub dac: PatchSmDac, + pub sdram: &'static mut [f32], + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +pub struct PatchSmDac { + c1: Option>, + c2: Option>, +} + +impl PatchSmDac { + fn new(c1: dac::C1, c2: dac::C2) -> Self { + Self { + c1: Some(c1), + c2: Some(c2), + } + } + + pub fn take_c1(&mut self) -> Option> { + self.c1.take() + } + + pub fn take_c2(&mut self) -> Option> { + self.c2.take() + } +} + +pub struct MinimalPatchSmSystem { + pub gpio: crate::gpio::PatchSmGPIO, + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +impl MinimalPatchSmSystem { + /// Initialize clocks + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, audio_sample_rate) + } + + pub fn new(resources: SystemResources) -> Self { + let delay = Delay::new(resources.syst, *resources.clocks); + + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + + // Set up GPIOs + let gpio = crate::gpio::PatchSmGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiob.pb14), + Some(gpiob.pb15), + Some(gpioc.pc14), + Some(gpioc.pc13), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiog.pg14), + Some(gpiog.pg13), + None, + Some(gpioa.pa7), + Some(gpioa.pa2), + Some(gpioa.pa6), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioc.pc4), + Some(gpioc.pc0), + Some(gpioc.pc1), + None, + Some(gpiob.pb4), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpioc.pc12), + Some(gpiod.pd2), + Some(gpioc.pc2), + Some(gpioc.pc3), + Some(gpiod.pd3), + ); + + PatchSmSystem::init_debug(resources.dcb, resources.dwt); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + info!("Minimal system initialization complete."); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + Self { + gpio, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} + +#[macro_export] +macro_rules! patch_sm_system_init { + ($core:ident, $device:ident, $ccdr:ident) => { + libdaisy::system_init!( + $core, + $device, + $ccdr, + libdaisy::audio::BLOCK_SIZE_MAX, + 48_000 + ); + }; + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr, $sample_rate:expr) => {{ + let resources = libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: $block_size, + sample_rate: $sample_rate, + }; + + libdaisy::system::PatchSmSystem::init(resources) + }}; +} + +#[macro_export] +macro_rules! patch_sm_minimal_init { + ($core:ident, $device:ident, $ccdr:ident) => {{ + let resources = ::libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: 0, + }; + + ::libdaisy::system::MinimalPatchSmSystem::new(resources) + }}; +} + +impl PatchSmSystem { + /// Initialize clocks + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, audio_sample_rate) + } + + /// Set up cache + pub fn init_cache( + scb: &mut cortex_m::peripheral::SCB, + cpuid: &mut cortex_m::peripheral::CPUID, + ) { + scb.enable_icache(); + scb.enable_dcache(cpuid); + } + + /// Enable debug + pub fn init_debug(dcb: &mut cortex_m::peripheral::DCB, dwt: &mut cortex_m::peripheral::DWT) { + dcb.enable_trace(); + cortex_m::peripheral::DWT::unlock(); + dwt.enable_cycle_counter(); + } + + /// Batteries included initialization + pub fn init(resources: SystemResources) -> PatchSmSystem { + info!("Starting system init"); + info!("Set up up DMA RAM in DRAM2..."); + crate::mpu::init_dma( + resources.mpu, + resources.scb, + START_OF_DRAM2 as *mut u32, + DMA_MEM_SIZE, + ); + + let mut delay = Delay::new(resources.syst, *resources.clocks); + + // Set up ADCs + let (adc1, adc2) = adc::adc12( + resources.adc1, + resources.adc2, + 2.MHz(), + &mut delay, + resources.adc12_rec, + resources.clocks, + ); + + Self::init_debug(resources.dcb, resources.dwt); + + info!("Setting up GPIOs..."); + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpioe = resources.gpioe.split(resources.gpioe_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + let gpioh = resources.gpioh.split(resources.gpioh_rec); + let gpioi = resources.gpioi.split(resources.gpioi_rec); + + // Configure SDRAM + info!("Setting up SDRAM..."); + let sdram = crate::sdram::Sdram::new( + resources.fmc, + resources.fmc_rec, + resources.clocks, + &mut delay, + resources.scb, + resources.mpu, + gpiod.pd0, + gpiod.pd1, + gpiod.pd8, + gpiod.pd9, + gpiod.pd10, + gpiod.pd14, + gpiod.pd15, + gpioe.pe0, + gpioe.pe1, + gpioe.pe7, + gpioe.pe8, + gpioe.pe9, + gpioe.pe10, + gpioe.pe11, + gpioe.pe12, + gpioe.pe13, + gpioe.pe14, + gpioe.pe15, + gpiof.pf0, + gpiof.pf1, + gpiof.pf2, + gpiof.pf3, + gpiof.pf4, + gpiof.pf5, + gpiof.pf11, + gpiof.pf12, + gpiof.pf13, + gpiof.pf14, + gpiof.pf15, + gpiog.pg0, + gpiog.pg1, + gpiog.pg2, + gpiog.pg4, + gpiog.pg5, + gpiog.pg8, + gpiog.pg15, + gpioh.ph2, + gpioh.ph3, + gpioh.ph5, + gpioh.ph8, + gpioh.ph9, + gpioh.ph10, + gpioh.ph11, + gpioh.ph12, + gpioh.ph13, + gpioh.ph14, + gpioh.ph15, + gpioi.pi0, + gpioi.pi1, + gpioi.pi2, + gpioi.pi3, + gpioi.pi4, + gpioi.pi5, + gpioi.pi6, + gpioi.pi7, + gpioi.pi9, + gpioi.pi10, + ) + .into(); + + let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); + + info!("Set up Audio..."); + let audio = Audio::patch_sm( + dma1_streams.0, + dma1_streams.1, + resources.sai1, + resources.sai1_rec, + resources.i2c2, + resources.i2c2_rec, + gpioe.pe2, + gpioe.pe3, + gpioe.pe4, + gpioe.pe5, + gpioe.pe6, + gpiob.pb10, + gpiob.pb11, + resources.clocks, + &mut delay, + resources.block_size, + resources.sample_rate, + ); + + // Set up GPIOs + let gpio = crate::gpio::PatchSmGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiob.pb14), + Some(gpiob.pb15), + Some(gpioc.pc14), + Some(gpioc.pc13), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiog.pg14), + Some(gpiog.pg13), + None, + Some(gpioa.pa7), + Some(gpioa.pa2), + Some(gpioa.pa6), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioc.pc4), + Some(gpioc.pc0), + Some(gpioc.pc1), + None, + Some(gpiob.pb4), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpioc.pc12), + Some(gpiod.pd2), + Some(gpioc.pc2), + Some(gpioc.pc3), + Some(gpiod.pd3), + ); + + // Set up cache + Self::init_cache(resources.scb, resources.cpuid); + + info!("Set up DAC..."); + let (c1, c2) = dac::dac(resources.dac, (gpioa.pa4, gpioa.pa5), resources.dac12_rec); + + info!("Patch Submodule system init done!"); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + PatchSmSystem { + gpio, + audio, + adc1, + adc2, + dac: PatchSmDac::new(c1, c2), + sdram, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} diff --git a/src/system/seed.rs b/src/system/seed.rs new file mode 100644 index 0000000..89ddb09 --- /dev/null +++ b/src/system/seed.rs @@ -0,0 +1,620 @@ +use log::info; +use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; + +use crate::{audio::Audio, *}; + +use super::*; + +pub struct SeedSystem { + pub gpio: crate::gpio::SeedGPIO, + pub audio: audio::Audio, + pub adc1: adc::Adc, + pub adc2: adc::Adc, + pub dac: SeedDac, + pub sdram: &'static mut [f32], + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +pub struct SeedDac { + c1: Option>, + c2: Option>, + state: SeedDacState, +} + +enum SeedDacState { + Uninitialized { + dac: stm32::DAC, + dac12_rec: rcc::rec::Dac12, + }, + Channel1Initialized, + Channel2Initialized, + BothInitialized, +} + +impl SeedDac { + fn new(dac: stm32::DAC, dac12_rec: rcc::rec::Dac12) -> Self { + Self { + c1: None, + c2: None, + state: SeedDacState::Uninitialized { dac, dac12_rec }, + } + } + + pub fn both_initialized(&self) -> bool { + matches!(self.state, SeedDacState::BothInitialized) + } + + pub fn init_channels( + &mut self, + c1_pin: hal::gpio::gpioa::PA4, + c2_pin: hal::gpio::gpioa::PA5, + ) -> Option<()> { + match self.state { + SeedDacState::BothInitialized => return Some(()), + SeedDacState::Channel1Initialized | SeedDacState::Channel2Initialized => { + return None; + } + SeedDacState::Uninitialized { .. } => {} + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::BothInitialized) + else { + return None; + }; + let (c1, c2) = dac::dac(dac, (c1_pin, c2_pin), dac12_rec); + + self.c1 = Some(c1); + self.c2 = Some(c2); + Some(()) + } + + fn init_c1(&mut self, c1_pin: hal::gpio::gpioa::PA4) -> Option<()> { + if self.c1.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::Channel1Initialized) + else { + return None; + }; + let c1 = dac::dac(dac, c1_pin, dac12_rec); + self.c1 = Some(c1); + Some(()) + } + + fn init_c2(&mut self, c2_pin: hal::gpio::gpioa::PA5) -> Option<()> { + if self.c2.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::Channel2Initialized) + else { + return None; + }; + let c2 = dac::dac(dac, c2_pin, dac12_rec); + self.c2 = Some(c2); + Some(()) + } + + pub fn take_c1_with_pin( + &mut self, + c1_pin: hal::gpio::gpioa::PA4, + ) -> Option> { + if self.c1.is_none() { + self.init_c1(c1_pin)?; + } + self.c1.take() + } + + pub fn take_c2_with_pin( + &mut self, + c2_pin: hal::gpio::gpioa::PA5, + ) -> Option> { + if self.c2.is_none() { + self.init_c2(c2_pin)?; + } + self.c2.take() + } + + pub fn take_c1(&mut self) -> Option> { + self.c1.take() + } + + pub fn take_c2(&mut self) -> Option> { + self.c2.take() + } +} + +pub struct MinimalSeedSystem { + pub gpio: crate::gpio::SeedGPIO, + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +impl MinimalSeedSystem { + /// Initialize clocks + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, sample_rate) + } + + pub fn new(resources: SystemResources) -> Self { + let delay = Delay::new(resources.syst, *resources.clocks); + + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + + // Set up GPIOs + let gpio = crate::gpio::SeedGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpiob.pb12), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpiod.pd2), + Some(gpioc.pc12), + Some(gpiog.pg10), + Some(gpiog.pg11), + Some(gpiob.pb4), + Some(gpiob.pb5), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiob.pb6), + Some(gpiob.pb7), + Some(gpioc.pc0), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioa.pa7), + Some(gpioa.pa6), + Some(gpioc.pc1), + Some(gpioc.pc4), + Some(gpioa.pa5), + Some(gpioa.pa4), + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiod.pd11), + Some(gpiog.pg9), + Some(gpioa.pa2), + Some(gpiob.pb14), + Some(gpiob.pb15), + None, + None, + ); + + SeedSystem::init_debug(resources.dcb, resources.dwt); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + info!("Minimal system initialization complete."); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + Self { + gpio, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} + +#[macro_export] +macro_rules! seed_system_init { + ($core:ident, $device:ident, $ccdr:ident) => { + libdaisy::system_init!( + $core, + $device, + $ccdr, + libdaisy::audio::BLOCK_SIZE_MAX, + 48_000, + ); + }; + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr, $sample_rate:expr) => {{ + let resources = libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: $block_size, + sample_rate: $sample_rate, + }; + + libdaisy::system::SeedSystem::init(resources) + }}; +} + +#[macro_export] +macro_rules! seed_minimal_init { + ($core:ident, $device:ident, $ccdr:ident, $sample_rate:ident) => {{ + let resources = ::libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: 0, + sample_rate: $sample_rate, + }; + + ::libdaisy::system::MinimalSeedSystem::new(resources) + }}; +} + +#[derive(Clone, Copy)] +pub enum Version { + Seed, + Seed1_1, + Seed2DFM, + Seed3, +} + +impl SeedSystem { + fn detect_version( + s2dfm_pin: hal::gpio::gpiod::PD4, + seed1_1_pin: hal::gpio::gpiod::PD3, + seed3_pin: hal::gpio::gpioh::PH6, + ) -> Version { + let seed1_1_pin = seed1_1_pin.into_pull_up_input(); + let s2dfm_pin = s2dfm_pin.into_pull_up_input(); + let seed3_pin = seed3_pin.into_pull_up_input(); + + let seed1_1 = seed1_1_pin.is_low(); + let s2dfm = s2dfm_pin.is_low(); + let seed3 = seed3_pin.is_low(); + + // Deinitialize the pins after reading + s2dfm_pin.into_analog(); + seed1_1_pin.into_analog(); + seed3_pin.into_analog(); + + if seed1_1 { + Version::Seed1_1 + } else if s2dfm { + Version::Seed2DFM + } else if seed3 { + Version::Seed3 + } else { + Version::Seed + } + } + + /// Initialize clocks + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, sample_rate) + } + + /// Set up cache + pub fn init_cache( + scb: &mut cortex_m::peripheral::SCB, + cpuid: &mut cortex_m::peripheral::CPUID, + ) { + scb.enable_icache(); + scb.enable_dcache(cpuid); + } + + /// Enable debug + pub fn init_debug(dcb: &mut cortex_m::peripheral::DCB, dwt: &mut cortex_m::peripheral::DWT) { + dcb.enable_trace(); + cortex_m::peripheral::DWT::unlock(); + dwt.enable_cycle_counter(); + } + + /// Batteries included initialization + pub fn init(resources: SystemResources) -> SeedSystem { + info!("Starting system init"); + info!("Set up up DMA RAM in DRAM2..."); + crate::mpu::init_dma( + resources.mpu, + resources.scb, + START_OF_DRAM2 as *mut u32, + DMA_MEM_SIZE, + ); + + let mut delay = Delay::new(resources.syst, *resources.clocks); + + // Set up ADCs + let (adc1, adc2) = adc::adc12( + resources.adc1, + resources.adc2, + 2.MHz(), + &mut delay, + resources.adc12_rec, + resources.clocks, + ); + + Self::init_debug(resources.dcb, resources.dwt); + + info!("Setting up GPIOs..."); + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpioe = resources.gpioe.split(resources.gpioe_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + let gpioh = resources.gpioh.split(resources.gpioh_rec); + let gpioi = resources.gpioi.split(resources.gpioi_rec); + + // Configure SDRAM + info!("Setting up SDRAM..."); + let sdram = crate::sdram::Sdram::new( + resources.fmc, + resources.fmc_rec, + resources.clocks, + &mut delay, + resources.scb, + resources.mpu, + gpiod.pd0, + gpiod.pd1, + gpiod.pd8, + gpiod.pd9, + gpiod.pd10, + gpiod.pd14, + gpiod.pd15, + gpioe.pe0, + gpioe.pe1, + gpioe.pe7, + gpioe.pe8, + gpioe.pe9, + gpioe.pe10, + gpioe.pe11, + gpioe.pe12, + gpioe.pe13, + gpioe.pe14, + gpioe.pe15, + gpiof.pf0, + gpiof.pf1, + gpiof.pf2, + gpiof.pf3, + gpiof.pf4, + gpiof.pf5, + gpiof.pf11, + gpiof.pf12, + gpiof.pf13, + gpiof.pf14, + gpiof.pf15, + gpiog.pg0, + gpiog.pg1, + gpiog.pg2, + gpiog.pg4, + gpiog.pg5, + gpiog.pg8, + gpiog.pg15, + gpioh.ph2, + gpioh.ph3, + gpioh.ph5, + gpioh.ph8, + gpioh.ph9, + gpioh.ph10, + gpioh.ph11, + gpioh.ph12, + gpioh.ph13, + gpioh.ph14, + gpioh.ph15, + gpioi.pi0, + gpioi.pi1, + gpioi.pi2, + gpioi.pi3, + gpioi.pi4, + gpioi.pi5, + gpioi.pi6, + gpioi.pi7, + gpioi.pi9, + gpioi.pi10, + ) + .into(); + + let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); + + info!("Set up Audio..."); + let version = Self::detect_version(gpiod.pd4, gpiod.pd3, gpioh.ph6); + + let audio = Audio::seed( + dma1_streams.0, + dma1_streams.1, + resources.sai1, + resources.sai1_rec, + resources.i2c2, + resources.i2c2_rec, + gpioe.pe2, + gpioe.pe3, + gpioe.pe4, + gpioe.pe5, + gpioe.pe6, + gpioh.ph4, + gpiob.pb11, + resources.clocks, + version, + &mut delay, + resources.block_size, + resources.sample_rate, + ); + + let (d31, d32) = match version { + Version::Seed2DFM => (Some(gpioc.pc2), Some(gpioc.pc3)), + _ => (None, None), + }; + + // Set up GPIOs + let gpio = crate::gpio::SeedGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpiob.pb12), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpiod.pd2), + Some(gpioc.pc12), + Some(gpiog.pg10), + Some(gpiog.pg11), + Some(gpiob.pb4), + Some(gpiob.pb5), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiob.pb6), + Some(gpiob.pb7), + Some(gpioc.pc0), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioa.pa7), + Some(gpioa.pa6), + Some(gpioc.pc1), + Some(gpioc.pc4), + Some(gpioa.pa5), + Some(gpioa.pa4), + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiod.pd11), + Some(gpiog.pg9), + Some(gpioa.pa2), + Some(gpiob.pb14), + Some(gpiob.pb15), + d31, + d32, + ); + + // Set up cache + Self::init_cache(resources.scb, resources.cpuid); + + info!("Seed system init done!"); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + SeedSystem { + gpio, + audio, + adc1, + adc2, + dac: SeedDac::new(resources.dac, resources.dac12_rec), + sdram, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +}