diff --git a/.cargo/config.toml b/.cargo/config.toml index 697121a1..0e413f56 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,5 @@ [target.thumbv7em-none-eabihf] runner = 'probe-rs run --connect-under-reset' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", -] [build] target = "thumbv7em-none-eabihf" diff --git a/.vscode/settings.json b/.vscode/settings.json index 5ecc8746..252fbac0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "rust-analyzer.check.allTargets": false, "rust-analyzer.check.extraArgs": [ "--examples", + "--tests" ], "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", "rust-analyzer.cargo.features": [ diff --git a/Cargo.toml b/Cargo.toml index b1cb7421..279e89d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ optional = true [dev-dependencies] cortex-m-rt = "0.7.2" defmt-rtt = "0.4.0" -defmt-test = "0.3.2" +embedded-test = "0.6.1" cortex-m-rtic = "1.1.4" cortex-m-semihosting = "0.3.5" panic-probe = { version = "0.3.0", features = ["print-defmt"] } @@ -104,6 +104,7 @@ defmt = [ "nb/defmt-0-3", "embedded-hal/defmt-03", "embedded-io/defmt-03", + "embedded-test/defmt", ] cordic = ["dep:fixed"] adc3 = [] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..e7f6595e --- /dev/null +++ b/build.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + // stm32 specific + println!("cargo:rustc-link-arg=-Tlink.x"); + + // add linker script for embedded-test!! + println!("cargo::rustc-link-arg-tests=-Tembedded-test.x"); + + // Check if the `defmt` feature is enabled, and if so link its linker script + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg=-Tdefmt.x"); + } +} diff --git a/examples/utils/logger.rs b/examples/utils/logger.rs index a5e38d06..9b99e118 100644 --- a/examples/utils/logger.rs +++ b/examples/utils/logger.rs @@ -12,6 +12,7 @@ cfg_if::cfg_if! { cfg_if::cfg_if! { if #[cfg(feature = "log-itm")] { + #[cfg(not(test))] use panic_itm as _; use lazy_static::lazy_static; @@ -57,6 +58,7 @@ cfg_if::cfg_if! { } else if #[cfg(feature = "defmt")] { use defmt_rtt as _; // global logger + #[cfg(not(test))] use panic_probe as _; #[allow(unused_imports)] pub use defmt::Logger; @@ -67,6 +69,7 @@ cfg_if::cfg_if! { pub fn init() {} } else if #[cfg(feature = "log-semihost")] { + #[cfg(not(test))] use panic_semihosting as _; use lazy_static::lazy_static; @@ -104,6 +107,7 @@ cfg_if::cfg_if! { } } else { + #[cfg(not(test))] use panic_halt as _; #[allow(dead_code)] diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 00000000..bda96aa2 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,67 @@ +use fugit::{ExtU32, MicrosDurationU32}; +use stm32g4xx_hal::stm32; + +#[non_exhaustive] +pub struct Timer; + +impl Timer { + #[allow(dead_code)] + pub fn enable_timer(cp: &mut stm32::CorePeripherals) -> Self { + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + Timer + } + + /// Returns duration since timer start + pub fn now(&self) -> MicrosDurationU32 { + (stm32::DWT::cycle_count() / CYCLES_PER_US).micros() + } +} + +#[allow(dead_code)] +pub fn is_pax_low(pin: u8) -> bool { + let gpioa = unsafe { &*stm32::GPIOA::PTR }; + gpioa.idr().read().idr(pin).is_low() +} + +#[allow(dead_code)] +#[derive(Debug, defmt::Format)] +pub struct ErrorTimedOut; + +#[allow(dead_code)] +pub fn await_lo( + timer: &Timer, + pin: u8, + timeout: MicrosDurationU32, +) -> Result { + await_p(timer, || is_pax_low(pin), timeout) +} + +#[allow(dead_code)] +pub fn await_hi( + timer: &Timer, + pin: u8, + timeout: MicrosDurationU32, +) -> Result { + await_p(timer, || !is_pax_low(pin), timeout) +} + +#[allow(dead_code)] +pub fn await_p( + timer: &Timer, + mut p: impl FnMut() -> bool, + timeout: MicrosDurationU32, +) -> Result { + let before = timer.now(); + + loop { + let passed_time = timer.now() - before; + if p() { + return Ok(passed_time); + } + if passed_time > timeout { + return Err(ErrorTimedOut); + } + } +} diff --git a/tests/nucleo-g474.rs b/tests/nucleo-g474.rs index 9d153434..3e9957d4 100644 --- a/tests/nucleo-g474.rs +++ b/tests/nucleo-g474.rs @@ -4,10 +4,10 @@ #[path = "../examples/utils/mod.rs"] mod utils; +mod common; + use utils::logger::debug; -use core::ops::FnMut; -use core::result::Result; use fugit::{ExtU32, HertzU32, MicrosDurationU32}; use hal::delay::DelayExt; use hal::stm32; @@ -16,16 +16,7 @@ use stm32g4xx_hal as hal; pub const F_SYS: HertzU32 = HertzU32::MHz(16); pub const CYCLES_PER_US: u32 = F_SYS.raw() / 1_000_000; -pub fn enable_timer(cp: &mut stm32::CorePeripherals) { - cp.DCB.enable_trace(); - cp.DWT.enable_cycle_counter(); -} - -pub fn now() -> MicrosDurationU32 { - (stm32::DWT::cycle_count() / CYCLES_PER_US).micros() -} - -#[defmt_test::tests] +#[embedded_test::tests] mod tests { use embedded_hal::pwm::SetDutyCycle; use fixed::types::I1F15; @@ -47,13 +38,15 @@ mod tests { stm32::GPIOA, }; + use crate::common::{await_hi, await_lo, is_pax_low}; + type Timer = crate::common::Timer<{ crate::CYCLES_PER_US }>; + #[test] fn gpio_push_pull() { use super::*; - // TODO: Is it ok to steal these? - let cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut delay = cp.SYST.delay(&rcc.clocks); defmt::dbg!(rcc.clocks.sys_clk); @@ -61,67 +54,55 @@ mod tests { let gpioa = dp.GPIOA.split(&mut rcc); let _pa1_important_dont_use_as_output = gpioa.pa1.into_floating_input(); let mut pin = gpioa.pa8.into_push_pull_output(); + let pin_num = 8; // PA8 pin.set_high(); delay.delay(1.millis()); // Give the pin plenty of time to go high - { - let gpioa = unsafe { &*GPIOA::PTR }; - assert!(!is_pax_low(gpioa, 8)); - } + assert!(!is_pax_low(pin_num)); pin.set_low(); delay.delay(1.millis()); // Give the pin plenty of time to go low - { - let gpioa = unsafe { &*GPIOA::PTR }; - assert!(is_pax_low(gpioa, 8)); - } + assert!(is_pax_low(pin_num)); } #[test] fn gpio_open_drain() { use super::*; - // TODO: Is it ok to steal these? - let cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut delay = cp.SYST.delay(&rcc.clocks); let gpioa = dp.GPIOA.split(&mut rcc); let _pa1_important_dont_use_as_output = gpioa.pa1.into_floating_input(); let mut pin = gpioa.pa8.into_open_drain_output(); + let pin_num = 8; // PA8 // Enable pull-up resistor { let gpioa = unsafe { &*GPIOA::PTR }; - gpioa.pupdr().modify(|_, w| w.pupdr8().pull_up()); + gpioa.pupdr().modify(|_, w| w.pupdr(pin_num).pull_up()); } pin.set_high(); delay.delay(1.millis()); // Give the pin plenty of time to go high assert!(pin.is_high()); - { - let gpioa = unsafe { &*GPIOA::PTR }; - assert!(!is_pax_low(gpioa, 8)); - } + assert!(!is_pax_low(pin_num)); pin.set_low(); delay.delay(1.millis()); // Give the pin plenty of time to go low assert!(pin.is_low()); - { - let gpioa = unsafe { &*GPIOA::PTR }; - assert!(is_pax_low(gpioa, 8)); - } + assert!(is_pax_low(pin_num)); } #[test] fn pwm() { use super::*; - // TODO: Is it ok to steal these? - let mut cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; - enable_timer(&mut cp); + let mut cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); + let timer = Timer::enable_timer(&mut cp); let mut rcc = dp.RCC.constrain(); assert_eq!(rcc.clocks.sys_clk, F_SYS); @@ -129,28 +110,26 @@ mod tests { let gpioa = dp.GPIOA.split(&mut rcc); let _pa1_important_dont_use_as_output = gpioa.pa1.into_floating_input(); let pin: PA8 = gpioa.pa8.into_alternate(); + let pin_num = 8; // PA8 let mut pwm = dp.TIM1.pwm(pin, 1000u32.Hz(), &mut rcc); pwm.set_duty_cycle_percent(50).unwrap(); pwm.enable(); - let gpioa = unsafe { &*GPIOA::PTR }; - let min: MicrosDurationU32 = 495u32.micros(); let max: MicrosDurationU32 = 505u32.micros(); debug!("Awaiting first rising edge..."); - let duration_until_lo = await_lo(gpioa, max).unwrap(); - let first_lo_duration = await_hi(gpioa, max).unwrap(); + let duration_until_lo = await_lo(&timer, pin_num, max).unwrap(); + let first_lo_duration = await_hi(&timer, pin_num, max).unwrap(); let mut hi_duration = 0.micros(); let mut lo_duration = 0.micros(); for _ in 0..10 { // Make sure the timer half periods are within 495-505us - - hi_duration = await_lo(gpioa, max).unwrap(); + hi_duration = await_lo(&timer, pin_num, max).unwrap(); assert!( hi_duration > min && hi_duration < max, "hi: {} < {} < {}", @@ -159,7 +138,7 @@ mod tests { max ); - lo_duration = await_hi(gpioa, max).unwrap(); + lo_duration = await_hi(&timer, pin_num, max).unwrap(); assert!( lo_duration > min && lo_duration < max, "lo: {} < {} < {}", @@ -189,7 +168,7 @@ mod tests { use super::*; - let dp = unsafe { stm32::Peripherals::steal() }; + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut cordic = dp @@ -223,9 +202,8 @@ mod tests { fn adc() { use super::*; - // TODO: Is it ok to steal these? - let cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut delay = cp.SYST.delay(&rcc.clocks); @@ -264,9 +242,8 @@ mod tests { dma::{channel::DMAExt, config::DmaConfig, TransferExt}, }; - // TODO: Is it ok to steal these? - let cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut delay = cp.SYST.delay(&rcc.clocks); @@ -328,41 +305,3 @@ mod tests { assert!((20.0..35.0).contains(&temp), "20.0 < {} < 35.0", temp); } } - -fn is_pax_low(gpioa: &stm32::gpioa::RegisterBlock, x: u8) -> bool { - gpioa.idr().read().idr(x).is_low() -} - -#[derive(Debug, defmt::Format)] -struct ErrorTimedOut; - -fn await_lo( - gpioa: &stm32::gpioa::RegisterBlock, - timeout: MicrosDurationU32, -) -> Result { - await_p(|| is_pax_low(gpioa, 8), timeout) -} - -fn await_hi( - gpioa: &stm32::gpioa::RegisterBlock, - timeout: MicrosDurationU32, -) -> Result { - await_p(|| !is_pax_low(gpioa, 8), timeout) -} - -fn await_p( - mut p: impl FnMut() -> bool, - timeout: MicrosDurationU32, -) -> Result { - let before = now(); - - loop { - let passed_time = now() - before; - if p() { - return Ok(passed_time); - } - if passed_time > timeout { - return Err(ErrorTimedOut); - } - } -} diff --git a/tests/nucleo-g474_w_jumpers.rs b/tests/nucleo-g474_w_jumpers.rs index b43455f3..64d3d4e5 100644 --- a/tests/nucleo-g474_w_jumpers.rs +++ b/tests/nucleo-g474_w_jumpers.rs @@ -6,6 +6,8 @@ #[path = "../examples/utils/mod.rs"] mod utils; +mod common; + use stm32g4xx_hal::adc::{self, AdcClaim, AdcCommonExt}; use stm32g4xx_hal::comparator::{self, ComparatorSplit}; use stm32g4xx_hal::dac::{self, DacExt, DacOut}; @@ -17,7 +19,7 @@ use stm32g4xx_hal::rcc::{self, RccExt}; use hal::stm32; use stm32g4xx_hal as hal; -#[defmt_test::tests] +#[embedded_test::tests] mod tests { use embedded_hal::delay::DelayNs; use stm32g4xx_hal::{ @@ -150,7 +152,7 @@ mod tests { /// | \ /// dac ---> | + \ /// | *-----> - /// Vref---> | - / + /// Vref---> | - / /// | / /// | / /// @@ -309,9 +311,8 @@ fn setup_opamp_comp_dac() -> Peripherals { //op1+ PA1 -> A1 //DAC1_OUT1 PA4 -> A2 - // TODO: Is it ok to steal these? - let cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); let mut rcc = dp.RCC.constrain(); let mut delay = cp.SYST.delay(&rcc.clocks);