Merge pull request #255 from quartiq/rj/timestamp-tweaks
Rj/timestamp tweaks
This commit is contained in:
commit
14abaad7de
|
@ -22,8 +22,9 @@ impl Vec5 {
|
||||||
pub fn lowpass(f: f32, q: f32, k: f32) -> Self {
|
pub fn lowpass(f: f32, q: f32, k: f32) -> Self {
|
||||||
// 3rd order Taylor approximation of sin and cos.
|
// 3rd order Taylor approximation of sin and cos.
|
||||||
let f = f * 2. * PI;
|
let f = f * 2. * PI;
|
||||||
let fsin = f - f * f * f / 6.;
|
let f2 = f * f * 0.5;
|
||||||
let fcos = 1. - f * f / 2.;
|
let fcos = 1. - f2;
|
||||||
|
let fsin = f * (1. - f2 / 3.);
|
||||||
let alpha = fsin / (2. * q);
|
let alpha = fsin / (2. * q);
|
||||||
// IIR uses Q2.30 fixed point
|
// IIR uses Q2.30 fixed point
|
||||||
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32;
|
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32;
|
||||||
|
|
|
@ -83,24 +83,13 @@ const APP: () = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main DSP processing routine for Stabilizer.
|
/// Main DSP processing routine.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// See `dual-iir` for general notes on processing time and timing.
|
||||||
/// Processing time for the DSP application code is bounded by the following constraints:
|
|
||||||
///
|
///
|
||||||
/// DSP application code starts after the ADC has generated a batch of samples and must be
|
/// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal.
|
||||||
/// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer
|
/// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale.
|
||||||
/// time). If this constraint is not met, firmware will panic due to an ADC input overrun.
|
/// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available.
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
#[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch, lockin, timestamper, pll], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch, lockin, timestamper, pll], priority=2)]
|
||||||
fn process(c: process::Context) {
|
fn process(c: process::Context) {
|
||||||
let adc_samples = [
|
let adc_samples = [
|
||||||
|
@ -117,8 +106,14 @@ const APP: () = {
|
||||||
let iir_state = c.resources.iir_state;
|
let iir_state = c.resources.iir_state;
|
||||||
let lockin = c.resources.lockin;
|
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(
|
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, // frequency settling time (log2 counter cycles), TODO: expose
|
||||||
22, // phase settling time, TODO: expose
|
22, // phase settling time, TODO: expose
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,7 +154,6 @@ pub fn setup(
|
||||||
// Configure the timer to count at the designed tick rate. We will manually set the
|
// Configure the timer to count at the designed tick rate. We will manually set the
|
||||||
// period below.
|
// period below.
|
||||||
timer2.pause();
|
timer2.pause();
|
||||||
timer2.reset_counter();
|
|
||||||
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
||||||
|
@ -213,13 +212,15 @@ pub fn setup(
|
||||||
timer5.pause();
|
timer5.pause();
|
||||||
timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
// The timestamp timer must run at exactly a multiple of the sample timer based on the
|
// The timestamp timer runs at the counter cycle period as the sampling timers.
|
||||||
// batch size. To accomodate this, we manually set the prescaler identical to the sample
|
// To accomodate this, we manually set the prescaler identical to the sample
|
||||||
// timer, but use a period that is longer.
|
// timer, but use maximum overflow period.
|
||||||
let mut timer = timers::TimestampTimer::new(timer5);
|
let mut timer = timers::TimestampTimer::new(timer5);
|
||||||
|
|
||||||
let period = digital_input_stamper::calculate_timestamp_timer_period();
|
// TODO: Check hardware synchronization of timestamping and the sampling timers
|
||||||
timer.set_period_ticks(period);
|
// for phase shift determinism.
|
||||||
|
|
||||||
|
timer.set_period_ticks(u32::MAX);
|
||||||
|
|
||||||
timer
|
timer
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,42 +25,6 @@
|
||||||
///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If
|
///! 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.
|
///! timestamping is desired in DI1, a separate timer + capture channel will be necessary.
|
||||||
use super::{hal, timers};
|
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.
|
/// The timestamper for DI0 reference clock inputs.
|
||||||
pub struct InputStamper {
|
pub struct InputStamper {
|
||||||
|
@ -98,15 +62,12 @@ impl InputStamper {
|
||||||
/// Get the latest timestamp that has occurred.
|
/// Get the latest timestamp that has occurred.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// This function must be called sufficiently often. If an over-capture event occurs, this
|
/// This function must be called at least as often as timestamps arrive.
|
||||||
/// function will panic, as this indicates a timestamp was inadvertently dropped.
|
/// If an over-capture event occurs, this function will clear the overflow,
|
||||||
///
|
/// and return a new timestamp of unknown recency an `Err()`.
|
||||||
/// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at
|
/// Note that this indicates at least one timestamp was inadvertently dropped.
|
||||||
/// most one timestamp will occur in each data processing cycle.
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn latest_timestamp(&mut self) -> Option<u32> {
|
pub fn latest_timestamp(&mut self) -> Result<Option<u32>, Option<u32>> {
|
||||||
self.capture_channel
|
self.capture_channel.latest_capture()
|
||||||
.latest_capture()
|
|
||||||
.expect("DI0 timestamp overrun")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,28 +281,26 @@ macro_rules! timer_channels {
|
||||||
impl [< Channel $index InputCapture >] {
|
impl [< Channel $index InputCapture >] {
|
||||||
/// Get the latest capture from the channel.
|
/// Get the latest capture from the channel.
|
||||||
#[allow(dead_code)]
|
#[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.
|
// Note(unsafe): This channel owns all access to the specific timer channel.
|
||||||
// Only atomic operations on completed on the timer registers.
|
// Only atomic operations on completed on the timer registers.
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
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
|
// Read the capture value. Reading the captured value clears the flag in the
|
||||||
// status register automatically.
|
// status register automatically.
|
||||||
let ccx = regs.[< ccr $index >].read();
|
Some(regs.[< ccr $index >].read().ccr().bits())
|
||||||
Some(ccx.ccr().bits())
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read SR again to check for a potential over-capture. If there is an
|
// Read SR again to check for a potential over-capture. If there is an
|
||||||
// overcapture, return an error.
|
// overcapture, return an error.
|
||||||
if regs.sr.read().[< cc $index of >]().bit_is_clear() {
|
if regs.sr.read().[< cc $index of >]().bit_is_set() {
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
regs.sr.modify(|_, w| w.[< cc $index of >]().clear_bit());
|
regs.sr.modify(|_, w| w.[< cc $index of >]().clear_bit());
|
||||||
Err(())
|
Err(result)
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue