From f250e036ca78e9ea2b9e8831a916b5d6afc5ffff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 12:46:33 +0100 Subject: [PATCH 01/16] rpll: simplify parameters, add one test --- dsp/src/rpll.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/dsp/src/rpll.rs b/dsp/src/rpll.rs index d4bd86f..e312393 100644 --- a/dsp/src/rpll.rs +++ b/dsp/src/rpll.rs @@ -94,7 +94,6 @@ mod test { struct Harness { rpll: RPLL, - dt2: u8, shift_frequency: u8, shift_phase: u8, noise: i32, @@ -109,7 +108,6 @@ mod test { fn default() -> Self { Self { rpll: RPLL::new(8), - dt2: 8, shift_frequency: 9, shift_phase: 8, noise: 0, @@ -122,7 +120,7 @@ mod test { } fn run(&mut self, n: usize) -> (Vec, Vec) { - assert!(self.period >= 1 << self.dt2); + assert!(self.period >= 1 << self.rpll.dt2); assert!(self.period < 1 << self.shift_frequency); assert!(self.period < 1 << self.shift_phase + 1); @@ -130,7 +128,7 @@ mod test { let mut f = Vec::::new(); for _ in 0..n { let timestamp = if self.time - self.next_noisy >= 0 { - assert!(self.time - self.next_noisy < 1 << self.dt2); + assert!(self.time - self.next_noisy < 1 << self.rpll.dt2); self.next = self.next.wrapping_add(self.period); let timestamp = self.next_noisy; let p_noise = self.rng.gen_range(-self.noise..=self.noise); @@ -151,23 +149,23 @@ mod test { // phase error y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32)); - let p_ref = 1 << 32 + self.dt2; + let p_ref = 1 << 32 + self.rpll.dt2; let p_sig = fi as u64 * self.period as u64; // relative frequency error f.push( p_sig.wrapping_sub(p_ref) as i64 as f32 - / 2f32.powi(32 + self.dt2 as i32), + / 2f32.powi(32 + self.rpll.dt2 as i32), ); // advance time - self.time = self.time.wrapping_add(1 << self.dt2); + self.time = self.time.wrapping_add(1 << self.rpll.dt2); } (y, f) } fn measure(&mut self, n: usize, limits: [f32; 4]) { - let t_settle = (1 << self.shift_frequency - self.dt2 + 4) - + (1 << self.shift_phase - self.dt2 + 4); + let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4) + + (1 << self.shift_phase - self.rpll.dt2 + 4); self.run(t_settle); let (y, f) = self.run(n); @@ -268,4 +266,18 @@ mod test { h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]); } + + #[test] + fn batch_fast_narrow() { + let mut h = Harness::default(); + h.rpll.dt2 = 8 + 3; + h.period = 2431; + h.next = 35281; + h.next_noisy = h.next; + h.noise = 100; + h.shift_frequency = 23; + h.shift_phase = 23; + + h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]); + } } From 7ce90c4d319aa62c53cd0046041b7529b75f5b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 12:47:35 +0100 Subject: [PATCH 02/16] input stamper: add deglitching --- src/hardware/digital_input_stamper.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hardware/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs index 1aa0bb1..cc764f5 100644 --- a/src/hardware/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -47,6 +47,10 @@ impl InputStamper { let input_capture = timer_channel.into_input_capture(timers::tim5::CaptureSource4::TI4); + // FIXME: hack in de-glitching filter + let regs = unsafe { &*hal::stm32::TIM5::ptr() }; + regs.ccmr2_input().modify(|_, w| w.ic4f().bits(0b0011)); + Self { capture_channel: input_capture, _di0_trigger: trigger, From f47ee38d314f906710da7fd0f0d8473eee3cf2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 12:48:25 +0100 Subject: [PATCH 03/16] move sample ticks and buffer size to design parameters --- src/bin/lockin-external.rs | 14 ++++++++------ src/bin/lockin-internal.rs | 7 ++++--- src/hardware/adc.rs | 4 ++-- src/hardware/configuration.rs | 19 +++++++++---------- src/hardware/dac.rs | 4 ++-- src/hardware/design_parameters.rs | 10 ++++++++++ src/hardware/mod.rs | 2 +- src/lib.rs | 10 ---------- 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index d3b3410..ae25d15 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -11,9 +11,7 @@ use rtic::cyccnt::{Instant, U32Ext}; use heapless::{consts::*, String}; -use stabilizer::{ - hardware, server, ADC_SAMPLE_TICKS_LOG2, SAMPLE_BUFFER_SIZE_LOG2, -}; +use stabilizer::{hardware, hardware::design_parameters, server}; use dsp::{iir, iir_int, lockin::Lockin, rpll::RPLL, Accu}; use hardware::{ @@ -52,7 +50,10 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + SAMPLE_BUFFER_SIZE_LOG2); + let pll = RPLL::new( + design_parameters::ADC_SAMPLE_TICKS_LOG2 + + design_parameters::SAMPLE_BUFFER_SIZE_LOG2, + ); let lockin = Lockin::new( iir_int::Vec5::lowpass(1e-3, 0.707, 2.), // TODO: expose @@ -126,8 +127,9 @@ const APP: () = { let phase_offset: i32 = 0; // TODO: expose let sample_frequency = ((pll_frequency - // .wrapping_add(1 << SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias - >> SAMPLE_BUFFER_SIZE_LOG2) as i32) + // .wrapping_add(1 << design_parameters::SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias + >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) + as i32) .wrapping_mul(harmonic); let sample_phase = phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic)); diff --git a/src/bin/lockin-internal.rs b/src/bin/lockin-internal.rs index 4974be2..cadf189 100644 --- a/src/bin/lockin-internal.rs +++ b/src/bin/lockin-internal.rs @@ -4,13 +4,13 @@ use dsp::{iir_int, lockin::Lockin, Accu}; use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; -use stabilizer::{hardware, SAMPLE_BUFFER_SIZE, SAMPLE_BUFFER_SIZE_LOG2}; +use stabilizer::{hardware, hardware::design_parameters}; // A constant sinusoid to send on the DAC output. // Full-scale gives a +/- 10V amplitude waveform. Scale it down to give +/- 1V. const ONE: i16 = (0.1 * u16::MAX as f32) as _; const SQRT2: i16 = (ONE as f32 * 0.707) as _; -const DAC_SEQUENCE: [i16; SAMPLE_BUFFER_SIZE] = +const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] = [ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2]; #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] @@ -83,7 +83,8 @@ const APP: () = { // Reference phase and frequency are known. let pll_phase = 0; - let pll_frequency = 1i32 << (32 - SAMPLE_BUFFER_SIZE_LOG2); + let pll_frequency = + 1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2); // Harmonic index of the LO: -1 to _de_modulate the fundamental let harmonic: i32 = -1; diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 1cb6c17..3f2fa5d 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -74,9 +74,9 @@ ///! double-buffered mode offers less overhead due to the DMA disable/enable procedure). use stm32h7xx_hal as hal; -use crate::SAMPLE_BUFFER_SIZE; - +use super::design_parameters::SAMPLE_BUFFER_SIZE; use super::timers; + use hal::dma::{ config::Priority, dma::{DMAReq, DmaConfig}, diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index e173dc7..0b4b156 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -1,11 +1,6 @@ ///! Stabilizer hardware configuration ///! ///! This file contains all of the hardware-specific configuration of Stabilizer. -use crate::ADC_SAMPLE_TICKS; - -#[cfg(feature = "pounder_v1_1")] -use crate::SAMPLE_BUFFER_SIZE; - #[cfg(feature = "pounder_v1_1")] use core::convert::TryInto; @@ -157,7 +152,8 @@ pub fn setup( timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); let mut sampling_timer = timers::SamplingTimer::new(timer2); - sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32); + sampling_timer + .set_period_ticks((design_parameters::ADC_SAMPLE_TICKS - 1) as u32); // The sampling timer is used as the master timer for the shadow-sampling timer. Thus, // it generates a trigger whenever it is enabled. @@ -181,7 +177,8 @@ pub fn setup( let mut shadow_sampling_timer = timers::ShadowSamplingTimer::new(timer3); - shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); + shadow_sampling_timer + .set_period_ticks(design_parameters::ADC_SAMPLE_TICKS - 1); // The shadow sampling timer is a slave-mode timer to the sampling timer. It should // always be in-sync - thus, we configure it to operate in slave mode using "Trigger @@ -726,7 +723,8 @@ pub fn setup( let sample_frequency = { let timer_frequency: hal::time::Hertz = design_parameters::TIMER_FREQUENCY.into(); - timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32 + timer_frequency.0 as f32 + / design_parameters::ADC_SAMPLE_TICKS as f32 }; let sample_period = 1.0 / sample_frequency; @@ -773,8 +771,9 @@ pub fn setup( }; let period = (tick_ratio - * ADC_SAMPLE_TICKS as f32 - * SAMPLE_BUFFER_SIZE as f32) as u32 + * design_parameters::ADC_SAMPLE_TICKS as f32 + * design_parameters::SAMPLE_BUFFER_SIZE as f32) + as u32 / 4; timestamp_timer.set_period_ticks((period - 1).try_into().unwrap()); let tim8_channels = timestamp_timer.channels(); diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 5ca65fb..d41ae8c 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -52,9 +52,9 @@ ///! served promptly after the transfer completes. use stm32h7xx_hal as hal; -use crate::SAMPLE_BUFFER_SIZE; - +use super::design_parameters::SAMPLE_BUFFER_SIZE; use super::timers; + use hal::dma::{ dma::{DMAReq, DmaConfig}, traits::TargetAddress, diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 3de7c15..9951ee5 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -39,3 +39,13 @@ pub const DDS_SYSTEM_CLK: MegaHertz = /// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk). #[allow(dead_code)] pub const DDS_SYNC_CLK_DIV: u8 = 4; + +// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is +// equal to 10ns per tick. +// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz +pub const ADC_SAMPLE_TICKS_LOG2: u8 = 8; +pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; + +// The desired ADC sample processing buffer size. +pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; +pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index f912057..b54b6f7 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -11,7 +11,7 @@ mod adc; mod afe; mod configuration; mod dac; -mod design_parameters; +pub mod design_parameters; mod digital_input_stamper; mod eeprom; mod pounder; diff --git a/src/lib.rs b/src/lib.rs index ab2623d..c252f37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,3 @@ extern crate log; pub mod hardware; pub mod server; - -// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is -// equal to 10ns per tick. -// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -pub const ADC_SAMPLE_TICKS_LOG2: u8 = 8; -pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; - -// The desired ADC sample processing buffer size. -pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; -pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; From d32378e6c41a81ea73e816e89108644d59bb5dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 12:48:58 +0100 Subject: [PATCH 04/16] lockin-external: ignore timestamps related to capture overflows --- src/bin/lockin-external.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index ae25d15..a36e5d6 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -113,7 +113,7 @@ const APP: () = { .resources .timestamper .latest_timestamp() - .unwrap_or_else(|t| t) // Ignore timer capture overflows. + .unwrap_or(None) // Ignore data from timer capture overflows. .map(|t| t as i32); let (pll_phase, pll_frequency) = c.resources.pll.update( timestamp, From 8314844aeb59cfb7e47670e0946024f4177540bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 13:36:24 +0100 Subject: [PATCH 05/16] pounder: moved SAMPLE_BUFFER_SIZE --- src/hardware/pounder/timestamp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/pounder/timestamp.rs b/src/hardware/pounder/timestamp.rs index 7e39a94..0c06192 100644 --- a/src/hardware/pounder/timestamp.rs +++ b/src/hardware/pounder/timestamp.rs @@ -26,7 +26,7 @@ use stm32h7xx_hal as hal; use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer}; -use crate::{hardware::timers, SAMPLE_BUFFER_SIZE}; +use crate::hardware::{design_parameters::SAMPLE_BUFFER_SIZE, timers}; // Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the // working data provided to the application. These buffers must exist in a DMA-accessible memory From 473bdaa9bcd903a55fb278ba0a250fce6122d712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 15:21:05 +0100 Subject: [PATCH 06/16] iir_int: use f64 for extreme filters --- dsp/src/iir_int.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsp/src/iir_int.rs b/dsp/src/iir_int.rs index daff43c..c0a17ad 100644 --- a/dsp/src/iir_int.rs +++ b/dsp/src/iir_int.rs @@ -1,4 +1,4 @@ -use core::f32::consts::PI; +use core::f64::consts::PI; use serde::{Deserialize, Serialize}; /// Generic vector for integer IIR filter. @@ -19,7 +19,7 @@ impl Vec5 { /// /// # Returns /// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1. - pub fn lowpass(f: f32, q: f32, k: f32) -> Self { + pub fn lowpass(f: f64, q: f64, k: f64) -> Self { // 3rd order Taylor approximation of sin and cos. let f = f * 2. * PI; let f2 = f * f * 0.5; @@ -27,7 +27,7 @@ impl Vec5 { 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; + let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64; let b0 = (k / 2. * (1. - fcos) / a0) as _; let a1 = (2. * fcos / a0) as _; let a2 = ((alpha - 1.) / a0) as _; @@ -97,7 +97,7 @@ mod test { #[test] fn lowpass_gen() { - let ba = Vec5::lowpass(1e-3, 1. / 2f32.sqrt(), 2.); + let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2e5); println!("{:?}", ba.0); } } From 2d492055f30144396b6214e6d44b4e4b75aba473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 15:42:29 +0100 Subject: [PATCH 07/16] pounder stamper: overflow at u32 boundary --- src/hardware/configuration.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 0b4b156..fe36f08 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -759,23 +759,13 @@ pub fn setup( // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate. + // This is less than fCK_INT/3 of the timer as required for oversampling the trigger. timestamp_timer.set_external_clock(timers::Prescaler::Div4); timestamp_timer.start(); - // We want the pounder timestamp timer to overflow once per batch. - let tick_ratio = { - let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0 - as f32 - / design_parameters::DDS_SYNC_CLK_DIV as f32; - sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32 - }; - - let period = (tick_ratio - * design_parameters::ADC_SAMPLE_TICKS as f32 - * design_parameters::SAMPLE_BUFFER_SIZE as f32) - as u32 - / 4; - timestamp_timer.set_period_ticks((period - 1).try_into().unwrap()); + // Set the timer to wrap at the u32 boundary to meet the PLL periodicity. + // Scale and wrap before or after the PLL. + timestamp_timer.set_period_ticks(u32::MAX); let tim8_channels = timestamp_timer.channels(); pounder::timestamp::Timestamper::new( From f19988a1bdb7102a0f6669519f1047ea1fe55576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 15:42:45 +0100 Subject: [PATCH 08/16] up the sample rate --- src/hardware/design_parameters.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 9951ee5..ddc0614 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -42,8 +42,8 @@ pub const DDS_SYNC_CLK_DIV: u8 = 4; // The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is // equal to 10ns per tick. -// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -pub const ADC_SAMPLE_TICKS_LOG2: u8 = 8; +// Currently, the sample rate is equal to: Fsample = 100/128 MHz ~ 800 KHz +pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7; pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; // The desired ADC sample processing buffer size. From 0343e5d8abadca622a89af3acc9f624b94beb37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 16:51:34 +0100 Subject: [PATCH 09/16] pounder timer is u16 --- src/hardware/configuration.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index fe36f08..d7a07ed 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -1,9 +1,6 @@ ///! Stabilizer hardware configuration ///! ///! This file contains all of the hardware-specific configuration of Stabilizer. -#[cfg(feature = "pounder_v1_1")] -use core::convert::TryInto; - use smoltcp::{iface::Routes, wire::Ipv4Address}; use stm32h7xx_hal::{ @@ -763,9 +760,9 @@ pub fn setup( timestamp_timer.set_external_clock(timers::Prescaler::Div4); timestamp_timer.start(); - // Set the timer to wrap at the u32 boundary to meet the PLL periodicity. + // Set the timer to wrap at the u16 boundary to meet the PLL periodicity. // Scale and wrap before or after the PLL. - timestamp_timer.set_period_ticks(u32::MAX); + timestamp_timer.set_period_ticks(u16::MAX); let tim8_channels = timestamp_timer.channels(); pounder::timestamp::Timestamper::new( From 47d8a74524a3d100c2865e3ab4c68db891607628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 4 Feb 2021 17:01:18 +0100 Subject: [PATCH 10/16] ci: simplify nightly --- .github/bors.toml | 2 +- .github/workflows/ci.yml | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/bors.toml b/.github/bors.toml index 7168739..722246d 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -3,5 +3,5 @@ delete_merged_branches = true status = [ "style", "test (stable)", - "compile (stable, false)", + "compile (stable)", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 851ef53..f4b66b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,22 +35,18 @@ jobs: compile: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.optional }} + continue-on-error: ${{ matrix.toolchain == 'nightly' }} strategy: matrix: toolchain: [stable] features: [''] - optional: [false] include: - toolchain: beta features: '' - optional: false - toolchain: stable features: pounder_v1_1 - optional: false - toolchain: nightly features: nightly - optional: true steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 From deed11f11027f3abd0156057eb14e07ffe79f77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 5 Feb 2021 18:59:22 +0100 Subject: [PATCH 11/16] lockin-external: simplify --- src/bin/lockin-external.rs | 216 +++---------------------------------- 1 file changed, 16 insertions(+), 200 deletions(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index a36e5d6..337ae99 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -4,28 +4,13 @@ use stm32h7xx_hal as hal; -#[macro_use] -extern crate log; +use stabilizer::{hardware, hardware::design_parameters}; -use rtic::cyccnt::{Instant, U32Ext}; - -use heapless::{consts::*, String}; - -use stabilizer::{hardware, hardware::design_parameters, server}; - -use dsp::{iir, iir_int, lockin::Lockin, rpll::RPLL, Accu}; +use dsp::{iir_int, lockin::Lockin, rpll::RPLL, Accu}; use hardware::{ Adc0Input, Adc1Input, Dac0Output, Dac1Output, InputStamper, AFE0, AFE1, }; -const SCALE: f32 = i16::MAX as _; - -const TCP_RX_BUFFER_SIZE: usize = 8192; -const TCP_TX_BUFFER_SIZE: usize = 8192; - -// The number of cascaded IIR biquads per channel. Select 1 or 2! -const IIR_CASCADE_LENGTH: usize = 1; - #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] const APP: () = { struct Resources { @@ -34,12 +19,6 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), net_interface: hardware::Ethernet, - // Format: iir_state[ch][cascade-no][coeff] - #[init([[iir::Vec5([0.; 5]); IIR_CASCADE_LENGTH]; 2])] - iir_state: [[iir::Vec5; IIR_CASCADE_LENGTH]; 2], - #[init([[iir::IIR::new(1./(1 << 16) as f32, -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2])] - iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], - timestamper: InputStamper, pll: RPLL, lockin: Lockin, @@ -93,7 +72,7 @@ const APP: () = { /// 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)] + #[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll], priority=2)] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), @@ -105,8 +84,6 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let iir_ch = c.resources.iir_ch; - let iir_state = c.resources.iir_state; let lockin = c.resources.lockin; let timestamp = c @@ -117,8 +94,8 @@ const APP: () = { .map(|t| t as i32); let (pll_phase, pll_frequency) = c.resources.pll.update( timestamp, - 22, // frequency settling time (log2 counter cycles), TODO: expose - 22, // phase settling time, TODO: expose + 21, // frequency settling time (log2 counter cycles), TODO: expose + 21, // phase settling time, TODO: expose ); // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) @@ -144,187 +121,26 @@ const APP: () = { .last() .unwrap(); - // convert i/q to power/phase, - let power_phase = true; // TODO: expose - - let mut output = if power_phase { + let conf = "frequency_discriminator"; + let output = match conf { // Convert from IQ to power and phase. - [output.abs_sqr() as _, output.arg() as _] - } else { - [output.0 as _, output.1 as _] + "power_phase" => [output.abs_sqr(), output.arg()], + "frequency_discriminator" => [pll_frequency as i32, output.arg()], + _ => [output.0, output.1], }; - // Filter power and phase through IIR filters. - // Note: Normalization to be done in filters. Phase will wrap happily. - for j in 0..iir_state[0].len() { - for k in 0..output.len() { - output[k] = - iir_ch[k][j].update(&mut iir_state[k][j], output[k]); - } - } - - // Note(unsafe): range clipping to i16 is ensured by IIR filters above. // Convert to DAC data. for i in 0..dac_samples[0].len() { - unsafe { - dac_samples[0][i] = - output[0].to_int_unchecked::() as u16 ^ 0x8000; - dac_samples[1][i] = - output[1].to_int_unchecked::() as u16 ^ 0x8000; - } + dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000; + dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000; } } - #[idle(resources=[net_interface, iir_state, iir_ch, afes])] - fn idle(mut c: idle::Context) -> ! { - let mut socket_set_entries: [_; 8] = Default::default(); - let mut sockets = - smoltcp::socket::SocketSet::new(&mut socket_set_entries[..]); - - let mut rx_storage = [0; TCP_RX_BUFFER_SIZE]; - let mut tx_storage = [0; TCP_TX_BUFFER_SIZE]; - let tcp_handle = { - let tcp_rx_buffer = - smoltcp::socket::TcpSocketBuffer::new(&mut rx_storage[..]); - let tcp_tx_buffer = - smoltcp::socket::TcpSocketBuffer::new(&mut tx_storage[..]); - let tcp_socket = - smoltcp::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - sockets.add(tcp_socket) - }; - - let mut server = server::Server::new(); - - let mut time = 0u32; - let mut next_ms = Instant::now(); - - // TODO: Replace with reference to CPU clock from CCDR. - next_ms += 400_000.cycles(); - + #[idle(resources=[afes])] + fn idle(_: idle::Context) -> ! { loop { - let tick = Instant::now() > next_ms; - - if tick { - next_ms += 400_000.cycles(); - time += 1; - } - - { - let socket = - &mut *sockets.get::(tcp_handle); - if socket.state() == smoltcp::socket::TcpState::CloseWait { - socket.close(); - } else if !(socket.is_open() || socket.is_listening()) { - socket - .listen(1235) - .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); - } else { - server.poll(socket, |req| { - info!("Got request: {:?}", req); - stabilizer::route_request!(req, - readable_attributes: [ - "stabilizer/iir/state": (|| { - let state = c.resources.iir_state.lock(|iir_state| - server::Status { - t: time, - x0: iir_state[0][0].0[0], - y0: iir_state[0][0].0[2], - x1: iir_state[1][0].0[0], - y1: iir_state[1][0].0[2], - }); - - Ok::(state) - }), - // "_b" means cascades 2nd IIR - "stabilizer/iir_b/state": (|| { let state = c.resources.iir_state.lock(|iir_state| - server::Status { - t: time, - x0: iir_state[0][IIR_CASCADE_LENGTH-1].0[0], - y0: iir_state[0][IIR_CASCADE_LENGTH-1].0[2], - x1: iir_state[1][IIR_CASCADE_LENGTH-1].0[0], - y1: iir_state[1][IIR_CASCADE_LENGTH-1].0[2], - }); - - Ok::(state) - }), - "stabilizer/afe0/gain": (|| c.resources.afes.0.get_gain()), - "stabilizer/afe1/gain": (|| c.resources.afes.1.get_gain()) - ], - - modifiable_attributes: [ - "stabilizer/iir0/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][0] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][0] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir_b0/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir_b1/state": server::IirRequest,(|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/afe0/gain": hardware::AfeGain, (|gain| { - c.resources.afes.0.set_gain(gain); - Ok::<(), ()>(()) - }), - "stabilizer/afe1/gain": hardware::AfeGain, (|gain| { - c.resources.afes.1.set_gain(gain); - Ok::<(), ()>(()) - }) - ] - ) - }); - } - } - - let sleep = match c.resources.net_interface.poll( - &mut sockets, - smoltcp::time::Instant::from_millis(time as i64), - ) { - Ok(changed) => !changed, - Err(smoltcp::Error::Unrecognized) => true, - Err(e) => { - info!("iface poll error: {:?}", e); - true - } - }; - - if sleep { - cortex_m::asm::wfi(); - } + // TODO: Implement network interface. + cortex_m::asm::wfi(); } } From 1b46f081c1362fe7ba5230899921bac9c989bd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 8 Feb 2021 11:26:58 +0100 Subject: [PATCH 12/16] better formatting --- src/bin/lockin-external.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 337ae99..74ca836 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -100,7 +100,8 @@ const APP: () = { // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) let harmonic: i32 = -1; // TODO: expose - // Demodulation LO phase offset + + // Demodulation LO phase offset let phase_offset: i32 = 0; // TODO: expose let sample_frequency = ((pll_frequency From 611bd3e855b9e6dd6ef68a342f0ea8002bd93075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 8 Feb 2021 15:24:52 +0100 Subject: [PATCH 13/16] ad9959/pounder: tweaks * make a trait public * use self-test * this hasn't been tested --- src/hardware/configuration.rs | 4 +++- src/hardware/mod.rs | 2 +- src/hardware/pounder/mod.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index d7a07ed..52db011 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -597,7 +597,7 @@ pub fn setup( let ref_clk: hal::time::Hertz = design_parameters::DDS_REF_CLK.into(); - let ad9959 = ad9959::Ad9959::new( + let mut ad9959 = ad9959::Ad9959::new( qspi_interface, reset_pin, &mut io_update, @@ -608,6 +608,8 @@ pub fn setup( ) .unwrap(); + ad9959.self_test().unwrap(); + // Return IO_Update gpiog.pg7 = io_update.into_analog(); diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index b54b6f7..41bca1b 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -14,7 +14,7 @@ mod dac; pub mod design_parameters; mod digital_input_stamper; mod eeprom; -mod pounder; +pub mod pounder; mod timers; pub use adc::{Adc0Input, Adc1Input}; diff --git a/src/hardware/pounder/mod.rs b/src/hardware/pounder/mod.rs index 3a85937..0497a37 100644 --- a/src/hardware/pounder/mod.rs +++ b/src/hardware/pounder/mod.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -mod attenuators; +pub mod attenuators; mod dds_output; pub mod hrtimer; mod rf_power; From 31781a9d0e12c06474fdb0eaaccc30fc034f9a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 9 Feb 2021 12:17:48 +0100 Subject: [PATCH 14/16] iir_int: rounding bias --- dsp/src/iir_int.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsp/src/iir_int.rs b/dsp/src/iir_int.rs index c0a17ad..f0ce157 100644 --- a/dsp/src/iir_int.rs +++ b/dsp/src/iir_int.rs @@ -28,9 +28,9 @@ impl Vec5 { let alpha = fsin / (2. * q); // IIR uses Q2.30 fixed point let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64; - let b0 = (k / 2. * (1. - fcos) / a0) as _; - let a1 = (2. * fcos / a0) as _; - let a2 = ((alpha - 1.) / a0) as _; + let b0 = (k / 2. * (1. - fcos) / a0 + 0.5) as _; + let a1 = (2. * fcos / a0 + 0.5) as _; + let a2 = ((alpha - 1.) / a0 + 0.5) as _; Self([b0, 2 * b0, b0, a1, a2]) } @@ -97,7 +97,7 @@ mod test { #[test] fn lowpass_gen() { - let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2e5); + let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2.); println!("{:?}", ba.0); } } From 2e358dea263b82c2a5d4f9b7d1236190666c1f37 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 9 Feb 2021 14:36:50 +0100 Subject: [PATCH 15/16] Adding support for input capture prefilter configuration --- src/hardware/digital_input_stamper.rs | 8 ++++---- src/hardware/timers.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/hardware/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs index cc764f5..baddeba 100644 --- a/src/hardware/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -44,12 +44,12 @@ impl InputStamper { ) -> Self { // Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the // capture source. - let input_capture = + let mut input_capture = timer_channel.into_input_capture(timers::tim5::CaptureSource4::TI4); - // FIXME: hack in de-glitching filter - let regs = unsafe { &*hal::stm32::TIM5::ptr() }; - regs.ccmr2_input().modify(|_, w| w.ic4f().bits(0b0011)); + // Do not prescale the input capture signal - require 8 consecutive samples to record an + // incoming event - this prevents spurious glitches from triggering captures. + input_capture.configure_filter(timers::InputFilter::Div1N8); Self { capture_channel: input_capture, diff --git a/src/hardware/timers.rs b/src/hardware/timers.rs index e2cbbc8..fe90677 100644 --- a/src/hardware/timers.rs +++ b/src/hardware/timers.rs @@ -48,6 +48,13 @@ pub enum SlaveMode { Trigger = 0b0110, } +/// Optional input capture preconditioning filter configurations. +#[allow(dead_code)] +pub enum InputFilter { + Div1N1 = 0b0000, + Div1N8 = 0b0011, +} + macro_rules! timer_channels { ($name:ident, $TY:ident, $size:ty) => { paste::paste! { @@ -334,6 +341,16 @@ macro_rules! timer_channels { let regs = unsafe { &*<$TY>::ptr() }; regs.sr.read().[< cc $index of >]().bit_is_set() } + + /// Configure the input capture input pre-filter. + /// + /// # Args + /// * `filter` - The desired input filter stage configuration. Defaults to disabled. + #[allow(dead_code)] + pub fn configure_filter(&mut self, filter: super::InputFilter) { + let regs = unsafe { &*<$TY>::ptr() }; + regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8)); + } } // Note(unsafe): This manually implements DMA support for input-capture channels. This From 724768a72e9ac423c8ce10ae9bf6230e64c09cc7 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 9 Feb 2021 14:37:49 +0100 Subject: [PATCH 16/16] Adding safety docs --- src/hardware/timers.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hardware/timers.rs b/src/hardware/timers.rs index fe90677..78d27b6 100644 --- a/src/hardware/timers.rs +++ b/src/hardware/timers.rs @@ -348,6 +348,8 @@ macro_rules! timer_channels { /// * `filter` - The desired input filter stage configuration. Defaults to disabled. #[allow(dead_code)] pub fn configure_filter(&mut self, filter: super::InputFilter) { + // 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() }; regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8)); }