Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions self-tests/README.md
Original file line number Diff line number Diff line change
@@ -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/)
16 changes: 16 additions & 0 deletions self-tests/f401/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -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",
]
46 changes: 46 additions & 0 deletions self-tests/f401/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"

2 changes: 2 additions & 0 deletions self-tests/f401/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Self-tests running on an STM32F401RETx

4 changes: 4 additions & 0 deletions self-tests/f401/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
// Link embedded-test file only when running `cargo test`
println!("cargo::rustc-link-arg-tests=-Tembedded-test.x");
}
11 changes: 11 additions & 0 deletions self-tests/f401/memory.x
Original file line number Diff line number Diff line change
@@ -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);
1 change: 1 addition & 0 deletions self-tests/f401/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#![no_std]
61 changes: 61 additions & 0 deletions self-tests/f401/src/main.rs
Original file line number Diff line number Diff line change
@@ -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()
}
107 changes: 107 additions & 0 deletions self-tests/f401/tests/gpio.rs
Original file line number Diff line number Diff line change
@@ -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());
// }
}
Loading