Skip to content

Commit cd3f60c

Browse files
committed
Add hrtim tests
1 parent 8b1dcdf commit cd3f60c

File tree

3 files changed

+300
-1
lines changed

3 files changed

+300
-1
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ optional = true
5656
[dev-dependencies]
5757
cortex-m-rt = "0.7.2"
5858
defmt-rtt = "0.4.0"
59-
embedded-test = "0.6.1"
59+
embedded-test = "0.6.2"
6060
cortex-m-rtic = "1.1.4"
6161
cortex-m-semihosting = "0.3.5"
6262
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
@@ -150,6 +150,11 @@ name = "nucleo-g474_w_jumpers"
150150
harness = false
151151
required-features = ["stm32g474", "defmt"]
152152

153+
[[test]]
154+
name = "nucleo-g474_hrtim"
155+
harness = false
156+
required-features = ["stm32g474", "defmt", "hrtim"]
157+
153158
[lib]
154159
test = false
155160

tests/common/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,59 @@ pub fn await_p<const CYCLES_PER_US: u32>(
6565
}
6666
}
6767
}
68+
69+
#[allow(dead_code)]
70+
pub fn test_pwm<const CYCLES_PER_US: u32>(
71+
timer: &Timer<CYCLES_PER_US>,
72+
pin_num: u8,
73+
t_lo: MicrosDurationU32,
74+
t_hi: MicrosDurationU32,
75+
t_max_deviation: MicrosDurationU32,
76+
first_start_mult: u32,
77+
) {
78+
defmt::debug!("Awaiting first rising edge...");
79+
80+
let t_lo_min = t_lo - t_max_deviation;
81+
let t_lo_max = t_lo + t_max_deviation;
82+
let t_hi_min = t_hi - t_max_deviation;
83+
let t_hi_max = t_hi + t_max_deviation;
84+
85+
let mut hi_duration = 0u32.micros();
86+
let mut lo_duration = 0.micros();
87+
88+
let duration_until_lo = await_lo(timer, pin_num, t_hi_max).unwrap();
89+
let first_lo_duration = await_hi(timer, pin_num, t_lo_max * first_start_mult).unwrap(); // Some extra time until started
90+
91+
for _ in 0..100 {
92+
// Make sure the timer half periods are within 495-505us
93+
hi_duration = await_lo(timer, pin_num, t_hi_max).unwrap();
94+
//defmt::debug!("hi_duration: {}", hi_duration);
95+
defmt::assert!(
96+
hi_duration > t_hi_min && hi_duration < t_hi_max,
97+
"hi: {} < {} < {}",
98+
t_hi_min,
99+
hi_duration,
100+
t_hi_max
101+
);
102+
103+
lo_duration = await_hi(timer, pin_num, t_lo_max).unwrap();
104+
//defmt::debug!("lo_duration: {}", lo_duration);
105+
defmt::assert!(
106+
lo_duration > t_lo_min && lo_duration < t_lo_max,
107+
"lo: {} < {} < {}",
108+
t_lo_min,
109+
lo_duration,
110+
t_lo_max
111+
);
112+
//defmt::debug!("");
113+
}
114+
115+
// Prints deferred until here to not mess up timing
116+
defmt::debug!("Waited ~{} until low", duration_until_lo);
117+
defmt::debug!("First low half period: {}", first_lo_duration);
118+
119+
defmt::debug!("High half period: {}", hi_duration);
120+
defmt::debug!("Low half period: {}", lo_duration);
121+
122+
defmt::debug!("Done!");
123+
}

