From 638dbeb2548d17edd213da2865b6ba18d9e8bb36 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 18 May 2025 22:20:57 +0200 Subject: [PATCH 1/7] More work on dac trigger and tests --- Cargo.toml | 4 + examples/utils/mod.rs | 1 + examples/utils/test.rs | 49 ++++ src/hrtim/dac_trigger.rs | 37 +++ src/hrtim/mod.rs | 34 ++- tests/nucleo-g474_w_jumpers-hrtim.rs | 321 +++++++++++++++++++++++++++ 6 files changed, 438 insertions(+), 8 deletions(-) create mode 100644 examples/utils/test.rs create mode 100644 src/hrtim/dac_trigger.rs create mode 100644 tests/nucleo-g474_w_jumpers-hrtim.rs diff --git a/Cargo.toml b/Cargo.toml index cfe5af67..686f5d51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,10 @@ name = "nucleo-g474_hrtim" harness = false required-features = ["stm32g474", "defmt", "hrtim"] +[[test]] +name = "nucleo-g474_w_jumpers-hrtim" +harness = false + [lib] test = false diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 35e91cb9..70b951cb 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -1,3 +1,4 @@ //! Utilities for examples pub mod logger; +pub mod test; diff --git a/examples/utils/test.rs b/examples/utils/test.rs new file mode 100644 index 00000000..6c2b32ea --- /dev/null +++ b/examples/utils/test.rs @@ -0,0 +1,49 @@ +#![allow(dead_code)] + +use fugit::MicrosDurationU32; + +use stm32g4xx_hal::stm32; + +pub fn is_pax_low(gpioa: &stm32::gpioa::RegisterBlock, x: u8) -> bool { + gpioa.idr().read().idr(x).is_low() +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub struct ErrorTimedOut; + +pub fn await_lo( + gpioa: &stm32::gpioa::RegisterBlock, + pin: u8, + timeout: MicrosDurationU32, + now: impl FnMut() -> MicrosDurationU32, +) -> Result { + await_p(|| is_pax_low(gpioa, pin), timeout, now) +} + +pub fn await_hi( + gpioa: &stm32::gpioa::RegisterBlock, + pin: u8, + timeout: MicrosDurationU32, + now: impl FnMut() -> MicrosDurationU32, +) -> Result { + await_p(|| !is_pax_low(gpioa, pin), timeout, now) +} + +pub fn await_p( + mut p: impl FnMut() -> bool, + timeout: MicrosDurationU32, + mut now: impl FnMut() -> 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/src/hrtim/dac_trigger.rs b/src/hrtim/dac_trigger.rs new file mode 100644 index 00000000..a89b4766 --- /dev/null +++ b/src/hrtim/dac_trigger.rs @@ -0,0 +1,37 @@ +use stm32_hrtim::DacResetOnCounterReset; +use stm32_hrtim::{ + compare_register::HrCr2, output::HrOut1, timer::HrTim, DacResetOnOut1Set, DacResetTrigger, + DacStepOnCmp2, DacStepOnOut1Rst, DacStepTrigger, +}; + +use crate::dac::{IncTriggerSource, TriggerSource as RstTriggerSource}; +use crate::stm32; + +// TODO: use crate::stasis instead of references +macro_rules! impl_dac_triggers { + ($($TIM:ident: $bits:expr),*) => {$( + unsafe impl IncTriggerSource for &HrCr2 { + const BITS: u8 = $bits; + } + unsafe impl IncTriggerSource for &HrOut1 { + const BITS: u8 = $bits; + } + + unsafe impl RstTriggerSource for &HrTim { + const BITS: u8 = $bits; + } + unsafe impl RstTriggerSource for &HrOut1 { + const BITS: u8 = $bits; + } + )*}; +} + +// RM0440 DAC1 interconnection +impl_dac_triggers! { + HRTIM_TIMA: 9, + HRTIM_TIMB: 10, + HRTIM_TIMC: 11, + HRTIM_TIMD: 12, + HRTIM_TIME: 13, + HRTIM_TIMF: 14 +} diff --git a/src/hrtim/mod.rs b/src/hrtim/mod.rs index e2ea79a0..ad88cde7 100644 --- a/src/hrtim/mod.rs +++ b/src/hrtim/mod.rs @@ -1,5 +1,6 @@ pub mod adc_trigger; pub mod capture; +pub mod dac_trigger; pub mod external_event; pub mod fault; @@ -13,7 +14,7 @@ use crate::{ use stm32_hrtim::{ control::{HrPwmControl, HrTimOngoingCalibration}, output::{HrOut1, HrOut2, ToHrOut}, - HrParts, HrPwmBuilder, + DacResetTrigger, DacStepTrigger, HrParts, HrPwmBuilder, }; pub use stm32_hrtim; @@ -33,18 +34,31 @@ impl HrControltExt for crate::stm32::HRTIM_COMMON { } } -pub trait HrPwmBuilderExt> { - fn finalize(self, control: &mut HrPwmControl) -> HrParts>; +pub trait HrPwmBuilderExt, DacRst, DacStp> +where + DacRst: DacResetTrigger, + DacStp: DacStepTrigger, +{ + fn finalize( + self, + control: &mut HrPwmControl, + ) -> HrParts, DacRst, DacStp>; } + macro_rules! impl_finalize { ($($TIMX:ident),+) => {$( - impl> HrPwmBuilderExt<$TIMX, PSCL, PINS> - for HrPwmBuilder<$TIMX, PSCL, stm32_hrtim::PreloadSource, PINS> + impl HrPwmBuilderExt<$TIMX, PSCL, PINS, DacRst, DacStp> + for HrPwmBuilder<$TIMX, PSCL, stm32_hrtim::PreloadSource, PINS, DacRst, DacStp> + where + PSCL: stm32_hrtim::HrtimPrescaler, + PINS: HrtimPin<$TIMX>, + DacRst: DacResetTrigger, + DacStp: DacStepTrigger { fn finalize( self, control: &mut HrPwmControl, - ) -> HrParts<$TIMX, PSCL, >::Out> { + ) -> HrParts<$TIMX, PSCL, >::Out, DacRst, DacStp> { let pins = self._init(control); pins.connect_to_hrtim(); unsafe { MaybeUninit::uninit().assume_init() } @@ -93,8 +107,12 @@ macro_rules! pins_helper { ($TIMX:ty, $HrOutY:ident, $CHY:ident<$CHY_AF:literal>) => { //impl sealed::Sealed<$TIMX> for $CHY {} - unsafe impl ToHrOut<$TIMX> for $CHY { - type Out = $HrOutY<$TIMX, PSCL>; + unsafe impl ToHrOut<$TIMX, DacRst, DacStp> for $CHY + where + DacRst: DacResetTrigger, + DacStp: DacStepTrigger, + { + type Out = $HrOutY<$TIMX, PSCL, DacRst, DacStp>; } impl HrtimPin<$TIMX> for $CHY { diff --git a/tests/nucleo-g474_w_jumpers-hrtim.rs b/tests/nucleo-g474_w_jumpers-hrtim.rs new file mode 100644 index 00000000..79cbcf48 --- /dev/null +++ b/tests/nucleo-g474_w_jumpers-hrtim.rs @@ -0,0 +1,321 @@ +#![no_std] +#![no_main] + +// Requires a jumper from A1<->A2 (arduino naming) aka PA1<->PA4 + +#[path = "../examples/utils/mod.rs"] +mod utils; + +use core::ops::Sub; + +use fugit::{ExtU32, HertzU32, MicrosDurationU32}; +use stm32_hrtim::compare_register::HrCompareRegister; +use stm32_hrtim::control::HrPwmControl; +use stm32_hrtim::external_event::{self, ExternalEventSource, ToExternalEventSource}; +use stm32_hrtim::output::HrOut1; +use stm32_hrtim::pac::HRTIM_TIMA; +use stm32_hrtim::timer_eev_cfg::{EevCfg, EevCfgs, EventFilter}; +use stm32_hrtim::{ + DacResetOnCounterReset, DacStepOnCmp2, HrParts, HrPwmAdvExt, HrtimPrescaler, Pscl128, +}; +use stm32g4xx_hal::adc::{self, AdcClaim, AdcCommonExt}; +use stm32g4xx_hal::comparator::{self, Comparator, ComparatorExt, ComparatorSplit}; +use stm32g4xx_hal::dac::{self, DacExt, DacOut, SawtoothConfig}; +use stm32g4xx_hal::delay::{self, SYSTDelayExt}; +use stm32g4xx_hal::gpio::{self, GpioExt}; +use stm32g4xx_hal::hrtim::external_event::EevInputExt; +use stm32g4xx_hal::hrtim::{HrControltExt, HrPwmBuilderExt}; +use stm32g4xx_hal::pwr::PwrExt; +use stm32g4xx_hal::rcc::{self, RccExt}; + +use hal::stm32; +use stm32g4xx_hal as hal; +use stm32g4xx_hal::stasis::{Freeze, Frozen}; + +pub const F_SYS: HertzU32 = HertzU32::MHz(120); +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() +} + +#[embedded_test::tests] +mod tests { + use fugit::ExtU32; + use stm32_hrtim::{ + compare_register::HrCompareRegister, output::HrOutput, timer::HrTimer, HrtimPrescaler, + Pscl128, + }; + use stm32g4xx_hal::{dac::DacOut, stm32::GPIOA}; + + use crate::{ + abs_diff, now, + utils::test::{await_hi, await_lo}, + F_SYS, + }; + + /// | \ + /// | \ + /// value_dac ---> | + \ + /// | *-----> + /// ref_dac ---> | - / + /// | / + /// | / + /// + #[test] + fn hrtim_slope_compensated() { + type Prescaler = Pscl128; + type Us = fugit::Duration; + + let prescaler = Pscl128; + let period = 0xFFFF; + let dac_step_per_trigger = 550; + let cr2_ticks_per_dac_trigger = 1024; + + let crate::Peripherals { + mut timer, + mut hr_control, + eev_input4, + comp, + mut value_dac, + mut ref_dac, + adc, + pa2, + rcc, + delay, + } = super::setup( + prescaler, + period, + dac_step_per_trigger, + cr2_ticks_per_dac_trigger, + ); + + timer.cr1.set_duty(period / 10); // Set blanking window to give the ref_dac time to reset + //out.enable_rst_event(&cr1); // Set low on compare match with cr1 + timer.out.enable_rst_event(&eev_input4); // Set low on compare match with cr1 + timer.out.enable_set_event(&timer.timer); // Set high at new period + + timer.out.enable(); + timer.timer.start(&mut hr_control.control); + + defmt::println!("state: {}", timer.out.get_state()); + + //out.enable_rst_event(&eev_input4); + + let gpioa = unsafe { &*GPIOA::PTR }; + let f_pwm = (F_SYS * 32) / (Prescaler::VALUE as u32 * period as u32); + let period_duration: Us = f_pwm.into_duration(); + defmt::println!("period_duration: {}", period_duration); + let timeout: Us = 2 * period_duration; + + ref_dac.set_value(4095); + value_dac.set_value(2048); + + loop { + let out = comp.output(); + let ref_dac = ref_dac.get_value(); + let value_dac = value_dac.get_value(); + let cnt = timer.timer.get_counter_value(); + defmt::println!( + "out: {}, ref: {}, val: {}, cnt: {}", + out, + ref_dac, + value_dac, + cnt + ); + } + + // TODO: My device seems to have quite a bit of offset error on ref_dac DAC3CH1 + for s_ref in 4..=255 { + let s_ref = s_ref << 4; // Convert from 8 to 12 bits + if s_ref & 0xFF == 0 { + defmt::println!("{}/{}...", s_ref, 4095); + } + + ref_dac.set_value(s_ref); + for s_value in 0..=255 { + let s_value = s_value << 4; // Convert from 8 to 12 bits + + let value_dac_as_u16: u16 = s_value << 4; // Convert from 12 to 16 bits + let expected_duty = value_dac_as_u16.saturating_sub(s_ref); + let expected_pw = (timeout * u32::from(expected_duty)) / u32::from(period); + + defmt::debug!("Awaiting first rising edge..."); + value_dac.set_value(s_value); + loop { + let out = comp.output(); + defmt::println!("out: {}", out); + } + let duration_until_lo = await_lo(gpioa, 8, timeout, now).unwrap(); + let first_lo_duration = await_hi(gpioa, 8, timeout, now); + + let (period, duty) = match first_lo_duration { + Ok(lo_duration) => { + let duty = await_lo(gpioa, 8, timeout, now).unwrap(); + let period = lo_duration + duty; + (period, duty) + } + Err(_) => (timeout, 0u32.micros()), + }; + + let duty_tolerance: Us = 20.micros(); + let period_tolerance: Us = 20.micros(); + + let out = comp.output(); + assert!( + abs_diff(expected_pw, duty) < duty_tolerance, + "expected_duty: {}, duty: {}", + expected_duty, + duty, + ); + assert!( + abs_diff(period_duration, period) < period_tolerance, + "timeout: {}, period: {}", + period_duration, + period, + ); + } + } + } +} + +fn abs_diff(a: T, b: T) -> T::Output { + a.max(b) - a.min(b) +} + +/// Vrefint = 1.212V typical +/// Vref+ = 3.3V for nucleo +/// 1.212V/3.3V*4095 = 1504 adc value +const VREF_ADC_BITS: u16 = 1504; + +struct Peripherals { + timer: + HrParts, DacResetOnCounterReset, DacStepOnCmp2>, + hr_control: HrPwmControl, + eev_input4: ExternalEventSource<4, false>, + comp: Comparator, + value_dac: dac::Dac1Ch1<{ dac::M_EXT_PIN }, dac::Enabled>, + ref_dac: Frozen, 1>, + adc: adc::Adc, + pa2: gpio::gpioa::PA2, + rcc: rcc::Rcc, + delay: delay::SystDelay, +} + +fn setup( + prescaler: PSCL, + period: u16, + dac_step_per_trigger: u16, + cr2_ticks_per_dac_trigger: u16, +) -> Peripherals { + //op1+ PA1 -> A1 + //DAC1_OUT1 PA4 -> A2 + + // TODO: Is it ok to steal these? + let mut cp = unsafe { stm32::CorePeripherals::steal() }; + let dp = unsafe { stm32::Peripherals::steal() }; + // Set system frequency to 16MHz * 15/1/2 = 120MHz + // This would lead to HrTim running at 120MHz * 32 = 3.84... + defmt::info!("rcc"); + let pwr = dp.PWR.constrain().freeze(); + let mut rcc = dp.RCC.freeze( + rcc::Config::pll().pll_cfg(rcc::PllConfig { + mux: rcc::PllSrc::HSI, + n: rcc::PllNMul::MUL_15, + m: rcc::PllMDiv::DIV_1, + r: Some(rcc::PllRDiv::DIV_2), + + ..Default::default() + }), + pwr, + ); + assert_eq!(rcc.clocks.sys_clk, F_SYS); + enable_timer(&mut cp); + let mut delay = cp.SYST.delay(&rcc.clocks); + + let adc12_common = dp + .ADC12_COMMON + .claim(adc::config::ClockMode::AdcHclkDiv4, &mut rcc); + let adc = adc12_common.claim(dp.ADC1, &mut delay); + + let gpioa = dp.GPIOA.split(&mut rcc); + let pa1 = gpioa.pa1.into_analog(); + let pa2 = gpioa.pa2.into_analog(); + let pa4 = gpioa.pa4.into_analog(); + let pa8 = gpioa.pa8; + let pa9 = gpioa.pa9; + + let dac1ch1 = dp.DAC1.constrain(pa4, &mut rcc); + let dac3ch1 = dp.DAC3.constrain(dac::Dac3IntSig1, &mut rcc); + + let mut value_dac = dac1ch1.calibrate_buffer(&mut delay).enable(&mut rcc); + let ref_dac = dac3ch1; + value_dac.set_value(0); + + let (comp, ..) = dp.COMP.split(&mut rcc); + + type Comparator = comparator::Comparator; + type Prescaler = Pscl128; + + let (mut hr_control, _, eev_inputs) = + dp.HRTIM_COMMON.hr_control(&mut rcc).wait_for_calibration(); + + let eev_input4 = eev_inputs + .eev_input4 + .bind(&unsafe { + // Safety + // We will set up the real comparator before eev_input4 is + // connected to the timer output + core::mem::transmute::<(), Comparator>(()) + }) + .edge_or_polarity(external_event::EdgeOrPolarity::Polarity( + stm32_hrtim::Polarity::ActiveHigh, + )) + .finalize(&mut hr_control); + + let mut hr_control = hr_control.constrain(); + let eev_cfgs = + EevCfgs::default().eev4(EevCfg::default().filter(EventFilter::BlankingResetToCmp1)); + let mut timer = dp + .HRTIM_TIMA + .pwm_advanced(pa8) + .prescaler(prescaler) + .period(period) + .dac_trigger_cfg(DacResetOnCounterReset, DacStepOnCmp2) + .eev_cfg(eev_cfgs) + .finalize(&mut hr_control); + + timer.cr2.set_duty(cr2_ticks_per_dac_trigger); + let ref_dac = ref_dac.enable_sawtooth_generator( + SawtoothConfig::with_slope( + stm32g4xx_hal::dac::CountingDirection::Decrement, + dac_step_per_trigger, + ) + .inc_trigger(&timer.cr2) + .reset_trigger(&timer.timer), + &mut rcc, + ); + + let (ref_dac, [comp_ref]) = ref_dac.freeze(); + let comp: Comparator = comp + .comparator(pa1, comp_ref, comparator::Config::default(), &rcc.clocks) + .enable(); + + Peripherals { + timer, + hr_control, + eev_input4, + comp, + value_dac, + ref_dac, + adc, + pa2, + rcc, + delay, + } +} From b6567238b14e8e33b70805f3c0a7e0489639e265 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Tue, 27 May 2025 00:00:17 +0200 Subject: [PATCH 2/7] Fix dac trigger types --- src/hrtim/mod.rs | 7 ++++--- tests/nucleo-g474_w_jumpers-hrtim.rs | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/hrtim/mod.rs b/src/hrtim/mod.rs index ad88cde7..9051034e 100644 --- a/src/hrtim/mod.rs +++ b/src/hrtim/mod.rs @@ -34,8 +34,9 @@ impl HrControltExt for crate::stm32::HRTIM_COMMON { } } -pub trait HrPwmBuilderExt, DacRst, DacStp> +pub trait HrPwmBuilderExt where + PINS: ToHrOut, DacRst: DacResetTrigger, DacStp: DacStepTrigger, { @@ -51,14 +52,14 @@ macro_rules! impl_finalize { for HrPwmBuilder<$TIMX, PSCL, stm32_hrtim::PreloadSource, PINS, DacRst, DacStp> where PSCL: stm32_hrtim::HrtimPrescaler, - PINS: HrtimPin<$TIMX>, + PINS: HrtimPin<$TIMX> + ToHrOut<$TIMX, DacRst, DacStp>, DacRst: DacResetTrigger, DacStp: DacStepTrigger { fn finalize( self, control: &mut HrPwmControl, - ) -> HrParts<$TIMX, PSCL, >::Out, DacRst, DacStp> { + ) -> HrParts<$TIMX, PSCL, >::Out, DacRst, DacStp> { let pins = self._init(control); pins.connect_to_hrtim(); unsafe { MaybeUninit::uninit().assume_init() } diff --git a/tests/nucleo-g474_w_jumpers-hrtim.rs b/tests/nucleo-g474_w_jumpers-hrtim.rs index 79cbcf48..edbbaca8 100644 --- a/tests/nucleo-g474_w_jumpers-hrtim.rs +++ b/tests/nucleo-g474_w_jumpers-hrtim.rs @@ -195,7 +195,7 @@ const VREF_ADC_BITS: u16 = 1504; struct Peripherals { timer: - HrParts, DacResetOnCounterReset, DacStepOnCmp2>, + HrParts, DacResetOnCounterReset, DacStepOnCmp2>, hr_control: HrPwmControl, eev_input4: ExternalEventSource<4, false>, comp: Comparator, @@ -281,7 +281,7 @@ fn setup( let mut hr_control = hr_control.constrain(); let eev_cfgs = EevCfgs::default().eev4(EevCfg::default().filter(EventFilter::BlankingResetToCmp1)); - let mut timer = dp + let mut timer: HrParts<_, PSCL, HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, DacResetOnCounterReset, DacStepOnCmp2> = dp .HRTIM_TIMA .pwm_advanced(pa8) .prescaler(prescaler) @@ -306,6 +306,7 @@ fn setup( .comparator(pa1, comp_ref, comparator::Config::default(), &rcc.clocks) .enable(); + let timer: HrParts<_, _, HrOut1<_, _, DacResetOnCounterReset, DacStepOnCmp2>, _, _> = timer; Peripherals { timer, hr_control, From 4b78443158599c6812691a7a08504bbfe05f0660 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 22 Jun 2025 16:08:26 +0200 Subject: [PATCH 3/7] Cleanup after rabase --- examples/utils/mod.rs | 1 - examples/utils/test.rs | 49 -------------------------- tests/nucleo-g474_w_jumpers-hrtim.rs | 51 +++++++++++++++------------- 3 files changed, 28 insertions(+), 73 deletions(-) delete mode 100644 examples/utils/test.rs diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 70b951cb..35e91cb9 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -1,4 +1,3 @@ //! Utilities for examples pub mod logger; -pub mod test; diff --git a/examples/utils/test.rs b/examples/utils/test.rs deleted file mode 100644 index 6c2b32ea..00000000 --- a/examples/utils/test.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![allow(dead_code)] - -use fugit::MicrosDurationU32; - -use stm32g4xx_hal::stm32; - -pub fn is_pax_low(gpioa: &stm32::gpioa::RegisterBlock, x: u8) -> bool { - gpioa.idr().read().idr(x).is_low() -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug)] -pub struct ErrorTimedOut; - -pub fn await_lo( - gpioa: &stm32::gpioa::RegisterBlock, - pin: u8, - timeout: MicrosDurationU32, - now: impl FnMut() -> MicrosDurationU32, -) -> Result { - await_p(|| is_pax_low(gpioa, pin), timeout, now) -} - -pub fn await_hi( - gpioa: &stm32::gpioa::RegisterBlock, - pin: u8, - timeout: MicrosDurationU32, - now: impl FnMut() -> MicrosDurationU32, -) -> Result { - await_p(|| !is_pax_low(gpioa, pin), timeout, now) -} - -pub fn await_p( - mut p: impl FnMut() -> bool, - timeout: MicrosDurationU32, - mut now: impl FnMut() -> 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-hrtim.rs b/tests/nucleo-g474_w_jumpers-hrtim.rs index edbbaca8..ebcaa0f0 100644 --- a/tests/nucleo-g474_w_jumpers-hrtim.rs +++ b/tests/nucleo-g474_w_jumpers-hrtim.rs @@ -6,6 +6,8 @@ #[path = "../examples/utils/mod.rs"] mod utils; +mod common; + use core::ops::Sub; use fugit::{ExtU32, HertzU32, MicrosDurationU32}; @@ -34,6 +36,7 @@ use stm32g4xx_hal::stasis::{Freeze, Frozen}; pub const F_SYS: HertzU32 = HertzU32::MHz(120); pub const CYCLES_PER_US: u32 = F_SYS.raw() / 1_000_000; +type Timer = crate::common::Timer; pub fn enable_timer(cp: &mut stm32::CorePeripherals) { cp.DCB.enable_trace(); @@ -54,9 +57,7 @@ mod tests { use stm32g4xx_hal::{dac::DacOut, stm32::GPIOA}; use crate::{ - abs_diff, now, - utils::test::{await_hi, await_lo}, - F_SYS, + abs_diff, common::{await_hi, await_lo}, now, F_SYS }; /// | \ @@ -78,7 +79,7 @@ mod tests { let cr2_ticks_per_dac_trigger = 1024; let crate::Peripherals { - mut timer, + mut hrtimer, mut hr_control, eev_input4, comp, @@ -88,22 +89,24 @@ mod tests { pa2, rcc, delay, + timer } = super::setup( prescaler, period, dac_step_per_trigger, cr2_ticks_per_dac_trigger, ); + let pin_num = 8; - timer.cr1.set_duty(period / 10); // Set blanking window to give the ref_dac time to reset + hrtimer.cr1.set_duty(period / 10); // Set blanking window to give the ref_dac time to reset //out.enable_rst_event(&cr1); // Set low on compare match with cr1 - timer.out.enable_rst_event(&eev_input4); // Set low on compare match with cr1 - timer.out.enable_set_event(&timer.timer); // Set high at new period + hrtimer.out.enable_rst_event(&eev_input4); // Set low on compare match with cr1 + hrtimer.out.enable_set_event(&hrtimer.timer); // Set high at new period - timer.out.enable(); - timer.timer.start(&mut hr_control.control); + hrtimer.out.enable(); + hrtimer.timer.start(&mut hr_control.control); - defmt::println!("state: {}", timer.out.get_state()); + defmt::println!("state: {}", hrtimer.out.get_state()); //out.enable_rst_event(&eev_input4); @@ -120,7 +123,7 @@ mod tests { let out = comp.output(); let ref_dac = ref_dac.get_value(); let value_dac = value_dac.get_value(); - let cnt = timer.timer.get_counter_value(); + let cnt = hrtimer.timer.get_counter_value(); defmt::println!( "out: {}, ref: {}, val: {}, cnt: {}", out, @@ -151,12 +154,12 @@ mod tests { let out = comp.output(); defmt::println!("out: {}", out); } - let duration_until_lo = await_lo(gpioa, 8, timeout, now).unwrap(); - let first_lo_duration = await_hi(gpioa, 8, timeout, now); + let duration_until_lo = await_lo(&timer, pin_num, timeout).unwrap(); + let first_lo_duration = await_hi(&timer, pin_num, timeout); let (period, duty) = match first_lo_duration { Ok(lo_duration) => { - let duty = await_lo(gpioa, 8, timeout, now).unwrap(); + let duty = await_lo(&timer, pin_num, timeout).unwrap(); let period = lo_duration + duty; (period, duty) } @@ -194,7 +197,7 @@ fn abs_diff(a: T, b: T) -> T::Output { const VREF_ADC_BITS: u16 = 1504; struct Peripherals { - timer: + hrtimer: HrParts, DacResetOnCounterReset, DacStepOnCmp2>, hr_control: HrPwmControl, eev_input4: ExternalEventSource<4, false>, @@ -205,6 +208,7 @@ struct Peripherals { pa2: gpio::gpioa::PA2, rcc: rcc::Rcc, delay: delay::SystDelay, + timer: Timer, } fn setup( @@ -217,8 +221,9 @@ fn setup( //DAC1_OUT1 PA4 -> A2 // TODO: Is it ok to steal these? - let mut cp = unsafe { stm32::CorePeripherals::steal() }; - let dp = unsafe { stm32::Peripherals::steal() }; + let mut cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); + let timer = Timer::enable_timer(&mut cp); // Set system frequency to 16MHz * 15/1/2 = 120MHz // This would lead to HrTim running at 120MHz * 32 = 3.84... defmt::info!("rcc"); @@ -281,7 +286,7 @@ fn setup( let mut hr_control = hr_control.constrain(); let eev_cfgs = EevCfgs::default().eev4(EevCfg::default().filter(EventFilter::BlankingResetToCmp1)); - let mut timer: HrParts<_, PSCL, HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, DacResetOnCounterReset, DacStepOnCmp2> = dp + let mut hrtimer: HrParts<_, PSCL, HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, DacResetOnCounterReset, DacStepOnCmp2> = dp .HRTIM_TIMA .pwm_advanced(pa8) .prescaler(prescaler) @@ -290,14 +295,14 @@ fn setup( .eev_cfg(eev_cfgs) .finalize(&mut hr_control); - timer.cr2.set_duty(cr2_ticks_per_dac_trigger); + hrtimer.cr2.set_duty(cr2_ticks_per_dac_trigger); let ref_dac = ref_dac.enable_sawtooth_generator( SawtoothConfig::with_slope( stm32g4xx_hal::dac::CountingDirection::Decrement, dac_step_per_trigger, ) - .inc_trigger(&timer.cr2) - .reset_trigger(&timer.timer), + .inc_trigger(&hrtimer.cr2) + .reset_trigger(&hrtimer.timer), &mut rcc, ); @@ -306,9 +311,8 @@ fn setup( .comparator(pa1, comp_ref, comparator::Config::default(), &rcc.clocks) .enable(); - let timer: HrParts<_, _, HrOut1<_, _, DacResetOnCounterReset, DacStepOnCmp2>, _, _> = timer; Peripherals { - timer, + hrtimer, hr_control, eev_input4, comp, @@ -318,5 +322,6 @@ fn setup( pa2, rcc, delay, + timer, } } From aeabc75b4c4c01ab02a38676837e22a336d12c52 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 22 Jun 2025 18:49:06 +0200 Subject: [PATCH 4/7] Fix test --- tests/nucleo-g474_w_jumpers-hrtim.rs | 271 +++++++++++++-------------- 1 file changed, 131 insertions(+), 140 deletions(-) diff --git a/tests/nucleo-g474_w_jumpers-hrtim.rs b/tests/nucleo-g474_w_jumpers-hrtim.rs index ebcaa0f0..b15cda71 100644 --- a/tests/nucleo-g474_w_jumpers-hrtim.rs +++ b/tests/nucleo-g474_w_jumpers-hrtim.rs @@ -8,23 +8,18 @@ mod utils; mod common; -use core::ops::Sub; - -use fugit::{ExtU32, HertzU32, MicrosDurationU32}; +use fugit::HertzU32; use stm32_hrtim::compare_register::HrCompareRegister; use stm32_hrtim::control::HrPwmControl; use stm32_hrtim::external_event::{self, ExternalEventSource, ToExternalEventSource}; use stm32_hrtim::output::HrOut1; use stm32_hrtim::pac::HRTIM_TIMA; use stm32_hrtim::timer_eev_cfg::{EevCfg, EevCfgs, EventFilter}; -use stm32_hrtim::{ - DacResetOnCounterReset, DacStepOnCmp2, HrParts, HrPwmAdvExt, HrtimPrescaler, Pscl128, -}; -use stm32g4xx_hal::adc::{self, AdcClaim, AdcCommonExt}; +use stm32_hrtim::{DacResetOnCounterReset, DacStepOnCmp2, HrParts, HrPwmAdvExt, HrtimPrescaler}; use stm32g4xx_hal::comparator::{self, Comparator, ComparatorExt, ComparatorSplit}; use stm32g4xx_hal::dac::{self, DacExt, DacOut, SawtoothConfig}; use stm32g4xx_hal::delay::{self, SYSTDelayExt}; -use stm32g4xx_hal::gpio::{self, GpioExt}; +use stm32g4xx_hal::gpio::GpioExt; use stm32g4xx_hal::hrtim::external_event::EevInputExt; use stm32g4xx_hal::hrtim::{HrControltExt, HrPwmBuilderExt}; use stm32g4xx_hal::pwr::PwrExt; @@ -36,29 +31,15 @@ use stm32g4xx_hal::stasis::{Freeze, Frozen}; pub const F_SYS: HertzU32 = HertzU32::MHz(120); pub const CYCLES_PER_US: u32 = F_SYS.raw() / 1_000_000; -type Timer = crate::common::Timer; - -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() -} +//type Timer = crate::common::Timer; #[embedded_test::tests] mod tests { - use fugit::ExtU32; + use embedded_hal::delay::DelayNs; use stm32_hrtim::{ - compare_register::HrCompareRegister, output::HrOutput, timer::HrTimer, HrtimPrescaler, - Pscl128, - }; - use stm32g4xx_hal::{dac::DacOut, stm32::GPIOA}; - - use crate::{ - abs_diff, common::{await_hi, await_lo}, now, F_SYS + compare_register::HrCompareRegister, output::HrOutput, timer::HrTimer, Pscl128, }; + use stm32g4xx_hal::dac::DacOut; /// | \ /// | \ @@ -70,13 +51,10 @@ mod tests { /// #[test] fn hrtim_slope_compensated() { - type Prescaler = Pscl128; - type Us = fugit::Duration; - let prescaler = Pscl128; let period = 0xFFFF; - let dac_step_per_trigger = 550; - let cr2_ticks_per_dac_trigger = 1024; + let dac_step_per_trigger = 16; + let cr2_ticks_per_dac_trigger = dac_step_per_trigger; let crate::Peripherals { mut hrtimer, @@ -85,130 +63,84 @@ mod tests { comp, mut value_dac, mut ref_dac, - adc, - pa2, - rcc, - delay, - timer + mut delay, + .. } = super::setup( prescaler, period, dac_step_per_trigger, cr2_ticks_per_dac_trigger, ); - let pin_num = 8; hrtimer.cr1.set_duty(period / 10); // Set blanking window to give the ref_dac time to reset - //out.enable_rst_event(&cr1); // Set low on compare match with cr1 + //out.enable_rst_event(&cr1); // Set low on compare match with cr1 hrtimer.out.enable_rst_event(&eev_input4); // Set low on compare match with cr1 hrtimer.out.enable_set_event(&hrtimer.timer); // Set high at new period - hrtimer.out.enable(); - hrtimer.timer.start(&mut hr_control.control); - - defmt::println!("state: {}", hrtimer.out.get_state()); - - //out.enable_rst_event(&eev_input4); - - let gpioa = unsafe { &*GPIOA::PTR }; - let f_pwm = (F_SYS * 32) / (Prescaler::VALUE as u32 * period as u32); - let period_duration: Us = f_pwm.into_duration(); - defmt::println!("period_duration: {}", period_duration); - let timeout: Us = 2 * period_duration; - ref_dac.set_value(4095); value_dac.set_value(2048); - loop { - let out = comp.output(); - let ref_dac = ref_dac.get_value(); - let value_dac = value_dac.get_value(); - let cnt = hrtimer.timer.get_counter_value(); - defmt::println!( - "out: {}, ref: {}, val: {}, cnt: {}", - out, - ref_dac, - value_dac, - cnt - ); - } - - // TODO: My device seems to have quite a bit of offset error on ref_dac DAC3CH1 - for s_ref in 4..=255 { - let s_ref = s_ref << 4; // Convert from 8 to 12 bits - if s_ref & 0xFF == 0 { - defmt::println!("{}/{}...", s_ref, 4095); - } - - ref_dac.set_value(s_ref); - for s_value in 0..=255 { - let s_value = s_value << 4; // Convert from 8 to 12 bits - - let value_dac_as_u16: u16 = s_value << 4; // Convert from 12 to 16 bits - let expected_duty = value_dac_as_u16.saturating_sub(s_ref); - let expected_pw = (timeout * u32::from(expected_duty)) / u32::from(period); - - defmt::debug!("Awaiting first rising edge..."); - value_dac.set_value(s_value); - loop { - let out = comp.output(); - defmt::println!("out: {}", out); - } - let duration_until_lo = await_lo(&timer, pin_num, timeout).unwrap(); - let first_lo_duration = await_hi(&timer, pin_num, timeout); + hrtimer.timer.start(&mut hr_control.control); - let (period, duty) = match first_lo_duration { - Ok(lo_duration) => { - let duty = await_lo(&timer, pin_num, timeout).unwrap(); - let period = lo_duration + duty; - (period, duty) - } - Err(_) => (timeout, 0u32.micros()), - }; + // Await first dac rst trigger + while ref_dac.get_value() == 0 {} - let duty_tolerance: Us = 20.micros(); - let period_tolerance: Us = 20.micros(); + hrtimer.out.enable(); + for i in 0..10 { + let mut last_cnt = 0; + while hrtimer.timer.get_counter_value() < dac_step_per_trigger {} + loop { let out = comp.output(); - assert!( - abs_diff(expected_pw, duty) < duty_tolerance, - "expected_duty: {}, duty: {}", - expected_duty, - duty, - ); - assert!( - abs_diff(period_duration, period) < period_tolerance, - "timeout: {}, period: {}", - period_duration, - period, - ); + let ref_dac = ref_dac.get_value(); + let inv_ref_dac = 4095 - ref_dac; + let value_dac = value_dac.get_value(); + let cnt = hrtimer.timer.get_counter_value(); + if cnt < last_cnt { + // New period + break; + } + last_cnt = cnt; + let count_as_12bit = cnt >> 4; + let range = count_as_12bit.saturating_sub(dac_step_per_trigger) + ..=count_as_12bit.saturating_add(dac_step_per_trigger); + if !range.contains(&inv_ref_dac) { + defmt::error!( + "-: {}, +: {}, out: {}, count_as_12bit: {}, i: {}, inv_ref_dac: {}", + ref_dac, + value_dac, + out as u8, + count_as_12bit, + i, + inv_ref_dac + ); + delay.delay_ms(20); + + panic!(); + } } } } } -fn abs_diff(a: T, b: T) -> T::Output { - a.max(b) - a.min(b) -} - -/// Vrefint = 1.212V typical -/// Vref+ = 3.3V for nucleo -/// 1.212V/3.3V*4095 = 1504 adc value -const VREF_ADC_BITS: u16 = 1504; - struct Peripherals { - hrtimer: - HrParts, DacResetOnCounterReset, DacStepOnCmp2>, + hrtimer: HrParts< + HRTIM_TIMA, + PSCL, + HrOut1, + DacResetOnCounterReset, + DacStepOnCmp2, + >, hr_control: HrPwmControl, eev_input4: ExternalEventSource<4, false>, comp: Comparator, value_dac: dac::Dac1Ch1<{ dac::M_EXT_PIN }, dac::Enabled>, ref_dac: Frozen, 1>, - adc: adc::Adc, - pa2: gpio::gpioa::PA2, - rcc: rcc::Rcc, + //adc: adc::Adc, + //pa2: gpio::gpioa::PA2, + //rcc: rcc::Rcc, delay: delay::SystDelay, - timer: Timer, + //timer: Timer, } fn setup( @@ -221,9 +153,9 @@ fn setup( //DAC1_OUT1 PA4 -> A2 // TODO: Is it ok to steal these? - let mut cp = stm32::CorePeripherals::take().unwrap(); + let cp = stm32::CorePeripherals::take().unwrap(); let dp = stm32::Peripherals::take().unwrap(); - let timer = Timer::enable_timer(&mut cp); + //let timer = Timer::enable_timer(&mut cp); // Set system frequency to 16MHz * 15/1/2 = 120MHz // This would lead to HrTim running at 120MHz * 32 = 3.84... defmt::info!("rcc"); @@ -240,20 +172,19 @@ fn setup( pwr, ); assert_eq!(rcc.clocks.sys_clk, F_SYS); - enable_timer(&mut cp); let mut delay = cp.SYST.delay(&rcc.clocks); - let adc12_common = dp - .ADC12_COMMON - .claim(adc::config::ClockMode::AdcHclkDiv4, &mut rcc); - let adc = adc12_common.claim(dp.ADC1, &mut delay); + //let adc12_common = dp + // .ADC12_COMMON + // .claim(adc::config::ClockMode::AdcHclkDiv4, &mut rcc); + //let adc = adc12_common.claim(dp.ADC1, &mut delay); let gpioa = dp.GPIOA.split(&mut rcc); let pa1 = gpioa.pa1.into_analog(); - let pa2 = gpioa.pa2.into_analog(); + //let pa2 = gpioa.pa2.into_analog(); let pa4 = gpioa.pa4.into_analog(); let pa8 = gpioa.pa8; - let pa9 = gpioa.pa9; + //let pa9 = gpioa.pa9; let dac1ch1 = dp.DAC1.constrain(pa4, &mut rcc); let dac3ch1 = dp.DAC3.constrain(dac::Dac3IntSig1, &mut rcc); @@ -265,7 +196,6 @@ fn setup( let (comp, ..) = dp.COMP.split(&mut rcc); type Comparator = comparator::Comparator; - type Prescaler = Pscl128; let (mut hr_control, _, eev_inputs) = dp.HRTIM_COMMON.hr_control(&mut rcc).wait_for_calibration(); @@ -286,7 +216,13 @@ fn setup( let mut hr_control = hr_control.constrain(); let eev_cfgs = EevCfgs::default().eev4(EevCfg::default().filter(EventFilter::BlankingResetToCmp1)); - let mut hrtimer: HrParts<_, PSCL, HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, DacResetOnCounterReset, DacStepOnCmp2> = dp + let mut hrtimer: HrParts< + _, + PSCL, + HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, + DacResetOnCounterReset, + DacStepOnCmp2, + > = dp .HRTIM_TIMA .pwm_advanced(pa8) .prescaler(prescaler) @@ -318,10 +254,65 @@ fn setup( comp, value_dac, ref_dac, - adc, - pa2, - rcc, + //adc, + //pa2, + //rcc, delay, - timer, + //timer, + } +} +/* +fn foo() { + // TODO: My device seems to have quite a bit of offset error on ref_dac DAC3CH1 + for s_ref in 4..=255 { + let s_ref = s_ref << 4; // Convert from 8 to 12 bits + if s_ref & 0xFF == 0 { + defmt::println!("{}/{}...", s_ref, 4095); + } + + ref_dac.set_value(s_ref); + for s_value in 0..=255 { + let s_value = s_value << 4; // Convert from 8 to 12 bits + + let value_dac_as_u16: u16 = s_value << 4; // Convert from 12 to 16 bits + let expected_duty = value_dac_as_u16.saturating_sub(s_ref); + let expected_pw = (timeout * u32::from(expected_duty)) / u32::from(period); + + defmt::debug!("Awaiting first rising edge..."); + value_dac.set_value(s_value); + loop { + let out = comp.output(); + defmt::println!("out: {}", out); + } + let duration_until_lo = await_lo(&timer, pin_num, timeout).unwrap(); + let first_lo_duration = await_hi(&timer, pin_num, timeout); + + let (period, duty) = match first_lo_duration { + Ok(lo_duration) => { + let duty = await_lo(&timer, pin_num, timeout).unwrap(); + let period = lo_duration + duty; + (period, duty) + } + Err(_) => (timeout, 0u32.micros()), + }; + + let duty_tolerance: Us = 20.micros(); + let period_tolerance: Us = 20.micros(); + + let out = comp.output(); + assert!( + abs_diff(expected_pw, duty) < duty_tolerance, + "expected_duty: {}, duty: {}", + expected_duty, + duty, + ); + assert!( + abs_diff(period_duration, period) < period_tolerance, + "timeout: {}, period: {}", + period_duration, + period, + ); + } } } +*/ From 7b99b774bfa1c5dc51842d8085059fa12bcf4d79 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 22 Jun 2025 21:48:06 +0200 Subject: [PATCH 5/7] Allow turning an enabled ADC into a dynamic one --- src/adc/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/adc/mod.rs b/src/adc/mod.rs index 9ed3a903..24451b21 100644 --- a/src/adc/mod.rs +++ b/src/adc/mod.rs @@ -1337,6 +1337,13 @@ impl Adc { } } + /// Releases the Adc as a DynamicAdc. + /// While this is not unsafe; using methods while the Adc is in the wrong state will mess it up. + #[inline(always)] + pub fn into_dynamic_adc(self) -> DynamicAdc { + self.adc + } + /// Starts conversion sequence. Waits for the hardware to indicate it's actually started. #[inline(always)] pub fn start_conversion(mut self) -> Adc { From ab6a69e19ac700ef5accdca9b961c6b72575d4f3 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 22 Jun 2025 21:48:57 +0200 Subject: [PATCH 6/7] Add hrtim Dac and adc trigger tests --- tests/nucleo-g474_hrtim.rs | 163 +++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 23 deletions(-) diff --git a/tests/nucleo-g474_hrtim.rs b/tests/nucleo-g474_hrtim.rs index 4e889667..e8a0e604 100644 --- a/tests/nucleo-g474_hrtim.rs +++ b/tests/nucleo-g474_hrtim.rs @@ -12,17 +12,21 @@ mod common; use common::test_pwm; use fugit::{ExtU32, HertzU32, MicrosDurationU32}; -use stm32_hrtim::compare_register::HrCompareRegister; -use stm32_hrtim::control::HrPwmControl; -use stm32_hrtim::deadtime::DeadtimeConfig; -use stm32_hrtim::output::HrOutput; -use stm32_hrtim::timer::HrTimer; -use stm32_hrtim::{HrParts, HrPwmAdvExt as _, Pscl64}; -use stm32g4xx_hal::delay::SYSTDelayExt; -use stm32g4xx_hal::gpio::{GpioExt, PinExt}; -use stm32g4xx_hal::hrtim::HrControltExt; -use stm32g4xx_hal::pwr::PwrExt; -use stm32g4xx_hal::rcc::{self, Rcc, RccExt}; +use stm32_hrtim::{ + compare_register::HrCompareRegister, + control::HrPwmControl, + deadtime::DeadtimeConfig, + output::{HrOutput, ToHrOut}, + timer::HrTimer, + DacResetOnCounterReset, DacStepOnCmp2, HrParts, HrPwmAdvExt as _, Pscl64, +}; +use stm32g4xx_hal::{ + delay::SYSTDelayExt, + gpio::{GpioExt, PinExt}, + hrtim::{HrControltExt, HrtimPin}, + pwr::PwrExt, + rcc::{self, Rcc, RccExt}, +}; use hal::stm32; use stm32g4xx_hal as hal; @@ -35,6 +39,10 @@ type Timer = crate::common::Timer; #[embedded_test::tests] mod tests { use embedded_hal::delay::DelayNs; + use stm32g4xx_hal::{ + adc::{self, temperature::Temperature, AdcClaim, AdcCommonExt}, + dac::{self, DacExt, DacOut, SawtoothConfig}, + }; #[test] fn simple() { @@ -63,7 +71,7 @@ mod tests { .. }, mut hr_control, - ) = setup(pin, None, dp.HRTIM_TIMA, dp.HRTIM_COMMON, &mut rcc); + ) = setup_hrtim(pin, None, dp.HRTIM_TIMA, dp.HRTIM_COMMON, &mut rcc); cr1.set_duty(PERIOD / 2); out.enable_rst_event(&cr1); // Set low on compare match with cr1 @@ -99,6 +107,104 @@ mod tests { deadtime_test(1, 68); } + + /// Test adc trigger and DAC trigger + /// + /// Setup hrtim with DAC to generate a Sawtooth waveform. Use a compare register to accurately + /// place the measurement point. + /// + /// | /| /| /| + /// | * / | / | / | + /// | * / | / | / | + /// | X | / | / | + /// | / * | / | / | + /// |/ * |/ |/ | + /// |--->* + /// cr3 indicates where in waveform to trigger ADC + /// + #[test] + fn adc_trigger() { + use super::*; + + let mut cp = stm32::CorePeripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); + + // Set system frequency to 16MHz * 15/1/2 = 120MHz + // This would lead to HrTim running at 120MHz * 32 = 3.84... + let mut rcc = setup_rcc_120MHz(dp.PWR, dp.RCC); + assert_eq!(rcc.clocks.sys_clk, F_SYS); + let mut delay = cp.SYST.delay(&rcc.clocks); + + let gpioa = dp.GPIOA.split(&mut rcc); + let pa1 = gpioa.pa1.into_analog(); + let pa4 = gpioa.pa4; + let pin = gpioa.pa8; + + let (mut hrtimer, mut hr_control) = + setup_hrtim(pin, None, dp.HRTIM_TIMA, dp.HRTIM_COMMON, &mut rcc); + + hrtimer.cr1.set_duty(PERIOD / 2); + hrtimer.out.enable_rst_event(&hrtimer.cr1); // Set low on compare match with cr1 + hrtimer.out.enable_set_event(&hrtimer.timer); // Set high at new period + + let dac_step_per_trigger = 16; + let cr2_ticks_per_dac_trigger = dac_step_per_trigger; + hrtimer.cr2.set_duty(cr2_ticks_per_dac_trigger); + let dac1ch1 = dp.DAC1.constrain(pa4, &mut rcc); + let mut ref_dac = dac1ch1.enable_sawtooth_generator( + SawtoothConfig::with_slope(dac::CountingDirection::Decrement, dac_step_per_trigger) + .inc_trigger(&hrtimer.cr2) + .reset_trigger(&hrtimer.timer), + &mut rcc, + ); + + hr_control.adc_trigger1.enable_source(&hrtimer.cr3); + + defmt::debug!("Setup Adc1"); + let mut adc12_common = dp + .ADC12_COMMON + .claim(adc::config::ClockMode::AdcHclkDiv4, &mut rcc); + let mut adc = adc12_common.claim(dp.ADC1, &mut delay); + + adc.set_external_trigger(( + adc::config::TriggerMode::RisingEdge, + (&hr_control.adc_trigger1).into(), + )); + adc12_common.enable_temperature(); + adc.set_continuous(hal::adc::config::Continuous::Discontinuous); + adc.reset_sequence(); + adc.configure_channel( + &pa1, + adc::config::Sequence::One, + adc::config::SampleTime::Cycles_12_5, + ); + let mut adc = adc.enable().into_dynamic_adc(); + hrtimer.timer.start(&mut hr_control.control); + + ref_dac.set_value(4095); + + defmt::debug!("Awaiting first dac rst trigger"); + while ref_dac.get_value() == 0 {} + + //out.enable(); + + adc.start_conversion(); + + for _ in 0..2 { + let margin = 256; + for sampling_point in (margin..(PERIOD - margin)).step_by(16) { + hrtimer.cr3.set_duty(sampling_point); // Set sampling point + let sampling_point_as_12bit = sampling_point >> 4; + + // Dac generates a sawtooth with negative slope which starts at 4095 every new period + let expected_dac_value = 4095 - sampling_point_as_12bit; + adc.wait_for_conversion_sequence(); + let adc_value = adc.current_sample(); + let diff = adc_value.abs_diff(expected_dac_value); + assert!(diff < 20); + } + } + } } #[allow(non_snake_case)] @@ -120,16 +226,26 @@ fn setup_rcc_120MHz(pwr: stm32::PWR, rcc: stm32::RCC) -> Rcc { ) } -fn setup>( +fn setup_hrtim

