From acb8b67ae7111fff2481bf58e416be4f8385a025 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 18 Sep 2024 15:07:29 +0200 Subject: [PATCH] update package - Add embassy example - improve timer API - restructure examples - restructure and improve SPI - Add REB1 M95M01 NVM module --- .github/workflows/ci.yml | 4 +- Cargo.toml | 15 +- README.md | 15 +- automation/Jenkinsfile | 4 +- board-tests/Cargo.toml | 3 +- bootloader/Cargo.toml | 25 + bootloader/src/lib.rs | 10 + bootloader/src/main.rs | 296 +++++ examples/README.md | 25 + examples/embassy/Cargo.toml | 40 + examples/embassy/src/lib.rs | 4 + examples/embassy/src/main.rs | 43 + examples/embassy/src/time_driver.rs | 333 ++++++ examples/rtic/Cargo.toml | 33 + .../rtic/src/bin}/blinky-button-rtic.rs | 37 +- .../examples => rtic/src/bin}/rtic-empty.rs | 0 .../src/bin/uart-rtic.rs} | 19 +- examples/rtic/src/lib.rs | 4 + examples/rtic/src/main.rs | 71 ++ examples/simple/Cargo.toml | 22 +- examples/simple/examples/cascade.rs | 4 +- examples/simple/examples/spi.rs | 152 ++- examples/simple/examples/timer-ticks.rs | 11 +- va108xx-hal/CHANGELOG.md | 14 + va108xx-hal/Cargo.toml | 7 +- va108xx-hal/src/lib.rs | 19 +- va108xx-hal/src/spi.rs | 1000 +++++++++++------ va108xx-hal/src/timer.rs | 170 ++- va108xx-hal/src/uart.rs | 14 +- va108xx-hal/src/utility.rs | 16 - vorago-reb1/CHANGELOG.md | 2 + vorago-reb1/Cargo.toml | 23 +- vorago-reb1/examples/adxl343-accelerometer.rs | 21 +- vorago-reb1/examples/max11619-adc.rs | 17 +- vorago-reb1/examples/nvm.rs | 64 ++ vorago-reb1/src/lib.rs | 1 + vorago-reb1/src/m95m01.rs | 172 +++ vorago-reb1/src/temp_sensor.rs | 2 +- vscode/launch.json | 102 +- vscode/tasks.json | 56 +- 40 files changed, 2197 insertions(+), 673 deletions(-) create mode 100644 bootloader/Cargo.toml create mode 100644 bootloader/src/lib.rs create mode 100644 bootloader/src/main.rs create mode 100644 examples/README.md create mode 100644 examples/embassy/Cargo.toml create mode 100644 examples/embassy/src/lib.rs create mode 100644 examples/embassy/src/main.rs create mode 100644 examples/embassy/src/time_driver.rs create mode 100644 examples/rtic/Cargo.toml rename {vorago-reb1/examples => examples/rtic/src/bin}/blinky-button-rtic.rs (81%) rename examples/{simple/examples => rtic/src/bin}/rtic-empty.rs (100%) rename examples/{simple/examples/uart-irq-rtic.rs => rtic/src/bin/uart-rtic.rs} (89%) create mode 100644 examples/rtic/src/lib.rs create mode 100644 examples/rtic/src/main.rs delete mode 100644 va108xx-hal/src/utility.rs create mode 100644 vorago-reb1/examples/nvm.rs create mode 100644 vorago-reb1/src/m95m01.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b23fa31..5904517 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal + - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1 clippy: name: Clippy diff --git a/Cargo.toml b/Cargo.toml index c8fd396..fd5b004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,10 @@ members = [ "va108xx", "va108xx-hal", "examples/simple", + "examples/rtic", + "examples/embassy", "board-tests", + "bootloader", ] exclude = [ @@ -17,7 +20,8 @@ codegen-units = 1 debug = 2 debug-assertions = true # <- incremental = false -opt-level = 'z' # <- +# This is problematic for stepping.. +# opt-level = 'z' # <- overflow-checks = true # <- # cargo build/run --release @@ -29,3 +33,12 @@ incremental = false lto = 'fat' opt-level = 3 # <- overflow-checks = false # <- + +[profile.small] +inherits = "release" +codegen-units = 1 +debug-assertions = false # <- +lto = true +opt-level = 'z' # <- +overflow-checks = false # <- +strip = true # Automatically strip symbols from the binary. diff --git a/README.md b/README.md index 94838bb..3fd8e07 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,13 @@ This workspace contains the following released crates: It also contains the following helper crates: -- The `board-tests` contains an application which can be used to test the libraries on the - board. -- The `examples` crates contains various example applications for the HAL and the PAC. +- The [`board-tests`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/board-tests) + contains an application which can be used to test the libraries on the board. +- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples) + folder contains various example applications crates using the HAL and the PAC. + This folder also contains dedicated example applications using the + [`RTIC`](https://rtic.rs/2/book/en/) and [`embassy`](https://github.com/embassy-rs/embassy) + native Rust RTOSes. ## Using the `.cargo/config.toml` file @@ -94,6 +98,8 @@ example. Assuming a working debug connection to your VA108xx board, you can debug using VS Code with the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). +Please make sure that [`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146) +are installed as well. Some sample configuration files for VS code were provided and can be used by running `cp -rT vscode .vscode` like specified above. After that, you can use `Run and Debug` @@ -108,4 +114,5 @@ configuration variables in your `settings.json`: - `"cortex-debug.gdbPath.osx"` The provided VS Code configurations also provide an integrated RTT logger, which you can access -via the terminal at `RTT Ch:0 console`. +via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to +work properly, `objdump-multiarch` and `nm-multiarch` need to be installed. diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index 832d6a0..7b401a0 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -25,7 +25,9 @@ pipeline { stage('Docs') { steps { sh """ - RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p va108xx-hal + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p vorago-reb1 """ } } diff --git a/board-tests/Cargo.toml b/board-tests/Cargo.toml index 01b7901..e906480 100644 --- a/board-tests/Cargo.toml +++ b/board-tests/Cargo.toml @@ -4,10 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -cortex-m-rtic = "1" -panic-halt = "0.2" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" +panic-halt = "0.2" rtt-target = "0.5" panic-rtt-target = "0.1.3" embedded-hal = "1" diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml new file mode 100644 index 0000000..9bef200 --- /dev/null +++ b/bootloader/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bootloader" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1" +embedded-hal-bus = "0.2" +dummy-pin = "1" +panic-rtt-target = { version = "0.1.3" } +panic-halt = { version = "0.2" } +rtt-target = { version = "0.5" } +crc = "3" + +[dependencies.va108xx-hal] +path = "../va108xx-hal" + +[dependencies.vorago-reb1] +path = "../vorago-reb1" + +[features] +default = [] +rtt-panic = [] diff --git a/bootloader/src/lib.rs b/bootloader/src/lib.rs new file mode 100644 index 0000000..2fc897a --- /dev/null +++ b/bootloader/src/lib.rs @@ -0,0 +1,10 @@ +#![no_std] + +use core::convert::Infallible; + +/// Simple trait which makes swapping the NVM easier. NVMs only need to implement this interface. +pub trait NvmInterface { + fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible>; + fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible>; + fn verify(&mut self, address: u32, data: &[u8]) -> Result; +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs new file mode 100644 index 0000000..06ab2b2 --- /dev/null +++ b/bootloader/src/main.rs @@ -0,0 +1,296 @@ +//! Vorago bootloader which can boot from two images. +#![no_main] +#![no_std] +use bootloader::NvmInterface; +use cortex_m_rt::entry; +use crc::{Crc, CRC_16_IBM_3740}; +#[cfg(not(feature = "rtt-panic"))] +use panic_halt as _; +#[cfg(feature = "rtt-panic")] +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{pac, time::Hertz}; +use vorago_reb1::m95m01::M95M01; + +// Useful for debugging and see what the bootloader is doing. Enabled currently, because +// the binary stays small enough. +const RTT_PRINTOUT: bool = true; +const DEBUG_PRINTOUTS: bool = false; + +// Dangerous option! An image with this option set to true will flash itself from RAM directly +// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM +// boot process. Please note that this will flash an image which will also always perform the +// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash +// loader to boot a bootloader without this feature. +const FLASH_SELF: bool = false; + +// Register definitions for Cortex-M0 SCB register. +pub const SCB_AIRCR_VECTKEY_POS: u32 = 16; +pub const SCB_AIRCR_VECTKEY_MSK: u32 = 0xFFFF << SCB_AIRCR_VECTKEY_POS; + +pub const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2; +pub const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1 << SCB_AIRCR_SYSRESETREQ_POS; + +const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +// Important bootloader addresses and offsets, vector table information. + +const BOOTLOADER_START_ADDR: u32 = 0x0; +const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2; +// This is also the maximum size of the bootloader. +const BOOTLOADER_END_ADDR: u32 = 0x3000; +const APP_A_START_ADDR: u32 = 0x3000; +const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8; +// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32. +const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4; +pub const APP_A_END_ADDR: u32 = 0x11000; +// The actual size of the image which is relevant for CRC calculation. +const APP_B_START_ADDR: u32 = 0x11000; +// The actual size of the image which is relevant for CRC calculation. +const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8; +// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32. +const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4; +pub const APP_B_END_ADDR: u32 = 0x20000; +pub const APP_IMG_SZ: u32 = 0xE800; + +pub const VECTOR_TABLE_OFFSET: u32 = 0x0; +pub const VECTOR_TABLE_LEN: u32 = 0xC0; +pub const RESET_VECTOR_OFFSET: u32 = 0x4; + +const CRC_ALGO: Crc = Crc::::new(&CRC_16_IBM_3740); + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum AppSel { + A, + B, +} + +pub struct NvmWrapper(pub M95M01); + +// Newtype pattern. We could now more easily swap the used NVM type. +impl NvmInterface for NvmWrapper { + fn write(&mut self, address: u32, data: &[u8]) -> Result<(), core::convert::Infallible> { + self.0.write(address, data) + } + + fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), core::convert::Infallible> { + self.0.read(address, buf) + } + + fn verify(&mut self, address: u32, data: &[u8]) -> Result { + self.0.verify(address, data) + } +} + +#[entry] +fn main() -> ! { + if RTT_PRINTOUT { + rtt_init_print!(); + rprintln!("-- VA108xx bootloader --"); + } + let mut dp = pac::Peripherals::take().unwrap(); + let cp = cortex_m::Peripherals::take().unwrap(); + + let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic); + + if FLASH_SELF { + let mut first_four_bytes: [u8; 4] = [0; 4]; + read_four_bytes_at_addr_zero(&mut first_four_bytes); + let bootloader_data = { + unsafe { + &*core::ptr::slice_from_raw_parts( + (BOOTLOADER_START_ADDR + 4) as *const u8, + (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize, + ) + } + }; + let mut digest = CRC_ALGO.digest(); + digest.update(&first_four_bytes); + digest.update(bootloader_data); + let bootloader_crc = digest.finalize(); + + nvm.write(0x0, &first_four_bytes) + .expect("writing to NVM failed"); + nvm.write(0x4, bootloader_data) + .expect("writing to NVM failed"); + if let Err(e) = nvm.verify(0x0, &first_four_bytes) { + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } + } + if let Err(e) = nvm.verify(0x4, bootloader_data) { + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } + } + + nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) + .expect("writing CRC failed"); + if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) { + if RTT_PRINTOUT { + rprintln!( + "error: CRC verification for bootloader self-flash failed: {:?}", + e + ); + } + } + } + + let mut nvm = NvmWrapper(nvm); + + // Check bootloader's CRC (and write it if blank) + check_own_crc(&dp.sysconfig, &cp, &mut nvm); + + if check_app_crc(AppSel::A) { + boot_app(&dp.sysconfig, &cp, AppSel::A) + } else if check_app_crc(AppSel::B) { + boot_app(&dp.sysconfig, &cp, AppSel::B) + } else { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("both images corrupt! booting image A"); + } + // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. + // Both images seem to be corrupt. Boot default image A. + boot_app(&dp.sysconfig, &cp, AppSel::A) + } +} + +fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut NvmWrapper) { + let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() }; + // I'd prefer to use [core::slice::from_raw_parts], but that is problematic + // because the address of the bootloader is 0x0, so the NULL check fails and the functions + // panics. + let mut first_four_bytes: [u8; 4] = [0; 4]; + read_four_bytes_at_addr_zero(&mut first_four_bytes); + let mut digest = CRC_ALGO.digest(); + digest.update(&first_four_bytes); + digest.update(unsafe { + &*core::ptr::slice_from_raw_parts( + (BOOTLOADER_START_ADDR + 4) as *const u8, + (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize, + ) + }); + let crc_calc = digest.finalize(); + if crc_exp == 0x0000 || crc_exp == 0xffff { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("BL CRC blank - prog new CRC"); + } + // Blank CRC, write it to NVM. + nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes()) + .expect("writing CRC failed"); + // The Vorago bootloader resets here. I am not sure why this is done but I think it is + // necessary because somehow the boot will not work if we just continue as usual. + // cortex_m::peripheral::SCB::sys_reset(); + } else if crc_exp != crc_calc { + // Bootloader is corrupted. Try to run App A. + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!( + "bootloader CRC corrupt, read {} and expected {}. booting image A immediately", + crc_calc, + crc_exp + ); + } + // TODO: Shift out minimal CCSDS frame to notify about bootloader corruption. + boot_app(sysconfig, cp, AppSel::A); + } +} + +// Reading from address 0x0 is problematic in Rust. +// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5. +// This solution falls back to assembler to deal with this. +fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) { + unsafe { + core::arch::asm!( + "ldr r0, [{0}]", // Load 4 bytes from src into r0 register + "str r0, [{1}]", // Store r0 register into first_four_bytes + in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0) + in(reg) buf as *mut [u8; 4], // Input: destination pointer + ); + } +} +fn check_app_crc(app_sel: AppSel) -> bool { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("Checking image {:?}", app_sel); + } + if app_sel == AppSel::A { + check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR) + } else { + check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR) + } +} + +fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) -> bool { + let crc_exp = unsafe { (crc_addr as *const u16).read_unaligned().to_be() }; + let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() }; + // Sanity check. + if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { + if RTT_PRINTOUT { + rprintln!("detected invalid app size {}", image_size); + } + return false; + } + let crc_calc = CRC_ALGO.checksum(unsafe { + core::slice::from_raw_parts(start_addr as *const u8, image_size as usize) + }); + if crc_calc == crc_exp { + return true; + } + false +} + +// The boot works by copying the interrupt vector table (IVT) of the respective app to the +// base address in code RAM (0x0) and then performing a soft reset. +fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("booting app {:?}", app_sel); + } + // Disable ROM protection. + syscfg.rom_prot().write(|w| unsafe { w.bits(1) }); + let base_addr = if app_sel == AppSel::A { + APP_A_START_ADDR + } else { + APP_B_START_ADDR + }; + // Clear all interrupts set. + unsafe { + cp.NVIC.icer[0].write(0xFFFFFFFF); + cp.NVIC.icpr[0].write(0xFFFFFFFF); + + // First 4 bytes done with inline assembly, writing to the physical address 0x0 can not + // be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2. + core::ptr::read(base_addr as *const u32); + core::arch::asm!( + "str {0}, [{1}]", // Load 4 bytes from src into r0 register + in(reg) base_addr, // Input: App vector table. + in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer + ); + core::slice::from_raw_parts_mut( + (BOOTLOADER_START_ADDR + 4) as *mut u32, + (VECTOR_TABLE_LEN - 4) as usize, + ) + .copy_from_slice(core::slice::from_raw_parts( + (base_addr + 4) as *const u32, + (VECTOR_TABLE_LEN - 4) as usize, + )); + } + /* Disable re-loading from FRAM/code ROM on soft reset */ + syscfg + .rst_cntl_rom() + .modify(|_, w| w.sysrstreq().clear_bit()); + soft_reset(cp); +} + +// Soft reset based on https://github.com/ARM-software/CMSIS_6/blob/5782d6f8057906d360f4b95ec08a2354afe5c9b9/CMSIS/Core/Include/core_cm0.h#L874. +fn soft_reset(cp: &cortex_m::Peripherals) -> ! { + // Ensure all outstanding memory accesses included buffered write are completed before reset. + cortex_m::asm::dsb(); + unsafe { + cp.SCB + .aircr + .write((0x5FA << SCB_AIRCR_VECTKEY_POS) | SCB_AIRCR_SYSRESETREQ_MSK); + } + // Ensure completion of memory access. + cortex_m::asm::dsb(); + + unreachable!(); +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..93d1893 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,25 @@ +VA108xx Example Applications +======== + +This folder contains various examples +Consult the main README first for setup of the repository. + +## Simple examples + +```rs +cargo run --example blinky +``` + +You can have a look at the `simple/examples` folder to see all available simple examples + +## RTIC example + +```rs +cargo run --bin rtic-example +``` + +## Embassy example + +```rs +cargo run --bin embassy-example +``` diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml new file mode 100644 index 0000000..632fd6b --- /dev/null +++ b/examples/embassy/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "embassy-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +embedded-hal = "1" + +rtt-target = { version = "0.5" } +panic-rtt-target = { version = "0.1" } +critical-section = "1" +portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]} + +embassy-sync = { version = "0.6.0" } +embassy-time = { version = "0.3.2" } +embassy-time-driver = { version = "0.1" } + +[dependencies.once_cell] +version = "1" +default-features = false +features = ["critical-section"] + +[dependencies.embassy-executor] +version = "0.6.0" +features = [ + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "integrated-timers", +] + +[dependencies.va108xx-hal] +path = "../../va108xx-hal" + +[features] +default = ["ticks-hz-1_000"] +ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"] +ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"] diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs new file mode 100644 index 0000000..b934a40 --- /dev/null +++ b/examples/embassy/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] +pub mod time_driver; + +pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs new file mode 100644 index 0000000..4dae3fa --- /dev/null +++ b/examples/embassy/src/main.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +use embassy_executor::Spawner; +use embassy_time::{Duration, Instant, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{gpio::PinsA, pac, prelude::*}; + +const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + rtt_init_print!(); + rprintln!("-- VA108xx Embassy Demo --"); + + let mut dp = pac::Peripherals::take().unwrap(); + + // Safety: Only called once here. + unsafe { + embassy_example::init( + &mut dp.sysconfig, + &dp.irqsel, + SYSCLK_FREQ, + dp.tim23, + dp.tim22, + ) + }; + + let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); + let mut led0 = porta.pa10.into_readable_push_pull_output(); + let mut led1 = porta.pa7.into_readable_push_pull_output(); + let mut led2 = porta.pa6.into_readable_push_pull_output(); + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + rprintln!("Current time: {}", Instant::now().as_secs()); + led0.toggle().ok(); + led1.toggle().ok(); + led2.toggle().ok(); + } +} diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs new file mode 100644 index 0000000..45a4e5b --- /dev/null +++ b/examples/embassy/src/time_driver.rs @@ -0,0 +1,333 @@ +//! This is a sample time driver implementation for the VA108xx family of devices, supporting +//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to +//! support more alarms. +//! +//! This driver implementation reserves interrupts OC31 and OC30 for the timekeeping. +use core::{cell::Cell, mem, ptr}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use portable_atomic::{AtomicU32, AtomicU8, Ordering}; + +use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ}; +use once_cell::sync::OnceCell; +use va108xx_hal::{ + clock::enable_peripheral_clock, + enable_interrupt, + pac::{self, interrupt}, + prelude::*, + timer::{enable_tim_clk, ValidTim}, + PeripheralSelect, +}; + +pub type TimekeeperClk = pac::Tim23; +pub type AlarmClk0 = pac::Tim22; +pub type AlarmClk1 = pac::Tim21; +pub type AlarmClk2 = pac::Tim20; + +const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::OC31; +const ALARM_IRQ: pac::Interrupt = pac::Interrupt::OC30; + +/// Initialization method for embassy +/// +/// # Safety +/// This has to be called once at initialization time to initiate the time driver for +/// embassy. +pub unsafe fn init( + syscfg: &mut pac::Sysconfig, + irqsel: &pac::Irqsel, + sysclk: impl Into, + timekeeper: TimekeeperClk, + alarm_tim: AlarmClk0, +) { + DRIVER.init(syscfg, irqsel, sysclk, timekeeper, alarm_tim) +} + +time_driver_impl!( + static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy { + periods: AtomicU32::new(0), + alarm_count: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT]) +}); + +/// Timekeeper interrupt. +#[interrupt] +#[allow(non_snake_case)] +fn OC31() { + DRIVER.on_interrupt_timekeeping() +} + +/// Alarm timer interrupt. +#[interrupt] +#[allow(non_snake_case)] +fn OC30() { + DRIVER.on_interrupt_alarm(0) +} + +#[inline(always)] +const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock { + // Safety: This is a static memory-mapped peripheral. + match idx { + 0 => unsafe { &*AlarmClk0::ptr() }, + 1 => unsafe { &*AlarmClk1::ptr() }, + 2 => unsafe { &*AlarmClk2::ptr() }, + _ => { + panic!("invalid alarm timer index") + } + } +} + +#[inline(always)] +const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock { + // Safety: This is a memory-mapped peripheral. + unsafe { &*TimekeeperClk::ptr() } +} + +struct AlarmState { + timestamp: Cell, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: Cell<*const ()>, + ctx: Cell<*mut ()>, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(ptr::null()), + ctx: Cell::new(ptr::null_mut()), + } + } +} + +unsafe impl Send for AlarmState {} + +const ALARM_COUNT: usize = 1; + +static SCALE: OnceCell = OnceCell::new(); + +pub struct TimerDriverEmbassy { + periods: AtomicU32, + alarm_count: AtomicU8, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, +} + +impl TimerDriverEmbassy { + fn init( + &self, + syscfg: &mut pac::Sysconfig, + irqsel: &pac::Irqsel, + sysclk: impl Into, + timekeeper: TimekeeperClk, + alarm_tim: AlarmClk0, + ) { + enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel); + enable_tim_clk(syscfg, TimekeeperClk::TIM_ID); + let sysclk = sysclk.into(); + // Initiate scale value here. This is required to convert timer ticks back to a timestamp. + SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap(); + timekeeper + .rst_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Decrementing counter. + timekeeper + .cnt_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Switch on. Timekeeping should always be done. + irqsel + .tim0(TimekeeperClk::TIM_ID as usize) + .write(|w| unsafe { w.bits(TIMEKEEPER_IRQ as u32) }); + unsafe { + enable_interrupt(TIMEKEEPER_IRQ); + } + timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit()); + timekeeper.enable().write(|w| unsafe { w.bits(1) }); + + enable_tim_clk(syscfg, AlarmClk0::TIM_ID); + + // Explicitely disable alarm timer until needed. + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + // Enable general interrupts. The IRQ enable of the peripheral remains cleared. + unsafe { + enable_interrupt(ALARM_IRQ); + } + irqsel + .tim0(AlarmClk0::TIM_ID as usize) + .write(|w| unsafe { w.bits(ALARM_IRQ as u32) }); + } + + // Should be called inside the IRQ of the timekeeper timer. + fn on_interrupt_timekeeping(&self) { + self.next_period(); + } + + // Should be called inside the IRQ of the alarm timer. + fn on_interrupt_alarm(&self, idx: usize) { + critical_section::with(|cs| { + if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() { + self.trigger_alarm(idx, cs) + } + }) + } + + fn next_period(&self) { + let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1; + let t = (period as u64) << 32; + critical_section::with(|cs| { + for i in 0..ALARM_COUNT { + let alarm = &self.alarms.borrow(cs)[i]; + let at = alarm.timestamp.get(); + let alarm_tim = alarm_tim(0); + if at < t { + self.trigger_alarm(i, cs); + } else { + let remaining_ticks = (at - t) * *SCALE.get().unwrap(); + if remaining_ticks <= u32::MAX as u64 { + alarm_tim.enable().write(|w| unsafe { w.bits(0) }); + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(remaining_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }) + } + } + } + }) + } + + fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { + // safety: we're allowed to assume the AlarmState is created by us, and + // we never create one that's out of bounds. + unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } + } + + fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + alarm_tim(n).ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = &self.alarms.borrow(cs)[n]; + // Setting the maximum value disables the alarm. + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + + // safety: + // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. + // - other than that we only store valid function pointers into alarm.callback + let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; + f(alarm.ctx.get()); + } +} + +impl Driver for TimerDriverEmbassy { + fn now(&self) -> u64 { + if SCALE.get().is_none() { + return 0; + } + let mut period1: u32; + let mut period2: u32; + let mut counter_val: u32; + + loop { + // Acquire ensures that we get the latest value of `periods` and + // no instructions can be reordered before the load. + period1 = self.periods.load(Ordering::Acquire); + + counter_val = u32::MAX - timekeeping_tim().cnt_value().read().bits(); + + // Double read to protect against race conditions when the counter is overflowing. + period2 = self.periods.load(Ordering::Relaxed); + if period1 == period2 { + let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap(); + return now; + } + } + } + + unsafe fn allocate_alarm(&self) -> Option { + let id = self + .alarm_count + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback( + &self, + alarm: embassy_time_driver::AlarmHandle, + callback: fn(*mut ()), + ctx: *mut (), + ) { + critical_section::with(|cs| { + let alarm = self.get_alarm(cs, alarm); + + alarm.callback.set(callback as *const ()); + alarm.ctx.set(ctx); + }) + } + + fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool { + if SCALE.get().is_none() { + return false; + } + critical_section::with(|cs| { + let n = alarm.id(); + let alarm_tim = alarm_tim(n.into()); + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = self.get_alarm(cs, alarm); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + alarm.timestamp.set(u64::MAX); + return false; + } + + // If it hasn't triggered yet, setup the relevant reset value, regardless of whether + // the interrupts are enabled or not. When they are enabled at a later point, the + // right value is already set. + + // If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm + // is not missed. + // + // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed + // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, + // and we don't do that here. + let safe_timestamp = timestamp.max(t + 3); + let timer_ticks = (safe_timestamp - t) * *SCALE.get().unwrap(); + alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) }); + if timer_ticks <= u32::MAX as u64 { + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(timer_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }); + } + // If it's too far in the future, don't enable timer yet. + // It will be enabled later by `next_period`. + + true + }) + } +} diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml new file mode 100644 index 0000000..be520eb --- /dev/null +++ b/examples/rtic/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "rtic-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +embedded-hal = "1" +embedded-io = "0.6" +rtt-target = { version = "0.5" } +# Even though we do not use this directly, we need to activate this feature explicitely +# so that RTIC compiles because thumv6 does not have CAS operations natively. +portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]} +panic-rtt-target = { version = "0.1" } + +[dependencies.va108xx-hal] +path = "../../va108xx-hal" + +[dependencies.vorago-reb1] +path = "../../vorago-reb1" + +[dependencies.rtic] +version = "2" +features = ["thumbv6-backend"] + +[dependencies.rtic-monotonics] +version = "2" +features = ["cortex-m-systick"] + +[dependencies.rtic-sync] +version = "1.3" +features = ["defmt-03"] diff --git a/vorago-reb1/examples/blinky-button-rtic.rs b/examples/rtic/src/bin/blinky-button-rtic.rs similarity index 81% rename from vorago-reb1/examples/blinky-button-rtic.rs rename to examples/rtic/src/bin/blinky-button-rtic.rs index c869cb7..e844230 100644 --- a/vorago-reb1/examples/blinky-button-rtic.rs +++ b/examples/rtic/src/bin/blinky-button-rtic.rs @@ -5,7 +5,7 @@ #[rtic::app(device = pac)] mod app { use panic_rtt_target as _; - use rtic_monotonics::systick::Systick; + use rtic_example::SYSCLK_FREQ; use rtt_target::{rprintln, rtt_init_default, set_print_channel}; use va108xx_hal::{ clock::{set_clk_div_register, FilterClkSel}, @@ -17,6 +17,8 @@ mod app { use vorago_reb1::button::Button; use vorago_reb1::leds::Leds; + rtic_monotonics::systick_monotonic!(Mono, 1_000); + #[derive(Debug, PartialEq)] pub enum PressMode { Toggle, @@ -44,17 +46,11 @@ mod app { struct Shared {} #[init] - fn init(ctx: init::Context) -> (Shared, Local) { + fn init(cx: init::Context) -> (Shared, Local) { let channels = rtt_init_default!(); set_print_channel(channels.up.0); rprintln!("-- Vorago Button IRQ Example --"); - // Initialize the systick interrupt & obtain the token to prove that we did - let systick_mono_token = rtic_monotonics::create_systick_token!(); - Systick::start( - ctx.core.SYST, - Hertz::from(50.MHz()).raw(), - systick_mono_token, - ); + Mono::start(cx.core.SYST, SYSCLK_FREQ.raw()); let mode = match CFG_MODE { // Ask mode from user via RTT @@ -64,7 +60,7 @@ mod app { }; rprintln!("Using {:?} mode", mode); - let mut dp = ctx.device; + let mut dp = cx.device; let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let edge_irq = match mode { PressMode::Toggle => InterruptEdge::HighToLow, @@ -117,12 +113,10 @@ mod app { let mode = cx.local.mode; if *mode == PressMode::Toggle { leds[0].toggle(); + } else if button.released() { + leds[0].off(); } else { - if button.released() { - leds[0].off(); - } else { - leds[0].on(); - } + leds[0].on(); } } @@ -138,14 +132,11 @@ mod app { let mut read; loop { read = down_channel.read(&mut read_buf); - for i in 0..read { - let val = read_buf[i] as char; - if val == '0' || val == '1' { - return if val == '0' { - PressMode::Toggle - } else { - PressMode::Keep - }; + for &byte in &read_buf[..read] { + match byte as char { + '0' => return PressMode::Toggle, + '1' => return PressMode::Keep, + _ => continue, // Ignore other characters } } } diff --git a/examples/simple/examples/rtic-empty.rs b/examples/rtic/src/bin/rtic-empty.rs similarity index 100% rename from examples/simple/examples/rtic-empty.rs rename to examples/rtic/src/bin/rtic-empty.rs diff --git a/examples/simple/examples/uart-irq-rtic.rs b/examples/rtic/src/bin/uart-rtic.rs similarity index 89% rename from examples/simple/examples/uart-irq-rtic.rs rename to examples/rtic/src/bin/uart-rtic.rs index 94781bd..50eafaf 100644 --- a/examples/simple/examples/uart-irq-rtic.rs +++ b/examples/rtic/src/bin/uart-rtic.rs @@ -14,14 +14,13 @@ mod app { use embedded_io::Write; use panic_rtt_target as _; - use rtic_monotonics::systick::Systick; + use rtic_example::SYSCLK_FREQ; use rtic_sync::make_channel; use rtt_target::{rprintln, rtt_init_print}; use va108xx_hal::{ gpio::PinsB, pac, prelude::*, - time::Hertz, uart::{self, IrqCfg, IrqResult, UartWithIrqBase}, }; @@ -44,19 +43,14 @@ mod app { pub timeout: bool, } + rtic_monotonics::systick_monotonic!(Mono, 1_000); + #[init] fn init(cx: init::Context) -> (Shared, Local) { rtt_init_print!(); - //set_print_channel(channels.up.0); rprintln!("-- VA108xx UART IRQ example application--"); - // Initialize the systick interrupt & obtain the token to prove that we did - let systick_mono_token = rtic_monotonics::create_systick_token!(); - Systick::start( - cx.core.SYST, - Hertz::from(50.MHz()).raw(), - systick_mono_token, - ); + Mono::start(cx.core.SYST, SYSCLK_FREQ.raw()); let mut dp = cx.device; let gpiob = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb); @@ -74,7 +68,6 @@ mod app { let (rx_info_tx, rx_info_rx) = make_channel!(RxInfo, 3); let rx_buf: [u8; 64] = [0; 64]; - //reply_handler::spawn().expect("spawning reply handler failed"); ( Shared { irq_uart, rx_buf }, Local { @@ -112,8 +105,8 @@ mod app { .expect("Read operation init failed"); let mut end_idx = 0; - for idx in 0..rx_buf.len() { - if (rx_buf[idx] as char) == '\n' { + for (idx, val) in rx_buf.iter().enumerate() { + if (*val as char) == '\n' { end_idx = idx; break; } diff --git a/examples/rtic/src/lib.rs b/examples/rtic/src/lib.rs new file mode 100644 index 0000000..5eea1ac --- /dev/null +++ b/examples/rtic/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] +use va108xx_hal::time::Hertz; + +pub const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs new file mode 100644 index 0000000..699dcd6 --- /dev/null +++ b/examples/rtic/src/main.rs @@ -0,0 +1,71 @@ +//! RTIC minimal blinky +#![no_main] +#![no_std] + +#[rtic::app(device = pac, dispatchers = [OC31, OC30, OC29])] +mod app { + use cortex_m::asm; + use embedded_hal::digital::StatefulOutputPin; + use panic_rtt_target as _; + use rtic_example::SYSCLK_FREQ; + use rtic_monotonics::systick::prelude::*; + use rtic_monotonics::Monotonic; + use rtt_target::{rprintln, rtt_init_print}; + use va108xx_hal::{ + gpio::{OutputReadablePushPull, Pin, PinsA, PA10, PA6, PA7}, + pac, + }; + + #[local] + struct Local { + led0: Pin, + led1: Pin, + led2: Pin, + } + + #[shared] + struct Shared {} + + rtic_monotonics::systick_monotonic!(Mono, 1_000); + + #[init] + fn init(mut cx: init::Context) -> (Shared, Local) { + rtt_init_print!(); + rprintln!("-- Vorago VA108xx RTIC template --"); + + Mono::start(cx.core.SYST, SYSCLK_FREQ.raw()); + + let porta = PinsA::new( + &mut cx.device.sysconfig, + Some(cx.device.ioconfig), + cx.device.porta, + ); + let led0 = porta.pa10.into_readable_push_pull_output(); + let led1 = porta.pa7.into_readable_push_pull_output(); + let led2 = porta.pa6.into_readable_push_pull_output(); + blinky::spawn().ok(); + (Shared {}, Local { led0, led1, led2 }) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + asm::nop(); + } + } + + #[task( + priority = 3, + local=[led0, led1, led2], + )] + async fn blinky(cx: blinky::Context) { + loop { + rprintln!("toggling LEDs"); + cx.local.led0.toggle().ok(); + cx.local.led1.toggle().ok(); + cx.local.led2.toggle().ok(); + Mono::delay(1000.millis()).await; + } + } +} diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 746aedb..8dae104 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -4,30 +4,20 @@ version = "0.1.0" edition = "2021" [dependencies] -panic-halt = "0.2" cortex-m = {version = "0.7", features = ["critical-section-single-core"]} -panic-rtt-target = "0.1" cortex-m-rt = "0.7" +panic-halt = "0.2" +panic-rtt-target = "0.1" +critical-section = "1" rtt-target = "0.5" -rtic-sync = { version = "1.3", features = ["defmt-03"] } embedded-hal = "1" embedded-hal-nb = "1" embedded-io = "0.6" cortex-m-semihosting = "0.5.0" -# I'd really like to use those, but it is tricky without probe-rs.. -# defmt = "0.3" -# defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] } -# panic-probe = { version = "0.3", features = ["print-defmt"] } - -[dependencies.rtic] -version = "2" -features = ["thumbv6-backend"] - -[dependencies.rtic-monotonics] -version = "1" -features = ["cortex-m-systick"] [dependencies.va108xx-hal] -version = "0.7" path = "../../va108xx-hal" features = ["rt", "defmt"] + +[dependencies.vorago-reb1] +path = "../../vorago-reb1" diff --git a/examples/simple/examples/cascade.rs b/examples/simple/examples/cascade.rs index 39cebb3..f29b56a 100644 --- a/examples/simple/examples/cascade.rs +++ b/examples/simple/examples/cascade.rs @@ -48,7 +48,7 @@ fn main() -> ! { let mut cascade_target_1 = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim4).auto_deactivate(true); cascade_target_1 - .cascade_0_source(CascadeSource::TimBase, Some(3)) + .cascade_0_source(CascadeSource::Tim(3)) .expect("Configuring cascade source for TIM4 failed"); let mut csd_cfg = CascadeCtrl { enb_start_src_csd0: true, @@ -75,7 +75,7 @@ fn main() -> ! { CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim5).auto_deactivate(true); // Set TIM4 as cascade source cascade_target_2 - .cascade_1_source(CascadeSource::TimBase, Some(4)) + .cascade_1_source(CascadeSource::Tim(4)) .expect("Configuring cascade source for TIM5 failed"); csd_cfg = CascadeCtrl::default(); diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs index f776b1e..ff0195f 100644 --- a/examples/simple/examples/spi.rs +++ b/examples/simple/examples/spi.rs @@ -16,7 +16,7 @@ use va108xx_hal::{ pac::{self, interrupt}, prelude::*, pwm::{default_ms_irq_handler, set_up_ms_tick}, - spi::{self, Spi, SpiBase, TransferConfig}, + spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs}, IrqCfg, }; @@ -24,8 +24,7 @@ use va108xx_hal::{ pub enum ExampleSelect { // Enter loopback mode. It is not necessary to tie MOSI/MISO together for this Loopback, - // Send a test buffer and print everything received - TestBuffer, + MosiMisoTiedTogetherManually, } #[derive(PartialEq, Debug)] @@ -55,6 +54,8 @@ fn main() -> ! { dp.tim0, ); + let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz()) + .expect("creating SPI clock config failed"); let spia_ref: RefCell>> = RefCell::new(None); let spib_ref: RefCell>> = RefCell::new(None); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); @@ -79,7 +80,6 @@ fn main() -> ! { dp.spia, (sck, miso, mosi), spi_cfg, - None, ); spia.set_fill_word(FILL_WORD); spia_ref.borrow_mut().replace(spia.downgrade()); @@ -96,7 +96,6 @@ fn main() -> ! { dp.spia, (sck, miso, mosi), spi_cfg, - None, ); spia.set_fill_word(FILL_WORD); spia_ref.borrow_mut().replace(spia.downgrade()); @@ -113,7 +112,6 @@ fn main() -> ! { dp.spib, (sck, miso, mosi), spi_cfg, - None, ); spib.set_fill_word(FILL_WORD); spib_ref.borrow_mut().replace(spib.downgrade()); @@ -123,17 +121,21 @@ fn main() -> ! { match SPI_BUS_SEL { SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => { if let Some(ref mut spi) = *spia_ref.borrow_mut() { - let transfer_cfg = - TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false); + let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs( + Some(spi_clk_cfg), + Some(SPI_MODE), + BLOCKMODE, + false, + ); spi.cfg_transfer(&transfer_cfg); } } SpiBusSelect::SpiBPortB => { if let Some(ref mut spi) = *spib_ref.borrow_mut() { let hw_cs_pin = pinsb.pb2.into_funsel_1(); - let transfer_cfg = TransferConfig::new( - SPI_SPEED_KHZ.kHz(), - SPI_MODE, + let transfer_cfg = TransferConfigWithHwcs::new( + Some(spi_clk_cfg), + Some(SPI_MODE), Some(hw_cs_pin), BLOCKMODE, false, @@ -149,92 +151,64 @@ fn main() -> ! { match SPI_BUS_SEL { SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => { if let Some(ref mut spi) = *spia_ref.borrow_mut() { - if EXAMPLE_SEL == ExampleSelect::Loopback { - // Can't really verify correct reply here. - spi.write(&[0x42]).expect("write failed"); - // Because of the loopback mode, we should get back the fill word here. - spi.read(&mut reply_buf[0..1]).unwrap(); - assert_eq!(reply_buf[0], FILL_WORD); - delay.delay_ms(500_u32); + // Can't really verify correct reply here. + spi.write(&[0x42]).expect("write failed"); + // Because of the loopback mode, we should get back the fill word here. + spi.read(&mut reply_buf[0..1]).unwrap(); + assert_eq!(reply_buf[0], FILL_WORD); + delay.delay_ms(500_u32); - let tx_buf: [u8; 3] = [0x01, 0x02, 0x03]; - spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap(); - assert_eq!(tx_buf, reply_buf[0..3]); - rprintln!( - "Received reply: {}, {}, {}", - reply_buf[0], - reply_buf[1], - reply_buf[2] - ); - delay.delay_ms(500_u32); + let tx_buf: [u8; 3] = [0x01, 0x02, 0x03]; + spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap(); + assert_eq!(tx_buf, reply_buf[0..3]); + rprintln!( + "Received reply: {}, {}, {}", + reply_buf[0], + reply_buf[1], + reply_buf[2] + ); + delay.delay_ms(500_u32); - let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01]; - spi.transfer_in_place(&mut tx_rx_buf).unwrap(); - rprintln!( - "Received reply: {}, {}, {}", - tx_rx_buf[0], - tx_rx_buf[1], - tx_rx_buf[2] - ); - assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]); - } else { - let send_buf: [u8; 3] = [0x01, 0x02, 0x03]; - spi.transfer(&mut reply_buf[0..3], &send_buf).unwrap(); - rprintln!( - "Received reply: {}, {}, {}", - reply_buf[0], - reply_buf[1], - reply_buf[2] - ); - delay.delay_ms(1000_u32); - } + let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01]; + spi.transfer_in_place(&mut tx_rx_buf).unwrap(); + rprintln!( + "Received reply: {}, {}, {}", + tx_rx_buf[0], + tx_rx_buf[1], + tx_rx_buf[2] + ); + assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]); } } SpiBusSelect::SpiBPortB => { if let Some(ref mut spi) = *spib_ref.borrow_mut() { - if EXAMPLE_SEL == ExampleSelect::Loopback { - // Can't really verify correct reply here. - spi.write(&[0x42]).expect("write failed"); - // Need small delay.. otherwise we will read back the sent byte (which we don't want here). - // The write function will return as soon as all bytes were shifted out, ignoring the - // reply bytes. - delay.delay_us(50); - // Because of the loopback mode, we should get back the fill word here. - spi.read(&mut reply_buf[0..1]).unwrap(); - assert_eq!(reply_buf[0], FILL_WORD); - delay.delay_ms(500_u32); + // Can't really verify correct reply here. + spi.write(&[0x42]).expect("write failed"); + // Because of the loopback mode, we should get back the fill word here. + spi.read(&mut reply_buf[0..1]).unwrap(); + assert_eq!(reply_buf[0], FILL_WORD); + delay.delay_ms(500_u32); - let tx_buf: [u8; 3] = [0x01, 0x02, 0x03]; - spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap(); - assert_eq!(tx_buf, reply_buf[0..3]); - rprintln!( - "Received reply: {}, {}, {}", - reply_buf[0], - reply_buf[1], - reply_buf[2] - ); - delay.delay_ms(500_u32); + let tx_buf: [u8; 3] = [0x01, 0x02, 0x03]; + spi.transfer(&mut reply_buf[0..3], &tx_buf).unwrap(); + assert_eq!(tx_buf, reply_buf[0..3]); + rprintln!( + "Received reply: {}, {}, {}", + reply_buf[0], + reply_buf[1], + reply_buf[2] + ); + delay.delay_ms(500_u32); - let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01]; - spi.transfer_in_place(&mut tx_rx_buf).unwrap(); - rprintln!( - "Received reply: {}, {}, {}", - tx_rx_buf[0], - tx_rx_buf[1], - tx_rx_buf[2] - ); - assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]); - } else { - let send_buf: [u8; 3] = [0x01, 0x02, 0x03]; - spi.transfer(&mut reply_buf[0..3], &send_buf).unwrap(); - rprintln!( - "Received reply: {}, {}, {}", - reply_buf[0], - reply_buf[1], - reply_buf[2] - ); - delay.delay_ms(1000_u32); - } + let mut tx_rx_buf: [u8; 3] = [0x03, 0x02, 0x01]; + spi.transfer_in_place(&mut tx_rx_buf).unwrap(); + rprintln!( + "Received reply: {}, {}, {}", + tx_rx_buf[0], + tx_rx_buf[1], + tx_rx_buf[2] + ); + assert_eq!(&tx_rx_buf[0..3], &[0x03, 0x02, 0x01]); } } } diff --git a/examples/simple/examples/timer-ticks.rs b/examples/simple/examples/timer-ticks.rs index af341c4..241e4ab 100644 --- a/examples/simple/examples/timer-ticks.rs +++ b/examples/simple/examples/timer-ticks.rs @@ -3,8 +3,8 @@ #![no_std] use core::cell::Cell; -use cortex_m::interrupt::Mutex; use cortex_m_rt::entry; +use critical_section::Mutex; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va108xx_hal::{ @@ -83,11 +83,12 @@ fn main() -> ! { } } loop { - let current_ms = cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()); + let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get()); if current_ms - last_ms >= 1000 { - last_ms = current_ms; + // To prevent drift. + last_ms += 1000; rprintln!("MS counter: {}", current_ms); - let second = cortex_m::interrupt::free(|cs| SEC_COUNTER.borrow(cs).get()); + let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get()); rprintln!("Second counter: {}", second); } cortex_m::asm::delay(10000); @@ -110,7 +111,7 @@ fn OC0() { #[interrupt] #[allow(non_snake_case)] fn OC1() { - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { let mut sec = SEC_COUNTER.borrow(cs).get(); sec += 1; SEC_COUNTER.borrow(cs).set(sec); diff --git a/va108xx-hal/CHANGELOG.md b/va108xx-hal/CHANGELOG.md index 30161ef..135e3e4 100644 --- a/va108xx-hal/CHANGELOG.md +++ b/va108xx-hal/CHANGELOG.md @@ -6,6 +6,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [unreleased] + +## Changed + +- Improves `CascardSource` handling and general API when chosing cascade sources. +- Replaced `utility::unmask_irq` by `enable_interrupt` and `disable_interrupt` API. +- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now + only expects a configuration structure and the transfer configuration needs to be applied in a + separate step. + +## Fixes + +- Fixes for SPI peripheral: Flush implementation was incorrect and should now flush properly. + ## [v0.7.0] 2024-07-04 - Replace `uarta` and `uartb` `Uart` constructors by `new` constructor diff --git a/va108xx-hal/Cargo.toml b/va108xx-hal/Cargo.toml index 2a10125..1e3ce57 100644 --- a/va108xx-hal/Cargo.toml +++ b/va108xx-hal/Cargo.toml @@ -19,7 +19,7 @@ embedded-hal-nb = "1" embedded-io = "0.6" fugit = "0.3" typenum = "1" -defmt = { version = "0.3", optional = true } +critical-section = "1" delegate = "0.12" [dependencies.va108xx] @@ -38,9 +38,14 @@ default-features = false version = "1.14" default-features = false +[dependencies.defmt] +version = "0.3" +optional = true + [features] default = ["rt"] rt = ["va108xx/rt"] +defmt = ["dep:defmt", "fugit/defmt"] [package.metadata.docs.rs] all-features = true diff --git a/va108xx-hal/src/lib.rs b/va108xx-hal/src/lib.rs index 71857ec..2e5d994 100644 --- a/va108xx-hal/src/lib.rs +++ b/va108xx-hal/src/lib.rs @@ -15,7 +15,6 @@ pub mod time; pub mod timer; pub mod typelevel; pub mod uart; -pub mod utility; #[derive(Debug, Eq, Copy, Clone, PartialEq)] pub enum FunSel { @@ -98,3 +97,21 @@ pub fn port_mux( } } } + +/// Enable a specific interrupt using the NVIC peripheral. +/// +/// # Safety +/// +/// This function is `unsafe` because it can break mask-based critical sections. +#[inline] +pub unsafe fn enable_interrupt(irq: pac::Interrupt) { + unsafe { + cortex_m::peripheral::NVIC::unmask(irq); + } +} + +/// Disable a specific interrupt using the NVIC peripheral. +#[inline] +pub fn disable_interrupt(irq: pac::Interrupt) { + cortex_m::peripheral::NVIC::mask(irq); +} diff --git a/va108xx-hal/src/spi.rs b/va108xx-hal/src/spi.rs index e05a3a7..219cb9f 100644 --- a/va108xx-hal/src/spi.rs +++ b/va108xx-hal/src/spi.rs @@ -1,8 +1,14 @@ -//! API for the SPI peripheral +//! API for the SPI peripheral. +//! +//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure. +//! These provide the [embedded_hal::spi] traits, but also offer a low level interface +//! via the [SpiLowLevel] trait. //! //! ## Examples //! //! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/spi.rs) +//! - [REB1 ADC example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/max11519-adc.rs) +//! - [REB1 EEPROM library](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/src/m95m01.rs) use crate::{ clock::enable_peripheral_clock, gpio::pin::{ @@ -17,7 +23,7 @@ use crate::{ PeripheralSelect, }; use core::{convert::Infallible, fmt::Debug, marker::PhantomData, ops::Deref}; -use embedded_hal::spi::{Mode, MODE_0, MODE_1, MODE_2, MODE_3}; +use embedded_hal::spi::{Mode, MODE_0}; //================================================================================================== // Defintions @@ -26,6 +32,10 @@ use embedded_hal::spi::{Mode, MODE_0, MODE_1, MODE_2, MODE_3}; // FIFO has a depth of 16. const FILL_DEPTH: usize = 12; +pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31; + +pub const DEFAULT_CLK_DIV: u16 = 2; + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum HwChipSelectId { Id0 = 0, @@ -221,6 +231,23 @@ hw_cs_pins!( // SPIC +// Dummy pin defintion for the ROM SCK. +pub struct RomSck; +// Dummy pin defintion for the ROM MOSI. +pub struct RomMosi; +// Dummy pin defintion for the ROM MISO. +pub struct RomMiso; +// Dummy pin defintion for the ROM chip select. +pub struct RomCs; + +impl Sealed for RomSck {} +impl PinSck for RomSck {} +impl Sealed for RomMosi {} +impl PinMosi for RomMosi {} +impl Sealed for RomMiso {} +impl PinMiso for RomMiso {} +impl Sealed for RomCs {} + hw_cs_pins!( pac::Spic, SpiPort::Portc: (PB9, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB0), @@ -237,39 +264,38 @@ hw_cs_pins!( (PA20, AltFunc3, HwChipSelectId::Id4, HwCs4SpiCPortA), ); +impl HwCsProvider for RomCs { + const CS_ID: HwChipSelectId = HwChipSelectId::Id0; + const SPI_PORT: SpiPort = SpiPort::Portc; +} +impl OptionalHwCs for RomCs {} + //================================================================================================== // Config //================================================================================================== -pub trait GenericTransferConfig { +pub trait TransferConfigProvider { fn sod(&mut self, sod: bool); fn blockmode(&mut self, blockmode: bool); fn mode(&mut self, mode: Mode); - fn frequency(&mut self, spi_clk: Hertz); + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); fn hw_cs_id(&self) -> u8; } /// This struct contains all configuration parameter which are transfer specific /// and might change for transfers to different SPI slaves -#[derive(Copy, Clone)] -pub struct TransferConfig { - pub spi_clk: Hertz, - pub mode: Mode, - /// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to - /// false +#[derive(Copy, Clone, Debug)] +pub struct TransferConfigWithHwcs { pub hw_cs: Option, - pub sod: bool, - /// If this is enabled, all data in the FIFO is transmitted in a single frame unless - /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the - /// duration of multiple data words - pub blockmode: bool, + pub cfg: TransferConfig, } /// Type erased variant of the transfer configuration. This is required to avoid generics in /// the SPI constructor. -pub struct ReducedTransferConfig { - pub spi_clk: Hertz, - pub mode: Mode, +#[derive(Copy, Clone, Debug)] +pub struct TransferConfig { + pub clk_cfg: Option, + pub mode: Option, pub sod: bool, /// If this is enabled, all data in the FIFO is transmitted in a single frame unless /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the @@ -278,62 +304,67 @@ pub struct ReducedTransferConfig { pub hw_cs: HwChipSelectId, } -impl TransferConfig { - pub fn new_no_hw_cs(spi_clk: impl Into, mode: Mode, blockmode: bool, sod: bool) -> Self { - TransferConfig { - spi_clk: spi_clk.into(), - mode, +impl TransferConfigWithHwcs { + pub fn new_no_hw_cs( + clk_cfg: Option, + mode: Option, + blockmode: bool, + sod: bool, + ) -> Self { + TransferConfigWithHwcs { hw_cs: None, - sod, - blockmode, + cfg: TransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwChipSelectId::Invalid, + }, } } } -impl TransferConfig { +impl TransferConfigWithHwcs { pub fn new( - spi_clk: impl Into, - mode: Mode, + clk_cfg: Option, + mode: Option, hw_cs: Option, blockmode: bool, sod: bool, ) -> Self { - TransferConfig { - spi_clk: spi_clk.into(), - mode, + TransferConfigWithHwcs { hw_cs, - sod, - blockmode, + cfg: TransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwCs::CS_ID, + }, } } - pub fn downgrade(self) -> ReducedTransferConfig { - ReducedTransferConfig { - spi_clk: self.spi_clk, - mode: self.mode, - sod: self.sod, - blockmode: self.blockmode, - hw_cs: HwCs::CS_ID, - } + pub fn downgrade(self) -> TransferConfig { + self.cfg } } -impl GenericTransferConfig for TransferConfig { +impl TransferConfigProvider for TransferConfigWithHwcs { /// Slave Output Disable fn sod(&mut self, sod: bool) { - self.sod = sod; + self.cfg.sod = sod; } fn blockmode(&mut self, blockmode: bool) { - self.blockmode = blockmode; + self.cfg.blockmode = blockmode; } fn mode(&mut self, mode: Mode) { - self.mode = mode; + self.cfg.mode = Some(mode); } - fn frequency(&mut self, spi_clk: Hertz) { - self.spi_clk = spi_clk; + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) { + self.cfg.clk_cfg = Some(clk_cfg); } fn hw_cs_id(&self) -> u8 { @@ -341,26 +372,62 @@ impl GenericTransferConfig for TransferConfig { } } -#[derive(Default)] /// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details pub struct SpiConfig { - /// Serial clock rate divider. Together with the CLKPRESCALE register, it determines - /// the SPI clock rate in master mode. 0 by default. Specifying a higher value - /// limits the maximum attainable SPI speed - pub scrdv: u8, + clk: SpiClkConfig, + // SPI mode configuration + pub init_mode: Mode, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words. Defaults to true. + pub blockmode: bool, + /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty. + /// Currently enabled by default. + pub bmstall: bool, /// By default, configure SPI for master mode (ms == false) ms: bool, /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control - sod: bool, + pub slave_output_disable: bool, /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally - lbm: bool, + pub loopback_mode: bool, /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details - pub mdlycap: bool, + pub master_delayer_capture: bool, +} + +impl Default for SpiConfig { + fn default() -> Self { + Self { + init_mode: MODE_0, + blockmode: true, + bmstall: true, + // Default value is definitely valid. + clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(), + ms: Default::default(), + slave_output_disable: Default::default(), + loopback_mode: Default::default(), + master_delayer_capture: Default::default(), + } + } } impl SpiConfig { pub fn loopback(mut self, enable: bool) -> Self { - self.lbm = enable; + self.loopback_mode = enable; + self + } + + pub fn blockmode(mut self, enable: bool) -> Self { + self.blockmode = enable; + self + } + + pub fn mode(mut self, mode: Mode) -> Self { + self.init_mode = mode; + self + } + + pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { + self.clk = clk_cfg; self } @@ -370,7 +437,7 @@ impl SpiConfig { } pub fn slave_output_disable(mut self, sod: bool) -> Self { - self.sod = sod; + self.slave_output_disable = sod; self } } @@ -404,6 +471,36 @@ impl WordProvider for u16 { // Spi //================================================================================================== +/// Low level access trait for the SPI peripheral. +pub trait SpiLowLevel { + /// Low level function to write a word to the SPI FIFO but also checks whether + /// there is actually data in the FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; + + /// Low level function to write a word to the SPI FIFO without checking whether + /// there FIFO is full. + /// + /// This does not necesarily mean there is a space in the FIFO available. + /// Use [Self::write_fifo] function to write a word into the FIFO reliably. + fn write_fifo_unchecked(&self, data: u32); + + /// Low level function to read a word from the SPI FIFO. Must be preceeded by a + /// [Self::write_fifo] call. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn read_fifo(&self) -> nb::Result; + + /// Low level function to read a word from from the SPI FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. + fn read_fifo_unchecked(&self) -> u32; +} + pub struct SpiBase { spi: SpiInstance, cfg: SpiConfig, @@ -411,6 +508,7 @@ pub struct SpiBase { /// Fill word for read-only SPI transactions. pub fill_word: Word, blockmode: bool, + bmstall: bool, word: PhantomData, } @@ -419,173 +517,162 @@ pub struct Spi { pins: Pins, } -// Re-export this so it can be used for the constructor -pub use crate::typelevel::NoneT; - -impl< - SpiI: Instance, - Sck: PinSck, - Miso: PinMiso, - Mosi: PinMosi, - Word: WordProvider, - > Spi -where - >::Error: core::fmt::Debug, -{ - /// Create a new SPI struct - /// - /// You can delete the pin type information by calling the - /// [`downgrade`](Self::downgrade) function - /// - /// ## Arguments - /// * `spi` - SPI bus to use - /// * `pins` - Pins to be used for SPI transactions. These pins are consumed - /// to ensure the pins can not be used for other purposes anymore - /// * `spi_cfg` - Configuration specific to the SPI bus - /// * `transfer_cfg` - Optional initial transfer configuration which includes - /// configuration which can change across individual SPI transfers like SPI mode - /// or SPI clock. If only one device is connected, this configuration only needs - /// to be done once. - /// * `syscfg` - Can be passed optionally to enable the peripheral clock - pub fn new( - syscfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - spi: SpiI, - pins: (Sck, Miso, Mosi), - spi_cfg: SpiConfig, - transfer_cfg: Option<&ReducedTransferConfig>, - ) -> Self { - enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); - let SpiConfig { - scrdv, - ms, - sod, - lbm, - mdlycap, - } = spi_cfg; - let mut mode = MODE_0; - let mut clk_prescale = 0x02; - let mut ss = 0; - let mut init_blockmode = false; - if let Some(transfer_cfg) = transfer_cfg { - mode = transfer_cfg.mode; - clk_prescale = sys_clk.into().raw() / (transfer_cfg.spi_clk.raw() * (scrdv as u32 + 1)); - if transfer_cfg.hw_cs != HwChipSelectId::Invalid { - ss = transfer_cfg.hw_cs as u8; - } - init_blockmode = transfer_cfg.blockmode; - } - - let (cpo_bit, cph_bit) = match mode { - MODE_0 => (false, false), - MODE_1 => (false, true), - MODE_2 => (true, false), - MODE_3 => (true, true), - }; - spi.ctrl0().write(|w| { - unsafe { - w.size().bits(Word::word_reg()); - w.scrdv().bits(scrdv); - // Clear clock phase and polarity. Will be set to correct value for each - // transfer - w.spo().bit(cpo_bit); - w.sph().bit(cph_bit) - } - }); - spi.ctrl1().write(|w| { - w.lbm().bit(lbm); - w.sod().bit(sod); - w.ms().bit(ms); - w.mdlycap().bit(mdlycap); - w.blockmode().bit(init_blockmode); - unsafe { w.ss().bits(ss) } - }); - - spi.fifo_clr().write(|w| { - w.rxfifo().set_bit(); - w.txfifo().set_bit() - }); - spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) }); - // Enable the peripheral as the last step as recommended in the - // programmers guide - spi.ctrl1().modify(|_, w| w.enable().set_bit()); - Spi { - inner: SpiBase { - spi, - cfg: spi_cfg, - sys_clk: sys_clk.into(), - fill_word: Default::default(), - blockmode: init_blockmode, - word: PhantomData, - }, - pins, - } - } - - #[inline] - pub fn cfg_clock(&mut self, spi_clk: impl Into) { - self.inner.cfg_clock(spi_clk); - } - - #[inline] - pub fn cfg_mode(&mut self, mode: Mode) { - self.inner.cfg_mode(mode); - } - - pub fn set_fill_word(&mut self, fill_word: Word) { - self.inner.fill_word = fill_word; - } - - pub fn fill_word(&self) -> Word { - self.inner.fill_word - } - - #[inline] - pub fn perid(&self) -> u32 { - self.inner.perid() - } - - pub fn cfg_transfer>(&mut self, transfer_cfg: &TransferConfig) { - self.inner.cfg_transfer(transfer_cfg); - } - - /// Releases the SPI peripheral and associated pins - pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { - (self.inner.spi, self.pins, self.inner.cfg) - } - - pub fn downgrade(self) -> SpiBase { - self.inner +pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { + match mode { + embedded_hal::spi::MODE_0 => (false, false), + embedded_hal::spi::MODE_1 => (false, true), + embedded_hal::spi::MODE_2 => (true, false), + embedded_hal::spi::MODE_3 => (true, true), } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiClkConfig { + prescale_val: u16, + scrdv: u8, +} + +impl SpiClkConfig { + pub fn prescale_val(&self) -> u16 { + self.prescale_val + } + pub fn scrdv(&self) -> u8 { + self.scrdv + } +} + +impl SpiClkConfig { + pub fn new(prescale_val: u16, scrdv: u8) -> Self { + Self { + prescale_val, + scrdv, + } + } + + pub fn from_div(div: u16) -> Result { + spi_clk_config_from_div(div) + } + + pub fn from_clk(sys_clk: impl Into, spi_clk: impl Into) -> Option { + clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap()) + } +} + +#[derive(Debug)] +pub enum SpiClkConfigError { + DivIsZero, + DivideValueNotEven, + ScrdvValueTooLarge, +} + +#[inline] +pub fn spi_clk_config_from_div(mut div: u16) -> Result { + if div == 0 { + return Err(SpiClkConfigError::DivIsZero); + } + if div % 2 != 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + let mut prescale_val = 0; + + // find largest (even) prescale value that divides into div + for i in (2..=0xfe).rev().step_by(2) { + if div % i == 0 { + prescale_val = i; + break; + } + } + + if prescale_val == 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + + div /= prescale_val; + if div > u8::MAX as u16 + 1 { + return Err(SpiClkConfigError::ScrdvValueTooLarge); + } + Ok(SpiClkConfig { + prescale_val, + scrdv: (div - 1) as u8, + }) +} + +#[inline] +pub fn clk_div_for_target_clock( + sys_clk: impl Into, + spi_clk: impl Into, +) -> Option { + let spi_clk = spi_clk.into(); + let sys_clk = sys_clk.into(); + if spi_clk > sys_clk { + return None; + } + + // Step 1: Calculate raw divider. + let raw_div = sys_clk.raw() / spi_clk.raw(); + let remainder = sys_clk.raw() % spi_clk.raw(); + + // Step 2: Round up if necessary. + let mut rounded_div = if remainder * 2 >= spi_clk.raw() { + raw_div + 1 + } else { + raw_div + }; + + if rounded_div % 2 != 0 { + // Take slower clock conservatively. + rounded_div += 1; + } + if rounded_div > u16::MAX as u32 { + return None; + } + Some(rounded_div as u16) +} + +// Re-export this so it can be used for the constructor +pub use crate::typelevel::NoneT; + impl SpiBase where >::Error: core::fmt::Debug, { #[inline] - pub fn cfg_clock(&mut self, spi_clk: impl Into) { - let clk_prescale = - self.sys_clk.raw() / (spi_clk.into().raw() * (self.cfg.scrdv as u32 + 1)); + pub fn spi(&self) -> &SpiInstance { + &self.spi + } + + #[inline] + pub fn cfg_clock(&mut self, cfg: SpiClkConfig) { + self.spi + .ctrl0() + .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) }); self.spi .clkprescale() - .write(|w| unsafe { w.bits(clk_prescale) }); + .write(|w| unsafe { w.bits(cfg.prescale_val as u32) }); + } + + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { + let val = spi_clk_config_from_div(div)?; + self.cfg_clock(val); + Ok(()) } #[inline] pub fn cfg_mode(&mut self, mode: Mode) { - let (cpo_bit, cph_bit) = match mode { - MODE_0 => (false, false), - MODE_1 => (false, true), - MODE_2 => (true, false), - MODE_3 => (true, true), - }; + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); self.spi.ctrl0().modify(|_, w| { w.spo().bit(cpo_bit); w.sph().bit(cph_bit) }); } + #[inline] + pub fn fill_word(&self) -> Word { + self.fill_word + } + #[inline] pub fn clear_tx_fifo(&self) { self.spi.fifo_clr().write(|w| w.txfifo().set_bit()); @@ -601,6 +688,7 @@ where self.spi.perid().read().bits() } + /// Configure the hardware chip select given a hardware chip select ID. #[inline] pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) { if hw_cs == HwChipSelectId::Invalid { @@ -615,11 +703,15 @@ where }); } + /// Configure the hardware chip select given a physical hardware CS pin. #[inline] pub fn cfg_hw_cs_with_pin>(&mut self, _: &HwCs) { self.cfg_hw_cs(HwCs::CS_ID); } + /// Disables the hardware chip select functionality. This can be used when performing + /// external chip select handling, for example with GPIO pins. + #[inline] pub fn cfg_hw_cs_disable(&mut self) { self.spi.ctrl1().modify(|_, w| { w.sod().set_bit(); @@ -627,15 +719,22 @@ where }); } + /// Utility function to configure all relevant transfer parameters in one go. + /// This is useful if multiple devices with different clock and mode configurations + /// are connected to one bus. pub fn cfg_transfer>( &mut self, - transfer_cfg: &TransferConfig, + transfer_cfg: &TransferConfigWithHwcs, ) { - self.cfg_clock(transfer_cfg.spi_clk); - self.cfg_mode(transfer_cfg.mode); - self.blockmode = transfer_cfg.blockmode; + if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg { + self.cfg_clock(trans_clk_div); + } + if let Some(mode) = transfer_cfg.cfg.mode { + self.cfg_mode(mode); + } + self.blockmode = transfer_cfg.cfg.blockmode; self.spi.ctrl1().modify(|_, w| { - if transfer_cfg.sod { + if transfer_cfg.cfg.sod { w.sod().set_bit(); } else if transfer_cfg.hw_cs.is_some() { w.sod().clear_bit(); @@ -645,7 +744,7 @@ where } else { w.sod().clear_bit(); } - if transfer_cfg.blockmode { + if transfer_cfg.cfg.blockmode { w.blockmode().set_bit(); } else { w.blockmode().clear_bit(); @@ -654,62 +753,65 @@ where }); } - /// Sends a word to the slave - #[inline(always)] - fn send_blocking(&self, word: Word) { - // TODO: Upper limit for wait cycles to avoid complete hangups? - while self.spi.status().read().tnf().bit_is_clear() {} - self.send(word) - } - - #[inline(always)] - fn send(&self, word: Word) { - self.spi.data().write(|w| unsafe { w.bits(word.into()) }); - } - - /// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call - #[inline(always)] - fn read_blocking(&self) -> Word { - // TODO: Upper limit for wait cycles to avoid complete hangups? - while self.spi.status().read().rne().bit_is_clear() {} - self.read_single_word() - } - - #[inline(always)] - fn read_single_word(&self) -> Word { - (self.spi.data().read().bits() & Word::MASK) - .try_into() - .unwrap() + fn flush_internal(&self) { + let mut status_reg = self.spi.status().read(); + while status_reg.tfe().bit_is_clear() + || status_reg.rne().bit_is_set() + || status_reg.busy().bit_is_set() + { + if status_reg.rne().bit_is_set() { + self.read_fifo_unchecked(); + } + status_reg = self.spi.status().read(); + } } fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { if words.is_empty() { return Ok(()); } - let mut status_reg = self.spi.status().read(); - // Wait until all bytes have been transferred. - while status_reg.tfe().bit_is_clear() { - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } - status_reg = self.spi.status().read(); - } - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } + self.flush_internal(); Ok(()) } - fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize { + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. Returns the amount of written bytes. + fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize { if self.blockmode { self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) } // Fill the first half of the write FIFO let mut current_write_idx = 0; - for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) { - self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx])); + let smaller_idx = core::cmp::min(FILL_DEPTH, words.len()); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(words[current_write_idx].into()); + } + current_write_idx += 1; + } + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()) + } + current_write_idx + } + + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. + fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize { + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + let smaller_idx = core::cmp::min(FILL_DEPTH, send_len); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(self.fill_word.into()); + } current_write_idx += 1; } if self.blockmode { @@ -719,51 +821,35 @@ where } } -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi +impl SpiLowLevel for SpiBase +where + >::Error: core::fmt::Debug, { - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - fill_word: Default::default(), - sys_clk: old_spi.inner.sys_clk, - word: PhantomData, - }, - pins: old_spi.pins, + #[inline(always)] + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> { + if self.spi.status().read().tnf().bit_is_clear() { + return Err(nb::Error::WouldBlock); } + self.write_fifo_unchecked(data); + Ok(()) } -} -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi -{ - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - sys_clk: old_spi.inner.sys_clk, - fill_word: Default::default(), - word: PhantomData, - }, - pins: old_spi.pins, + #[inline(always)] + fn write_fifo_unchecked(&self, data: u32) { + self.spi.data().write(|w| unsafe { w.bits(data) }); + } + + #[inline(always)] + fn read_fifo(&self) -> nb::Result { + if self.spi.status().read().rne().bit_is_clear() { + return Err(nb::Error::WouldBlock); } + Ok(self.read_fifo_unchecked()) + } + + #[inline(always)] + fn read_fifo_unchecked(&self) -> u32 { + self.spi.data().read().bits() } } @@ -778,16 +864,22 @@ where fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(None); + let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len()); loop { - if current_write_idx < words.len() { - self.send_blocking(self.fill_word); - current_write_idx += 1; - } if current_read_idx < words.len() { - words[current_read_idx] = self.read_blocking(); + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); current_read_idx += 1; } + if current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(self.fill_word.into()))?; + } + current_write_idx += 1; + } if current_read_idx >= words.len() && current_write_idx >= words.len() { break; } @@ -797,9 +889,13 @@ where fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); while current_write_idx < words.len() { - self.send_blocking(words[current_write_idx]); + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } current_write_idx += 1; // Ignore received words. if self.spi.status().read().rne().bit_is_set() { @@ -812,14 +908,22 @@ where fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { self.transfer_preparation(write)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(write)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write); while current_read_idx < read.len() || current_write_idx < write.len() { if current_write_idx < write.len() { - self.send_blocking(write[current_write_idx]); + if current_write_idx == write.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(write[current_write_idx].into()))?; + } current_write_idx += 1; } if current_read_idx < read.len() { - read[current_read_idx] = self.read_blocking(); + read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); current_read_idx += 1; } } @@ -830,15 +934,23 @@ where fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); while current_read_idx < words.len() || current_write_idx < words.len() { if current_write_idx < words.len() { - self.send_blocking(words[current_write_idx]); + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } current_write_idx += 1; } if current_read_idx < words.len() && current_read_idx < current_write_idx { - words[current_read_idx] = self.read_blocking(); + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); current_read_idx += 1; } } @@ -846,16 +958,204 @@ where } fn flush(&mut self) -> Result<(), Self::Error> { - let status_reg = self.spi.status().read(); - while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() { - if status_reg.rne().bit_is_set() { - self.read_single_word(); - } - } + self.flush_internal(); Ok(()) } } +impl< + SpiI: Instance, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + Word: WordProvider, + > Spi +where + >::Error: core::fmt::Debug, +{ + /// Create a new SPI struct + /// + /// You can delete the pin type information by calling the + /// [`downgrade`](Self::downgrade) function + /// + /// ## Arguments + /// * `syscfg` - Can be passed optionally to enable the peripheral clock + /// * `sys_clk` - System clock + /// * `spi` - SPI bus to use + /// * `pins` - Pins to be used for SPI transactions. These pins are consumed + /// to ensure the pins can not be used for other purposes anymore + /// * `spi_cfg` - Configuration specific to the SPI bus + pub fn new( + syscfg: &mut pac::Sysconfig, + sys_clk: impl Into, + spi: SpiI, + pins: (Sck, Miso, Mosi), + spi_cfg: SpiConfig, + ) -> Self { + enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); + let SpiConfig { + clk, + init_mode, + blockmode, + bmstall, + ms, + slave_output_disable, + loopback_mode, + master_delayer_capture, + } = spi_cfg; + + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode); + spi.ctrl0().write(|w| { + unsafe { + w.size().bits(Word::word_reg()); + w.scrdv().bits(clk.scrdv); + // Clear clock phase and polarity. Will be set to correct value for each + // transfer + w.spo().bit(cpo_bit); + w.sph().bit(cph_bit) + } + }); + + spi.ctrl1().write(|w| { + w.lbm().bit(loopback_mode); + w.sod().bit(slave_output_disable); + w.ms().bit(ms); + w.mdlycap().bit(master_delayer_capture); + w.blockmode().bit(blockmode); + w.bmstall().bit(bmstall); + unsafe { w.ss().bits(0) } + }); + spi.clkprescale() + .write(|w| unsafe { w.bits(clk.prescale_val as u32) }); + + spi.fifo_clr().write(|w| { + w.rxfifo().set_bit(); + w.txfifo().set_bit() + }); + // Enable the peripheral as the last step as recommended in the + // programmers guide + spi.ctrl1().modify(|_, w| w.enable().set_bit()); + Spi { + inner: SpiBase { + spi, + cfg: spi_cfg, + sys_clk: sys_clk.into(), + fill_word: Default::default(), + bmstall, + blockmode, + word: PhantomData, + }, + pins, + } + } + + delegate::delegate! { + to self.inner { + #[inline] + pub fn cfg_clock(&mut self, cfg: SpiClkConfig); + + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; + + #[inline] + pub fn cfg_mode(&mut self, mode: Mode); + + #[inline] + pub fn perid(&self) -> u32; + + #[inline] + pub fn fill_word(&self) -> Word; + + #[inline] + pub fn spi(&self) -> &SpiI; + + /// Configure the hardware chip select given a hardware chip select ID. + #[inline] + pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId); + + /// Configure the hardware chip select given a physical hardware CS pin. + #[inline] + pub fn cfg_hw_cs_with_pin>(&mut self, _hwcs: &HwCs); + + /// Disables the hardware chip select functionality. This can be used when performing + /// external chip select handling, for example with GPIO pins. + #[inline] + pub fn cfg_hw_cs_disable(&mut self); + + /// Utility function to configure all relevant transfer parameters in one go. + /// This is useful if multiple devices with different clock and mode configurations + /// are connected to one bus. + pub fn cfg_transfer>( + &mut self, transfer_cfg: &TransferConfigWithHwcs + ); + + /// Low level function to write a word to the SPI FIFO but also checks whether + /// there is actually data in the FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + #[inline(always)] + pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; + + /// Low level function to write a word to the SPI FIFO. + /// + /// This does not necesarily mean there is a space in the FIFO available. + /// Use [Self::write_fifo] function to write a word into the FIFO reliably using the + /// [nb] API. + #[inline(always)] + pub fn write_fifo_unchecked(&self, data: u32); + + /// Low level function to read a word from the SPI FIFO. Must be preceeded by a + /// [Self::write_fifo] call. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + #[inline(always)] + pub fn read_fifo(&self) -> nb::Result; + + /// Low level function to read a word from from the SPI FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. + #[inline(always)] + pub fn read_fifo_unchecked(&self) -> u32; + } + } + + pub fn set_fill_word(&mut self, fill_word: Word) { + self.inner.fill_word = fill_word; + } + + /// Releases the SPI peripheral and associated pins + pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { + (self.inner.spi, self.pins, self.inner.cfg) + } + + pub fn downgrade(self) -> SpiBase { + self.inner + } +} + +impl< + SpiI: Instance, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + Word: WordProvider, + > SpiLowLevel for Spi +where + >::Error: core::fmt::Debug, +{ + delegate::delegate! { + to self.inner { + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; + fn write_fifo_unchecked(&self, data: u32); + fn read_fifo(&self) -> nb::Result; + fn read_fifo_unchecked(&self) -> u32; + } + } +} + impl< SpiI: Instance, Word: WordProvider, @@ -877,23 +1177,63 @@ impl< where >::Error: core::fmt::Debug, { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.read(words) - } - - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - self.inner.write(words) - } - - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { - self.inner.transfer(read, write) - } - - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.transfer_in_place(words) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.inner.flush() + delegate::delegate! { + to self.inner { + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn flush(&mut self) -> Result<(), Self::Error>; + } + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + fill_word: Default::default(), + bmstall: old_spi.inner.bmstall, + sys_clk: old_spi.inner.sys_clk, + word: PhantomData, + }, + pins: old_spi.pins, + } + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + bmstall: old_spi.inner.bmstall, + sys_clk: old_spi.inner.sys_clk, + fill_word: Default::default(), + word: PhantomData, + }, + pins: old_spi.pins, + } } } diff --git a/va108xx-hal/src/timer.rs b/va108xx-hal/src/timer.rs index 05914fb..9c5bed8 100644 --- a/va108xx-hal/src/timer.rs +++ b/va108xx-hal/src/timer.rs @@ -7,6 +7,7 @@ pub use crate::IrqCfg; use crate::{ clock::{enable_peripheral_clock, PeripheralClocks}, + enable_interrupt, gpio::{ AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14, PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8, @@ -17,10 +18,9 @@ use crate::{ time::Hertz, timer, typelevel::Sealed, - utility::unmask_irq, }; use core::cell::Cell; -use cortex_m::interrupt::Mutex; +use critical_section::Mutex; use fugit::RateExtU32; const IRQ_DST_NONE: u32 = 0xffffffff; @@ -72,25 +72,46 @@ pub enum CascadeSel { Csd2 = 2, } +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidCascadeSourceId; + /// The numbers are the base numbers for bundles like PORTA, PORTB or TIM #[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] pub enum CascadeSource { - PortABase = 0, - PortBBase = 32, - TimBase = 64, + PortA(u8), + PortB(u8), + Tim(u8), RamSbe = 96, RamMbe = 97, RomSbe = 98, RomMbe = 99, Txev = 100, - ClockDividerBase = 120, + ClockDivider(u8), } -#[derive(Debug, PartialEq, Eq)] -pub enum TimerErrors { - Canceled, - /// Invalid input for Cascade source - InvalidCsdSourceInput, +impl CascadeSource { + fn id(&self) -> Result { + let port_check = |base: u8, id: u8, len: u8| { + if id > len - 1 { + return Err(InvalidCascadeSourceId); + } + Ok(base + id) + }; + match self { + CascadeSource::PortA(id) => port_check(0, *id, 32), + CascadeSource::PortB(id) => port_check(32, *id, 32), + CascadeSource::Tim(id) => port_check(64, *id, 24), + CascadeSource::RamSbe => Ok(96), + CascadeSource::RamMbe => Ok(97), + CascadeSource::RomSbe => Ok(98), + CascadeSource::RomMbe => Ok(99), + CascadeSource::Txev => Ok(100), + CascadeSource::ClockDivider(id) => port_check(120, *id, 8), + } + } } //================================================================================================== @@ -360,89 +381,26 @@ pub struct CountDownTimer { listening: bool, } -fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { +#[inline(always)] +pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { syscfg .tim_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) }); } +#[inline(always)] +pub fn disable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { + syscfg + .tim_clk_enable() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) }); +} + unsafe impl TimRegInterface for CountDownTimer { fn tim_id(&self) -> u8 { TIM::TIM_ID } } -macro_rules! csd_sel { - ($func_name:ident, $csd_reg:ident) => { - /// Configure the Cascade sources - pub fn $func_name( - &mut self, - src: CascadeSource, - id: Option, - ) -> Result<(), TimerErrors> { - let mut id_num = 0; - if let CascadeSource::PortABase - | CascadeSource::PortBBase - | CascadeSource::ClockDividerBase - | CascadeSource::TimBase = src - { - if id.is_none() { - return Err(TimerErrors::InvalidCsdSourceInput); - } - } - if id.is_some() { - id_num = id.unwrap(); - } - match src { - CascadeSource::PortABase => { - if id_num > 55 { - return Err(TimerErrors::InvalidCsdSourceInput); - } - self.tim.reg().$csd_reg().write(|w| unsafe { - w.cassel().bits(CascadeSource::PortABase as u8 + id_num) - }); - Ok(()) - } - CascadeSource::PortBBase => { - if id_num > 23 { - return Err(TimerErrors::InvalidCsdSourceInput); - } - self.tim.reg().$csd_reg().write(|w| unsafe { - w.cassel().bits(CascadeSource::PortBBase as u8 + id_num) - }); - Ok(()) - } - CascadeSource::TimBase => { - if id_num > 23 { - return Err(TimerErrors::InvalidCsdSourceInput); - } - self.tim.reg().$csd_reg().write(|w| unsafe { - w.cassel().bits(CascadeSource::TimBase as u8 + id_num) - }); - Ok(()) - } - CascadeSource::ClockDividerBase => { - if id_num > 7 { - return Err(TimerErrors::InvalidCsdSourceInput); - } - self.tim.reg().cascade0().write(|w| unsafe { - w.cassel() - .bits(CascadeSource::ClockDividerBase as u8 + id_num) - }); - Ok(()) - } - _ => { - self.tim - .reg() - .$csd_reg() - .write(|w| unsafe { w.cassel().bits(src as u8) }); - Ok(()) - } - } - } - }; -} - impl CountDownTimer { /// Configures a TIM peripheral as a periodic count down timer pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, tim: TIM) -> Self { @@ -554,18 +512,18 @@ impl CountDownTimer { #[inline(always)] pub fn enable(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.enable().set_bit()); if let Some(irq_cfg) = self.irq_cfg { self.enable_interrupt(); if irq_cfg.enable { - unmask_irq(irq_cfg.irq); + unsafe { enable_interrupt(irq_cfg.irq) }; } } + self.tim.reg().enable().write(|w| unsafe { w.bits(1) }); } #[inline(always)] pub fn disable(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); + self.tim.reg().enable().write(|w| unsafe { w.bits(0) }); } /// Disable the counter, setting both enable and active bit to 0 @@ -619,9 +577,32 @@ impl CountDownTimer { }); } - csd_sel!(cascade_0_source, cascade0); - csd_sel!(cascade_1_source, cascade1); - csd_sel!(cascade_2_source, cascade2); + pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { + let id = src.id()?; + self.tim + .reg() + .cascade0() + .write(|w| unsafe { w.cassel().bits(id) }); + Ok(()) + } + + pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { + let id = src.id()?; + self.tim + .reg() + .cascade1() + .write(|w| unsafe { w.cassel().bits(id) }); + Ok(()) + } + + pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { + let id = src.id()?; + self.tim + .reg() + .cascade2() + .write(|w| unsafe { w.cassel().bits(id) }); + Ok(()) + } pub fn curr_freq(&self) -> Hertz { self.curr_freq @@ -656,12 +637,13 @@ impl CountDownTimer { } } - pub fn cancel(&mut self) -> Result<(), TimerErrors> { + /// Returns [false] if the timer was not active, and true otherwise. + pub fn cancel(&mut self) -> bool { if !self.tim.reg().ctrl().read().enable().bit_is_set() { - return Err(TimerErrors::Canceled); + return false; } self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); - Ok(()) + true } } @@ -747,7 +729,7 @@ pub fn set_up_ms_delay_provider( /// This function can be called in a specified interrupt handler to increment /// the MS counter pub fn default_ms_irq_handler() { - cortex_m::interrupt::free(|cs| { + critical_section::with(|cs| { let mut ms = MS_COUNTER.borrow(cs).get(); ms += 1; MS_COUNTER.borrow(cs).set(ms); @@ -756,7 +738,7 @@ pub fn default_ms_irq_handler() { /// Get the current MS tick count pub fn get_ms_ticks() -> u32 { - cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()) + critical_section::with(|cs| MS_COUNTER.borrow(cs).get()) } //================================================================================================== diff --git a/va108xx-hal/src/uart.rs b/va108xx-hal/src/uart.rs index 4b35fa1..daabd22 100644 --- a/va108xx-hal/src/uart.rs +++ b/va108xx-hal/src/uart.rs @@ -3,7 +3,7 @@ //! ## Examples //! //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs) -//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart-irq-rtic.rs) +//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/va108xx-update-package/examples/rtic/src/bin/uart-rtic.rs) use core::{marker::PhantomData, ops::Deref}; use embedded_hal_nb::serial::Read; use fugit::RateExtU32; @@ -11,13 +11,13 @@ use fugit::RateExtU32; pub use crate::IrqCfg; use crate::{ clock::{enable_peripheral_clock, PeripheralClocks}, + enable_interrupt, gpio::pin::{ AltFunc1, AltFunc2, AltFunc3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30, PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9, }, pac::{self, uarta as uart_base}, time::Hertz, - utility::unmask_irq, PeripheralSelect, }; @@ -638,7 +638,7 @@ impl Instance for pac::Uartb { const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; } -impl UartWithIrqBase { +impl UartWithIrqBase { fn init(self, sys_cfg: Option<&mut pac::Sysconfig>, irq_sel: Option<&mut pac::Irqsel>) -> Self { if let Some(sys_cfg) = sys_cfg { enable_peripheral_clock(sys_cfg, PeripheralClocks::Irqsel) @@ -646,7 +646,7 @@ impl UartWithIrqBase { if let Some(irq_sel) = irq_sel { if self.irq_info.irq_cfg.route { irq_sel - .uart0(UART::IDX as usize) + .uart0(Uart::IDX as usize) .write(|w| unsafe { w.bits(self.irq_info.irq_cfg.irq as u32) }); } } @@ -676,7 +676,9 @@ impl UartWithIrqBase { self.uart.enable_tx(); self.enable_rx_irq_sources(enb_timeout_irq); if self.irq_info.irq_cfg.enable { - unmask_irq(self.irq_info.irq_cfg.irq); + unsafe { + enable_interrupt(self.irq_info.irq_cfg.irq); + } } Ok(()) } @@ -839,7 +841,7 @@ impl UartWithIrqBase { self.irq_info.rx_len = 0; } - pub fn release(self) -> UART { + pub fn release(self) -> Uart { self.uart.release() } } diff --git a/va108xx-hal/src/utility.rs b/va108xx-hal/src/utility.rs deleted file mode 100644 index e23b586..0000000 --- a/va108xx-hal/src/utility.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! # API for utility functions like the Error Detection and Correction (EDAC) block -//! -//! Some more information about the recommended scrub rates can be found on the -//! [Vorago White Paper website](https://www.voragotech.com/resources) in the -//! application note AN1212 -use crate::pac; - -/// Unmask and enable an IRQ with the given interrupt number -/// -/// ## Safety -/// -/// The unmask function can break mask-based critical sections -#[inline] -pub(crate) fn unmask_irq(irq: pac::Interrupt) { - unsafe { cortex_m::peripheral::NVIC::unmask(irq) }; -} diff --git a/vorago-reb1/CHANGELOG.md b/vorago-reb1/CHANGELOG.md index c15e67a..85939a7 100644 --- a/vorago-reb1/CHANGELOG.md +++ b/vorago-reb1/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +- Added M95M01 EEPROM module/API + ## [v0.5.1] 2024-07-04 - Update `va108xx-hal` dependency to v0.7.0 diff --git a/vorago-reb1/Cargo.toml b/vorago-reb1/Cargo.toml index 5512fde..e0b2b7e 100644 --- a/vorago-reb1/Cargo.toml +++ b/vorago-reb1/Cargo.toml @@ -14,12 +14,15 @@ categories = ["aerospace", "embedded", "no-std", "hardware-support"] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" +nb = "1" +bitfield = "0.17" [dependencies.max116xx-10bit] version = "0.3" [dependencies.va108xx-hal] -version = "0.7" +version = ">=0.7, <0.8" +path = "../va108xx-hal" features = ["rt"] [features] @@ -28,20 +31,10 @@ rt = ["va108xx-hal/rt"] [dev-dependencies] panic-halt = "0.2" nb = "1" - -[dev-dependencies.rtt-target] -version = "0.5" - -[dev-dependencies.panic-rtt-target] -version = "0.1" - -[dev-dependencies.rtic] -version = "2" -features = ["thumbv6-backend"] - -[dev-dependencies.rtic-monotonics] -version = "1" -features = ["cortex-m-systick"] +rtt-target = "0.5" +panic-rtt-target = "0.1" +embedded-hal-bus = "0.2" +dummy-pin = "1" [package.metadata.docs.rs] all-features = true diff --git a/vorago-reb1/examples/adxl343-accelerometer.rs b/vorago-reb1/examples/adxl343-accelerometer.rs index 483d4dc..ce4c3f5 100644 --- a/vorago-reb1/examples/adxl343-accelerometer.rs +++ b/vorago-reb1/examples/adxl343-accelerometer.rs @@ -5,15 +5,16 @@ #![no_main] #![no_std] use cortex_m_rt::entry; -use embedded_hal::spi::SpiBus; +use embedded_hal::spi::{SpiBus, MODE_3}; use embedded_hal::{delay::DelayNs, digital::OutputPin}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::spi::SpiClkConfig; use va108xx_hal::{ gpio::PinsA, pac, prelude::*, - spi::{Spi, SpiConfig, TransferConfig}, + spi::{Spi, SpiConfig}, timer::set_up_ms_delay_provider, }; @@ -31,7 +32,6 @@ fn main() -> ! { let mut dp = pac::Peripherals::take().unwrap(); let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); - let spi_cfg = SpiConfig::default(); let (sck, mosi, miso) = ( pinsa.pa20.into_funsel_2(), pinsa.pa19.into_funsel_2(), @@ -45,21 +45,20 @@ fn main() -> ! { .set_high() .expect("Setting ADC chip select high failed"); - let transfer_cfg = TransferConfig::new( - 1.MHz(), - embedded_hal::spi::MODE_3, - Some(cs_pin), - false, - true, - ); + let spi_cfg = SpiConfig::default() + .clk_cfg( + SpiClkConfig::from_clk(50.MHz(), 1.MHz()).expect("creating SPI clock config failed"), + ) + .mode(MODE_3) + .slave_output_disable(true); let mut spi = Spi::new( &mut dp.sysconfig, 50.MHz(), dp.spib, (sck, miso, mosi), spi_cfg, - Some(&transfer_cfg.downgrade()), ); + spi.cfg_hw_cs_with_pin(&cs_pin); let mut tx_rx_buf: [u8; 3] = [0; 3]; tx_rx_buf[0] = READ_MASK | DEVID_REG; diff --git a/vorago-reb1/examples/max11619-adc.rs b/vorago-reb1/examples/max11619-adc.rs index 8b76259..0271b8c 100644 --- a/vorago-reb1/examples/max11619-adc.rs +++ b/vorago-reb1/examples/max11619-adc.rs @@ -9,19 +9,19 @@ use core::convert::Infallible; use cortex_m_rt::entry; use embedded_hal::digital::OutputPin; -use embedded_hal::spi::{SpiBus, SpiDevice}; +use embedded_hal::spi::{SpiBus, SpiDevice, MODE_0}; use embedded_hal::{delay::DelayNs, spi}; use max116xx_10bit::VoltageRefMode; use max116xx_10bit::{AveragingConversions, AveragingResults}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va108xx_hal::spi::{NoneT, OptionalHwCs}; +use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig}; use va108xx_hal::timer::CountDownTimer; use va108xx_hal::{ gpio::PinsA, pac::{self, interrupt}, prelude::*, - spi::{Spi, SpiBase, SpiConfig, TransferConfig}, + spi::{Spi, SpiBase, SpiConfig}, timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg}, }; use va108xx_hal::{port_mux, FunSel, PortSel}; @@ -103,6 +103,8 @@ impl> SpiDevice for SpiWithHwCs ! { rtt_init_print!(); @@ -113,7 +115,7 @@ fn main() -> ! { IrqCfg::new(pac::Interrupt::OC0, true, true), &mut dp.sysconfig, Some(&mut dp.irqsel), - 50.MHz(), + SYS_CLK, dp.tim0, ); let delay = DelayMs::new(tim0).unwrap(); @@ -122,7 +124,10 @@ fn main() -> ! { } let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); - let spi_cfg = SpiConfig::default(); + let spi_cfg = SpiConfig::default() + .clk_cfg(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap()) + .mode(MODE_0) + .blockmode(true); let (sck, mosi, miso) = ( pinsa.pa20.into_funsel_2(), pinsa.pa19.into_funsel_2(), @@ -141,14 +146,12 @@ fn main() -> ! { .set_high() .expect("Setting accelerometer chip select high failed"); - let transfer_cfg = TransferConfig::::new(3.MHz(), spi::MODE_0, None, true, false); let spi = Spi::new( &mut dp.sysconfig, 50.MHz(), dp.spib, (sck, miso, mosi), spi_cfg, - Some(&transfer_cfg.downgrade()), ) .downgrade(); let delay_provider = CountDownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1); diff --git a/vorago-reb1/examples/nvm.rs b/vorago-reb1/examples/nvm.rs new file mode 100644 index 0000000..07bdb28 --- /dev/null +++ b/vorago-reb1/examples/nvm.rs @@ -0,0 +1,64 @@ +//! Example application which interfaces with the boot EEPROM. +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_hal::delay::DelayNs; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{pac, pwm::CountDownTimer, time::Hertz}; +use vorago_reb1::m95m01::M95M01; + +const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA108XX REB1 NVM example --"); + + let mut dp = pac::Peripherals::take().unwrap(); + + let mut timer = CountDownTimer::new(&mut dp.sysconfig, CLOCK_FREQ, dp.tim0); + let mut nvm = M95M01::new(&mut dp.sysconfig, CLOCK_FREQ, dp.spic); + let status_reg = nvm.read_status_reg().expect("reading status reg failed"); + if status_reg.zero_segment() == 0b111 { + panic!("status register unexpected values"); + } + + let mut orig_content: [u8; 16] = [0; 16]; + let mut read_buf: [u8; 16] = [0; 16]; + let write_buf: [u8; 16] = [0; 16]; + for (idx, val) in read_buf.iter_mut().enumerate() { + *val = idx as u8; + } + nvm.read(0x4000, &mut orig_content).unwrap(); + + // One byte write and read. + nvm.write(0x4000, &write_buf[0..1]).unwrap(); + nvm.read(0x4000, &mut read_buf[0..1]).unwrap(); + assert_eq!(write_buf[0], read_buf[0]); + read_buf.fill(0); + + // Four bytes write and read. + nvm.write(0x4000, &write_buf[0..4]).unwrap(); + nvm.read(0x4000, &mut read_buf[0..4]).unwrap(); + assert_eq!(&read_buf[0..4], &write_buf[0..4]); + read_buf.fill(0); + + // Full sixteen bytes + nvm.write(0x4000, &write_buf).unwrap(); + nvm.read(0x4000, &mut read_buf).unwrap(); + assert_eq!(&read_buf, &write_buf); + read_buf.fill(0); + + // 3 bytes + nvm.write(0x4000, &write_buf[0..3]).unwrap(); + nvm.read(0x4000, &mut read_buf[0..3]).unwrap(); + assert_eq!(&read_buf[0..3], &write_buf[0..3]); + + // Write back original content. + nvm.write(0x4000, &orig_content).unwrap(); + loop { + timer.delay_ms(500); + } +} diff --git a/vorago-reb1/src/lib.rs b/vorago-reb1/src/lib.rs index bc44aae..1ccd544 100644 --- a/vorago-reb1/src/lib.rs +++ b/vorago-reb1/src/lib.rs @@ -3,5 +3,6 @@ pub mod button; pub mod leds; +pub mod m95m01; pub mod max11619; pub mod temp_sensor; diff --git a/vorago-reb1/src/m95m01.rs b/vorago-reb1/src/m95m01.rs new file mode 100644 index 0000000..7dd794a --- /dev/null +++ b/vorago-reb1/src/m95m01.rs @@ -0,0 +1,172 @@ +//! Basic driver for the ST M95M01 EEPROM memory. +//! +//! This driver is used by the provided bootloader application for the REB1 +//! board. It provides a convenient wrapper around the HAL SPI to interface +//! with the EEPROM memory of the REB1 board. +//! +//! # Example +//! +//! - [REB1 EEPROM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/nvm.rs) +use core::convert::Infallible; +use embedded_hal::spi::SpiBus; + +bitfield::bitfield! { + pub struct StatusReg(u8); + impl Debug; + u8; + pub status_register_write_protect, _: 7; + pub zero_segment, _: 6, 4; + pub block_protection_bits, set_block_protection_bits: 3, 2; + pub write_enable_latch, _: 1; + pub write_in_progress, _: 0; +} + +// Registers. +pub mod regs { + /// Write status register command. + pub const WRSR: u8 = 0x01; + // Write command. + pub const WRITE: u8 = 0x02; + // Read command. + pub const READ: u8 = 0x03; + /// Write disable command. + pub const WRDI: u8 = 0x04; + /// Read status register command. + pub const RDSR: u8 = 0x05; + /// Write enable command. + pub const WREN: u8 = 0x06; +} + +use regs::*; +use va108xx_hal::{ + pac, + prelude::*, + spi::{RomMiso, RomMosi, RomSck, Spi, SpiConfig, BMSTART_BMSTOP_MASK}, +}; + +pub type RomSpi = Spi; + +/// Driver for the ST device M95M01 EEPROM memory. +/// +/// Specialized for the requirements of the VA108XX MCUs. +pub struct M95M01 { + pub spi: RomSpi, +} + +impl M95M01 { + pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, spi: pac::Spic) -> Self { + let spi = RomSpi::new( + syscfg, + sys_clk, + spi, + (RomSck, RomMiso, RomMosi), + SpiConfig::default(), + ); + let mut spi_dev = Self { spi }; + spi_dev.clear_block_protection().unwrap(); + spi_dev + } + + pub fn release(mut self) -> pac::Spic { + self.set_block_protection().unwrap(); + self.spi.release().0 + } + + // Wait until the write-in-progress state is cleared. This exposes a [nb] API, so this function + // will return [nb::Error::WouldBlock] if the EEPROM is still busy. + pub fn writes_are_done(&mut self) -> nb::Result<(), Infallible> { + let rdsr = self.read_status_reg()?; + if rdsr.write_in_progress() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } + + pub fn read_status_reg(&mut self) -> Result { + let mut write_read: [u8; 2] = [regs::RDSR, 0x00]; + self.spi.transfer_in_place(&mut write_read)?; + Ok(StatusReg(write_read[1])) + } + + pub fn write_enable(&mut self) -> Result<(), Infallible> { + self.spi.write(&[regs::WREN]) + } + + pub fn clear_block_protection(&mut self) -> Result<(), Infallible> { + // Has to be written separately. + self.write_enable()?; + self.spi.write(&[WRSR, 0x00]) + } + + pub fn set_block_protection(&mut self) -> Result<(), Infallible> { + let mut reg = StatusReg(0); + reg.set_block_protection_bits(0b11); + self.write_enable()?; + self.spi.write(&[WRSR, reg.0]) + } + + fn common_init_write_and_read(&mut self, address: u32, reg: u8) -> Result<(), Infallible> { + nb::block!(self.writes_are_done())?; + self.spi.flush()?; + if reg == WRITE { + self.write_enable()?; + self.spi.write_fifo_unchecked(WRITE as u32); + } else { + self.spi.write_fifo_unchecked(READ as u32); + } + self.spi.write_fifo_unchecked((address >> 16) & 0xff); + self.spi.write_fifo_unchecked((address >> 8) & 0xff); + self.spi.write_fifo_unchecked(address & 0xff); + Ok(()) + } + + fn common_read(&mut self, address: u32) -> Result<(), Infallible> { + self.common_init_write_and_read(address, READ)?; + for _ in 0..4 { + // Pump the FIFO. + self.spi.write_fifo_unchecked(0); + // Ignore the first 4 bytes. + self.spi.read_fifo_unchecked(); + } + Ok(()) + } + + pub fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Infallible> { + self.common_init_write_and_read(address, WRITE)?; + for val in data.iter().take(data.len() - 1) { + nb::block!(self.spi.write_fifo(*val as u32))?; + self.spi.read_fifo_unchecked(); + } + nb::block!(self + .spi + .write_fifo(*data.last().unwrap() as u32 | BMSTART_BMSTOP_MASK))?; + self.spi.flush()?; + nb::block!(self.writes_are_done())?; + Ok(()) + } + + pub fn read(&mut self, address: u32, buf: &mut [u8]) -> Result<(), Infallible> { + self.common_read(address)?; + for val in buf.iter_mut() { + nb::block!(self.spi.write_fifo(0))?; + *val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8; + } + nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?; + self.spi.flush()?; + Ok(()) + } + + pub fn verify(&mut self, address: u32, data: &[u8]) -> Result { + self.common_read(address)?; + for val in data.iter() { + nb::block!(self.spi.write_fifo(0))?; + let read_val = (nb::block!(self.spi.read_fifo()).unwrap() & 0xff) as u8; + if read_val != *val { + return Ok(false); + } + } + nb::block!(self.spi.write_fifo(BMSTART_BMSTOP_MASK))?; + self.spi.flush()?; + Ok(true) + } +} diff --git a/vorago-reb1/src/temp_sensor.rs b/vorago-reb1/src/temp_sensor.rs index a3266d0..25b20df 100644 --- a/vorago-reb1/src/temp_sensor.rs +++ b/vorago-reb1/src/temp_sensor.rs @@ -4,7 +4,7 @@ //! //! ## Examples //! -//! - [Temperature Sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs +//! - [Temperature Sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs) use embedded_hal::i2c::{I2c, SevenBitAddress}; use va108xx_hal::{ i2c::{Error, I2cMaster, I2cSpeed, InitError, MasterConfig}, diff --git a/vscode/launch.json b/vscode/launch.json index 356536c..eb3034e 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -20,7 +20,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -44,7 +44,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -68,7 +68,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -92,7 +92,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -116,7 +116,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -129,7 +129,7 @@ { "type": "cortex-debug", "request": "launch", - "name": "Debug UART", + "name": "UART Example", "servertype": "jlink", "cwd": "${workspaceRoot}", "device": "Cortex-M0", @@ -140,7 +140,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -164,7 +164,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -188,7 +188,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -212,7 +212,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -236,7 +236,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -284,7 +284,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -321,7 +321,7 @@ "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", "decoders": [ { "port": 0, @@ -340,12 +340,84 @@ "device": "Cortex-M0", "svdFile": "./va108xx/svd/va108xx.svd.patched", "preLaunchTask": "rust: cargo build uart irq", - "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/uart-irq-rtic", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-rtic", "interface": "jtag", "runToEntryPoint": "main", "rttConfig": { "enabled": true, - "address": "0x10000000", + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "REB1 NVM Example", + "servertype": "jlink", + "cwd": "${workspaceRoot}", + "device": "Cortex-M0", + "svdFile": "./va108xx/svd/va108xx.svd.patched", + "preLaunchTask": "reb1-nvm", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/examples/nvm", + "interface": "jtag", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "RTIC Example", + "servertype": "jlink", + "cwd": "${workspaceRoot}", + "device": "Cortex-M0", + "svdFile": "./va108xx/svd/va108xx.svd.patched", + "preLaunchTask": "rtic-example", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/rtic-example", + "interface": "jtag", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "Embassy Example", + "servertype": "jlink", + "cwd": "${workspaceRoot}", + "device": "Cortex-M0", + "svdFile": "./va108xx/svd/va108xx.svd.patched", + "preLaunchTask": "embassy-example", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/embassy-example", + "interface": "jtag", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", "decoders": [ { "port": 0, diff --git a/vscode/tasks.json b/vscode/tasks.json index c8eb183..314629e 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -67,8 +67,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "va108xx-hal", "--example", "uart", ], @@ -129,10 +127,8 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "--example", - "uart-irq-rtic", - "--features", - "rt" + "--bin", + "uart-rtic", ], "group": { "kind": "build", @@ -158,8 +154,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "blinky-leds", ], @@ -174,8 +168,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "blinky-button-irq", ], @@ -190,8 +182,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "adt75-temp-sensor", ], @@ -206,8 +196,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "blinky-button-rtic", ], @@ -222,8 +210,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "adxl343-accelerometer" ], @@ -238,8 +224,6 @@ "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", - "-p", - "vorago-reb1", "--example", "max11619-adc", ], @@ -248,5 +232,39 @@ "isDefault": true } }, + { + "label": "reb1-nvm", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--example", + "nvm", + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "rtic-example", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--bin", + "rtic-example", + ], + }, + { + "label": "embassy-example", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--bin", + "embassy-example", + ], + }, ] -} \ No newline at end of file +}