diff --git a/Cargo.lock b/Cargo.lock index 7fa03f0..0999288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -724,6 +724,7 @@ dependencies = [ "dsp", "embedded-hal", "enum-iterator", + "generic-array 0.14.4", "heapless", "log", "mcp23017", diff --git a/Cargo.toml b/Cargo.toml index 599e595..f384fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ dsp = { path = "dsp" } ad9959 = { path = "ad9959" } smoltcp-nal = "0.1.0" miniconf = "0.1" +generic-array = "0.14" [patch.crates-io.miniconf] git = "https://github.com/quartiq/miniconf.git" diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs index 6a977b4..613a133 100644 --- a/dsp/src/lockin.rs +++ b/dsp/src/lockin.rs @@ -1,12 +1,12 @@ use super::{Complex, ComplexExt, Lowpass, MulScaled}; -use generic_array::typenum::U2; +use generic_array::ArrayLength; #[derive(Clone, Default)] -pub struct Lockin { - state: [Lowpass; 2], +pub struct Lockin> { + state: [Lowpass; 2], } -impl Lockin { +impl> Lockin { /// Update the lockin with a sample taken at a given phase. pub fn update(&mut self, sample: i32, phase: i32, k: u8) -> Complex { // Get the LO signal for demodulation and mix the sample; diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 3b7c6a8..1b11c76 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -2,14 +2,12 @@ #![no_std] #![no_main] -use stm32h7xx_hal as hal; - -use stabilizer::{hardware, hardware::design_parameters}; - use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; +use generic_array::typenum::U4; use hardware::{ Adc0Input, Adc1Input, Dac0Output, Dac1Output, InputStamper, AFE0, AFE1, }; +use stabilizer::{hardware, hardware::design_parameters}; #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] const APP: () = { @@ -20,7 +18,7 @@ const APP: () = { timestamper: InputStamper, pll: RPLL, - lockin: Lockin, + lockin: Lockin, } #[init] @@ -88,22 +86,20 @@ const APP: () = { .map(|t| t as i32); let (pll_phase, pll_frequency) = c.resources.pll.update( timestamp, - 21, // frequency settling time (log2 counter cycles), TODO: expose - 21, // phase settling time, TODO: expose + 21, // frequency settling time (log2 counter cycles), + 21, // phase settling time ); // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) - let harmonic: i32 = -1; // TODO: expose + let harmonic: i32 = -1; // Demodulation LO phase offset - let phase_offset: i32 = 0; // TODO: expose + let phase_offset: i32 = 0; // Log2 lowpass time constant - let time_constant: u8 = 6; // TODO: expose + let time_constant: u8 = 6; let sample_frequency = ((pll_frequency - // half-up rounding bias - // .wrapping_add(1 << design_parameters::SAMPLE_BUFFER_SIZE_LOG2 - 1) >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) as i32) .wrapping_mul(harmonic); @@ -131,7 +127,7 @@ const APP: () = { Quadrature, } - let conf = Conf::FrequencyDiscriminator; // TODO: expose + let conf = Conf::FrequencyDiscriminator; let output = match conf { // Convert from IQ to power and phase. Conf::PowerPhase => [(output.log2() << 24) as _, output.arg()], @@ -149,14 +145,13 @@ const APP: () = { #[idle(resources=[afes])] fn idle(_: idle::Context) -> ! { loop { - // TODO: Implement network interface. cortex_m::asm::wfi(); } } #[task(binds = ETH, priority = 1)] fn eth(_: eth::Context) { - unsafe { hal::ethernet::interrupt_handler() } + unsafe { stm32h7xx_hal::ethernet::interrupt_handler() } } #[task(binds = SPI2, priority = 3)] @@ -166,7 +161,7 @@ const APP: () = { #[task(binds = SPI3, priority = 3)] fn spi3(_: spi3::Context) { - panic!("ADC0 input overrun"); + panic!("ADC1 input overrun"); } #[task(binds = SPI4, priority = 3)] diff --git a/src/bin/lockin-internal.rs b/src/bin/lockin-internal.rs index 1f9df6e..ee9da2f 100644 --- a/src/bin/lockin-internal.rs +++ b/src/bin/lockin-internal.rs @@ -3,6 +3,7 @@ #![no_main] use dsp::{Accu, Complex, ComplexExt, Lockin}; +use generic_array::typenum::U2; use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; use stabilizer::{hardware, hardware::design_parameters}; @@ -20,7 +21,7 @@ const APP: () = { adc: Adc1Input, dacs: (Dac0Output, Dac1Output), - lockin: Lockin, + lockin: Lockin, } #[init] @@ -44,24 +45,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 + /// This is an implementation of an internal-reference lockin on the ADC1 signal. + /// The reference at f_sample/8 is output on DAC0 and the phase of the demodulated + /// signal on DAC1. #[task(binds=DMA1_STR4, resources=[adc, dacs, lockin], priority=2)] fn process(c: process::Context) { let lockin = c.resources.lockin; @@ -71,29 +61,23 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - // DAC0 always generates a fixed sinusoidal output. - dac_samples[0] - .iter_mut() - .zip(DAC_SEQUENCE.iter()) - .for_each(|(d, s)| *d = *s as u16 ^ 0x8000); - // Reference phase and frequency are known. - let pll_phase = 0; + let pll_phase = 0i32; 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; // TODO: expose + // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) + let harmonic: i32 = -1; // Demodulation LO phase offset - let phase_offset: i32 = (0.25 * i32::MAX as f32) as i32; // TODO: expose + let phase_offset: i32 = 1 << 30; // Log2 lowpass time constant. let time_constant: u8 = 8; let sample_frequency = (pll_frequency as i32).wrapping_mul(harmonic); - let sample_phase = phase_offset - .wrapping_add((pll_phase as i32).wrapping_mul(harmonic)); + let sample_phase = + phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic)); let output: Complex = adc_samples .iter() @@ -109,15 +93,17 @@ const APP: () = { .unwrap() * 2; // Full scale assuming the 2f component is gone. - for value in dac_samples[1].iter_mut() { - *value = (output.arg() >> 16) as u16 ^ 0x8000; + // Convert to DAC data. + for (i, data) in DAC_SEQUENCE.iter().enumerate() { + // DAC0 always generates a fixed sinusoidal output. + dac_samples[0][i] = *data as u16 ^ 0x8000; + dac_samples[1][i] = (output.arg() >> 16) as u16 ^ 0x8000; } } #[idle(resources=[afes])] fn idle(_: idle::Context) -> ! { loop { - // TODO: Implement network interface. cortex_m::asm::wfi(); } }