( pins: P, deadtime_cfg: Option, hrtim_tima: stm32::HRTIM_TIMA, hrtim_common: stm32::HRTIM_COMMON, rcc: &mut Rcc, ) -> ( - HrParts>, + HrParts< + stm32::HRTIM_TIMA, + Pscl64, +

>::Out, + DacResetOnCounterReset, + DacStepOnCmp2, + >, HrPwmControl, -) { +) +where + P: HrtimPin + + ToHrOut, +{ use stm32g4xx_hal::hrtim::HrPwmBuilderExt; let (hr_control, ..) = hrtim_common.hr_control(rcc).wait_for_calibration(); let mut hr_control = hr_control.constrain(); @@ -138,19 +254,20 @@ fn setup>( // With a period of 60_000 set, this would be 60MHz/60_000 = 1kHz let prescaler = Pscl64; - let mut hr_tim_builder: stm32_hrtim::HrPwmBuilder< - stm32::HRTIM_TIMA, - Pscl64, - stm32_hrtim::PreloadSource, - P, - > = hrtim_tima + let mut hr_tim_builder = hrtim_tima .pwm_advanced(pins) .prescaler(prescaler) .period(PERIOD); + if let Some(dt) = deadtime_cfg { hr_tim_builder = hr_tim_builder.deadtime(dt); } - (hr_tim_builder.finalize(&mut hr_control), hr_control) + ( + hr_tim_builder + .dac_trigger_cfg(DacResetOnCounterReset, DacStepOnCmp2) + .finalize(&mut hr_control), + hr_control, + ) } fn deadtime_test(deadtime_rising_us: u32, deadtime_falling_us: u32) { @@ -189,7 +306,7 @@ fn deadtime_test(deadtime_rising_us: u32, deadtime_falling_us: u32) { .. }, mut hr_control, - ) = setup( + ) = setup_hrtim( (pin, complementary_pin), Some(deadtime_cfg), dp.HRTIM_TIMA, From 71e027a1659066ebe0f48f7a9dc88d3109307b19 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 6 Jul 2025 20:52:30 +0200 Subject: [PATCH 7/7] Pin default is default by default --- src/hrtim/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hrtim/mod.rs b/src/hrtim/mod.rs index 9051034e..d97fddbb 100644 --- a/src/hrtim/mod.rs +++ b/src/hrtim/mod.rs @@ -108,7 +108,7 @@ macro_rules! pins_helper { ($TIMX:ty, $HrOutY:ident, $CHY:ident<$CHY_AF:literal>) => { //impl sealed::Sealed<$TIMX> for $CHY {} - unsafe impl ToHrOut<$TIMX, DacRst, DacStp> for $CHY + unsafe impl ToHrOut<$TIMX, DacRst, DacStp> for $CHY where DacRst: DacResetTrigger, DacStp: DacStepTrigger, @@ -116,7 +116,7 @@ macro_rules! pins_helper { type Out = $HrOutY<$TIMX, PSCL, DacRst, DacStp>; } - impl HrtimPin<$TIMX> for $CHY { + impl HrtimPin<$TIMX> for $CHY { // Pin> fn connect_to_hrtim(self) { let _: $CHY> = self.into_alternate();