Skip to content

Commit 1990e99

Browse files
authored
Add Tests (#179)
* Add pwm test * Remove log-rtt * Not working... * Add more tests * Rename things * Add more tests * Fix DAC test * fmt
1 parent 8df6635 commit 1990e99

File tree

3 files changed

+347
-5
lines changed

3 files changed

+347
-5
lines changed

Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ optional = true
6565
[dev-dependencies]
6666
cortex-m-rt = "0.7.2"
6767
defmt-rtt = "0.4.0"
68+
defmt-test = "0.3.2"
6869
cortex-m-rtic = "1.1.4"
6970
cortex-m-semihosting = "0.3.5"
7071
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
@@ -119,6 +120,13 @@ codegen-units = 1
119120
incremental = false
120121
lto = true
121122

123+
[profile.test]
124+
opt-level = "s"
125+
debug = true
126+
codegen-units = 1
127+
incremental = false
128+
lto = true
129+
122130
[[example]]
123131
name = "flash_with_rtic"
124132
required-features = ["stm32g474"]
@@ -130,3 +138,10 @@ required-features = ["usb"]
130138
[[example]]
131139
name = "cordic"
132140
required-features = ["cordic"]
141+
142+
[[test]]
143+
name = "tests"
144+
harness = false
145+
146+
[lib]
147+
test = false

examples/utils/logger.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(unsafe_code)]
22
cfg_if::cfg_if! {
3-
if #[cfg(all(feature = "log-rtt", feature = "defmt"))] {
3+
if #[cfg(feature = "defmt")] {
44
#[allow(unused_imports)]
55
pub use defmt::{info, trace, warn, debug, error};
66

@@ -55,7 +55,7 @@ cfg_if::cfg_if! {
5555
}
5656

5757
}
58-
else if #[cfg(all(feature = "log-rtt"/*, feature = "defmt"*/))] {
58+
else if #[cfg(feature = "defmt")] {
5959
use defmt_rtt as _; // global logger
6060
use panic_probe as _;
6161
#[allow(unused_imports)]
@@ -66,9 +66,6 @@ cfg_if::cfg_if! {
6666
#[allow(dead_code)]
6767
pub fn init() {}
6868
}
69-
else if #[cfg(all(feature = "log-rtt", not(feature = "defmt")))] {
70-
// TODO
71-
}
7269
else if #[cfg(feature = "log-semihost")] {
7370
use panic_semihosting as _;
7471

