From b94022e74d9c148d66a33a8257ad8bea994e0b3b Mon Sep 17 00:00:00 2001 From: CuriouslyCurious Date: Wed, 3 Sep 2025 14:57:59 +0200 Subject: [PATCH] Add initial self-tests for f401re --- self-tests/README.md | 41 +++++++++++ self-tests/f401/.cargo/config.toml | 16 +++++ self-tests/f401/Cargo.toml | 46 +++++++++++++ self-tests/f401/README.md | 2 + self-tests/f401/build.rs | 4 ++ self-tests/f401/memory.x | 11 +++ self-tests/f401/src/lib.rs | 1 + self-tests/f401/src/main.rs | 61 ++++++++++++++++ self-tests/f401/tests/gpio.rs | 107 +++++++++++++++++++++++++++++ 9 files changed, 289 insertions(+) create mode 100644 self-tests/README.md create mode 100644 self-tests/f401/.cargo/config.toml create mode 100644 self-tests/f401/Cargo.toml create mode 100644 self-tests/f401/README.md create mode 100644 self-tests/f401/build.rs create mode 100644 self-tests/f401/memory.x create mode 100644 self-tests/f401/src/lib.rs create mode 100644 self-tests/f401/src/main.rs create mode 100644 self-tests/f401/tests/gpio.rs diff --git a/self-tests/README.md b/self-tests/README.md new file mode 100644 index 0000000..a6883c6 --- /dev/null +++ b/self-tests/README.md @@ -0,0 +1,41 @@ +# Hardware self-tests for the HAL +A collection of self-tests that run on actual hardware to ensure the HAL +behaves as expected. + +## Pre-requisities +[probe-rs](https://probe.rs/) for flashing and running the tests. The probe-rs version needs +to be version 0.24 or higher. + +Installation: +```sh +$ cargo install probe-rs-tools +``` + +### Note for musl-based systems +On musl-based system remember to disable static compilation when compiling probe-rs by instead +invoking the installation process with a `-crt-static`. + +```sh +$ RUSTFLAGS="-C target-feature=-crt-static" cargo install probe-rs-tools +``` + +## Running the tests +The test projects **HAS** to be launched from their respective directory, otherwise Cargo +gets over-eager and uses the wrong settings. + +Running the test-suite for a device (after hooking up the necessary wires) is as simple as: + +```sh +$ cargo test +``` + +This will invoke `probe-rs` which will flash the MCU with each of the test files as a +seperate firmware and then run the tests inside. + +## Writing new tests +See embedded-test's [README](https://github.com/probe-rs/embedded-test/) for how to +structure a test project. + +For a great run-through of embedded testing read [japaric's](https://github.com/japaric/), of +Ferrous Systems, blog series on it: +[https://ferrous-systems.com/blog/tags/embedded-rust-testing/](https://ferrous-systems.com/blog/tags/embedded-rust-testing/) diff --git a/self-tests/f401/.cargo/config.toml b/self-tests/f401/.cargo/config.toml new file mode 100644 index 0000000..408b6e0 --- /dev/null +++ b/self-tests/f401/.cargo/config.toml @@ -0,0 +1,16 @@ +[env] +DEFMT_log="debug" + +[build] +target = "thumbv7em-none-eabihf" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip stm32f401retx" +rustflags = [ + "-C", "linker=flip-link", # stack overflow protection + "-C", "link-arg=-Tlink.x", #cortex-m rt + "-C", "link-arg=-Tdefmt.x", # defmt logs + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + # "-C", "link-arg=--nmagic", +] diff --git a/self-tests/f401/Cargo.toml b/self-tests/f401/Cargo.toml new file mode 100644 index 0000000..d94e2c9 --- /dev/null +++ b/self-tests/f401/Cargo.toml @@ -0,0 +1,46 @@ +[workspace] +# Leave empty to prevent Cargo from being dumb + +[package] +name = "stm32-hal-self-test-f401" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" + +defmt = { version = "1.0.1" } +rtt-target = { version="0.6.1", features = ["defmt"] } +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +hal = { package = "stm32-hal2", path = "../../", features = ["f401"] } + +[dev-dependencies] +embedded-test = { version = "0.6.0", features = ["defmt"] } + +# To make plain `cargo test` work: Disable tests for the bin, +# because we are only using the intergration tests +# To make `cargo check --all-targets` work. +[[bin]] +name = "stm32-hal-self-test-f401" +test = false +bench = false + +# Same as above, to make plain `cargo test` work instead of `cargo test --tests` +[lib] +test = false +bench = false + +# Important: As we bring our own test harness, we need to disable the default one +[[test]] +name = "gpio" +harness = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(abc)'] } + +[profile.dev] +opt-level = "s" + diff --git a/self-tests/f401/README.md b/self-tests/f401/README.md new file mode 100644 index 0000000..d5a33e4 --- /dev/null +++ b/self-tests/f401/README.md @@ -0,0 +1,2 @@ +# Self-tests running on an STM32F401RETx + diff --git a/self-tests/f401/build.rs b/self-tests/f401/build.rs new file mode 100644 index 0000000..150470f --- /dev/null +++ b/self-tests/f401/build.rs @@ -0,0 +1,4 @@ +fn main() { + // Link embedded-test file only when running `cargo test` + println!("cargo::rustc-link-arg-tests=-Tembedded-test.x"); +} diff --git a/self-tests/f401/memory.x b/self-tests/f401/memory.x new file mode 100644 index 0000000..d4f6edb --- /dev/null +++ b/self-tests/f401/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); diff --git a/self-tests/f401/src/lib.rs b/self-tests/f401/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/self-tests/f401/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/self-tests/f401/src/main.rs b/self-tests/f401/src/main.rs new file mode 100644 index 0000000..11ece1c --- /dev/null +++ b/self-tests/f401/src/main.rs @@ -0,0 +1,61 @@ +//! This minimial example causes an LED to blink using a (blocking) systick delay. It's +//! the canonical "Hello world" of embedded programming. It demonstrates project structure, +//! printing text to the console, using systick delays, and setting GPIO state. + +#![deny(warnings)] +#![no_std] +#![no_main] + +use cortex_m::delay::Delay; +use cortex_m_rt::entry; // The runtime +use rtt_target; + +use hal::{ + self, + clocks::Clocks, + gpio::{Pin, PinMode, Port}, + pac, +}; + +// Import the panic handler +use panic_probe as _; + +// This marks the entrypoint of our application. + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_defmt!(); + // Set up CPU peripherals + let cp = cortex_m::Peripherals::take().unwrap(); + // Set up microcontroller peripherals + let _dp = pac::Peripherals::take().unwrap(); + + defmt::println!("Hello, world!"); + + let clock_cfg = Clocks::default(); + + // Write the clock configuration to the MCU. If you wish, you can modify `clock_cfg` above + // in accordance with [its docs](https://docs.rs/stm32-hal2/latest/stm32_hal2/clocks/index.html), + // and the `clock_cfg` example. + clock_cfg.setup().unwrap(); + + // Setup a delay, based on the Cortex-m systick. + let mut delay = Delay::new(cp.SYST, clock_cfg.systick()); + let mut led = Pin::new(Port::A, 5, PinMode::Output); + + loop { + led.set_low(); + defmt::debug!("Output pin is low."); + delay.delay_ms(1_000); + led.set_high(); + defmt::debug!("Output pin is high."); + delay.delay_ms(1_000); + } +} + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} diff --git a/self-tests/f401/tests/gpio.rs b/self-tests/f401/tests/gpio.rs new file mode 100644 index 0000000..0f56cfa --- /dev/null +++ b/self-tests/f401/tests/gpio.rs @@ -0,0 +1,107 @@ +// Test file for all parts of the GPIO API. + +// This test requires wires to be connected between the following pins: +// * pc12 <-> vdd +// * pd2 <-> gnd +// +// * pc8 <-> pc9 + +#![deny(warnings)] +#![no_std] +#![no_main] + +#[cfg(test)] +#[embedded_test::tests(setup = rtt_target::rtt_init_defmt!())] +mod tests { + use hal::{ + clocks::Clocks, + delay_ms, + gpio::{OutputType, Pin, PinMode, Port, Pull}, + }; + + const DELAY: u32 = 100; + + struct State { + input: Pin, + output: Pin, + clocks: Clocks, + } + + #[init] + fn init() -> State { + let clocks = Clocks::default(); + clocks.setup().unwrap(); + State { + input: Pin::new(Port::C, 9, PinMode::Input), + output: Pin::new(Port::C, 8, PinMode::Output), + clocks, + } + } + + // Sanity check + #[test] + fn vdd_is_high() { + let vdd = Pin::new(Port::C, 12, PinMode::Input); + assert!(vdd.is_high()); + } + + // Sanity check + #[test] + fn ground_is_low() { + let gnd = Pin::new(Port::D, 2, PinMode::Input); + assert!(gnd.is_low()); + } + + #[test] + fn push_pull_low(mut state: State) { + state.output.output_type(OutputType::PushPull); + state.output.set_low(); + defmt::assert!(state.output.is_low()); + defmt::assert!(state.input.is_low()); + } + + #[test] + fn push_pull_high(mut state: State) { + state.output.output_type(OutputType::PushPull); + state.output.set_high(); + defmt::assert!(state.output.is_high()); + defmt::assert!(state.input.is_high()); + } + + #[test] + fn pulldown_drive_input_low(mut state: State) { + state.output.output_type(OutputType::PushPull); + state.output.pull(Pull::Dn); + delay_ms(DELAY, state.clocks.apb1()); + defmt::assert!(state.output.is_low()); + defmt::assert!(state.input.is_low()); + } + + #[test] + fn pullup_drive_input_high(mut state: State) { + state.output.output_type(OutputType::PushPull); + state.output.pull(Pull::Up); + state.output.set_high(); + delay_ms(DELAY, state.clocks.apb1()); + // defmt::assert!(state.output.is_high()); + defmt::assert!(state.input.is_high()); + } + + #[test] + fn open_drain_low(mut state: State) { + state.output.output_type(OutputType::OpenDrain); + state.output.pull(Pull::Dn); + delay_ms(DELAY, state.clocks.apb1()); + defmt::assert!(state.output.is_low()); + defmt::assert!(state.input.is_low()); + } + + // #[test] + // fn open_drain_high(mut state: State) { + // state.output.output_type(OutputType::OpenDrain); + // state.output.pull(Pull::Up); + // delay_ms(DELAY, state.clocks.apb1()); + // defmt::assert!(state.output.is_high()); + // defmt::assert!(state.input.is_high()); + // } +}