├── .cargo └── config.toml ├── .github ├── bors.toml └── workflows │ ├── ci.yml │ ├── clippy.yml │ ├── nightly.yml │ └── rustfmt.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── blinky.rs ├── delay.rs ├── flash.rs ├── hid_blinky.rs ├── knob.rs ├── passthru.rs ├── sdmmc.rs ├── sdram.rs ├── switch.rs ├── toggle.rs ├── usb_midi.rs └── volume.rs ├── memory.x └── src ├── audio.rs ├── flash.rs ├── gpio.rs ├── hid.rs ├── lib.rs ├── logger.rs ├── mpu.rs ├── prelude.rs ├── sdmmc.rs ├── sdram.rs └── system.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # runner = 'arm-none-eabi-gdb' 3 | rustflags = [ 4 | # LLD (shipped with the Rust toolchain) is used as the default linker 5 | "-C", "link-arg=-Tlink.x", 6 | ] 7 | 8 | [build] 9 | target = "thumbv7em-none-eabihf" 10 | -------------------------------------------------------------------------------- /.github/bors.toml: -------------------------------------------------------------------------------- 1 | block_labels = ["wip"] 2 | delete_merged_branches = true 3 | status = [ 4 | "Rustfmt", 5 | "ci (1.82.0, log-rtt)", 6 | "ci (1.82.0, log-itm)", 7 | "ci (1.82.0, log-semihosting)", 8 | "ci (stable, log-rtt)", 9 | "ci (stable, log-itm))", 10 | "ci (stable, log-semihosting)", 11 | ] 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | branches: [ staging, trying, master ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: # All permutations of {rust, mcu} 16 | rust: 17 | - 1.82.0 # MSRV 18 | - stable 19 | logger: 20 | - log-rtt 21 | - log-itm 22 | - log-semihosting 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: ${{ matrix.rust }} 30 | target: thumbv7em-none-eabihf 31 | override: true 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | use-cross: true 35 | command: build 36 | args: --verbose --release --examples --target thumbv7em-none-eabihf --features ${{ matrix.logger }} 37 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ staging, trying, master ] 4 | pull_request: 5 | 6 | name: Clippy 7 | 8 | jobs: 9 | clippy_check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | components: clippy 16 | toolchain: stable 17 | target: thumbv7em-none-eabihf 18 | override: true 19 | - uses: actions-rs/clippy-check@v1 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | args: --examples --target thumbv7em-none-eabihf 23 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] # Do not run on staging / trying 4 | pull_request: 5 | 6 | name: Nightly rust 7 | 8 | jobs: 9 | ci: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | toolchain: nightly 17 | target: thumbv7em-none-eabihf 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | use-cross: true 22 | command: build 23 | args: --verbose --release --examples --target thumbv7em-none-eabihf 24 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ staging, trying, master ] 4 | pull_request: 5 | 6 | name: Code formatting check 7 | 8 | jobs: 9 | fmt: 10 | name: Rustfmt 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | - run: rustup component add rustfmt 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: fmt 23 | args: --all -- --check 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.hex 5 | *.bin 6 | .gdb_history 7 | Embed.toml 8 | 9 | # editor files 10 | .vscode/* 11 | !.vscode/*.md 12 | !.vscode/*.svd 13 | !.vscode/launch.json 14 | !.vscode/tasks.json 15 | !.vscode/extensions.json -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Matthew Meyer "] 3 | edition = "2018" 4 | description = "Hardware Abstraction Layer implementation for Daisy boards" 5 | keywords = ["cortex-m", "stm32h7xx", "stm32h750", "hal", "daisy"] 6 | readme = "README.md" 7 | name = "libdaisy" 8 | version = "0.1.0" 9 | license = "MIT" 10 | repository = "https://github.com/mtthw-meyer/libdaisy-rust.git" 11 | documentation = "https://docs.rs/libdaisy" 12 | exclude = [".gitignore"] 13 | 14 | [dependencies] 15 | cfg-if = "1.0" 16 | cortex-m-rtic = "1.0.0" 17 | cortex-m = "^0.7" 18 | debouncr = "0.2.2" 19 | log = "0.4.11" 20 | micromath = "2.1.0" 21 | panic-halt = "0.2.0" 22 | stm32h7xx-hal = { version = "0.16.0", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } 23 | stm32-fmc = "0.3.0" 24 | rtt-target = { version = "0.5.0", optional = true } 25 | panic-rtt-target = { version = "0.1.3", optional = true } 26 | lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } 27 | cortex-m-log = { version = "~0.8", features = ["itm", "semihosting", "log-integration"], optional = true } 28 | panic-itm = { version = "~0.4.2", optional = true } 29 | panic-semihosting = { version = "0.6.0", optional = true } 30 | cortex-m-semihosting = { version = "0.5.0", optional = true } 31 | stable_deref_trait = { version = "1.2.0", default-features = false } 32 | 33 | [features] 34 | default = [] 35 | 36 | log-rtt = ["rtt-target", "panic-rtt-target"] 37 | log-itm = ["panic-itm", "lazy_static", "cortex-m-log"] 38 | log-semihosting = ["panic-semihosting", "lazy_static", "cortex-m-log", "cortex-m-semihosting"] 39 | log-none = [] 40 | 41 | # this lets you use `cargo fix`! 42 | #[[bin]] 43 | #name = "libdaisy-rust" 44 | #test = false 45 | #bench = false 46 | 47 | [profile.dev] 48 | codegen-units = 1 # better optimizations 49 | debug = true # symbols are nice and they don't increase the size in flash 50 | incremental = false 51 | opt-level = "s" # optimize for binary size 52 | 53 | [profile.release] 54 | codegen-units = 1 # better optimizations 55 | debug = true # symbols are nice and they don't increase the size in flash 56 | lto = true # better optimizations 57 | opt-level = "s" # optimize for binary size 58 | 59 | [dev-dependencies] 60 | embedded-sdmmc = "0.5.0" 61 | usbd-midi = "0.3.0" 62 | num_enum = { version = "0.7.3", default-features = false } 63 | usb-device = "0.3.0" 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matthew Meyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Documentation](https://docs.rs/libdaisy) 2 | 3 | # libdaisy 4 | Hardware Abstraction Layer implementation for Daisy boards. 5 | 6 | ## Requirements 7 | * Hardware target 8 | ``` 9 | $ rustup target add thumbv7em-none-eabihf 10 | ``` 11 | 12 | * [cargo-binutils][cargo-binutils-url] 13 | ``` console 14 | $ cargo install cargo-binutils 15 | 16 | $ rustup component add llvm-tools-preview 17 | ``` 18 | # A Flashing Utility 19 | * [Electro-smith web programmer](https://electro-smith.github.io/Programmer/) 20 | 21 | OR 22 | 23 | * [dfu-util](http://dfu-util.sourceforge.net/) 24 | 25 | OR 26 | 27 | * [Probe.rs](https://probe.rs/) 28 | 29 | This requires a debug probe of some sort (e.g. ST link) and allows for fast debugging messages via RTT. 30 | 31 | cargo embed --features log-rtt --example passthru 32 | 33 | ## Build Examples 34 | cargo objcopy --example blinky --release -- -O binary blinky.bin 35 | 36 | cargo objcopy --example passthru --release -- -O binary passthru.bin 37 | 38 | [cargo-binutils-url]: https://github.com/rust-embedded/cargo-binutils 39 | 40 | # Minimum supported Rust version 41 | The Minimum Supported Rust Version (MSRV) at the moment is 1.82.0 42 | # Demos 43 | 44 | [Looper](https://github.com/mtthw-meyer/daisy-looper) - Basic one button looper. 45 | -------------------------------------------------------------------------------- /examples/blinky.rs: -------------------------------------------------------------------------------- 1 | //! examples/blinky.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true 8 | )] 9 | mod app { 10 | use log::info; 11 | // Includes a panic handler and optional logging facilities 12 | use libdaisy::logger; 13 | 14 | use stm32h7xx_hal::stm32; 15 | use stm32h7xx_hal::time::MilliSeconds; 16 | use stm32h7xx_hal::timer::Timer; 17 | 18 | use libdaisy::gpio; 19 | use libdaisy::system; 20 | 21 | #[shared] 22 | struct Shared {} 23 | 24 | #[local] 25 | struct Local { 26 | seed_led: gpio::SeedLed, 27 | timer2: Timer, 28 | } 29 | 30 | #[init] 31 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 32 | logger::init(); 33 | let mut system = system::System::init(ctx.core, ctx.device); 34 | info!("Startup done!"); 35 | 36 | system 37 | .timer2 38 | .set_freq(MilliSeconds::from_ticks(500).into_rate()); 39 | 40 | ( 41 | Shared {}, 42 | Local { 43 | seed_led: system.gpio.led, 44 | timer2: system.timer2, 45 | }, 46 | init::Monotonics(), 47 | ) 48 | } 49 | 50 | #[idle] 51 | fn idle(_cx: idle::Context) -> ! { 52 | loop { 53 | cortex_m::asm::nop(); 54 | } 55 | } 56 | 57 | #[task(binds = TIM2, local = [timer2, seed_led, led_is_on: bool = true])] 58 | fn blink(ctx: blink::Context) { 59 | ctx.local.timer2.clear_irq(); 60 | 61 | if *ctx.local.led_is_on { 62 | ctx.local.seed_led.set_high(); 63 | } else { 64 | ctx.local.seed_led.set_low(); 65 | } 66 | *ctx.local.led_is_on = !(*ctx.local.led_is_on); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/delay.rs: -------------------------------------------------------------------------------- 1 | //! examples/delay.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | 12 | use libdaisy::audio; 13 | use libdaisy::logger; 14 | use libdaisy::system; 15 | 16 | #[shared] 17 | struct Shared {} 18 | 19 | #[local] 20 | struct Local { 21 | audio: audio::Audio, 22 | buffer: audio::AudioBuffer, 23 | sdram: &'static mut [f32], 24 | } 25 | 26 | #[init] 27 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 28 | logger::init(); 29 | let system = system::System::init(ctx.core, ctx.device); 30 | let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; 31 | 32 | info!("Startup done!"); 33 | 34 | ( 35 | Shared {}, 36 | Local { 37 | audio: system.audio, 38 | buffer, 39 | sdram: system.sdram, 40 | }, 41 | init::Monotonics(), 42 | ) 43 | } 44 | 45 | // Non-default idle ensures chip doesn't go to sleep which causes issues for 46 | // probe.rs currently 47 | #[idle] 48 | fn idle(_ctx: idle::Context) -> ! { 49 | loop { 50 | cortex_m::asm::nop(); 51 | } 52 | } 53 | 54 | // Interrupt handler for audio 55 | #[task(binds = DMA1_STR1, local = [audio, buffer, sdram, index: usize = 0], priority = 8)] 56 | fn audio_handler(ctx: audio_handler::Context) { 57 | let audio = ctx.local.audio; 58 | let buffer = ctx.local.buffer; 59 | let sdram: &mut [f32] = ctx.local.sdram; 60 | let index: &mut usize = ctx.local.index; 61 | 62 | if audio.get_stereo(buffer) { 63 | for (left, right) in buffer { 64 | audio 65 | .push_stereo((sdram[*index], sdram[*index + 1])) 66 | .unwrap(); 67 | sdram[*index] = *left; 68 | sdram[*index + 1] = *right; 69 | *index = (*index + 2) % libdaisy::AUDIO_SAMPLE_RATE; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/flash.rs: -------------------------------------------------------------------------------- 1 | //! examples/flash.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | //this example simply runs some tests on the flash.. reading and writing, and if all passes, 6 | //flashes the LED at the end 7 | 8 | #[rtic::app( 9 | device = stm32h7xx_hal::stm32, 10 | peripherals = true, 11 | )] 12 | mod app { 13 | use libdaisy::{flash::FlashErase, gpio, logger, system}; 14 | use log::info; 15 | use stm32h7xx_hal::{nb, stm32, time::MilliSeconds, timer::Timer}; 16 | 17 | #[shared] 18 | struct Shared {} 19 | 20 | #[local] 21 | struct Local { 22 | seed_led: gpio::SeedLed, 23 | timer2: Timer, 24 | } 25 | 26 | #[init] 27 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 28 | logger::init(); 29 | let mut system = system::System::init(ctx.core, ctx.device); 30 | info!("Startup done!"); 31 | 32 | system 33 | .timer2 34 | .set_freq(MilliSeconds::from_ticks(500).into_rate()); 35 | 36 | let mut flash = system.flash; 37 | 38 | //takes some time 39 | nb::block!(flash.erase(FlashErase::Sector4K(0))).unwrap(); 40 | nb::block!(flash.erase(FlashErase::Sector4K(4096))).unwrap(); 41 | 42 | //read, should be all 0xFF 43 | let mut r = [0x0; 64]; 44 | flash.read(0, &mut r).unwrap(); 45 | assert_eq!(r[0], 0xFF); 46 | assert_eq!(r[31], 0xFF); 47 | 48 | nb::block!(flash.program(0, &[0x42])).unwrap(); 49 | //can program over 1s 50 | nb::block!(flash.program(32, &[0x1, 0xFF])).unwrap(); 51 | nb::block!(flash.program(33, &[0x2, 0x3])).unwrap(); 52 | 53 | //read the new values 54 | flash.read(0, &mut r).unwrap(); 55 | assert_eq!(r[0], 0x42); 56 | assert_eq!(r[32], 0x1); 57 | assert_eq!(r[33], 0x2); 58 | assert_eq!(r[34], 0x3); 59 | 60 | r[35] = 0x91; 61 | nb::block!(flash.program(0, &r)).unwrap(); 62 | flash.read(0, &mut r).unwrap(); 63 | assert_eq!(r[35], 0x91); 64 | 65 | flash.read(4096, &mut r).unwrap(); 66 | assert_eq!(r[0], 0xFF); 67 | r[0] = 0; 68 | nb::block!(flash.program(4096, &r)).unwrap(); 69 | flash.read(4096, &mut r).unwrap(); 70 | assert_eq!(r[0], 0x00); 71 | 72 | nb::block!(flash.erase(FlashErase::Sector4K(4096))).unwrap(); 73 | flash.read(4096, &mut r).unwrap(); 74 | assert_eq!(r[0], 0xFF); 75 | flash.read(0, &mut r).unwrap(); 76 | assert_eq!(r[35], 0x91); 77 | 78 | ( 79 | Shared {}, 80 | Local { 81 | seed_led: system.gpio.led, 82 | timer2: system.timer2, 83 | }, 84 | init::Monotonics(), 85 | ) 86 | } 87 | 88 | #[idle] 89 | fn idle(_cx: idle::Context) -> ! { 90 | loop { 91 | cortex_m::asm::nop(); 92 | } 93 | } 94 | 95 | #[task( binds = TIM2, local = [timer2, seed_led, led_is_on: bool = true] )] 96 | fn blink(ctx: blink::Context) { 97 | ctx.local.timer2.clear_irq(); 98 | 99 | if *ctx.local.led_is_on { 100 | ctx.local.seed_led.set_high(); 101 | } else { 102 | ctx.local.seed_led.set_low(); 103 | } 104 | *ctx.local.led_is_on = !(*ctx.local.led_is_on); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/hid_blinky.rs: -------------------------------------------------------------------------------- 1 | //! examples/hid_blinky.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | // Includes a panic handler and optional logging facilities 12 | use libdaisy::logger; 13 | 14 | use stm32h7xx_hal::adc; 15 | use stm32h7xx_hal::stm32; 16 | use stm32h7xx_hal::time::MilliSeconds; 17 | use stm32h7xx_hal::timer::Timer; 18 | 19 | use libdaisy::gpio::*; 20 | use libdaisy::hid; 21 | use libdaisy::prelude::*; 22 | use libdaisy::system; 23 | 24 | #[shared] 25 | struct Shared {} 26 | 27 | #[local] 28 | struct Local { 29 | led1: hid::Led, 30 | adc1: adc::Adc, 31 | control1: hid::AnalogControl>, 32 | timer2: Timer, 33 | } 34 | 35 | #[init] 36 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 37 | logger::init(); 38 | let mut system = system::System::init(ctx.core, ctx.device); 39 | 40 | system 41 | .timer2 42 | .set_freq(MilliSeconds::from_ticks(1).into_rate()); 43 | 44 | let mut led1 = hid::Led::new(system.gpio.led, false, 1000); 45 | led1.set_brightness(0.5); 46 | 47 | let mut adc1 = system.adc1.enable(); 48 | adc1.set_resolution(adc::Resolution::SixteenBit); 49 | let adc1_max = adc1.slope() as f32; 50 | 51 | let daisy15 = system 52 | .gpio 53 | .daisy15 54 | .take() 55 | .expect("Failed to get pin daisy15!") 56 | .into_analog(); 57 | 58 | let control1 = hid::AnalogControl::new(daisy15, adc1_max); 59 | 60 | ( 61 | Shared {}, 62 | Local { 63 | led1, 64 | adc1, 65 | control1, 66 | timer2: system.timer2, 67 | }, 68 | init::Monotonics(), 69 | ) 70 | } 71 | 72 | #[idle] 73 | fn idle(_cx: idle::Context) -> ! { 74 | loop { 75 | cortex_m::asm::nop(); 76 | } 77 | } 78 | 79 | #[task(binds = TIM2, local = [timer2, adc1, control1, led1])] 80 | fn interface_handler(ctx: interface_handler::Context) { 81 | ctx.local.timer2.clear_irq(); 82 | let adc1 = ctx.local.adc1; 83 | let led1 = ctx.local.led1; 84 | let control1 = ctx.local.control1; 85 | 86 | if let Ok(data) = adc1.read(control1.get_pin()) { 87 | control1.update(data); 88 | } 89 | 90 | let value = control1.get_value(); 91 | led1.set_blink(value, 1.0 - value); 92 | info!("{} {}", value, 1.0 - value); 93 | led1.update(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/knob.rs: -------------------------------------------------------------------------------- 1 | //! examples/knob.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | // Includes a panic handler and optional logging facilities 12 | use libdaisy::logger; 13 | 14 | use stm32h7xx_hal::adc; 15 | use stm32h7xx_hal::stm32; 16 | use stm32h7xx_hal::timer::Timer; 17 | 18 | use libdaisy::gpio::*; 19 | use libdaisy::hid; 20 | use libdaisy::prelude::*; 21 | use libdaisy::system; 22 | 23 | #[shared] 24 | struct Shared {} 25 | 26 | #[local] 27 | struct Local { 28 | led1: hid::Led>>, 29 | adc1: adc::Adc, 30 | control1: hid::AnalogControl>, 31 | timer2: Timer, 32 | } 33 | 34 | #[init] 35 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 36 | logger::init(); 37 | let mut system = system::System::init(ctx.core, ctx.device); 38 | 39 | let duty_cycle = 50; 40 | let resolution = 20; 41 | 42 | system.timer2.set_freq((duty_cycle * resolution).Hz()); 43 | 44 | let daisy28 = system 45 | .gpio 46 | .daisy28 47 | .take() 48 | .expect("Failed to get pin 28!") 49 | .into_push_pull_output(); 50 | 51 | let led1 = hid::Led::new(daisy28, false, resolution); 52 | 53 | let mut adc1 = system.adc1.enable(); 54 | adc1.set_resolution(adc::Resolution::SixteenBit); 55 | let adc1_max = adc1.slope() as f32; 56 | 57 | let daisy21 = system 58 | .gpio 59 | .daisy21 60 | .take() 61 | .expect("Failed to get pin 21!") 62 | .into_analog(); 63 | 64 | let control1 = hid::AnalogControl::new(daisy21, adc1_max); 65 | 66 | ( 67 | Shared {}, 68 | Local { 69 | led1, 70 | adc1, 71 | control1, 72 | timer2: system.timer2, 73 | }, 74 | init::Monotonics(), 75 | ) 76 | } 77 | 78 | #[idle] 79 | fn idle(_cx: idle::Context) -> ! { 80 | loop { 81 | cortex_m::asm::nop(); 82 | } 83 | } 84 | 85 | #[task( binds = TIM2, local = [timer2, adc1, control1, led1] )] 86 | fn interface_handler(ctx: interface_handler::Context) { 87 | ctx.local.timer2.clear_irq(); 88 | let adc1 = ctx.local.adc1; 89 | let led1 = ctx.local.led1; 90 | let control1 = ctx.local.control1; 91 | 92 | if let Ok(data) = adc1.read(control1.get_pin()) { 93 | control1.update(data); 94 | } 95 | 96 | led1.set_brightness(control1.get_value()); 97 | info!("{}", control1.get_value()); 98 | led1.update(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/passthru.rs: -------------------------------------------------------------------------------- 1 | //! examples/passthru.rs 2 | #![no_main] 3 | #![no_std] 4 | #[rtic::app( 5 | device = stm32h7xx_hal::stm32, 6 | peripherals = true, 7 | )] 8 | mod app { 9 | use log::info; 10 | 11 | use libdaisy::audio; 12 | use libdaisy::logger; 13 | use libdaisy::system; 14 | 15 | #[shared] 16 | struct Shared {} 17 | 18 | #[local] 19 | struct Local { 20 | audio: audio::Audio, 21 | buffer: audio::AudioBuffer, 22 | } 23 | 24 | #[init] 25 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 26 | logger::init(); 27 | let system = system::System::init(ctx.core, ctx.device); 28 | let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; 29 | 30 | info!("Startup done!"); 31 | 32 | ( 33 | Shared {}, 34 | Local { 35 | audio: system.audio, 36 | buffer, 37 | }, 38 | init::Monotonics(), 39 | ) 40 | } 41 | 42 | // Non-default idle ensures chip doesn't go to sleep which causes issues for 43 | // probe.rs currently 44 | #[idle] 45 | fn idle(_ctx: idle::Context) -> ! { 46 | loop { 47 | cortex_m::asm::nop(); 48 | } 49 | } 50 | 51 | // Interrupt handler for audio 52 | #[task(binds = DMA1_STR1, local = [audio, buffer], priority = 8)] 53 | fn audio_handler(ctx: audio_handler::Context) { 54 | let audio = ctx.local.audio; 55 | let buffer = ctx.local.buffer; 56 | 57 | if audio.get_stereo(buffer) { 58 | for (left, right) in buffer { 59 | audio.push_stereo((*left, *right)).unwrap(); 60 | } 61 | } else { 62 | info!("Error reading data!"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/sdmmc.rs: -------------------------------------------------------------------------------- 1 | //! examples/sdram.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | 12 | use embedded_sdmmc::{TimeSource, Timestamp, VolumeIdx, VolumeManager}; 13 | use libdaisy::{ 14 | gpio, 15 | // Includes a panic handler and optional logging facilities 16 | logger, 17 | prelude::*, 18 | sdmmc, 19 | system::System, 20 | }; 21 | use stm32h7xx_hal::{ 22 | sdmmc::{SdCard, Sdmmc}, 23 | stm32::SDMMC1, 24 | }; 25 | 26 | #[shared] 27 | struct Shared {} 28 | 29 | #[local] 30 | struct Local {} 31 | 32 | struct FakeTime; 33 | 34 | impl TimeSource for FakeTime { 35 | fn get_timestamp(&self) -> Timestamp { 36 | Timestamp { 37 | year_since_1970: 52, //2022 38 | zero_indexed_month: 0, 39 | zero_indexed_day: 0, 40 | hours: 0, 41 | minutes: 0, 42 | seconds: 1, 43 | } 44 | } 45 | } 46 | 47 | #[init] 48 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 49 | logger::init(); 50 | 51 | let device = ctx.device; 52 | let mut ccdr = System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); 53 | 54 | let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); 55 | let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); 56 | let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); 57 | let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); 58 | let gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); 59 | 60 | let mut gpio = gpio::GPIO::init( 61 | gpioc.pc7, 62 | gpiob.pb11, 63 | Some(gpiob.pb12), 64 | Some(gpioc.pc11), 65 | Some(gpioc.pc10), 66 | Some(gpioc.pc9), 67 | Some(gpioc.pc8), 68 | Some(gpiod.pd2), 69 | Some(gpioc.pc12), 70 | Some(gpiog.pg10), 71 | Some(gpiog.pg11), 72 | Some(gpiob.pb4), 73 | Some(gpiob.pb5), 74 | Some(gpiob.pb8), 75 | Some(gpiob.pb9), 76 | Some(gpiob.pb6), 77 | Some(gpiob.pb7), 78 | Some(gpioc.pc0), 79 | Some(gpioa.pa3), 80 | Some(gpiob.pb1), 81 | Some(gpioa.pa7), 82 | Some(gpioa.pa6), 83 | Some(gpioc.pc1), 84 | Some(gpioc.pc4), 85 | Some(gpioa.pa5), 86 | Some(gpioa.pa4), 87 | Some(gpioa.pa1), 88 | Some(gpioa.pa0), 89 | Some(gpiod.pd11), 90 | Some(gpiog.pg9), 91 | Some(gpioa.pa2), 92 | Some(gpiob.pb14), 93 | Some(gpiob.pb15), 94 | ); 95 | 96 | let mut sd = sdmmc::init( 97 | gpio.daisy1.take().unwrap(), 98 | gpio.daisy2.take().unwrap(), 99 | gpio.daisy3.take().unwrap(), 100 | gpio.daisy4.take().unwrap(), 101 | gpio.daisy5.take().unwrap(), 102 | gpio.daisy6.take().unwrap(), 103 | device.SDMMC1, 104 | ccdr.peripheral.SDMMC1, 105 | &mut ccdr.clocks, 106 | ); 107 | 108 | gpio.led.set_low(); 109 | if let Ok(_) = >::init(&mut sd, 50.MHz()) { 110 | info!("Got SD Card!"); 111 | let mut sd_fatfs = VolumeManager::new(sd.sdmmc_block_device(), FakeTime); 112 | if let Ok(sd_fatfs_volume) = sd_fatfs.get_volume(VolumeIdx(0)) { 113 | if let Ok(sd_fatfs_root_dir) = sd_fatfs.open_root_dir(&sd_fatfs_volume) { 114 | sd_fatfs 115 | .iterate_dir(&sd_fatfs_volume, &sd_fatfs_root_dir, |entry| { 116 | info!("{:?}", entry); 117 | }) 118 | .unwrap(); 119 | sd_fatfs.close_dir(&sd_fatfs_volume, sd_fatfs_root_dir); 120 | gpio.led.set_high(); 121 | } else { 122 | info!("Failed to get root dir"); 123 | } 124 | } else { 125 | info!("Failed to get volume 0"); 126 | } 127 | } else { 128 | info!("Failed to init SD Card"); 129 | } 130 | 131 | (Shared {}, Local {}, init::Monotonics()) 132 | } 133 | 134 | #[idle] 135 | fn idle(_ctx: idle::Context) -> ! { 136 | loop { 137 | cortex_m::asm::nop(); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /examples/sdram.rs: -------------------------------------------------------------------------------- 1 | //! examples/sdram.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | // Includes a panic handler and optional logging facilities 12 | use libdaisy::logger; 13 | 14 | use stm32h7xx_hal::stm32; 15 | use stm32h7xx_hal::time::MilliSeconds; 16 | use stm32h7xx_hal::timer::Timer; 17 | 18 | use libdaisy::gpio::*; 19 | use libdaisy::system; 20 | 21 | use micromath::F32Ext; 22 | 23 | #[shared] 24 | struct Shared {} 25 | 26 | #[local] 27 | struct Local { 28 | seed_led: SeedLed, 29 | timer2: Timer, 30 | } 31 | 32 | #[init] 33 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 34 | logger::init(); 35 | let mut system = system::System::init(ctx.core, ctx.device); 36 | 37 | system 38 | .timer2 39 | .set_freq(MilliSeconds::from_ticks(500).into_rate()); 40 | 41 | let sdram = system.sdram; 42 | 43 | let sdram_size_bytes = libdaisy::sdram::Sdram::bytes(); 44 | let sdram_size = sdram_size_bytes / core::mem::size_of::(); 45 | 46 | info!( 47 | "SDRAM size: {} bytes, {} words starting at {:?}", 48 | sdram_size_bytes, sdram_size, &sdram[0] as *const _ 49 | ); 50 | 51 | // Make sure that we're not reading memory from a previous test run 52 | info!("Clear memory..."); 53 | for item in sdram.iter_mut().take(sdram_size) { 54 | *item = 0.0; 55 | } 56 | 57 | info!("Write test pattern..."); 58 | let mut data: f32 = 0.0; 59 | for item in sdram.iter_mut().take(sdram_size) { 60 | *item = data; 61 | data = (data + 1.0) % core::f32::MAX; 62 | } 63 | 64 | info!("Read test pattern..."); 65 | let percent = (sdram_size as f64 / 100.0) as f32; 66 | data = 0.0; 67 | for (i, item) in sdram.iter_mut().enumerate().take(sdram_size) { 68 | assert!((*item - data).abs() < f32::EPSILON); 69 | data = (data + 1.0) % core::f32::MAX; 70 | 71 | if (i as f32 % (10.0 * percent)) == 0.0 { 72 | info!("{}% done", i as f32 / percent); 73 | } 74 | } 75 | info!("Test Success!"); 76 | 77 | ( 78 | Shared {}, 79 | Local { 80 | seed_led: system.gpio.led, 81 | timer2: system.timer2, 82 | }, 83 | init::Monotonics(), 84 | ) 85 | } 86 | 87 | #[idle] 88 | fn idle(_ctx: idle::Context) -> ! { 89 | loop { 90 | cortex_m::asm::nop(); 91 | } 92 | } 93 | 94 | #[task(binds = TIM2, local = [timer2, seed_led, led_is_on: bool = true])] 95 | fn blink(ctx: blink::Context) { 96 | ctx.local.timer2.clear_irq(); 97 | 98 | if *ctx.local.led_is_on { 99 | ctx.local.seed_led.set_high(); 100 | } else { 101 | ctx.local.seed_led.set_low(); 102 | } 103 | *ctx.local.led_is_on = !(*ctx.local.led_is_on); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /examples/switch.rs: -------------------------------------------------------------------------------- 1 | //! examples/switch.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | #[rtic::app( 6 | device = stm32h7xx_hal::stm32, 7 | peripherals = true, 8 | )] 9 | mod app { 10 | use log::info; 11 | // Includes a panic handler and optional logging facilities 12 | use libdaisy::logger; 13 | 14 | use stm32h7xx_hal::stm32; 15 | use stm32h7xx_hal::time::MilliSeconds; 16 | use stm32h7xx_hal::timer::Timer; 17 | 18 | use libdaisy::gpio::*; 19 | use libdaisy::hid; 20 | use libdaisy::prelude::*; 21 | use libdaisy::system; 22 | 23 | #[shared] 24 | struct Shared {} 25 | 26 | #[local] 27 | struct Local { 28 | seed_led: SeedLed, 29 | switch1: hid::Switch>, 30 | timer2: Timer, 31 | } 32 | 33 | #[init] 34 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 35 | logger::init(); 36 | let mut system = system::System::init(ctx.core, ctx.device); 37 | 38 | let daisy28 = system 39 | .gpio 40 | .daisy28 41 | .take() 42 | .expect("Failed to get pin daisy28!") 43 | .into_pull_up_input(); 44 | 45 | system 46 | .timer2 47 | .set_freq(MilliSeconds::from_ticks(1).into_rate()); 48 | 49 | // Switch rate is determined by timer freq 50 | let mut switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); 51 | switch1.set_double_thresh(Some(500)); 52 | switch1.set_held_thresh(Some(1500)); 53 | 54 | ( 55 | Shared {}, 56 | Local { 57 | seed_led: system.gpio.led, 58 | switch1, 59 | timer2: system.timer2, 60 | }, 61 | init::Monotonics(), 62 | ) 63 | } 64 | 65 | #[idle] 66 | fn idle(_ctx: idle::Context) -> ! { 67 | loop { 68 | cortex_m::asm::nop(); 69 | } 70 | } 71 | 72 | #[task(binds = TIM2, local = [timer2, seed_led, switch1, led_is_on: bool = false])] 73 | fn interface_handler(ctx: interface_handler::Context) { 74 | ctx.local.timer2.clear_irq(); 75 | let switch1 = ctx.local.switch1; 76 | switch1.update(); 77 | 78 | if switch1.is_held() { 79 | info!("Button held!"); 80 | *ctx.local.led_is_on = false; 81 | } 82 | 83 | if switch1.is_double() { 84 | info!("Button pressed twice!"); 85 | *ctx.local.led_is_on = true; 86 | } 87 | 88 | if *ctx.local.led_is_on { 89 | ctx.local.seed_led.set_high(); 90 | } else { 91 | ctx.local.seed_led.set_low(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/toggle.rs: -------------------------------------------------------------------------------- 1 | //! examples/toggle.rs 2 | #![no_main] 3 | #![no_std] 4 | #[rtic::app( 5 | device = stm32h7xx_hal::stm32, 6 | peripherals = true, 7 | )] 8 | mod app { 9 | use log::info; 10 | // Includes a panic handler and optional logging facilities 11 | use libdaisy::logger; 12 | 13 | use stm32h7xx_hal::stm32; 14 | use stm32h7xx_hal::timer::Timer; 15 | 16 | use libdaisy::gpio::*; 17 | use libdaisy::hid; 18 | use libdaisy::prelude::*; 19 | use libdaisy::system; 20 | use stm32h7xx_hal::time::Hertz; 21 | 22 | #[shared] 23 | struct Shared {} 24 | 25 | #[local] 26 | struct Local { 27 | seed_led: SeedLed, 28 | switch1: hid::Switch>, 29 | timer2: Timer, 30 | } 31 | 32 | #[init] 33 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 34 | logger::init(); 35 | let mut system = system::System::init(ctx.core, ctx.device); 36 | 37 | let daisy28 = system 38 | .gpio 39 | .daisy28 40 | .take() 41 | .expect("Failed to get pin daisy28!") 42 | .into_pull_up_input(); 43 | 44 | system.timer2.set_freq(Hertz::from_raw(100)); 45 | 46 | let switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); 47 | 48 | ( 49 | Shared {}, 50 | Local { 51 | seed_led: system.gpio.led, 52 | switch1, 53 | timer2: system.timer2, 54 | }, 55 | init::Monotonics(), 56 | ) 57 | } 58 | 59 | #[idle] 60 | fn idle(_ctx: idle::Context) -> ! { 61 | loop { 62 | cortex_m::asm::nop(); 63 | } 64 | } 65 | 66 | #[task(binds = TIM2, local = [timer2, seed_led, switch1, led_is_on: bool = false])] 67 | fn interface_handler(ctx: interface_handler::Context) { 68 | ctx.local.timer2.clear_irq(); 69 | let switch1 = ctx.local.switch1; 70 | switch1.update(); 71 | 72 | if switch1.is_falling() { 73 | info!("Button pressed!"); 74 | if *ctx.local.led_is_on { 75 | ctx.local.seed_led.set_high(); 76 | } else { 77 | ctx.local.seed_led.set_low(); 78 | } 79 | *ctx.local.led_is_on = !(*ctx.local.led_is_on); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/usb_midi.rs: -------------------------------------------------------------------------------- 1 | //! examples/usb_midi.rs 2 | #![no_main] 3 | #![no_std] 4 | 5 | //based on https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/usb_rtic.rs 6 | //and https://github.com/btrepp/usbd-midi 7 | 8 | #[rtic::app( 9 | device = stm32h7xx_hal::stm32, 10 | peripherals = true, 11 | )] 12 | mod app { 13 | use libdaisy::{gpio, prelude::*, system::System}; 14 | use stm32h7xx_hal::{ 15 | rcc::rec::UsbClkSel, 16 | stm32, 17 | time::MilliSeconds, 18 | timer::{Event, Timer}, 19 | usb_hs::{UsbBus, USB2}, 20 | }; 21 | 22 | use num_enum::TryFromPrimitive; 23 | use usb_device::prelude::*; 24 | use usbd_midi::{ 25 | data::{ 26 | byte::{from_traits::FromClamped, u7::U7}, 27 | midi::{channel::Channel as MidiChannel, message::Message, notes::Note}, 28 | usb::constants::USB_CLASS_NONE, 29 | usb_midi::{ 30 | midi_packet_reader::MidiPacketBufferReader, 31 | usb_midi_event_packet::UsbMidiEventPacket, 32 | }, 33 | }, 34 | midi_device::MidiClass, 35 | }; 36 | 37 | // Warning: EP_MEMORY may only be used for the UsbBusAllocator. Any 38 | // additional references are UB. 39 | static mut EP_MEMORY: [u32; 1024] = [0; 1024]; 40 | 41 | #[shared] 42 | struct Shared { 43 | usb: ( 44 | UsbDevice<'static, UsbBus>, 45 | MidiClass<'static, UsbBus>, 46 | ), 47 | } 48 | 49 | #[local] 50 | struct Local { 51 | seed_led: gpio::SeedLed, 52 | timer2: Timer, 53 | } 54 | 55 | #[init] 56 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 57 | let device = ctx.device; 58 | let mut ccdr = System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); 59 | let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run"); 60 | ccdr.peripheral.kernel_usb_clk_mux(UsbClkSel::Hsi48); 61 | 62 | /* 63 | unsafe { 64 | let pwr = &*stm32::PWR::ptr(); 65 | pwr.cr3.modify(|_, w| w.usbregen().set_bit()); 66 | while pwr.cr3.read().usb33rdy().bit_is_clear() {} 67 | } 68 | */ 69 | 70 | let mut timer2 = device.TIM2.timer( 71 | MilliSeconds::from_ticks(200).into_rate(), 72 | ccdr.peripheral.TIM2, 73 | &ccdr.clocks, 74 | ); 75 | timer2.listen(Event::TimeOut); 76 | 77 | let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); 78 | let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); 79 | let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); 80 | let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); 81 | let gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); 82 | 83 | let gpio = gpio::GPIO::init( 84 | gpioc.pc7, 85 | gpiob.pb11, 86 | Some(gpiob.pb12), 87 | Some(gpioc.pc11), 88 | Some(gpioc.pc10), 89 | Some(gpioc.pc9), 90 | Some(gpioc.pc8), 91 | Some(gpiod.pd2), 92 | Some(gpioc.pc12), 93 | Some(gpiog.pg10), 94 | Some(gpiog.pg11), 95 | Some(gpiob.pb4), 96 | Some(gpiob.pb5), 97 | Some(gpiob.pb8), 98 | Some(gpiob.pb9), 99 | Some(gpiob.pb6), 100 | Some(gpiob.pb7), 101 | Some(gpioc.pc0), 102 | Some(gpioa.pa3), 103 | Some(gpiob.pb1), 104 | Some(gpioa.pa7), 105 | Some(gpioa.pa6), 106 | Some(gpioc.pc1), 107 | Some(gpioc.pc4), 108 | Some(gpioa.pa5), 109 | Some(gpioa.pa4), 110 | Some(gpioa.pa1), 111 | Some(gpioa.pa0), 112 | Some(gpiod.pd11), 113 | Some(gpiog.pg9), 114 | Some(gpioa.pa2), 115 | Some(gpiob.pb14), 116 | Some(gpiob.pb15), 117 | ); 118 | 119 | let (pin_dm, pin_dp) = { (gpioa.pa11.into_alternate(), gpioa.pa12.into_alternate()) }; 120 | //float makes this a device 121 | gpioa.pa10.into_floating_input(); 122 | let usb = USB2::new( 123 | device.OTG2_HS_GLOBAL, 124 | device.OTG2_HS_DEVICE, 125 | device.OTG2_HS_PWRCLK, 126 | pin_dm, 127 | pin_dp, 128 | ccdr.peripheral.USB2OTG, 129 | &ccdr.clocks, 130 | ); 131 | 132 | #[allow(static_mut_refs)] 133 | let usb_bus = cortex_m::singleton!( 134 | : usb_device::class_prelude::UsbBusAllocator> = 135 | UsbBus::new(usb, unsafe { &mut EP_MEMORY }) 136 | ) 137 | .unwrap(); 138 | 139 | let midi = MidiClass::new(usb_bus, 1, 1).unwrap(); 140 | 141 | let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x16c0, 0x5e4)) 142 | .strings(&[StringDescriptors::default().product("daisy midi")]) 143 | .unwrap() 144 | .device_class(USB_CLASS_NONE) 145 | .build(); 146 | 147 | ( 148 | Shared { 149 | usb: (usb_dev, midi), 150 | }, 151 | Local { 152 | seed_led: gpio.led, 153 | timer2, 154 | }, 155 | init::Monotonics(), 156 | ) 157 | } 158 | 159 | #[idle] 160 | fn idle(_cx: idle::Context) -> ! { 161 | loop { 162 | cortex_m::asm::nop(); 163 | } 164 | } 165 | 166 | #[task( 167 | binds = TIM2, 168 | shared = [usb], 169 | local = [timer2, on: bool = true, note_num: u8 = 0] 170 | )] 171 | fn send_note(mut ctx: send_note::Context) { 172 | ctx.local.timer2.clear_irq(); 173 | let (local, shared) = (&mut ctx.local, &mut ctx.shared); 174 | shared.usb.lock(|(_usb_dev, midi)| { 175 | let on = *local.on; 176 | let note_num = *local.note_num; 177 | let chan = MidiChannel::Channel1; 178 | let note = Note::try_from_primitive(note_num).unwrap(); 179 | let vel = U7::from_clamped(127); 180 | let packet = UsbMidiEventPacket::from_midi( 181 | usbd_midi::data::usb_midi::cable_number::CableNumber::Cable0, 182 | if on { 183 | Message::NoteOn(chan, note, vel) 184 | } else { 185 | Message::NoteOff(chan, note, vel) 186 | }, 187 | ); 188 | if midi.send_message(packet).is_ok() { 189 | *local.on = !on; 190 | if !on { 191 | *local.note_num = (note_num + 1) % 127; 192 | } 193 | } 194 | }); 195 | } 196 | 197 | #[task(binds = OTG_FS, shared = [usb], local = [seed_led])] 198 | fn usb_event(mut ctx: usb_event::Context) { 199 | let (local, shared) = (&mut ctx.local, &mut ctx.shared); 200 | shared.usb.lock(|(usb_dev, midi)| { 201 | let led = &mut local.seed_led; 202 | 203 | if !usb_dev.poll(&mut [midi]) { 204 | return; 205 | } 206 | 207 | let mut buffer = [0; 64]; 208 | if let Ok(size) = midi.read(&mut buffer) { 209 | let buffer_reader = MidiPacketBufferReader::new(&buffer, size); 210 | for packet in buffer_reader.into_iter() { 211 | if let Ok(packet) = packet { 212 | match packet.message { 213 | Message::NoteOn(_, _, U7::MIN) | Message::NoteOff(..) => { 214 | led.set_low(); 215 | } 216 | Message::NoteOn(..) => { 217 | led.set_high(); 218 | } 219 | _ => {} 220 | } 221 | } 222 | } 223 | } 224 | }); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /examples/volume.rs: -------------------------------------------------------------------------------- 1 | //! examples/volume.rs 2 | #![allow(unused_imports)] 3 | #![no_main] 4 | #![no_std] 5 | 6 | #[rtic::app( 7 | device = stm32h7xx_hal::stm32, 8 | peripherals = true, 9 | )] 10 | mod app { 11 | //use rtic::cyccnt::U32Ext; 12 | 13 | use log::info; 14 | 15 | use stm32h7xx_hal::adc; 16 | use stm32h7xx_hal::stm32; 17 | use stm32h7xx_hal::timer::Timer; 18 | 19 | use libdaisy::audio; 20 | use libdaisy::gpio::*; 21 | use libdaisy::hid; 22 | use libdaisy::logger; 23 | use libdaisy::prelude::*; 24 | use libdaisy::system; 25 | use libdaisy::MILICYCLES; 26 | 27 | #[shared] 28 | struct Shared { 29 | control1: hid::AnalogControl>, 30 | } 31 | 32 | #[local] 33 | struct Local { 34 | audio: audio::Audio, 35 | buffer: audio::AudioBuffer, 36 | adc1: adc::Adc, 37 | timer2: Timer, 38 | } 39 | 40 | #[init] 41 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 42 | logger::init(); 43 | let mut system = system::System::init(ctx.core, ctx.device); 44 | let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; 45 | 46 | info!("Enable adc1"); 47 | let mut adc1 = system.adc1.enable(); 48 | adc1.set_resolution(adc::Resolution::SixteenBit); 49 | let adc1_max = adc1.slope() as f32; 50 | 51 | let daisy15 = system 52 | .gpio 53 | .daisy15 54 | .take() 55 | .expect("Failed to get pin daisy15!") 56 | .into_analog(); 57 | 58 | let mut control1 = hid::AnalogControl::new(daisy15, adc1_max); 59 | // Transform linear input into logarithmic 60 | control1.set_transform(|x| x * x); 61 | 62 | ( 63 | Shared { control1 }, 64 | Local { 65 | audio: system.audio, 66 | buffer, 67 | adc1, 68 | timer2: system.timer2, 69 | }, 70 | init::Monotonics(), 71 | ) 72 | } 73 | 74 | // Non-default idle ensures chip doesn't go to sleep which causes issues for 75 | // probe.rs currently 76 | #[idle] 77 | fn idle(_ctx: idle::Context) -> ! { 78 | loop { 79 | cortex_m::asm::nop(); 80 | } 81 | } 82 | 83 | // Interrupt handler for audio 84 | #[task(binds = DMA1_STR1, local = [audio, buffer], shared = [control1], priority = 8)] 85 | fn audio_handler(mut ctx: audio_handler::Context) { 86 | let audio_handler::LocalResources { audio, buffer } = ctx.local; 87 | 88 | if audio.get_stereo(buffer) { 89 | for (left, right) in buffer { 90 | ctx.shared.control1.lock(|c| { 91 | let volume = c.get_value(); 92 | info!("{}", volume); 93 | *left *= volume; 94 | *right *= volume; 95 | audio.push_stereo((*left, *right)).unwrap(); 96 | }); 97 | } 98 | } 99 | } 100 | 101 | #[task(binds = TIM2, local = [timer2, adc1], shared = [control1])] 102 | fn interface_handler(mut ctx: interface_handler::Context) { 103 | ctx.local.timer2.clear_irq(); 104 | let adc1 = ctx.local.adc1; 105 | 106 | ctx.shared.control1.lock(|control1| { 107 | if let Ok(data) = adc1.read(control1.get_pin()) { 108 | control1.update(data); 109 | }; 110 | }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | ENTRY(Reset_Handler) 2 | 3 | MEMORY 4 | { 5 | FLASH (RX) : ORIGIN = 0x08000000, LENGTH = 128K 6 | DTCMRAM (RWX) : ORIGIN = 0x20000000, LENGTH = 128K 7 | SRAM (RWX) : ORIGIN = 0x24000000, LENGTH = 512K 8 | RAM_D2 (RWX) : ORIGIN = 0x30000000, LENGTH = 288K 9 | RAM_D3 (RWX) : ORIGIN = 0x38000000, LENGTH = 64K 10 | ITCMRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 64K 11 | SDRAM (RWX) : ORIGIN = 0xc0000000, LENGTH = 64M 12 | QSPIFLASH (RX): ORIGIN = 0x90000000, LENGTH = 8M 13 | } 14 | 15 | /* stm32h7xx-hal uses a PROVIDE that expects RAM symbol to exist 16 | */ 17 | REGION_ALIAS(RAM, DTCMRAM); 18 | 19 | SECTIONS 20 | { 21 | .sram1_bss (NOLOAD) : 22 | { 23 | . = ALIGN(4); 24 | _ssram1_bss = .; 25 | 26 | PROVIDE(__sram1_bss_start__ = _sram1_bss); 27 | *(.sram1_bss) 28 | *(.sram1_bss*) 29 | . = ALIGN(4); 30 | _esram1_bss = .; 31 | 32 | PROVIDE(__sram1_bss_end__ = _esram1_bss); 33 | } > RAM_D2 34 | 35 | .sdram (NOLOAD) : 36 | { 37 | . = ALIGN(4); 38 | _ssdram_bss = .; 39 | 40 | PROVIDE(__sdram_start = _ssdram_bss); 41 | *(.sdram) 42 | *(.sdram*) 43 | . = ALIGN(4); 44 | _esdram = .; 45 | 46 | PROVIDE(__sdram_end = _esdram); 47 | } > SDRAM 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | //! Audio module. Handles audio startup and I/O. 2 | //! As well as converting between the S24 input and f32 for processing. 3 | use core::ops::{Deref, DerefMut}; 4 | 5 | use log::info; 6 | 7 | use stm32h7xx_hal::{ 8 | dma, 9 | gpio::{gpioe, Analog}, 10 | pac::{self}, 11 | rcc::{self, rec}, 12 | sai::{self, *}, 13 | stm32::{self, rcc::d2ccip1r::SAI1SEL_A}, 14 | traits::i2s::FullDuplex, 15 | }; 16 | 17 | // Process samples at 1000 Hz 18 | // With a circular buffer(*2) in stereo (*2) 19 | pub const BLOCK_SIZE_MAX: usize = 48; 20 | pub const DMA_BUFFER_SIZE: usize = BLOCK_SIZE_MAX * 2 * 2; 21 | 22 | pub type DmaBuffer = [u32; DMA_BUFFER_SIZE]; 23 | 24 | const START_OF_DRAM2: u32 = 0x30000000; 25 | const DMA_MEM_SIZE: usize = 32 * 1024; 26 | 27 | #[link_section = ".sram1_bss"] 28 | #[no_mangle] 29 | static mut TX_BUFFER: DmaBuffer = [0; DMA_BUFFER_SIZE]; 30 | #[link_section = ".sram1_bss"] 31 | #[no_mangle] 32 | static mut RX_BUFFER: DmaBuffer = [0; DMA_BUFFER_SIZE]; 33 | 34 | const FBIPMAX: f32 = 0.999985; 35 | const FBIPMIN: f32 = -FBIPMAX; 36 | const F32_TO_S24_SCALE: f32 = 8388608.0; // 2 ** 23 37 | const S24_TO_F32_SCALE: f32 = 1.0 / F32_TO_S24_SCALE; 38 | const S24_SIGN: i32 = 0x800000; 39 | /// Largest number of audio blocks for a single DMA operation 40 | pub const MAX_TRANSFER_SIZE: usize = BLOCK_SIZE_MAX * 2; 41 | 42 | pub type AudioBuffer = [(f32, f32); BLOCK_SIZE_MAX]; 43 | 44 | /// Raw pointer backed reference to the DMA buffers. It exists to avoid storing multiple aliasing 45 | /// `&mut` references to `TX_BUFFER` and `RX_BUFFER`, which is UB. 46 | /// # Safety 47 | /// References are created whenever the underlying buffer is accessed, but since the access is single 48 | /// threaded and the references are short lived this should be fine. 49 | /// This wrapper is only, and may only be, pointing to memory with a 'static lifetime. 50 | struct DmaBufferRawRef { 51 | ptr: *mut DmaBuffer, 52 | } 53 | impl Deref for DmaBufferRawRef { 54 | type Target = DmaBuffer; 55 | 56 | fn deref(&self) -> &Self::Target { 57 | unsafe { &*self.ptr } 58 | } 59 | } 60 | impl DerefMut for DmaBufferRawRef { 61 | fn deref_mut(&mut self) -> &mut Self::Target { 62 | unsafe { &mut *self.ptr } 63 | } 64 | } 65 | unsafe impl stable_deref_trait::StableDeref for DmaBufferRawRef {} 66 | // Required for using the buffer in practice. No more dangerous than sending a `&mut DmaBuffer` 67 | unsafe impl Send for DmaBufferRawRef {} 68 | 69 | type DmaInputStream = dma::Transfer< 70 | dma::dma::Stream1, 71 | stm32::SAI1, 72 | dma::PeripheralToMemory, 73 | DmaBufferRawRef, 74 | dma::DBTransfer, 75 | >; 76 | 77 | type DmaOutputStream = dma::Transfer< 78 | dma::dma::Stream0, 79 | stm32::SAI1, 80 | dma::MemoryToPeripheral, 81 | DmaBufferRawRef, 82 | dma::DBTransfer, 83 | >; 84 | 85 | type StereoIteratorHandle = fn(StereoIterator, &mut Output); 86 | 87 | #[derive(Debug, Copy, Clone, PartialEq)] 88 | struct S24(i32); 89 | 90 | impl From for S24 { 91 | fn from(x: i32) -> S24 { 92 | S24(x) 93 | } 94 | } 95 | 96 | impl From for S24 { 97 | fn from(x: u32) -> S24 { 98 | S24(x as i32) 99 | } 100 | } 101 | 102 | impl From for i32 { 103 | fn from(x: S24) -> i32 { 104 | x.0 105 | } 106 | } 107 | 108 | impl From for u32 { 109 | fn from(x: S24) -> u32 { 110 | x.0 as u32 111 | } 112 | } 113 | 114 | impl From for S24 { 115 | fn from(x: f32) -> S24 { 116 | let x = if x <= FBIPMIN { 117 | FBIPMIN 118 | } else if x >= FBIPMAX { 119 | FBIPMAX 120 | } else { 121 | x 122 | }; 123 | S24((x * F32_TO_S24_SCALE) as i32) 124 | } 125 | } 126 | 127 | impl From for f32 { 128 | fn from(x: S24) -> f32 { 129 | ((x.0 ^ S24_SIGN) - S24_SIGN) as f32 * S24_TO_F32_SCALE 130 | } 131 | } 132 | 133 | /// Core struct for handling audio I/O 134 | pub struct Audio { 135 | sai: sai::Sai, 136 | input: Input, 137 | output: Output, 138 | input_stream: DmaInputStream, 139 | output_stream: DmaOutputStream, 140 | } 141 | 142 | impl Audio { 143 | /// Setup audio handler 144 | pub fn new( 145 | dma1_d: stm32::DMA1, 146 | dma1_p: rec::Dma1, 147 | sai1_d: stm32::SAI1, 148 | sai1_p: rec::Sai1, 149 | 150 | pe2: gpioe::PE2, 151 | pe3: gpioe::PE3, 152 | pe4: gpioe::PE4, 153 | pe5: gpioe::PE5, 154 | pe6: gpioe::PE6, 155 | 156 | clocks: &rcc::CoreClocks, 157 | mpu: &mut cortex_m::peripheral::MPU, 158 | scb: &mut cortex_m::peripheral::SCB, 159 | ) -> Self { 160 | info!("Setup up DMA..."); 161 | crate::mpu::dma_init(mpu, scb, START_OF_DRAM2 as *mut u32, DMA_MEM_SIZE); 162 | 163 | let dma1_streams = dma::dma::StreamsTuple::new(dma1_d, dma1_p); 164 | 165 | // dma1 stream 0 166 | let tx_buffer = DmaBufferRawRef { 167 | ptr: &raw mut TX_BUFFER, 168 | }; 169 | let dma_config = dma::dma::DmaConfig::default() 170 | .priority(dma::config::Priority::High) 171 | .memory_increment(true) 172 | .peripheral_increment(false) 173 | .circular_buffer(true) 174 | .fifo_enable(false); 175 | let mut output_stream: dma::Transfer<_, _, dma::MemoryToPeripheral, _, _> = 176 | dma::Transfer::init( 177 | dma1_streams.0, 178 | unsafe { pac::Peripherals::steal().SAI1 }, 179 | tx_buffer, 180 | None, 181 | dma_config, 182 | ); 183 | 184 | // dma1 stream 1 185 | let rx_buffer = DmaBufferRawRef { 186 | ptr: &raw mut RX_BUFFER, 187 | }; 188 | let dma_config = dma_config 189 | .transfer_complete_interrupt(true) 190 | .half_transfer_interrupt(true); 191 | let mut input_stream: dma::Transfer<_, _, dma::PeripheralToMemory, _, _> = 192 | dma::Transfer::init( 193 | dma1_streams.1, 194 | unsafe { pac::Peripherals::steal().SAI1 }, 195 | rx_buffer, 196 | None, 197 | dma_config, 198 | ); 199 | 200 | info!("Setup up SAI..."); 201 | let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); 202 | let master_config = I2SChanConfig::new(I2SDir::Tx).set_frame_sync_active_high(true); 203 | let slave_config = I2SChanConfig::new(I2SDir::Rx) 204 | .set_sync_type(I2SSync::Internal) 205 | .set_frame_sync_active_high(true); 206 | 207 | let pins_a = ( 208 | pe2.into_alternate(), // MCLK_A 209 | pe5.into_alternate(), // SCK_A 210 | pe4.into_alternate(), // FS_A 211 | pe6.into_alternate(), // SD_A 212 | Some(pe3.into_alternate()), // SD_B 213 | ); 214 | 215 | // Hand off to audio module 216 | let mut sai = sai1_d.i2s_ch_a( 217 | pins_a, 218 | crate::AUDIO_SAMPLE_HZ, 219 | I2SDataSize::BITS_24, 220 | sai1_rec, 221 | clocks, 222 | I2sUsers::new(master_config).add_slave(slave_config), 223 | ); 224 | 225 | input_stream.start(|_sai1_rb| { 226 | sai.enable_dma(SaiChannel::ChannelB); 227 | }); 228 | 229 | output_stream.start(|sai1_rb| { 230 | sai.enable_dma(SaiChannel::ChannelA); 231 | 232 | // wait until sai1's fifo starts to receive data 233 | info!("Sai1 fifo waiting to receive data."); 234 | while sai1_rb.cha().sr.read().flvl().is_empty() {} 235 | info!("Audio started!"); 236 | sai.enable(); 237 | sai.try_send(0, 0).unwrap(); 238 | }); 239 | let input = Input::new(DmaBufferRawRef { 240 | ptr: &raw mut RX_BUFFER, 241 | }); 242 | let output = Output::new(DmaBufferRawRef { 243 | ptr: &raw mut TX_BUFFER, 244 | }); 245 | info!( 246 | "{:?}, {:?}", 247 | &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 248 | ); 249 | Audio { 250 | sai, 251 | input_stream, 252 | output_stream, 253 | input, 254 | output, 255 | } 256 | } 257 | 258 | /// Check interrupts and set indexes for I/O 259 | fn read(&mut self) -> bool { 260 | // Check interrupt(s) 261 | if self.input_stream.get_half_transfer_flag() { 262 | self.input_stream.clear_half_transfer_interrupt(); 263 | self.input.set_index(0); 264 | self.output.set_index(0); 265 | true 266 | } else if self.input_stream.get_transfer_complete_flag() { 267 | self.input_stream.clear_transfer_complete_interrupt(); 268 | self.input.set_index(MAX_TRANSFER_SIZE); 269 | self.output.set_index(MAX_TRANSFER_SIZE); 270 | true 271 | } else { 272 | false 273 | } 274 | } 275 | 276 | /// Directly pass received audio to output without any processing. 277 | pub fn passthru(&mut self) { 278 | // Copy data 279 | if self.read() { 280 | let mut index = 0; 281 | let mut out_index = self.output.index; 282 | while index < MAX_TRANSFER_SIZE { 283 | self.output.buffer[out_index] = self.input.buffer[index + self.input.index]; 284 | self.output.buffer[out_index + 1] = self.input.buffer[index + self.input.index + 1]; 285 | index += 2; 286 | out_index += 2; 287 | } 288 | } 289 | } 290 | 291 | /// Gets the audio input from the DMA memory and writes it to buffer 292 | pub fn get_stereo(&mut self, buffer: &mut AudioBuffer) -> bool { 293 | if self.read() { 294 | for (i, (left, right)) in StereoIterator::new( 295 | &self.input.buffer[self.input.index..self.input.index + MAX_TRANSFER_SIZE], 296 | ) 297 | .enumerate() 298 | { 299 | buffer[i] = (left, right); 300 | } 301 | true 302 | } else { 303 | false 304 | } 305 | } 306 | 307 | fn get_stereo_iter(&mut self) -> Option { 308 | if self.read() { 309 | return Some(StereoIterator::new( 310 | &self.input.buffer[self.input.index..MAX_TRANSFER_SIZE], 311 | )); 312 | } 313 | None 314 | } 315 | 316 | /// Push data to the DMA buffer for output 317 | /// Call this once per sample per call to [get_stereo()](Audio#get_stereo) 318 | pub fn push_stereo(&mut self, data: (f32, f32)) -> Result<(), ()> { 319 | self.output.push(data) 320 | } 321 | } 322 | 323 | struct Input { 324 | index: usize, 325 | buffer: DmaBufferRawRef, 326 | } 327 | 328 | impl Input { 329 | /// Create a new Input from a DmaBuffer 330 | fn new(buffer: DmaBufferRawRef) -> Self { 331 | Self { index: 0, buffer } 332 | } 333 | 334 | fn set_index(&mut self, index: usize) { 335 | self.index = index; 336 | } 337 | 338 | /// Get StereoIterator(interleaved) iterator 339 | pub fn get_stereo_iter(&self) -> Option { 340 | Some(StereoIterator::new(&self.buffer[..2])) 341 | } 342 | } 343 | 344 | struct Output { 345 | index: usize, 346 | buffer: DmaBufferRawRef, 347 | } 348 | 349 | impl Output { 350 | /// Create a new Input from a DmaBuffer 351 | fn new(buffer: DmaBufferRawRef) -> Self { 352 | Self { index: 0, buffer } 353 | } 354 | 355 | fn set_index(&mut self, index: usize) { 356 | self.index = index; 357 | } 358 | 359 | pub fn push(&mut self, data: (f32, f32)) -> Result<(), ()> { 360 | if self.index < (MAX_TRANSFER_SIZE * 2) { 361 | self.buffer[self.index] = S24::from(data.0).into(); 362 | self.buffer[self.index + 1] = S24::from(data.1).into(); 363 | self.index += 2; 364 | return Ok(()); 365 | } 366 | Err(()) 367 | } 368 | } 369 | 370 | struct StereoIterator<'a> { 371 | index: usize, 372 | buf: &'a [u32], 373 | } 374 | 375 | impl<'a> StereoIterator<'a> { 376 | fn new(buf: &'a [u32]) -> Self { 377 | Self { index: 0, buf } 378 | } 379 | } 380 | 381 | impl Iterator for StereoIterator<'_> { 382 | type Item = (f32, f32); 383 | 384 | fn next(&mut self) -> Option { 385 | if self.index < self.buf.len() { 386 | self.index += 2; 387 | Some(( 388 | S24(self.buf[self.index - 2] as i32).into(), 389 | S24(self.buf[self.index - 1] as i32).into(), 390 | )) 391 | } else { 392 | None 393 | } 394 | } 395 | } 396 | 397 | struct Mono<'a> { 398 | index: usize, 399 | buf: &'a [i32], 400 | } 401 | 402 | impl<'a> Mono<'a> { 403 | fn new(buf: &'a [i32]) -> Self { 404 | Self { index: 0, buf } 405 | } 406 | } 407 | 408 | impl Iterator for Mono<'_> { 409 | type Item = f32; 410 | 411 | fn next(&mut self) -> Option { 412 | if self.index < self.buf.len() { 413 | self.index += 2; 414 | Some(S24(self.buf[self.index - 1]).into()) 415 | } else { 416 | None 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/flash.rs: -------------------------------------------------------------------------------- 1 | //! IS25LP064A: 64Mbit/8Mbyte flash memory 2 | //! 3 | //! https://www.issi.com/WW/pdf/25LP032-64A-B.pdf 4 | //! 5 | 6 | use stm32h7xx_hal::{ 7 | gpio::{gpiof, gpiog, Analog, Speed}, 8 | nb::Error as nbError, 9 | prelude::*, 10 | rcc, 11 | xspi::{Config, QspiError, QspiMode, QspiWord}, 12 | }; 13 | 14 | pub type FlashResult = Result; 15 | pub type NBFlashResult = stm32h7xx_hal::nb::Result; 16 | 17 | /// Flash erasure enum 18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 19 | pub enum FlashErase { 20 | ///The whole chip 21 | Chip, 22 | ///4Kbyte sector address 23 | Sector4K(u32), 24 | ///32Kbyte block address 25 | Block32K(u32), 26 | ///64Kbyte block address 27 | Block64K(u32), 28 | } 29 | 30 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 31 | enum FlashState { 32 | Idle, 33 | Programming { address: u32, chunk: u32 }, 34 | Erasing(FlashErase), 35 | } 36 | 37 | /// Flash memory peripheral 38 | pub struct Flash { 39 | qspi: stm32h7xx_hal::xspi::Qspi, 40 | state: FlashState, 41 | } 42 | 43 | /* 44 | * 45 | * 6.1 STATUS REGISTER 46 | * Status Register Format and Status Register Bit Definitionsare described in Table 6.1 & Table 6.2. 47 | * 0: WIP write in progress(0 == read, 1 == busy) 48 | * 1: WEL write enable (1 == enabled) 49 | * 2-5: BP block protection (0 indicates not protected [default]) 50 | * 6: Quad enable, quad output function enable, (1 = enable) 51 | * 7: Status register write disable (1 == write protected [0 = default]) 52 | * 53 | * 6.3 READ REGISTER 54 | * 55 | * 56 | * 8.11 SECTOR ERASE OPERATION (SER, D7h/20h) 57 | * * instruction, 3 byte address 58 | * * WEL is reset after 59 | * 8.12 BLOCK ERASE OPERATION (BER32K:52h, BER64K:D8h) 60 | * * instruction, 3 byte address 61 | * * WEL is reset after 62 | * 8.13 CHIP ERASE OPERATION (CER, C7h/60h) 63 | * * instruction only 64 | * * WEL is reset after 65 | * 8.14 WRITE ENABLE OPERATION (WREN, 06h) 66 | * * instruction only 67 | * * sets WEL 68 | * 8.16 READ STATUS REGISTER OPERATION (RDSR, 05h) 69 | * * instruction, 1 byte read 70 | */ 71 | 72 | impl Flash { 73 | fn wait(&mut self) { 74 | while self.qspi.is_busy().is_err() {} 75 | } 76 | 77 | fn write_complete(&mut self) -> FlashResult { 78 | match self.status() { 79 | Ok(status) => Ok(status & 0x01 == 0), 80 | Err(e) => return Err(e), 81 | } 82 | } 83 | 84 | fn wait_write(&mut self) -> FlashResult<()> { 85 | loop { 86 | if self.write_complete()? { 87 | return Ok(()); 88 | } 89 | } 90 | } 91 | 92 | fn write_command(&mut self, cmd: u8) -> FlashResult<()> { 93 | self.wait(); 94 | self.qspi 95 | .write_extended(QspiWord::U8(cmd), QspiWord::None, QspiWord::None, &[]) 96 | } 97 | 98 | fn write_reg(&mut self, cmd: u8, data: u8) -> FlashResult<()> { 99 | self.wait(); 100 | self.qspi 101 | .write_extended(QspiWord::U8(cmd), QspiWord::None, QspiWord::None, &[data]) 102 | } 103 | 104 | fn enable_write(&mut self) -> FlashResult<()> { 105 | self.write_command(0x06) 106 | } 107 | 108 | fn assert_info(&mut self) { 109 | let mut info: [u8; 3] = [0; 3]; 110 | self.wait(); 111 | self.qspi 112 | .read_extended( 113 | QspiWord::U8(0x9F), 114 | QspiWord::None, 115 | QspiWord::None, 116 | 0, 117 | &mut info, 118 | ) 119 | .unwrap(); 120 | assert_eq!(&info, &[157, 96, 23]); 121 | } 122 | 123 | fn status(&mut self) -> FlashResult { 124 | let mut status: [u8; 1] = [0xFF]; 125 | self.wait(); 126 | self.qspi 127 | .read_extended( 128 | QspiWord::U8(0x05), 129 | QspiWord::None, 130 | QspiWord::None, 131 | 0, 132 | &mut status, 133 | ) 134 | .map(|_| status[0]) 135 | } 136 | 137 | /// Reset the internal state, only if you know what you're doing 138 | pub unsafe fn reset(&mut self) { 139 | self.state = FlashState::Idle; 140 | } 141 | 142 | /// Initialize the flash quad spi interface 143 | pub fn new( 144 | regs: stm32h7xx_hal::device::QUADSPI, 145 | prec: rcc::rec::Qspi, 146 | clocks: &rcc::CoreClocks, 147 | pf6: gpiof::PF6, 148 | pf7: gpiof::PF7, 149 | pf8: gpiof::PF8, 150 | pf9: gpiof::PF9, 151 | pf10: gpiof::PF10, 152 | pg6: gpiog::PG6, 153 | ) -> Self { 154 | let _ncs = pg6.into_alternate::<10>().speed(Speed::VeryHigh); //QUADSPI_BK1_NCS 155 | 156 | let sck = pf10.into_alternate().speed(Speed::VeryHigh); 157 | let io0 = pf8.into_alternate().speed(Speed::VeryHigh); 158 | let io1 = pf9.into_alternate().speed(Speed::VeryHigh); 159 | let io2 = pf7.into_alternate().speed(Speed::VeryHigh); 160 | let io3 = pf6.into_alternate().speed(Speed::VeryHigh); 161 | 162 | let config = Config::new(133.MHz()).mode(QspiMode::OneBit); 163 | let qspi = regs.bank1((sck, io0, io1, io2, io3), config, &clocks, prec); 164 | 165 | let mut flash = Flash { 166 | qspi, 167 | state: FlashState::Idle, 168 | }; 169 | 170 | //enable quad 171 | flash.enable_write().unwrap(); 172 | flash.write_command(0x35).unwrap(); 173 | flash.qspi.configure_mode(QspiMode::FourBit).unwrap(); 174 | 175 | flash.enable_write().unwrap(); 176 | //only enable write, nothing else 177 | flash.write_reg(0x01, 0b0000_0010).unwrap(); 178 | flash.wait_write().unwrap(); 179 | flash.assert_info(); 180 | 181 | //setup read parameters, no wrap, default strength, default burst, 8 dummy cycles 182 | //pg 19 183 | flash.enable_write().unwrap(); 184 | flash.write_reg(0xC0, 0b1111_1000).unwrap(); 185 | flash.wait_write().unwrap(); 186 | 187 | flash 188 | } 189 | 190 | /// Erase all or some of the chip. 191 | /// 192 | /// Remarks: 193 | /// - Erasing sets all the bits in the given area to `1`. 194 | /// - The memory array of the IS25LP064A/032A is organized into uniform 4 Kbyte sectors or 195 | /// 32/64 Kbyte uniform blocks (a block consists of eight/sixteen adjacent sectors 196 | /// respectively). 197 | pub fn erase(&mut self, op: FlashErase) -> NBFlashResult<()> { 198 | match self.state { 199 | FlashState::Erasing(e) => { 200 | assert_eq!(e, op); 201 | if self.write_complete()? { 202 | self.state = FlashState::Idle; 203 | Ok(()) 204 | } else { 205 | Err(nbError::WouldBlock) 206 | } 207 | } 208 | FlashState::Idle => { 209 | self.enable_write()?; 210 | self.wait(); 211 | match op { 212 | FlashErase::Chip => self.write_command(0x60), 213 | FlashErase::Sector4K(a) => self.qspi.write_extended( 214 | QspiWord::U8(0xD7), 215 | QspiWord::U24(a as _), 216 | QspiWord::None, 217 | &[], 218 | ), 219 | FlashErase::Block32K(a) => self.qspi.write_extended( 220 | QspiWord::U8(0x52), 221 | QspiWord::U24(a as _), 222 | QspiWord::None, 223 | &[], 224 | ), 225 | FlashErase::Block64K(a) => self.qspi.write_extended( 226 | QspiWord::U8(0xD8), 227 | QspiWord::U24(a as _), 228 | QspiWord::None, 229 | &[], 230 | ), 231 | }?; 232 | self.state = FlashState::Erasing(op); 233 | Err(nbError::WouldBlock) 234 | } 235 | _ => panic!("not idle or erasing"), 236 | } 237 | } 238 | 239 | /// Read `data` out of the flash starting at the given `address` 240 | pub fn read(&mut self, address: u32, data: &mut [u8]) -> FlashResult<()> { 241 | assert_eq!(self.state, FlashState::Idle); 242 | let mut addr = address; 243 | //see page 34 for allowing to skip instruction 244 | assert!((addr as usize + data.len()) < 0x800000); 245 | for chunk in data.chunks_mut(32) { 246 | self.wait(); 247 | self.qspi.read_extended( 248 | QspiWord::U8(0xEB), 249 | QspiWord::U24(addr), 250 | QspiWord::U8(0x00), //only A in top byte does anything 251 | 8, 252 | chunk, 253 | )?; 254 | addr += 32; 255 | } 256 | Ok(()) 257 | } 258 | 259 | /// Program `data` into the flash starting at the given `address` 260 | /// 261 | /// Remarks: 262 | /// - This operation can only set 1s to 0s, you must use `erase` to set a 0 to a 1. 263 | /// - The starting byte can be anywhere within the page (256 byte chunk). When the end of the 264 | /// page is reached, the address will wrap around to the beginning of the same page. If the 265 | /// data to be programmed are less than a full page, the data of all other bytes on the same 266 | /// page will remain unchanged. 267 | pub fn program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { 268 | let prog = |flash: &mut Self, chunk_index: u32| -> NBFlashResult<()> { 269 | if let Some(chunk) = data.chunks(32).nth(chunk_index as usize) { 270 | flash.enable_write()?; 271 | flash.wait(); 272 | flash.qspi.write_extended( 273 | QspiWord::U8(0x02), 274 | QspiWord::U24(address + chunk_index * 32), 275 | QspiWord::None, 276 | chunk, 277 | )?; 278 | flash.state = FlashState::Programming { 279 | address, 280 | chunk: chunk_index, 281 | }; 282 | Err(nbError::WouldBlock) 283 | } else { 284 | flash.state = FlashState::Idle; 285 | Ok(()) 286 | } 287 | }; 288 | match self.state { 289 | FlashState::Idle => prog(self, 0), 290 | FlashState::Programming { 291 | address: addr, 292 | chunk, 293 | } => { 294 | assert_eq!(addr, address); 295 | if self.write_complete()? { 296 | prog(self, chunk + 1) 297 | } else { 298 | Err(nbError::WouldBlock) 299 | } 300 | } 301 | _ => panic!("invalid state for programming"), 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/gpio.rs: -------------------------------------------------------------------------------- 1 | //! GPIO module. Contains pins by Daisy names. 2 | //! Provides access to the Seed LED and codec reset. 3 | use stm32h7xx_hal::gpio; 4 | use stm32h7xx_hal::gpio::gpioc::PC7; 5 | 6 | // use stm32h7xx_hal::hal::digital::v2::InputPin; 7 | #[allow(unused_imports)] 8 | use stm32h7xx_hal::gpio::{Alternate, Analog, Input, Output, Pull, PushPull}; 9 | 10 | pub use gpio::gpioa::PA0 as Daisy25; 11 | pub use gpio::gpioa::PA1 as Daisy24; 12 | pub use gpio::gpioa::PA2 as Daisy28; 13 | pub use gpio::gpioa::PA3 as Daisy16; 14 | pub use gpio::gpioa::PA4 as Daisy23; 15 | pub use gpio::gpioa::PA5 as Daisy22; 16 | pub use gpio::gpioa::PA6 as Daisy19; 17 | pub use gpio::gpioa::PA7 as Daisy18; 18 | pub use gpio::gpiob::PB1 as Daisy17; 19 | pub use gpio::gpiob::PB12 as Daisy0; 20 | pub use gpio::gpiob::PB14 as Daisy29; 21 | pub use gpio::gpiob::PB15 as Daisy30; 22 | pub use gpio::gpiob::PB4 as Daisy9; 23 | pub use gpio::gpiob::PB5 as Daisy10; 24 | pub use gpio::gpiob::PB6 as Daisy13; 25 | pub use gpio::gpiob::PB7 as Daisy14; 26 | pub use gpio::gpiob::PB8 as Daisy11; 27 | pub use gpio::gpiob::PB9 as Daisy12; 28 | pub use gpio::gpioc::PC0 as Daisy15; 29 | pub use gpio::gpioc::PC1 as Daisy20; 30 | pub use gpio::gpioc::PC10 as Daisy2; 31 | pub use gpio::gpioc::PC11 as Daisy1; 32 | pub use gpio::gpioc::PC12 as Daisy6; 33 | pub use gpio::gpioc::PC4 as Daisy21; 34 | pub use gpio::gpioc::PC8 as Daisy4; 35 | pub use gpio::gpioc::PC9 as Daisy3; 36 | pub use gpio::gpiod::PD11 as Daisy26; 37 | pub use gpio::gpiod::PD2 as Daisy5; 38 | pub use gpio::gpiog::PG10 as Daisy7; 39 | pub use gpio::gpiog::PG11 as Daisy8; 40 | pub use gpio::gpiog::PG9 as Daisy27; 41 | 42 | pub type SeedLed = PC7>; 43 | 44 | use crate::*; 45 | 46 | /// GPIO struct for holding Daisy GPIO pins 47 | #[allow(clippy::upper_case_acronyms)] 48 | pub struct GPIO { 49 | pub led: SeedLed, 50 | codec: gpio::gpiob::PB11>, 51 | pub daisy0: Option>, 52 | pub daisy1: Option>, 53 | pub daisy2: Option>, 54 | pub daisy3: Option>, 55 | pub daisy4: Option>, 56 | pub daisy5: Option>, 57 | pub daisy6: Option>, 58 | pub daisy7: Option>, 59 | pub daisy8: Option>, 60 | pub daisy9: Option>>, 61 | pub daisy10: Option>, 62 | pub daisy11: Option>, 63 | pub daisy12: Option>, 64 | pub daisy13: Option>, 65 | pub daisy14: Option>, 66 | pub daisy15: Option>, 67 | pub daisy16: Option>, 68 | pub daisy17: Option>, 69 | pub daisy18: Option>, 70 | pub daisy19: Option>, 71 | pub daisy20: Option>, 72 | pub daisy21: Option>, 73 | pub daisy22: Option>, 74 | pub daisy23: Option>, 75 | pub daisy24: Option>, 76 | pub daisy25: Option>, 77 | pub daisy26: Option>, 78 | pub daisy27: Option>, 79 | pub daisy28: Option>, 80 | pub daisy29: Option>, 81 | pub daisy30: Option>, 82 | } 83 | 84 | impl GPIO { 85 | /// Initialize the GPIOs 86 | pub fn init( 87 | seed_led: gpio::gpioc::PC7, 88 | codec: gpio::gpiob::PB11, 89 | daisy0: Option>, 90 | daisy1: Option>, 91 | daisy2: Option>, 92 | daisy3: Option>, 93 | daisy4: Option>, 94 | daisy5: Option>, 95 | daisy6: Option>, 96 | daisy7: Option>, 97 | daisy8: Option>, 98 | daisy9: Option>>, 99 | daisy10: Option>, 100 | daisy11: Option>, 101 | daisy12: Option>, 102 | daisy13: Option>, 103 | daisy14: Option>, 104 | daisy15: Option>, 105 | daisy16: Option>, 106 | daisy17: Option>, 107 | daisy18: Option>, 108 | daisy19: Option>, 109 | daisy20: Option>, 110 | daisy21: Option>, 111 | daisy22: Option>, 112 | daisy23: Option>, 113 | daisy24: Option>, 114 | daisy25: Option>, 115 | daisy26: Option>, 116 | daisy27: Option>, 117 | daisy28: Option>, 118 | daisy29: Option>, 119 | daisy30: Option>, 120 | ) -> GPIO { 121 | let led = seed_led.into_push_pull_output(); 122 | let codec = codec.into_push_pull_output(); 123 | let mut gpio = Self { 124 | led, 125 | codec, 126 | daisy0, 127 | daisy1, 128 | daisy2, 129 | daisy3, 130 | daisy4, 131 | daisy5, 132 | daisy6, 133 | daisy7, 134 | daisy8, 135 | daisy9, 136 | daisy10, 137 | daisy11, 138 | daisy12, 139 | daisy13, 140 | daisy14, 141 | daisy15, 142 | daisy16, 143 | daisy17, 144 | daisy18, 145 | daisy19, 146 | daisy20, 147 | daisy21, 148 | daisy22, 149 | daisy23, 150 | daisy24, 151 | daisy25, 152 | daisy26, 153 | daisy27, 154 | daisy28, 155 | daisy29, 156 | daisy30, 157 | }; 158 | gpio.reset_codec(); 159 | gpio 160 | } 161 | 162 | /// Reset the AK4556 codec chip 163 | pub fn reset_codec(&mut self) { 164 | self.codec.set_low(); 165 | delay_ms(5); 166 | self.codec.set_high(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/hid.rs: -------------------------------------------------------------------------------- 1 | //! Interface abstractions for switches, potentiometers, etc. 2 | #[allow(unused_imports)] 3 | use stm32h7xx_hal::gpio::{Analog, Input, Output, PushPull}; 4 | use stm32h7xx_hal::hal::digital::v2::{InputPin, OutputPin}; 5 | 6 | use debouncr::{debounce_4, Debouncer, Edge, Repeat4}; 7 | use micromath::F32Ext; 8 | 9 | /// Define the types for a transformation function for AnalogControl 10 | pub type TransformFn = fn(f32) -> f32; 11 | 12 | /// If the switch is a pull-up or pull-down type 13 | pub enum SwitchType { 14 | PullUp, 15 | PullDown, 16 | } 17 | 18 | /// LED blink status 19 | pub enum BlinkStatus { 20 | Disabled, 21 | On, 22 | Off, 23 | } 24 | 25 | /// Process state information from a 2 state switch. 26 | /// [Debouncr](https://github.com/dbrgn/debouncr/) with a 4 sample array is used for debouncing. 27 | pub struct Switch { 28 | pin: T, 29 | state: Debouncer, 30 | falling: bool, 31 | rising: bool, 32 | switch_type: SwitchType, 33 | double_threshold: Option, 34 | held_threshold: Option, 35 | was_pressed: bool, 36 | held_counter: u32, 37 | last_press_counter: u32, 38 | single_press: bool, 39 | double_press: bool, 40 | } 41 | 42 | impl Switch 43 | where 44 | T: InputPin, 45 | ::Error: core::fmt::Debug, 46 | { 47 | /// Create a new Switch. 48 | pub fn new(pin: T, switch_type: SwitchType) -> Self { 49 | Self { 50 | pin, 51 | state: debounce_4(false), 52 | falling: false, 53 | rising: false, 54 | switch_type, 55 | double_threshold: None, 56 | held_threshold: None, 57 | was_pressed: false, 58 | held_counter: 0, 59 | last_press_counter: 0, 60 | single_press: false, 61 | double_press: false, 62 | } 63 | } 64 | 65 | /// Set the threshold in number of calls to update. 66 | pub fn set_held_thresh(&mut self, held_threshold: Option) { 67 | self.held_threshold = if let Some(held_threshold) = held_threshold { 68 | Some(held_threshold) 69 | } else { 70 | None 71 | }; 72 | } 73 | 74 | /// Set the threshold in number of calls to update. 75 | pub fn set_double_thresh(&mut self, double_threshold: Option) { 76 | self.double_threshold = if let Some(double_threshold) = double_threshold { 77 | Some(double_threshold) 78 | } else { 79 | None 80 | }; 81 | } 82 | 83 | /// Read the state of the switch and update status. This should be called on a timer. 84 | pub fn update(&mut self) { 85 | let is_pressed = self.is_pressed(); 86 | 87 | // Handle event 88 | if let Some(edge) = self.state.update(is_pressed) { 89 | match edge { 90 | Edge::Falling => self.falling = true, 91 | Edge::Rising => self.rising = true, 92 | } 93 | } else { 94 | self.falling = false; 95 | self.rising = false; 96 | } 97 | 98 | // Handle double press logic 99 | if let Some(double_threshold) = self.double_threshold { 100 | // If we exceed the threshold for a double press reset it 101 | // Otherwise the counter will eventually wrap around and panic 102 | if self.single_press { 103 | self.last_press_counter += 1; 104 | if self.last_press_counter > double_threshold { 105 | self.single_press = false; 106 | } 107 | } 108 | 109 | if self.falling { 110 | if self.single_press && self.last_press_counter < double_threshold { 111 | self.double_press = true; 112 | self.single_press = false; 113 | } else { 114 | self.single_press = true; 115 | self.last_press_counter = 0; 116 | } 117 | } else { 118 | self.double_press = false; 119 | } 120 | } 121 | 122 | // Handle held counter 123 | if is_pressed { 124 | self.held_counter += 1; 125 | } 126 | if self.rising { 127 | self.held_counter = 0; 128 | } 129 | } 130 | 131 | /// If the switch state is high 132 | pub fn is_high(&self) -> bool { 133 | self.state.is_high() 134 | } 135 | 136 | /// If the switch state is low 137 | pub fn is_low(&self) -> bool { 138 | self.state.is_low() 139 | } 140 | 141 | /// If the switch is pressed 142 | pub fn is_pressed(&self) -> bool { 143 | match self.switch_type { 144 | SwitchType::PullUp => self.pin.is_low().unwrap(), 145 | SwitchType::PullDown => self.pin.is_high().unwrap(), 146 | } 147 | } 148 | 149 | /// If the switch is rising 150 | pub fn is_rising(&self) -> bool { 151 | self.rising 152 | } 153 | 154 | /// If the switch is falling 155 | pub fn is_falling(&self) -> bool { 156 | self.falling 157 | } 158 | 159 | /// If the switch is being held 160 | pub fn is_held(&self) -> bool { 161 | if let Some(held_threshold) = self.held_threshold { 162 | return self.falling && self.held_counter >= held_threshold; 163 | } 164 | false 165 | } 166 | 167 | /// If the switch pressed twice inside the provided threshold 168 | pub fn is_double(&self) -> bool { 169 | self.double_press 170 | } 171 | } 172 | 173 | const ANALOG_ARR_SIZE: usize = 4; 174 | const ANALOG_ARR_SIZE_F32: f32 = ANALOG_ARR_SIZE as f32; 175 | 176 | /// Contains the state of an analog control (e.g. a potentiometer). 177 | pub struct AnalogControl { 178 | state: [f32; ANALOG_ARR_SIZE], 179 | scale: f32, 180 | transform: Option, 181 | pin: T, 182 | index: usize, 183 | } 184 | 185 | impl AnalogControl { 186 | /// Create a new AnalogControl. 187 | pub fn new(pin: T, scale: f32) -> Self { 188 | Self { 189 | state: [0.0; ANALOG_ARR_SIZE], 190 | scale, 191 | transform: None, 192 | pin, 193 | index: 0, 194 | } 195 | } 196 | 197 | /// Set the scaling 198 | pub fn set_scale(&mut self, scale: f32) { 199 | self.scale = scale; 200 | } 201 | 202 | /// Provide an optional transformation function. 203 | /// 204 | /// # Example 205 | /// 206 | /// ```rust 207 | /// // Transform linear input into logarithmic 208 | ///let mut control1 = hid::AnalogControl::new(daisy15, adc1_max); 209 | ///control1.set_transform(|x| x * x); 210 | ///``` 211 | pub fn set_transform(&mut self, transform: TransformFn) { 212 | self.transform = Some(transform); 213 | } 214 | 215 | /// Update control value. This should be called on a timer. 216 | /// Typically you would read data from an ADC and supply it to this. 217 | /// 218 | /// # Example 219 | /// 220 | /// ```rust 221 | /// if let Ok(data) = adc1.read(control1.get_pin()) { 222 | /// control1.update(data); 223 | /// } 224 | /// ``` 225 | pub fn update(&mut self, value: u32) { 226 | self.state[self.index] = value as f32 / self.scale; 227 | self.index = (self.index + 1) % ANALOG_ARR_SIZE; 228 | } 229 | 230 | /// Get the value of the control with any applied scaling and/or transformation. 231 | pub fn get_value(&self) -> f32 { 232 | let mut value = self.state.iter().sum(); 233 | value /= ANALOG_ARR_SIZE_F32; 234 | if let Some(tfn) = self.transform { 235 | value = tfn(value); 236 | } 237 | value 238 | } 239 | 240 | /// Get the pin associated with this control. 241 | pub fn get_pin(&mut self) -> &mut T { 242 | &mut self.pin 243 | } 244 | } 245 | 246 | /// Basic LED implementation with a PWM like functional. Does not implement PWM via hardware. 247 | pub struct Led { 248 | pin: T, 249 | /// inverts the brightness level 250 | invert: bool, 251 | /// resolution is the number of brightness levels 252 | resolution: u32, 253 | brightness: f32, 254 | pwm: f32, 255 | blink_on: Option, 256 | blink_off: Option, 257 | blink_counter: u32, 258 | blink_status: BlinkStatus, 259 | } 260 | 261 | impl Led 262 | where 263 | T: OutputPin, 264 | { 265 | /// Create a new LED. 266 | pub fn new(pin: T, invert: bool, resolution: u32) -> Self { 267 | Self { 268 | pin, 269 | invert, 270 | resolution, 271 | brightness: 0.0, 272 | pwm: 0.0, 273 | blink_on: None, 274 | blink_off: None, 275 | blink_counter: 0, 276 | blink_status: BlinkStatus::Disabled, 277 | } 278 | } 279 | 280 | /// Set the brightness of the LED from 0.0 to 1.0. 281 | pub fn set_brightness(&mut self, value: f32) { 282 | let value = if value > 1.0 { 283 | 1.0 284 | } else if value < 0.0 { 285 | 0.0 286 | } else { 287 | value 288 | }; 289 | match self.invert { 290 | // Bias for slower transitions in the low brightness range 291 | // TODO configurable? 292 | true => self.brightness = value.sqrt(), 293 | false => self.brightness = value * value, 294 | } 295 | } 296 | 297 | /// Enable blink functionality. 298 | /// Times are in resolution multiplied by blink_on/blink_off. 299 | pub fn set_blink(&mut self, blink_on: f32, blink_off: f32) { 300 | self.blink_on = Some((blink_on * self.resolution as f32) as u32); 301 | self.blink_off = Some((blink_off * self.resolution as f32) as u32); 302 | } 303 | 304 | /// Disable blink. 305 | pub fn clear_blink(&mut self) { 306 | self.blink_on = None; 307 | self.blink_off = None; 308 | self.blink_status = BlinkStatus::Disabled; 309 | } 310 | 311 | /// Update LED status. This should be called on a timer. 312 | pub fn update(&mut self) { 313 | // Calculate blink status 314 | if let (Some(blink_on), Some(blink_off)) = (self.blink_on, self.blink_off) { 315 | self.blink_counter += 1; 316 | self.blink_status = match self.blink_status { 317 | BlinkStatus::On => { 318 | if self.blink_counter > blink_on { 319 | self.blink_counter = 0; 320 | BlinkStatus::Off 321 | } else { 322 | BlinkStatus::On 323 | } 324 | } 325 | BlinkStatus::Off => { 326 | if self.blink_counter > blink_off { 327 | self.blink_counter = 0; 328 | BlinkStatus::On 329 | } else { 330 | BlinkStatus::Off 331 | } 332 | } 333 | BlinkStatus::Disabled => BlinkStatus::On, 334 | }; 335 | }; 336 | 337 | self.pwm += 1.0 / self.resolution as f32; 338 | if self.pwm > 1.0 { 339 | self.pwm -= 1.0; 340 | } 341 | 342 | let is_bright = if self.brightness > self.pwm { 343 | true 344 | } else { 345 | false 346 | }; 347 | match self.blink_status { 348 | BlinkStatus::On => self.pin.set_high().ok().unwrap(), 349 | BlinkStatus::Off => self.pin.set_low().ok().unwrap(), 350 | BlinkStatus::Disabled => { 351 | if (is_bright && !self.invert) || (!is_bright && self.invert) { 352 | self.pin.set_high().ok().unwrap(); 353 | } else { 354 | self.pin.set_low().ok().unwrap(); 355 | } 356 | } 357 | }; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(dead_code)] 3 | 4 | // #[macro_use(singleton)] 5 | // extern crate cortex_m; 6 | 7 | use cortex_m::asm::delay as delay_cycles; 8 | 9 | use stm32h7xx_hal::time::Hertz; 10 | 11 | pub const MILLI: u32 = 1_000; 12 | pub const AUDIO_FRAME_RATE_HZ: u32 = 1_000; 13 | pub const AUDIO_BLOCK_SIZE: u16 = 48; 14 | pub const AUDIO_SAMPLE_RATE: usize = 48_000; 15 | pub const AUDIO_SAMPLE_HZ: Hertz = Hertz::from_raw(48_000); 16 | pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32); 17 | 18 | pub const MILICYCLES: u32 = CLOCK_RATE_HZ.raw() / MILLI; 19 | 20 | pub type FrameTimer = stm32h7xx_hal::timer::Timer; 21 | 22 | pub use stm32h7xx_hal as hal; 23 | 24 | pub mod audio; 25 | pub mod flash; 26 | pub mod gpio; 27 | pub mod hid; 28 | pub mod logger; 29 | pub mod mpu; 30 | pub mod prelude; 31 | pub mod sdmmc; 32 | pub mod sdram; 33 | pub mod system; 34 | 35 | // Delay for ms, note if interrupts are active delay time will extend 36 | pub fn delay_ms(ms: u32) { 37 | delay_cycles(ms * MILICYCLES); 38 | } 39 | 40 | // pub fn ms_to_cycles(ms: u32) { 41 | 42 | // } 43 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | //! Logger modules provides optional setup routines for several logging methods 2 | cfg_if::cfg_if! { 3 | if #[cfg(any(feature = "log-itm"))] { 4 | use panic_itm as _; 5 | 6 | use lazy_static::lazy_static; 7 | use log::LevelFilter; 8 | 9 | pub use cortex_m_log::log::Logger; 10 | 11 | use cortex_m_log::{ 12 | destination::Itm as ItmDest, 13 | printer::itm::InterruptSync, 14 | modes::InterruptFree, 15 | printer::itm::ItmSync 16 | }; 17 | 18 | lazy_static! { 19 | static ref LOGGER: Logger> = Logger { 20 | level: LevelFilter::Info, 21 | inner: InterruptSync::new(ItmDest::new(cortex_m::Peripherals::take().unwrap().ITM)), 22 | }; 23 | } 24 | 25 | /// Initialize logging via ITM 26 | pub fn init() { 27 | cortex_m_log::log::init(&LOGGER).unwrap(); 28 | } 29 | 30 | } 31 | else if #[cfg(any(feature = "log-rtt"))] { 32 | use panic_rtt_target as _; 33 | 34 | use log::{Level, Metadata, Record, LevelFilter}; 35 | use rtt_target::{rprintln, rtt_init_print}; 36 | 37 | pub struct Logger { 38 | level: Level, 39 | } 40 | 41 | static LOGGER: Logger = Logger { 42 | level: Level::Info, 43 | }; 44 | 45 | /// Initialize logging via RTT 46 | pub fn init() { 47 | rtt_init_print!(); 48 | log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info)).unwrap(); 49 | } 50 | 51 | impl log::Log for Logger { 52 | fn enabled(&self, metadata: &Metadata) -> bool { 53 | metadata.level() <= self.level 54 | 55 | } 56 | 57 | fn log(&self, record: &Record) { 58 | rprintln!("{} - {}", record.level(), record.args()); 59 | } 60 | 61 | fn flush(&self) {} 62 | } 63 | } 64 | else if #[cfg(any(feature = "log-semihosting"))] { 65 | use panic_semihosting as _; 66 | 67 | use lazy_static::lazy_static; 68 | use log::LevelFilter; 69 | 70 | pub use cortex_m_log::log::Logger; 71 | use cortex_m_log::printer::semihosting; 72 | use cortex_m_log::printer::semihosting::Semihosting; 73 | use cortex_m_log::modes::InterruptOk; 74 | use cortex_m_semihosting::hio::HostStream; 75 | 76 | lazy_static! { 77 | static ref LOGGER: Logger> = Logger { 78 | level: LevelFilter::Info, 79 | inner: semihosting::InterruptOk::<_>::stdout().expect("Get Semihosting stdout"), 80 | }; 81 | } 82 | 83 | /// Initialize logging via semihosting 84 | pub fn init() { 85 | cortex_m_log::log::init(&LOGGER).unwrap(); 86 | } 87 | } 88 | else if #[cfg(any(feature = "log-none"))] { 89 | /// no log and no panic handler 90 | pub fn init() {} 91 | } 92 | else { 93 | use panic_halt as _; 94 | /// Initialize logging if feature is enabled, otherwise does nothing 95 | pub fn init() {} 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/mpu.rs: -------------------------------------------------------------------------------- 1 | //!Configure MPU 2 | 3 | /// 4 | /// Based on example from: 5 | /// https://github.com/richardeoin/stm32h7-fmc/blob/master/examples/stm32h747i-disco.rs 6 | /// 7 | /// Memory address in location will be 32-byte aligned. 8 | /// 9 | /// # Panics 10 | /// 11 | /// Function will panic if `size` is not a power of 2. Function 12 | /// will panic if `size` is not at least 32 bytes. 13 | 14 | /// Refer to ARM®v7-M Architecture Reference Manual ARM DDI 0403 15 | /// Version E.b Section B3.5 16 | use log::info; 17 | 18 | const MEMFAULTENA: u32 = 1 << 16; 19 | const REGION_FULL_ACCESS: u32 = 0x03; 20 | const REGION_ENABLE: u32 = 0x01; 21 | 22 | pub fn disable(mpu: &mut cortex_m::peripheral::MPU, scb: &mut cortex_m::peripheral::SCB) { 23 | unsafe { 24 | /* Make sure outstanding transfers are done */ 25 | cortex_m::asm::dmb(); 26 | scb.shcsr.modify(|r| r & !MEMFAULTENA); 27 | /* Disable the MPU and clear the control register*/ 28 | mpu.ctrl.write(0); 29 | } 30 | } 31 | 32 | pub fn enable(mpu: &mut cortex_m::peripheral::MPU, scb: &mut cortex_m::peripheral::SCB) { 33 | const MPU_ENABLE: u32 = 0x01; 34 | const MPU_DEFAULT_MMAP_FOR_PRIVILEGED: u32 = 0x04; 35 | 36 | unsafe { 37 | mpu.ctrl 38 | .modify(|r| r | MPU_DEFAULT_MMAP_FOR_PRIVILEGED | MPU_ENABLE); 39 | 40 | scb.shcsr.modify(|r| r | MEMFAULTENA); 41 | 42 | // Ensure MPU settings take effect 43 | cortex_m::asm::dsb(); 44 | cortex_m::asm::isb(); 45 | } 46 | } 47 | 48 | fn log2minus1(sz: u32) -> u32 { 49 | for x in 5..=31 { 50 | if sz == (1 << x) { 51 | return x - 1; 52 | } 53 | } 54 | panic!("Unknown memory region size!"); 55 | } 56 | 57 | /// Setup MPU for dma 58 | pub fn dma_init( 59 | mpu: &mut cortex_m::peripheral::MPU, 60 | scb: &mut cortex_m::peripheral::SCB, 61 | location: *mut u32, 62 | size: usize, 63 | ) { 64 | disable(mpu, scb); 65 | 66 | const REGION_NUMBER0: u32 = 0x00; 67 | const REGION_SHAREABLE: u32 = 0x01; 68 | const REGION_TEX: u32 = 0b001; 69 | const REGION_CB: u32 = 0b00; 70 | 71 | assert_eq!( 72 | size & (size - 1), 73 | 0, 74 | "Memory region size must be a power of 2" 75 | ); 76 | assert_eq!( 77 | size & 0x1F, 78 | 0, 79 | "Memory region size must be 32 bytes or more" 80 | ); 81 | 82 | info!("Memory Size 0x{:x}", log2minus1(size as u32)); 83 | 84 | // Configure region 0 85 | // 86 | // Strongly ordered 87 | unsafe { 88 | mpu.rnr.write(REGION_NUMBER0); 89 | mpu.rbar.write((location as u32) & !0x1F); 90 | mpu.rasr.write( 91 | (REGION_FULL_ACCESS << 24) 92 | | (REGION_TEX << 19) 93 | | (REGION_SHAREABLE << 18) 94 | | (REGION_CB << 16) 95 | | (log2minus1(size as u32) << 1) 96 | | REGION_ENABLE, 97 | ); 98 | } 99 | 100 | enable(mpu, scb); 101 | } 102 | 103 | /// Setup MPU for the sdram 104 | pub fn sdram_init( 105 | mpu: &mut cortex_m::peripheral::MPU, 106 | scb: &mut cortex_m::peripheral::SCB, 107 | location: *mut u32, 108 | size: usize, 109 | ) { 110 | disable(mpu, scb); 111 | 112 | // SDRAM 113 | const REGION_NUMBER1: u32 = 0x01; 114 | 115 | assert_eq!( 116 | size & (size - 1), 117 | 0, 118 | "SDRAM memory region size must be a power of 2" 119 | ); 120 | assert_eq!( 121 | size & 0x1F, 122 | 0, 123 | "SDRAM memory region size must be 32 bytes or more" 124 | ); 125 | 126 | info!("SDRAM Memory Size 0x{:x}", log2minus1(size as u32)); 127 | 128 | // Configure region 1 129 | // 130 | // Strongly ordered 131 | unsafe { 132 | mpu.rnr.write(REGION_NUMBER1); 133 | mpu.rbar.write((location as u32) & !0x1F); 134 | mpu.rasr 135 | .write((REGION_FULL_ACCESS << 24) | (log2minus1(size as u32) << 1) | REGION_ENABLE); 136 | } 137 | 138 | enable(mpu, scb); 139 | } 140 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude 2 | pub use stm32h7xx_hal::gpio::{Alternate, Analog, Input, Output, PushPull}; 3 | pub use stm32h7xx_hal::hal::digital::v2::InputPin; 4 | pub use stm32h7xx_hal::hal::digital::v2::OutputPin; 5 | pub use stm32h7xx_hal::prelude::*; 6 | -------------------------------------------------------------------------------- /src/sdmmc.rs: -------------------------------------------------------------------------------- 1 | use stm32h7xx_hal::{ 2 | self as hal, 3 | gpio::{self, Analog, Speed}, 4 | prelude::*, 5 | sdmmc::SdmmcPeripheral, 6 | stm32, 7 | }; 8 | 9 | /// Boiler plate to create SDMMC1 10 | pub fn init( 11 | daisy1: gpio::gpioc::PC11, 12 | daisy2: gpio::gpioc::PC10, 13 | daisy3: gpio::gpioc::PC9, 14 | daisy4: gpio::gpioc::PC8, 15 | daisy5: gpio::gpiod::PD2, 16 | daisy6: gpio::gpioc::PC12, 17 | 18 | device: stm32::SDMMC1, 19 | sdmmc1: hal::rcc::rec::Sdmmc1, 20 | clocks: &hal::rcc::CoreClocks, 21 | ) -> hal::sdmmc::Sdmmc { 22 | /* 23 | * libDaisy 24 | * PC12 - SDMMC1 CK 25 | * PD2 - SDMMC1 CMD 26 | * PC8 - SDMMC1 D0 27 | * PC9 - SDMMC1 D1 (optional) 28 | * PC10 - SDMMC1 D2 (optional) 29 | * PC11 - SDMMC1 D3 (optional) 30 | */ 31 | 32 | // SDMMC pins 33 | let clk = daisy6 34 | .into_alternate() 35 | .internal_pull_up(false) 36 | .speed(Speed::VeryHigh); 37 | let cmd = daisy5 38 | .into_alternate() 39 | .internal_pull_up(true) 40 | .speed(Speed::VeryHigh); 41 | let d0 = daisy4 42 | .into_alternate() 43 | .internal_pull_up(true) 44 | .speed(Speed::VeryHigh); 45 | let d1 = daisy3 46 | .into_alternate() 47 | .internal_pull_up(true) 48 | .speed(Speed::VeryHigh); 49 | let d2 = daisy2 50 | .into_alternate() 51 | .internal_pull_up(true) 52 | .speed(Speed::VeryHigh); 53 | let d3 = daisy1 54 | .into_alternate() 55 | .internal_pull_up(true) 56 | .speed(Speed::VeryHigh); 57 | 58 | // Create SDMMC 59 | device.sdmmc((clk, cmd, d0, d1, d2, d3), sdmmc1, clocks) 60 | } 61 | -------------------------------------------------------------------------------- /src/sdram.rs: -------------------------------------------------------------------------------- 1 | //! Sdram 2 | use stm32_fmc::devices::as4c16m32msa_6; 3 | use stm32h7xx_hal::{ 4 | gpio::{gpiod, gpioe, gpiof, gpiog, gpioh, gpioi, Analog}, 5 | hal::blocking::delay::DelayUs, 6 | prelude::*, 7 | rcc, stm32, 8 | }; 9 | 10 | /// Configure pins for the FMC controller 11 | macro_rules! fmc_pins { 12 | ($($pin:expr),*) => { 13 | ( 14 | $( 15 | $pin.into_push_pull_output() 16 | .speed(stm32h7xx_hal::gpio::Speed::VeryHigh) 17 | .into_alternate() 18 | .internal_pull_up(true) 19 | ),* 20 | ) 21 | }; 22 | } 23 | 24 | /// Struct that owns the sdram 25 | pub struct Sdram { 26 | inner: *mut u32, 27 | } 28 | 29 | impl Sdram { 30 | /// Initialize the sdram 31 | pub fn new>( 32 | fmc_d: stm32::FMC, 33 | fmc_p: rcc::rec::Fmc, 34 | clocks: &rcc::CoreClocks, 35 | delay: &mut D, 36 | scb: &mut cortex_m::peripheral::SCB, 37 | mpu: &mut cortex_m::peripheral::MPU, 38 | dd0: gpiod::PD0, 39 | dd1: gpiod::PD1, 40 | dd8: gpiod::PD8, 41 | dd9: gpiod::PD9, 42 | dd10: gpiod::PD10, 43 | dd14: gpiod::PD14, 44 | dd15: gpiod::PD15, 45 | ee0: gpioe::PE0, 46 | ee1: gpioe::PE1, 47 | ee7: gpioe::PE7, 48 | ee8: gpioe::PE8, 49 | ee9: gpioe::PE9, 50 | ee10: gpioe::PE10, 51 | ee11: gpioe::PE11, 52 | ee12: gpioe::PE12, 53 | ee13: gpioe::PE13, 54 | ee14: gpioe::PE14, 55 | ee15: gpioe::PE15, 56 | ff0: gpiof::PF0, 57 | ff1: gpiof::PF1, 58 | ff2: gpiof::PF2, 59 | ff3: gpiof::PF3, 60 | ff4: gpiof::PF4, 61 | ff5: gpiof::PF5, 62 | ff11: gpiof::PF11, 63 | ff12: gpiof::PF12, 64 | ff13: gpiof::PF13, 65 | ff14: gpiof::PF14, 66 | ff15: gpiof::PF15, 67 | gg0: gpiog::PG0, 68 | gg1: gpiog::PG1, 69 | gg2: gpiog::PG2, 70 | gg4: gpiog::PG4, 71 | gg5: gpiog::PG5, 72 | gg8: gpiog::PG8, 73 | gg15: gpiog::PG15, 74 | hh2: gpioh::PH2, 75 | hh3: gpioh::PH3, 76 | hh5: gpioh::PH5, 77 | hh8: gpioh::PH8, 78 | hh9: gpioh::PH9, 79 | hh10: gpioh::PH10, 80 | hh11: gpioh::PH11, 81 | hh12: gpioh::PH12, 82 | hh13: gpioh::PH13, 83 | hh14: gpioh::PH14, 84 | hh15: gpioh::PH15, 85 | ii0: gpioi::PI0, 86 | ii1: gpioi::PI1, 87 | ii2: gpioi::PI2, 88 | ii3: gpioi::PI3, 89 | ii4: gpioi::PI4, 90 | ii5: gpioi::PI5, 91 | ii6: gpioi::PI6, 92 | ii7: gpioi::PI7, 93 | ii9: gpioi::PI9, 94 | ii10: gpioi::PI10, 95 | ) -> Self { 96 | let sdram_pins = fmc_pins! { 97 | // A0-A12 98 | ff0, ff1, ff2, ff3, 99 | ff4, ff5, ff12, ff13, 100 | ff14, ff15, gg0, gg1, 101 | gg2, 102 | // BA0-BA1 103 | gg4, gg5, 104 | // D0-D31 105 | dd14, dd15, dd0, dd1, 106 | ee7, ee8, ee9, ee10, 107 | ee11, ee12, ee13, ee14, 108 | ee15, dd8, dd9, dd10, 109 | hh8, hh9, hh10, hh11, 110 | hh12, hh13, hh14, hh15, 111 | ii0, ii1, ii2, ii3, 112 | ii6, ii7, ii9, ii10, 113 | // NBL0 - NBL3 114 | ee0, ee1, ii4, ii5, 115 | hh2, // SDCKE0 116 | gg8, // SDCLK 117 | gg15, // SDNCAS 118 | hh3, // SDNE0 119 | ff11, // SDRAS 120 | hh5 // SDNWE 121 | }; 122 | 123 | let ram_ptr = fmc_d 124 | .sdram(sdram_pins, as4c16m32msa_6::As4c16m32msa {}, fmc_p, clocks) 125 | .init(delay); 126 | crate::mpu::sdram_init(mpu, scb, ram_ptr, Self::bytes()); 127 | Self { inner: ram_ptr } 128 | } 129 | 130 | /// Get the total number of bytes that this ram has. 131 | pub const fn bytes() -> usize { 132 | 64 * 1024 * 1024 133 | } 134 | 135 | /// Get a pointer to the first word of the ram. 136 | pub fn inner(&self) -> *mut u32 { 137 | self.inner 138 | } 139 | } 140 | 141 | impl Into<&'static mut [T]> for Sdram { 142 | fn into(self) -> &'static mut [T] { 143 | unsafe { 144 | core::slice::from_raw_parts_mut( 145 | self.inner as *mut T, 146 | Self::bytes() / core::mem::size_of::(), 147 | ) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/system.rs: -------------------------------------------------------------------------------- 1 | //! Contains setup for Daisy board hardware. 2 | #![allow(dead_code)] 3 | // #![allow(unused_variables)] 4 | 5 | use log::info; 6 | 7 | use stm32h7xx_hal::{ 8 | adc, 9 | delay::Delay, 10 | prelude::*, 11 | rcc, stm32, 12 | stm32::TIM2, 13 | time::{Hertz, MegaHertz, MilliSeconds}, 14 | timer::Event, 15 | timer::Timer, 16 | }; 17 | 18 | use crate::audio::Audio; 19 | use crate::*; 20 | 21 | const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16); 22 | const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200); 23 | const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200); 24 | 25 | // PCLKx 26 | const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4); 27 | // 49_152_344 28 | // PLL1 29 | const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; 30 | const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); 31 | const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); 32 | // PLL2 33 | const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); 34 | 35 | const PLL3_P_HZ: Hertz = Hertz::from_raw(AUDIO_SAMPLE_HZ.raw() * 256); 36 | 37 | pub struct System { 38 | pub gpio: crate::gpio::GPIO, 39 | pub audio: audio::Audio, 40 | pub exti: stm32::EXTI, 41 | pub syscfg: stm32::SYSCFG, 42 | pub adc1: adc::Adc, 43 | pub adc2: adc::Adc, 44 | pub timer2: Timer, 45 | pub sdram: &'static mut [f32], 46 | pub flash: crate::flash::Flash, 47 | } 48 | 49 | impl System { 50 | /// Initialize clocks 51 | pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { 52 | // Power 53 | let pwr = pwr.constrain(); 54 | let vos = pwr.vos0(syscfg).freeze(); 55 | 56 | rcc.constrain() 57 | .use_hse(HSE_CLOCK_MHZ.convert()) 58 | .sys_ck(CLOCK_RATE_HZ) 59 | .pclk1(PCLK_HZ) // DMA clock 60 | // PLL1 61 | .pll1_strategy(rcc::PllConfigStrategy::Iterative) 62 | .pll1_p_ck(PLL1_P_HZ) 63 | .pll1_q_ck(PLL1_Q_HZ) 64 | .pll1_r_ck(PLL1_R_HZ) 65 | // PLL2 66 | .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input 67 | // PLL3 68 | .pll3_strategy(rcc::PllConfigStrategy::FractionalNotLess) 69 | .pll3_p_ck(PLL3_P_HZ) // used for SAI1 70 | .freeze(vos, &syscfg) 71 | } 72 | 73 | /// Setup cache 74 | pub fn init_cache( 75 | scb: &mut cortex_m::peripheral::SCB, 76 | cpuid: &mut cortex_m::peripheral::CPUID, 77 | ) { 78 | scb.enable_icache(); 79 | scb.enable_dcache(cpuid); 80 | } 81 | 82 | /// Enable debug 83 | pub fn init_debug(dcb: &mut cortex_m::peripheral::DCB, dwt: &mut cortex_m::peripheral::DWT) { 84 | dcb.enable_trace(); 85 | cortex_m::peripheral::DWT::unlock(); 86 | dwt.enable_cycle_counter(); 87 | } 88 | 89 | /// Batteries included initialization 90 | pub fn init(mut core: rtic::export::Peripherals, device: stm32::Peripherals) -> System { 91 | info!("Starting system init"); 92 | let mut ccdr = Self::init_clocks(device.PWR, device.RCC, &device.SYSCFG); 93 | 94 | // log_clocks(&ccdr); 95 | let mut delay = Delay::new(core.SYST, ccdr.clocks); 96 | // Setup ADCs 97 | let (adc1, adc2) = adc::adc12( 98 | device.ADC1, 99 | device.ADC2, 100 | 4.MHz(), 101 | &mut delay, 102 | ccdr.peripheral.ADC12, 103 | &ccdr.clocks, 104 | ); 105 | 106 | Self::init_debug(&mut core.DCB, &mut core.DWT); 107 | 108 | // Timers 109 | let mut timer2 = device.TIM2.timer( 110 | MilliSeconds::from_ticks(100).into_rate(), 111 | ccdr.peripheral.TIM2, 112 | &mut ccdr.clocks, 113 | ); 114 | timer2.listen(Event::TimeOut); 115 | 116 | // let mut timer3 = device 117 | // .TIM3 118 | // .timer(1.ms(), ccdr.peripheral.TIM3, &mut ccdr.clocks); 119 | // timer3.listen(Event::TimeOut); 120 | 121 | info!("Setting up GPIOs..."); 122 | let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); 123 | let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); 124 | let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); 125 | let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); 126 | let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE); 127 | let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF); 128 | let gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); 129 | let gpioh = device.GPIOH.split(ccdr.peripheral.GPIOH); 130 | let gpioi = device.GPIOI.split(ccdr.peripheral.GPIOI); 131 | 132 | // Configure SDRAM 133 | info!("Setting up SDRAM..."); 134 | let sdram = crate::sdram::Sdram::new( 135 | device.FMC, 136 | ccdr.peripheral.FMC, 137 | &ccdr.clocks, 138 | &mut delay, 139 | &mut core.SCB, 140 | &mut core.MPU, 141 | gpiod.pd0, 142 | gpiod.pd1, 143 | gpiod.pd8, 144 | gpiod.pd9, 145 | gpiod.pd10, 146 | gpiod.pd14, 147 | gpiod.pd15, 148 | gpioe.pe0, 149 | gpioe.pe1, 150 | gpioe.pe7, 151 | gpioe.pe8, 152 | gpioe.pe9, 153 | gpioe.pe10, 154 | gpioe.pe11, 155 | gpioe.pe12, 156 | gpioe.pe13, 157 | gpioe.pe14, 158 | gpioe.pe15, 159 | gpiof.pf0, 160 | gpiof.pf1, 161 | gpiof.pf2, 162 | gpiof.pf3, 163 | gpiof.pf4, 164 | gpiof.pf5, 165 | gpiof.pf11, 166 | gpiof.pf12, 167 | gpiof.pf13, 168 | gpiof.pf14, 169 | gpiof.pf15, 170 | gpiog.pg0, 171 | gpiog.pg1, 172 | gpiog.pg2, 173 | gpiog.pg4, 174 | gpiog.pg5, 175 | gpiog.pg8, 176 | gpiog.pg15, 177 | gpioh.ph2, 178 | gpioh.ph3, 179 | gpioh.ph5, 180 | gpioh.ph8, 181 | gpioh.ph9, 182 | gpioh.ph10, 183 | gpioh.ph11, 184 | gpioh.ph12, 185 | gpioh.ph13, 186 | gpioh.ph14, 187 | gpioh.ph15, 188 | gpioi.pi0, 189 | gpioi.pi1, 190 | gpioi.pi2, 191 | gpioi.pi3, 192 | gpioi.pi4, 193 | gpioi.pi5, 194 | gpioi.pi6, 195 | gpioi.pi7, 196 | gpioi.pi9, 197 | gpioi.pi10, 198 | ) 199 | .into(); 200 | 201 | info!("Setup up Audio..."); 202 | let audio = Audio::new( 203 | device.DMA1, 204 | ccdr.peripheral.DMA1, 205 | device.SAI1, 206 | ccdr.peripheral.SAI1, 207 | gpioe.pe2, 208 | gpioe.pe3, 209 | gpioe.pe4, 210 | gpioe.pe5, 211 | gpioe.pe6, 212 | &ccdr.clocks, 213 | &mut core.MPU, 214 | &mut core.SCB, 215 | ); 216 | 217 | // Setup GPIOs 218 | let gpio = crate::gpio::GPIO::init( 219 | gpioc.pc7, 220 | gpiob.pb11, 221 | Some(gpiob.pb12), 222 | Some(gpioc.pc11), 223 | Some(gpioc.pc10), 224 | Some(gpioc.pc9), 225 | Some(gpioc.pc8), 226 | Some(gpiod.pd2), 227 | Some(gpioc.pc12), 228 | Some(gpiog.pg10), 229 | Some(gpiog.pg11), 230 | Some(gpiob.pb4), 231 | Some(gpiob.pb5), 232 | Some(gpiob.pb8), 233 | Some(gpiob.pb9), 234 | Some(gpiob.pb6), 235 | Some(gpiob.pb7), 236 | Some(gpioc.pc0), 237 | Some(gpioa.pa3), 238 | Some(gpiob.pb1), 239 | Some(gpioa.pa7), 240 | Some(gpioa.pa6), 241 | Some(gpioc.pc1), 242 | Some(gpioc.pc4), 243 | Some(gpioa.pa5), 244 | Some(gpioa.pa4), 245 | Some(gpioa.pa1), 246 | Some(gpioa.pa0), 247 | Some(gpiod.pd11), 248 | Some(gpiog.pg9), 249 | Some(gpioa.pa2), 250 | Some(gpiob.pb14), 251 | Some(gpiob.pb15), 252 | ); 253 | 254 | // Setup cache 255 | Self::init_cache(&mut core.SCB, &mut core.CPUID); 256 | 257 | info!("System init done!"); 258 | 259 | //setup flash 260 | let flash = crate::flash::Flash::new( 261 | device.QUADSPI, 262 | ccdr.peripheral.QSPI, 263 | &ccdr.clocks, 264 | gpiof.pf6, 265 | gpiof.pf7, 266 | gpiof.pf8, 267 | gpiof.pf9, 268 | gpiof.pf10, 269 | gpiog.pg6, 270 | ); 271 | 272 | System { 273 | gpio, 274 | audio, 275 | exti: device.EXTI, 276 | syscfg: device.SYSCFG, 277 | adc1, 278 | adc2, 279 | timer2, 280 | sdram, 281 | flash, 282 | } 283 | } 284 | } 285 | 286 | fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { 287 | info!("Core {}", ccdr.clocks.c_ck()); 288 | info!("hclk {}", ccdr.clocks.hclk()); 289 | info!("pclk1 {}", ccdr.clocks.pclk1()); 290 | info!("pclk2 {}", ccdr.clocks.pclk2()); 291 | info!("pclk3 {}", ccdr.clocks.pclk2()); 292 | info!("pclk4 {}", ccdr.clocks.pclk4()); 293 | info!( 294 | "PLL1\nP: {:?}\nQ: {:?}\nR: {:?}", 295 | ccdr.clocks.pll1_p_ck(), 296 | ccdr.clocks.pll1_q_ck(), 297 | ccdr.clocks.pll1_r_ck() 298 | ); 299 | info!( 300 | "PLL2\nP: {:?}\nQ: {:?}\nR: {:?}", 301 | ccdr.clocks.pll2_p_ck(), 302 | ccdr.clocks.pll2_q_ck(), 303 | ccdr.clocks.pll2_r_ck() 304 | ); 305 | info!( 306 | "PLL3\nP: {:?}\nQ: {:?}\nR: {:?}", 307 | ccdr.clocks.pll3_p_ck(), 308 | ccdr.clocks.pll3_q_ck(), 309 | ccdr.clocks.pll3_r_ck() 310 | ); 311 | } 312 | --------------------------------------------------------------------------------