tests/nucleo-g474_hrtim.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
// Requires a jumper from A1<->A2 (arduino naming) aka PA1<->PA4
5+
6+
#[path = "../examples/utils/mod.rs"]
7+
mod utils;
8+
9+
use utils::logger::debug;
10+
11+
mod common;
12+
13+
use common::test_pwm;
14+
use fugit::{ExtU32, HertzU32, MicrosDurationU32};
15+
use stm32_hrtim::compare_register::HrCompareRegister;
16+
use stm32_hrtim::control::HrPwmControl;
17+
use stm32_hrtim::deadtime::DeadtimeConfig;
18+
use stm32_hrtim::output::HrOutput;
19+
use stm32_hrtim::timer::HrTimer;
20+
use stm32_hrtim::{HrParts, HrPwmAdvExt as _, Pscl64};
21+
use stm32g4xx_hal::delay::SYSTDelayExt;
22+
use stm32g4xx_hal::gpio::{GpioExt, PinExt};
23+
use stm32g4xx_hal::hrtim::HrControltExt;
24+
use stm32g4xx_hal::pwr::PwrExt;
25+
use stm32g4xx_hal::rcc::{self, Rcc, RccExt};
26+
27+
use hal::stm32;
28+
use stm32g4xx_hal as hal;
29+
30+
const PERIOD: u16 = 60_000;
31+
const F_SYS: HertzU32 = HertzU32::MHz(120);
32+
const CYCLES_PER_US: u32 = F_SYS.raw() / 1_000_000;
33+
type Timer = crate::common::Timer<CYCLES_PER_US>;
34+
35+
#[embedded_test::tests]
36+
mod tests {
37+
use embedded_hal::delay::DelayNs;
38+
39+
#[test]
40+
fn simple() {
41+
use super::*;
42+
43+
let mut cp = stm32::CorePeripherals::take().unwrap();
44+
let dp = stm32::Peripherals::take().unwrap();
45+
let timer = Timer::enable_timer(&mut cp);
46+
47+
// Set system frequency to 16MHz * 15/1/2 = 120MHz
48+
// This would lead to HrTim running at 120MHz * 32 = 3.84...
49+
let mut rcc = setup_rcc_120MHz(dp.PWR, dp.RCC);
50+
assert_eq!(rcc.clocks.sys_clk, F_SYS);
51+
let mut delay = cp.SYST.delay(&rcc.clocks);
52+
53+
let gpioa = dp.GPIOA.split(&mut rcc);
54+
let _pa1_important_dont_use_as_output = gpioa.pa1.into_floating_input();
55+
let pin = gpioa.pa8;
56+
let pin_num = pin.pin_id();
57+
58+
let (
59+
HrParts {
60+
timer: mut hrtimer,
61+
mut cr1,
62+
mut out,
63+
..
64+
},
65+
mut hr_control,
66+
) = setup(pin, None, dp.HRTIM_TIMA, dp.HRTIM_COMMON, &mut rcc);
67+
68+
cr1.set_duty(PERIOD / 2);
69+
out.enable_rst_event(&cr1); // Set low on compare match with cr1
70+
out.enable_set_event(&hrtimer); // Set high at new period
71+
out.enable();
72+
hrtimer.start(&mut hr_control.control);
73+
74+
let t_hi = 500.micros();
75+
let t_lo = 500.micros();
76+
let t_max_deviation = 2.micros();
77+
test_pwm(&timer, pin_num, t_lo, t_hi, t_max_deviation, 10);
78+
79+
delay.delay_ms(20); // Give the host some time to read logging messages
80+
}
81+
82+
#[test]
83+
fn deadtime_68us_68us() {
84+
use super::*;
85+
86+
deadtime_test(68, 68);
87+
}
88+
89+
#[test]
90+
fn deadtime_68us_1us() {
91+
use super::*;
92+
93+
deadtime_test(68, 1);
94+
}
95+
96+
#[test]
97+
fn deadtime_1us_68us() {
98+
use super::*;
99+
100+
deadtime_test(1, 68);
101+
}
102+
}
103+
104+
#[allow(non_snake_case)]
105+
fn setup_rcc_120MHz(pwr: stm32::PWR, rcc: stm32::RCC) -> Rcc {
106+
debug!("rcc");
107+
// Set system frequency to 16MHz * 15/1/2 = 120MHz
108+
// This would lead to HrTim running at 120MHz * 32 = 3.84...
109+
let pwr = pwr.constrain().freeze();
110+
rcc.freeze(
111+
rcc::Config::pll().pll_cfg(rcc::PllConfig {
112+
mux: rcc::PllSrc::HSI,
113+
n: rcc::PllNMul::MUL_15,
114+
m: rcc::PllMDiv::DIV_1,
115+
r: Some(rcc::PllRDiv::DIV_2),
116+
117+
..Default::default()
118+
}),
119+
pwr,
120+
)
121+
}
122+
123+
fn setup<P: stm32g4xx_hal::hrtim::HrtimPin<stm32::HRTIM_TIMA>>(
124+
pins: P,
125+
deadtime_cfg: Option<DeadtimeConfig>,
126+
hrtim_tima: stm32::HRTIM_TIMA,
127+
hrtim_common: stm32::HRTIM_COMMON,
128+
rcc: &mut Rcc,
129+
) -> (
130+
HrParts<stm32::HRTIM_TIMA, Pscl64, P::Out<Pscl64>>,
131+
HrPwmControl,
132+
) {
133+
use stm32g4xx_hal::hrtim::HrPwmBuilderExt;
134+
let (hr_control, ..) = hrtim_common.hr_control(rcc).wait_for_calibration();
135+
let mut hr_control = hr_control.constrain();
136+
137+
// ...with a prescaler of 64 this gives us a HrTimer with a tick rate of 60MHz
138+
// With a period of 60_000 set, this would be 60MHz/60_000 = 1kHz
139+
let prescaler = Pscl64;
140+
141+
let mut hr_tim_builder: stm32_hrtim::HrPwmBuilder<
142+
stm32::HRTIM_TIMA,
143+
Pscl64,
144+
stm32_hrtim::PreloadSource,
145+
P,
146+
> = hrtim_tima
147+
.pwm_advanced(pins)
148+
.prescaler(prescaler)
149+
.period(PERIOD);
150+
if let Some(dt) = deadtime_cfg {
151+
hr_tim_builder = hr_tim_builder.deadtime(dt);
152+
}
153+
(hr_tim_builder.finalize(&mut hr_control), hr_control)
154+
}
155+
156+
fn deadtime_test(deadtime_rising_us: u32, deadtime_falling_us: u32) {
157+
let mut cp = stm32::CorePeripherals::take().unwrap();
158+
let dp = stm32::Peripherals::take().unwrap();
159+
let timer = Timer::enable_timer(&mut cp);
160+
161+
let deadtime_cfg = DeadtimeConfig::default()
162+
.prescaler(stm32_hrtim::deadtime::DeadtimePrescaler::ThrtimMul16)
163+
.deadtime_rising_value(
164+
(deadtime_rising_us * F_SYS.to_MHz() / 16)
165+
.try_into()
166+
.unwrap(),
167+
) // ~(1/120MHz)*16*500 ~= 67us
168+
.deadtime_falling_value(
169+
(deadtime_falling_us * F_SYS.to_MHz() / 16)
170+
.try_into()
171+
.unwrap(),
172+
);
173+
174+
let mut rcc = setup_rcc_120MHz(dp.PWR, dp.RCC);
175+
assert_eq!(rcc.clocks.sys_clk, F_SYS);
176+
177+
let gpioa = dp.GPIOA.split(&mut rcc);
178+
let _pa1_important_dont_use_as_output = gpioa.pa1.into_floating_input();
179+
let pin = gpioa.pa8;
180+
let pin_num = pin.pin_id();
181+
let complementary_pin = gpioa.pa9;
182+
let complementary_pin_num = complementary_pin.pin_id();
183+
184+
let (
185+
HrParts {
186+
timer: mut hrtimer,
187+
mut cr1,
188+
out: (mut out1, mut out2),
189+
..
190+
},
191+
mut hr_control,
192+
) = setup(
193+
(pin, complementary_pin),
194+
Some(deadtime_cfg),
195+
dp.HRTIM_TIMA,
196+
dp.HRTIM_COMMON,
197+
&mut rcc,
198+
);
199+
200+
cr1.set_duty(PERIOD / 2);
201+
out1.enable_rst_event(&cr1); // Set low on compare match with cr1
202+
out1.enable_set_event(&hrtimer); // Set high at new period
203+
out1.enable();
204+
out2.enable();
205+
hrtimer.start(&mut hr_control.control);
206+
207+
let t_max_deviation = 2.micros();
208+
{
209+
let t_hi = (500 - deadtime_rising_us).micros();
210+
let t_lo = (500 + deadtime_rising_us).micros();
211+
test_pwm(&timer, pin_num, t_lo, t_hi, t_max_deviation, 10);
212+
}
213+
214+
{
215+
let t_hi = (500 - deadtime_falling_us).micros();
216+
let t_lo = (500 + deadtime_falling_us).micros();
217+
test_pwm(
218+
&timer,
219+
complementary_pin_num,
220+
t_lo,
221+
t_hi,
222+
t_max_deviation,
223+
10,
224+
);
225+
}
226+
227+
let gpioa = unsafe { &*stm32::GPIOA::PTR };
228+
229+
// Check that both output are not active at the same time
230+
let before = timer.now();
231+
while (timer.now() - before) < MicrosDurationU32::millis(1000) {
232+
let idr = gpioa.idr().read();
233+
234+
let p = idr.idr(pin_num).is_high();
235+
let compl_p = idr.idr(complementary_pin_num).is_high();
236+
assert!(!(p && compl_p), "Both outputs active at the same time");
237+
}
238+
}

0 commit comments

Comments
 (0)