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; diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index f3717bb..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 = [ @@ -117,8 +106,14 @@ const APP: () = { let iir_state = c.resources.iir_state; let lockin = c.resources.lockin; + 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( - c.resources.timestamper.latest_timestamp().map(|t| t as i32), + 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 3f01b27..e173dc7 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); @@ -213,13 +212,15 @@ pub fn setup( timer5.pause(); 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); - let period = digital_input_stamper::calculate_timestamp_timer_period(); - timer.set_period_ticks(period); + // TODO: Check hardware synchronization of timestamping and the sampling timers + // for phase shift determinism. + + 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..6bd8629 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 { @@ -98,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) } }