Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rj/timestamp tweaks #255

Merged
merged 8 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions dsp/src/iir_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 12 additions & 17 deletions src/bin/lockin-external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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.
jordens marked this conversation as resolved.
Show resolved Hide resolved
.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
);
Expand Down
13 changes: 7 additions & 6 deletions src/hardware/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
jordens marked this conversation as resolved.
Show resolved Hide resolved

timer
};
Expand Down
51 changes: 6 additions & 45 deletions src/hardware/digital_input_stamper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<u32> {
self.capture_channel
.latest_capture()
.expect("DI0 timestamp overrun")
pub fn latest_timestamp(&mut self) -> Result<Option<u32>, Option<u32>> {
self.capture_channel.latest_capture()
}
}
16 changes: 7 additions & 9 deletions src/hardware/timers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<$size>, ()> {
pub fn latest_capture(&mut self) -> Result<Option<$size>, 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)
}
}

Expand Down