From 4475a2d04003c271e601051096c883308bb56197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 11:36:10 +0100 Subject: [PATCH 1/8] timestamping: full u32 range The sampling timer and the timestamping timer have the same period. The sampling interval and the batch size are powers of two. If the timestamping timer wraps at a power of two larger than the batch period, it will wrap in sync with the batch period. Even if it didn't the RPLL would handle that. But it requires that the timer wraps at the u32/i32 boundary (or be shifted left to wrap there). --- src/hardware/configuration.rs | 3 +-- src/hardware/digital_input_stamper.rs | 36 --------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 3f01b27..0b3e2fc 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -218,8 +218,7 @@ pub fn setup( // timer, but use a period that is longer. let mut timer = timers::TimestampTimer::new(timer5); - let period = digital_input_stamper::calculate_timestamp_timer_period(); - timer.set_period_ticks(period); + timer.set_period_ticks(u32::MAX); timer }; diff --git a/src/hardware/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs index 4262800..9cda62b 100644 --- a/src/hardware/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -25,42 +25,6 @@ ///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If ///! timestamping is desired in DI1, a separate timer + capture channel will be necessary. use super::{hal, timers}; -use crate::{ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE}; - -/// Calculate the period of the digital input timestamp timer. -/// -/// # Note -/// The period returned will be 1 less than the required period in timer ticks. The value returned -/// can be immediately programmed into a hardware timer period register. -/// -/// The period is calculated to be some power-of-two multiple of the batch size, such that N batches -/// will occur between each timestamp timer overflow. -/// -/// # Returns -/// A 32-bit value that can be programmed into a hardware timer period register. -pub fn calculate_timestamp_timer_period() -> u32 { - // Calculate how long a single batch requires in timer ticks. - let batch_duration_ticks: u64 = - SAMPLE_BUFFER_SIZE as u64 * ADC_SAMPLE_TICKS as u64; - - // Calculate the largest power-of-two that is less than or equal to - // `batches_per_overflow`. This is completed by eliminating the least significant - // bits of the value until only the msb remains, which is always a power of two. - let batches_per_overflow: u64 = - (1u64 + u32::MAX as u64) / batch_duration_ticks; - let mut j = batches_per_overflow; - while (j & (j - 1)) != 0 { - j = j & (j - 1); - } - - // Once the number of batches per timestamp overflow is calculated, we can figure out the final - // period of the timestamp timer. The period is always 1 larger than the value configured in the - // register. - let period: u64 = batch_duration_ticks * j - 1u64; - assert!(period <= u32::MAX as u64); - - period as u32 -} /// The timestamper for DI0 reference clock inputs. pub struct InputStamper { From 854ed29b1a19f0cca3c9bcce62361ba67e59f8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 12:34:07 +0100 Subject: [PATCH 2/8] timestamp: pass overflows to the top and ignore them there --- src/bin/lockin-external.rs | 8 +++++++- src/hardware/digital_input_stamper.rs | 15 ++++++--------- src/hardware/timers.rs | 16 +++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index f3717bb..1d3f11d 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -117,8 +117,14 @@ const APP: () = { let iir_state = c.resources.iir_state; let lockin = c.resources.lockin; + let t = c + .resources + .timestamper + .latest_timestamp() + .unwrap_or_else(|t| t) // Ignore timer capture overflows. + .map(|t| t as i32); let (pll_phase, pll_frequency) = c.resources.pll.update( - c.resources.timestamper.latest_timestamp().map(|t| t as i32), + t, 22, // frequency settling time (log2 counter cycles), TODO: expose 22, // phase settling time, TODO: expose ); diff --git a/src/hardware/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs index 9cda62b..6bd8629 100644 --- a/src/hardware/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -62,15 +62,12 @@ impl InputStamper { /// Get the latest timestamp that has occurred. /// /// # Note - /// This function must be called sufficiently often. If an over-capture event occurs, this - /// function will panic, as this indicates a timestamp was inadvertently dropped. - /// - /// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at - /// most one timestamp will occur in each data processing cycle. + /// This function must be called at least as often as timestamps arrive. + /// If an over-capture event occurs, this function will clear the overflow, + /// and return a new timestamp of unknown recency an `Err()`. + /// Note that this indicates at least one timestamp was inadvertently dropped. #[allow(dead_code)] - pub fn latest_timestamp(&mut self) -> Option { - self.capture_channel - .latest_capture() - .expect("DI0 timestamp overrun") + pub fn latest_timestamp(&mut self) -> Result, Option> { + self.capture_channel.latest_capture() } } diff --git a/src/hardware/timers.rs b/src/hardware/timers.rs index 7199730..b686220 100644 --- a/src/hardware/timers.rs +++ b/src/hardware/timers.rs @@ -281,28 +281,26 @@ macro_rules! timer_channels { impl [< Channel $index InputCapture >] { /// Get the latest capture from the channel. #[allow(dead_code)] - pub fn latest_capture(&mut self) -> Result, ()> { + pub fn latest_capture(&mut self) -> Result, Option<$size>> { // Note(unsafe): This channel owns all access to the specific timer channel. // Only atomic operations on completed on the timer registers. let regs = unsafe { &*<$TY>::ptr() }; - let sr = regs.sr.read(); - let result = if sr.[< cc $index if >]().bit_is_set() { + let result = if regs.sr.read().[< cc $index if >]().bit_is_set() { // Read the capture value. Reading the captured value clears the flag in the // status register automatically. - let ccx = regs.[< ccr $index >].read(); - Some(ccx.ccr().bits()) + Some(regs.[< ccr $index >].read().ccr().bits()) } else { None }; // Read SR again to check for a potential over-capture. If there is an // overcapture, return an error. - if regs.sr.read().[< cc $index of >]().bit_is_clear() { - Ok(result) - } else { + if regs.sr.read().[< cc $index of >]().bit_is_set() { regs.sr.modify(|_, w| w.[< cc $index of >]().clear_bit()); - Err(()) + Err(result) + } else { + Ok(result) } } From e1c87c149f4ed3b76eba343703d5d3879cafb3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 13:25:45 +0100 Subject: [PATCH 3/8] timestamping_timer: also reset counter --- src/hardware/configuration.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 0b3e2fc..aa0b918 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -211,6 +211,7 @@ pub fn setup( // Configure the timer to count at the designed tick rate. We will manually set the // period below. timer5.pause(); + timer5.reset_counter(); timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); // The timestamp timer must run at exactly a multiple of the sample timer based on the From ddbfa9d98831edd86ace42301a28481bc231045e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 14:34:48 +0100 Subject: [PATCH 4/8] timestamping: docs and naming --- src/bin/lockin-external.rs | 4 ++-- src/hardware/configuration.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 1d3f11d..0933f17 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -117,14 +117,14 @@ const APP: () = { let iir_state = c.resources.iir_state; let lockin = c.resources.lockin; - let t = c + let timestamp = c .resources .timestamper .latest_timestamp() .unwrap_or_else(|t| t) // Ignore timer capture overflows. .map(|t| t as i32); let (pll_phase, pll_frequency) = c.resources.pll.update( - t, + timestamp, 22, // frequency settling time (log2 counter cycles), TODO: expose 22, // phase settling time, TODO: expose ); diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index aa0b918..cbe4a1d 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -211,12 +211,11 @@ pub fn setup( // Configure the timer to count at the designed tick rate. We will manually set the // period below. timer5.pause(); - timer5.reset_counter(); timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); - // The timestamp timer must run at exactly a multiple of the sample timer based on the - // batch size. To accomodate this, we manually set the prescaler identical to the sample - // timer, but use a period that is longer. + // The timestamp timer runs at the counter cycle period as the sampling timers. + // To accomodate this, we manually set the prescaler identical to the sample + // timer, but use maximum overflow period. let mut timer = timers::TimestampTimer::new(timer5); timer.set_period_ticks(u32::MAX); From dcc71d5d11ab93acf9dad1ce8a3ed79d738dc295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 15:41:47 +0100 Subject: [PATCH 5/8] iir: tweak math a bit --- dsp/src/iir_int.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dsp/src/iir_int.rs b/dsp/src/iir_int.rs index 93626de..daff43c 100644 --- a/dsp/src/iir_int.rs +++ b/dsp/src/iir_int.rs @@ -22,8 +22,9 @@ impl Vec5 { pub fn lowpass(f: f32, q: f32, k: f32) -> Self { // 3rd order Taylor approximation of sin and cos. let f = f * 2. * PI; - let fsin = f - f * f * f / 6.; - let fcos = 1. - f * f / 2.; + let f2 = f * f * 0.5; + let fcos = 1. - f2; + let fsin = f * (1. - f2 / 3.); let alpha = fsin / (2. * q); // IIR uses Q2.30 fixed point let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32; From 145b48074eed1aa979be6a013edd3feb69dd8b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 15:42:51 +0100 Subject: [PATCH 6/8] timers: remove spurious tim2 reset --- src/hardware/configuration.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index cbe4a1d..acd98ae 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -154,7 +154,6 @@ pub fn setup( // Configure the timer to count at the designed tick rate. We will manually set the // period below. timer2.pause(); - timer2.reset_counter(); timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); let mut sampling_timer = timers::SamplingTimer::new(timer2); From bd71136cdf2f096e460b12737ac68e2a2cf5d0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 15:46:50 +0100 Subject: [PATCH 7/8] hw/config: add TODO on synchronization --- src/hardware/configuration.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index acd98ae..e173dc7 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -217,6 +217,9 @@ pub fn setup( // timer, but use maximum overflow period. let mut timer = timers::TimestampTimer::new(timer5); + // TODO: Check hardware synchronization of timestamping and the sampling timers + // for phase shift determinism. + timer.set_period_ticks(u32::MAX); timer From e423eff0e2c290af51c9ed73d871a85bfb32453d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 2 Feb 2021 15:50:31 +0100 Subject: [PATCH 8/8] lockin-external: add doc --- src/bin/lockin-external.rs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 0933f17..11d4958 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -83,24 +83,13 @@ const APP: () = { } } - /// Main DSP processing routine for Stabilizer. + /// Main DSP processing routine. /// - /// # Note - /// Processing time for the DSP application code is bounded by the following constraints: + /// See `dual-iir` for general notes on processing time and timing. /// - /// DSP application code starts after the ADC has generated a batch of samples and must be - /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer - /// time). If this constraint is not met, firmware will panic due to an ADC input overrun. - /// - /// The DSP application code must also fill out the next DAC output buffer in time such that the - /// DAC can switch to it when it has completed the current buffer. If this constraint is not met - /// it's possible that old DAC codes will be generated on the output and the output samples will - /// be delayed by 1 batch. - /// - /// Because the ADC and DAC operate at the same rate, these two constraints actually implement - /// the same time bounds, meeting one also means the other is also met. - /// - /// TODO: document lockin + /// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal. + /// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale. + /// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available. #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch, lockin, timestamper, pll], priority=2)] fn process(c: process::Context) { let adc_samples = [