tests/tests.rs

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
#[path = "../examples/utils/mod.rs"]
5+
mod utils;
6+
7+
use utils::logger::debug;
8+
9+
use core::ops::FnMut;
10+
use core::result::Result;
11+
use fugit::{ExtU32, HertzU32, MicrosDurationU32};
12+
use hal::delay::DelayExt;
13+
use hal::stm32;
14+
use stm32g4xx_hal as hal;
15+
16+
pub const F_SYS: HertzU32 = HertzU32::MHz(16);
17+
pub const CYCLES_PER_US: u32 = F_SYS.raw() / 1_000_000;
18+
19+
pub fn enable_timer(cp: &mut stm32::CorePeripherals) {
20+
cp.DCB.enable_trace();
21+
cp.DWT.enable_cycle_counter();
22+
}
23+
24+
pub fn now() -> MicrosDurationU32 {
25+
(stm32::DWT::cycle_count() / CYCLES_PER_US).micros()
26+
}
27+
28+
#[defmt_test::tests]
29+
mod tests {
30+
use embedded_hal::{
31+
delay::DelayNs,
32+
digital::{InputPin, OutputPin},
33+
pwm::SetDutyCycle,
34+
};
35+
use fixed::types::I1F15;
36+
use fugit::RateExtU32;
37+
use stm32g4xx_hal::{
38+
adc::{self, AdcClaim, Temperature, Vref},
39+
cordic::{
40+
op::{dynamic::Mode, Magnitude, SinCos, Sqrt},
41+
prec::P60,
42+
scale::N0,
43+
types::{Q15, Q31},
44+
Ext,
45+
},
46+
dac::{DacExt, DacOut},
47+
delay::SYSTDelayExt,
48+
gpio::{GpioExt, AF6},
49+
pwm::PwmExt,
50+
rcc::RccExt,
51+
signature::{VrefCal, VDDA_CALIB},
52+
stm32::GPIOA,
53+
};
54+
55+
#[test]
56+
fn gpio_push_pull() {
57+
use super::*;
58+
59+
// TODO: Is it ok to steal these?
60+
let cp = unsafe { stm32::CorePeripherals::steal() };
61+
let dp = unsafe { stm32::Peripherals::steal() };
62+
let mut rcc = dp.RCC.constrain();
63+
let mut delay = cp.SYST.delay(&rcc.clocks);
64+
defmt::dbg!(rcc.clocks.sys_clk);
65+
66+
let gpioa = dp.GPIOA.split(&mut rcc);
67+
let mut pin = gpioa.pa8.into_push_pull_output();
68+
69+
pin.set_high().unwrap();
70+
delay.delay(1.millis()); // Give the pin plenty of time to go high
71+
assert!(pin.is_high().unwrap());
72+
{
73+
let gpioa = unsafe { &*GPIOA::PTR };
74+
assert!(!is_pax_low(gpioa, 8));
75+
}
76+
77+
pin.set_low().unwrap();
78+
delay.delay(1.millis()); // Give the pin plenty of time to go low
79+
assert!(pin.is_low().unwrap());
80+
{
81+
let gpioa = unsafe { &*GPIOA::PTR };
82+
assert!(is_pax_low(gpioa, 8));
83+
}
84+
}
85+
86+
#[test]
87+
fn gpio_open_drain() {
88+
use super::*;
89+
90+
// TODO: Is it ok to steal these?
91+
let cp = unsafe { stm32::CorePeripherals::steal() };
92+
let dp = unsafe { stm32::Peripherals::steal() };
93+
let mut rcc = dp.RCC.constrain();
94+
let mut delay = cp.SYST.delay(&rcc.clocks);
95+
96+
let gpioa = dp.GPIOA.split(&mut rcc);
97+
let mut pin = gpioa.pa8.into_open_drain_output();
98+
99+
// Enable pull-up resistor
100+
{
101+
let gpioa = unsafe { &*GPIOA::PTR };
102+
gpioa.pupdr().modify(|_, w| w.pupdr8().pull_up());
103+
}
104+
105+
pin.set_high().unwrap();
106+
delay.delay(1.millis()); // Give the pin plenty of time to go high
107+
assert!(pin.is_high().unwrap());
108+
{
109+
let gpioa = unsafe { &*GPIOA::PTR };
110+
assert!(!is_pax_low(gpioa, 8));
111+
}
112+
113+
pin.set_low().unwrap();
114+
delay.delay(1.millis()); // Give the pin plenty of time to go low
115+
assert!(pin.is_low().unwrap());
116+
{
117+
let gpioa = unsafe { &*GPIOA::PTR };
118+
assert!(is_pax_low(gpioa, 8));
119+
}
120+
}
121+
122+
#[test]
123+
fn pwm() {
124+
use super::*;
125+
126+
// TODO: Is it ok to steal these?
127+
let mut cp = unsafe { stm32::CorePeripherals::steal() };
128+
let dp = unsafe { stm32::Peripherals::steal() };
129+
enable_timer(&mut cp);
130+
131+
let mut rcc = dp.RCC.constrain();
132+
assert_eq!(rcc.clocks.sys_clk, F_SYS);
133+
134+
let gpioa = dp.GPIOA.split(&mut rcc);
135+
let pin: stm32g4xx_hal::gpio::gpioa::PA8<stm32g4xx_hal::gpio::Alternate<AF6>> =
136+
gpioa.pa8.into_alternate();
137+
138+
let mut pwm = dp.TIM1.pwm(pin, 1000u32.Hz(), &mut rcc);
139+
140+
pwm.set_duty_cycle_percent(50).unwrap();
141+
pwm.enable();
142+
143+
let gpioa = unsafe { &*GPIOA::PTR };
144+
145+
let min: MicrosDurationU32 = 495u32.micros();
146+
let max: MicrosDurationU32 = 505u32.micros();
147+
148+
debug!("Awaiting first rising edge...");
149+
let duration_until_lo = await_lo(&gpioa, max).unwrap();
150+
let first_lo_duration = await_hi(&gpioa, max).unwrap();
151+
152+
let mut hi_duration = 0.micros();
153+
let mut lo_duration = 0.micros();
154+
155+
for _ in 0..10 {
156+
// Make sure the timer half periods are within 495-505us
157+
158+
hi_duration = await_lo(&gpioa, max).unwrap();
159+
assert!(
160+
hi_duration > min && hi_duration < max,
161+
"hi: {} < {} < {}",
162+
min,
163+
hi_duration,
164+
max
165+
);
166+
167+
lo_duration = await_hi(&gpioa, max).unwrap();
168+
assert!(
169+
lo_duration > min && lo_duration < max,
170+
"lo: {} < {} < {}",
171+
min,
172+
lo_duration,
173+
max
174+
);
175+
}
176+
177+
// Prints deferred until here to not mess up timing
178+
debug!("Waited ~{} until low", duration_until_lo);
179+
debug!("First low half period: {}", first_lo_duration);
180+
181+
debug!("High half period: {}", hi_duration);
182+
debug!("Low half period: {}", lo_duration);
183+
184+
debug!("Done!");
185+
186+
pwm.disable();
187+
}
188+
189+
#[test]
190+
fn cordic() {
191+
fn is_almost_equals(a: f32, b: f32) -> bool {
192+
(a - b).abs() < 0.001
193+
}
194+
195+
use super::*;
196+
197+
let dp = unsafe { stm32::Peripherals::steal() };
198+
let mut rcc = dp.RCC.constrain();
199+
200+
let mut cordic = dp
201+
.CORDIC
202+
.constrain(&mut rcc)
203+
.freeze::<Q15, Q31, P60, SinCos>(); // 16 bit arguments, 32 bit results, compute sine and cosine, 60 iterations
204+
205+
// static operation (zero overhead)
206+
207+
cordic.start(I1F15::from_num(-0.25 /* -45 degreees */));
208+
209+
let (sin, cos) = cordic.result();
210+
211+
debug!("sin: {}, cos: {}", sin.to_num::<f32>(), cos.to_num::<f32>());
212+
assert!(is_almost_equals(sin.to_num::<f32>(), -0.707));
213+
assert!(is_almost_equals(cos.to_num::<f32>(), 0.707));
214+
215+
// dynamic operation
216+
217+
let mut cordic = cordic.into_dynamic();
218+
219+
let sqrt = cordic.run::<Sqrt<N0>>(I1F15::from_num(0.25));
220+
debug!("sqrt: {}", sqrt.to_num::<f32>());
221+
assert!(is_almost_equals(sqrt.to_num::<f32>(), 0.5));
222+
let magnitude = cordic.run::<Magnitude>((I1F15::from_num(0.25), I1F15::from_num(0.5)));
223+
debug!("magnitude: {}", magnitude.to_num::<f32>());
224+
assert!(is_almost_equals(magnitude.to_num::<f32>(), 0.559));
225+
}
226+
227+
#[test]
228+
fn adc() {
229+
use super::*;
230+
231+
// TODO: Is it ok to steal these?
232+
let cp = unsafe { stm32::CorePeripherals::steal() };
233+
let dp = unsafe { stm32::Peripherals::steal() };
234+
let rcc = dp.RCC.constrain();
235+
let mut delay = cp.SYST.delay(&rcc.clocks);
236+
237+
let mut adc = dp
238+
.ADC1
239+
.claim(adc::ClockSource::SystemClock, &rcc, &mut delay, true);
240+
241+
adc.enable_temperature(&dp.ADC12_COMMON);
242+
adc.enable_vref(&dp.ADC12_COMMON);
243+
let sample_time = adc::config::SampleTime::Cycles_640_5;
244+
245+
let vref = adc.convert(&Vref, sample_time);
246+
let vref_cal = VrefCal::get().read();
247+
let vdda = VDDA_CALIB * vref_cal as u32 / vref as u32;
248+
debug!("vdda: {}mV", vdda);
249+
assert!((3200..3400).contains(&vdda));
250+
251+
let vref = Vref::sample_to_millivolts_ext(vref, vdda, adc::config::Resolution::Twelve);
252+
debug!("vref: {}mV", vref);
253+
assert!((1182..1232).contains(&vref)); // From G474 datasheet
254+
255+
let temperature_reading = adc.convert(&Temperature, sample_time);
256+
let temp = Temperature::temperature_to_degrees_centigrade(
257+
temperature_reading,
258+
vdda as f32 / 1000.,
259+
adc::config::Resolution::Twelve,
260+
);
261+
debug!("temp: {}°C", temp);
262+
assert!((20.0..30.0).contains(&temp), "20.0 < {} < 30.0", temp);
263+
}
264+
265+
#[test]
266+
fn dac() {
267+
use super::*;
268+
269+
// TODO: Is it ok to steal these?
270+
let cp = unsafe { stm32::CorePeripherals::steal() };
271+
let dp = unsafe { stm32::Peripherals::steal() };
272+
let mut rcc = dp.RCC.constrain();
273+
let mut delay = cp.SYST.delay(&rcc.clocks);
274+
275+
let gpioa = dp.GPIOA.split(&mut rcc);
276+
let pa4 = gpioa.pa4.into_floating_input();
277+
let dac1ch1 = dp.DAC1.constrain(pa4, &mut rcc);
278+
279+
let gpioa = unsafe { &*GPIOA::PTR };
280+
281+
// dac_manual will have its value set manually
282+
let mut dac = dac1ch1.calibrate_buffer(&mut delay).enable();
283+
284+
dac.set_value(0);
285+
delay.delay_ms(1);
286+
assert!(is_pax_low(&gpioa, 4));
287+
288+
dac.set_value(4095);
289+
delay.delay_ms(1);
290+
assert!(!is_pax_low(&gpioa, 4));
291+
}
292+
}
293+
294+
fn is_pax_low(gpioa: &stm32::gpioa::RegisterBlock, x: u8) -> bool {
295+
gpioa.idr().read().idr(x).is_low()
296+
}
297+
298+
#[derive(Debug, defmt::Format)]
299+
struct ErrorTimedOut;
300+
301+
fn await_lo(
302+
gpioa: &stm32::gpioa::RegisterBlock,
303+
timeout: MicrosDurationU32,
304+
) -> Result<MicrosDurationU32, ErrorTimedOut> {
305+
await_p(|| is_pax_low(gpioa, 8), timeout)
306+
}
307+
308+
fn await_hi(
309+
gpioa: &stm32::gpioa::RegisterBlock,
310+
timeout: MicrosDurationU32,
311+
) -> Result<MicrosDurationU32, ErrorTimedOut> {
312+
await_p(|| !is_pax_low(gpioa, 8), timeout)
313+
}
314+
315+
fn await_p(
316+
mut p: impl FnMut() -> bool,
317+
timeout: MicrosDurationU32,
318+
) -> Result<MicrosDurationU32, ErrorTimedOut> {
319+
let before = now();
320+
321+
loop {
322+
let passed_time = now() - before;
323+
if p() {
324+
return Ok(passed_time);
325+
}
326+
if passed_time > timeout {
327+
return Err(ErrorTimedOut);
328+
}
329+
}
330+
}

0 commit comments

Comments
 (0)