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/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 { 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..d97fddbb 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,32 @@ impl HrControltExt for crate::stm32::HRTIM_COMMON { } } -pub trait HrPwmBuilderExt> { - fn finalize(self, control: &mut HrPwmControl) -> HrParts>; +pub trait HrPwmBuilderExt +where + PINS: ToHrOut, + 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> + ToHrOut<$TIMX, DacRst, DacStp>, + 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,11 +108,15 @@ 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 { + impl HrtimPin<$TIMX> for $CHY { // Pin> fn connect_to_hrtim(self) { let _: $CHY> = self.into_alternate(); 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, diff --git a/tests/nucleo-g474_w_jumpers-hrtim.rs b/tests/nucleo-g474_w_jumpers-hrtim.rs new file mode 100644 index 00000000..b15cda71 --- /dev/null +++ b/tests/nucleo-g474_w_jumpers-hrtim.rs @@ -0,0 +1,318 @@ +#![no_std] +#![no_main] + +// Requires a jumper from A1<->A2 (arduino naming) aka PA1<->PA4 + +#[path = "../examples/utils/mod.rs"] +mod utils; + +mod common; + +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}; +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::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; +//type Timer = crate::common::Timer; + +#[embedded_test::tests] +mod tests { + use embedded_hal::delay::DelayNs; + use stm32_hrtim::{ + compare_register::HrCompareRegister, output::HrOutput, timer::HrTimer, Pscl128, + }; + use stm32g4xx_hal::dac::DacOut; + + /// | \ + /// | \ + /// value_dac ---> | + \ + /// | *-----> + /// ref_dac ---> | - / + /// | / + /// | / + /// + #[test] + fn hrtim_slope_compensated() { + let prescaler = Pscl128; + let period = 0xFFFF; + let dac_step_per_trigger = 16; + let cr2_ticks_per_dac_trigger = dac_step_per_trigger; + + let crate::Peripherals { + mut hrtimer, + mut hr_control, + eev_input4, + comp, + mut value_dac, + mut ref_dac, + mut delay, + .. + } = super::setup( + prescaler, + period, + dac_step_per_trigger, + cr2_ticks_per_dac_trigger, + ); + + 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 + 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 + + ref_dac.set_value(4095); + value_dac.set_value(2048); + + hrtimer.timer.start(&mut hr_control.control); + + // Await first dac rst trigger + while ref_dac.get_value() == 0 {} + + 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(); + 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!(); + } + } + } + } +} + +struct Peripherals { + 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, + delay: delay::SystDelay, + //timer: Timer, +} + +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 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"); + 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); + 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; + + 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 hrtimer: HrParts< + _, + PSCL, + HrOut1<_, PSCL, DacResetOnCounterReset, DacStepOnCmp2>, + DacResetOnCounterReset, + DacStepOnCmp2, + > = dp + .HRTIM_TIMA + .pwm_advanced(pa8) + .prescaler(prescaler) + .period(period) + .dac_trigger_cfg(DacResetOnCounterReset, DacStepOnCmp2) + .eev_cfg(eev_cfgs) + .finalize(&mut hr_control); + + 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(&hrtimer.cr2) + .reset_trigger(&hrtimer.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 { + hrtimer, + hr_control, + eev_input4, + comp, + value_dac, + ref_dac, + //adc, + //pa2, + //rcc, + delay, + //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, + ); + } + } +} +*/