Merge pull request #255 from quartiq/rj/timestamp-tweaks

Rj/timestamp tweaks
This commit is contained in:
Robert Jördens 2021-02-02 16:11:33 +01:00 committed by GitHub
commit 14abaad7de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 79 deletions

View File

@ -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;

View File

@ -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
);

View File

@ -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
};

View File

@ -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<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()
}
}

View File

@ -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)
}
}