From 330f67d3c81cf53fce0d6e104ef746e5df98d24c Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 15 Apr 2021 14:40:47 +0200 Subject: [PATCH 01/34] Adding WIP telemetry implementation for dual-iir --- src/bin/dual-iir.rs | 86 +++++++++++++++++++++++++++---- src/hardware/configuration.rs | 18 +++++-- src/hardware/design_parameters.rs | 2 +- src/hardware/mod.rs | 2 + src/hardware/system_timer.rs | 68 ++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 src/hardware/system_timer.rs diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 4a27d487c..c4ef154ab 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -7,12 +7,12 @@ use stm32h7xx_hal as hal; use stabilizer::hardware; use miniconf::{minimq, Miniconf, MqttInterface}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use dsp::iir; use hardware::{ Adc0Input, Adc1Input, AfeGain, CycleCounter, Dac0Output, Dac1Output, - NetworkStack, AFE0, AFE1, + NetworkStack, SystemTimer, AFE0, AFE1, }; const SCALE: f32 = i16::MAX as _; @@ -20,10 +20,18 @@ const SCALE: f32 = i16::MAX as _; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; -#[derive(Debug, Deserialize, Miniconf)] +#[derive(Debug, Deserialize, Miniconf, Copy, Clone)] pub struct Settings { afe: [AfeGain; 2], iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], + telemetry_period_secs: u16, +} + +#[derive(Serialize, Clone)] +pub struct Telemetry { + latest_samples: [i16; 2], + latest_outputs: [i16; 2], + digital_inputs: [bool; 2], } impl Default for Settings { @@ -31,11 +39,22 @@ impl Default for Settings { Self { afe: [AfeGain::G1, AfeGain::G1], iir_ch: [[iir::IIR::new(1., -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2], + telemetry_period_secs: 10, } } } -#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +impl Default for Telemetry { + fn default() -> Self { + Self { + latest_samples: [0, 0], + latest_outputs: [0, 0], + digital_inputs: [false, false], + } + } +} + +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = crate::hardware::SystemTimer)] const APP: () = { struct Resources { afes: (AFE0, AFE1), @@ -43,6 +62,8 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), mqtt_interface: MqttInterface, + telemetry: Telemetry, + settings: Settings, clock: CycleCounter, // Format: iir_state[ch][cascade-no][coeff] @@ -52,7 +73,7 @@ const APP: () = { iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], } - #[init] + #[init(schedule = [telemetry])] fn init(c: init::Context) -> init::LateResources { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); @@ -82,7 +103,9 @@ const APP: () = { stabilizer.dacs.1.start(); // Start sampling ADCs. - stabilizer.adc_dac_timer.start(); + //stabilizer.adc_dac_timer.start(); + + c.schedule.telemetry(c.start).unwrap(); init::LateResources { mqtt_interface, @@ -90,6 +113,8 @@ const APP: () = { adcs: stabilizer.adcs, dacs: stabilizer.dacs, clock: stabilizer.cycle_counter, + settings: Settings::default(), + telemetry: Telemetry::default(), } } @@ -109,7 +134,7 @@ const APP: () = { /// /// 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. - #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch, telemetry], priority=2)] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), @@ -136,6 +161,14 @@ const APP: () = { dac_samples[channel][sample] = y as u16 ^ 0x8000; } } + + // Update telemetry measurements. + // TODO: Should we report these as voltages? + c.resources.telemetry.latest_samples = + [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; + + c.resources.telemetry.latest_outputs = + [dac_samples[0][0] as i16, dac_samples[1][0] as i16]; } #[idle(resources=[mqtt_interface, clock], spawn=[settings_update])] @@ -162,7 +195,7 @@ const APP: () = { if update { c.spawn.settings_update().unwrap(); } else if sleep { - cortex_m::asm::wfi(); + //cortex_m::asm::wfi(); } } Err(miniconf::MqttError::Network( @@ -173,18 +206,53 @@ const APP: () = { } } - #[task(priority = 1, resources=[mqtt_interface, afes, iir_ch])] + #[task(priority = 1, resources=[mqtt_interface, afes, settings, iir_ch])] fn settings_update(mut c: settings_update::Context) { let settings = &c.resources.mqtt_interface.settings; // Update the IIR channels. c.resources.iir_ch.lock(|iir| *iir = settings.iir_ch); + // Update currently-cached settings. + *c.resources.settings = *settings; + // Update AFEs c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); } + #[task(priority = 1, resources=[mqtt_interface, settings, telemetry], schedule=[telemetry])] + fn telemetry(mut c: telemetry::Context) { + let telemetry = c.resources.telemetry.lock(|telemetry| { + // TODO: Incorporate digital input status. + telemetry.digital_inputs = [false, false]; + telemetry.clone() + }); + + // Serialize telemetry outside of a critical section to prevent blocking the processing + // task. + let _telemetry = miniconf::serde_json_core::to_string::< + heapless::consts::U256, + _, + >(&telemetry) + .unwrap(); + + //c.resources.mqtt_interface.client(|client| { + // // TODO: Incorporate current MQTT prefix instead of hard-coded value. + // client.publish("dt/sinara/dual-iir/telemetry", telemetry.as_bytes(), minimq::QoS::AtMostOnce, &[]).ok() + //}); + + // Schedule the telemetry task in the future. + c.schedule + .telemetry( + c.scheduled + + SystemTimer::ticks_from_secs( + c.resources.settings.telemetry_period_secs as u32, + ), + ) + .unwrap(); + } + #[task(binds = ETH, priority = 1)] fn eth(_: eth::Context) { unsafe { hal::ethernet::interrupt_handler() } diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 0351db96a..31d47d2e3 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -13,8 +13,8 @@ use embedded_hal::digital::v2::{InputPin, OutputPin}; use super::{ adc, afe, cycle_counter::CycleCounter, dac, design_parameters, - digital_input_stamper, eeprom, pounder, timers, DdsOutput, NetworkStack, - AFE0, AFE1, + digital_input_stamper, eeprom, pounder, system_timer, timers, DdsOutput, + NetworkStack, AFE0, AFE1, }; pub struct NetStorage { @@ -96,7 +96,7 @@ static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); /// `Some(devices)` if pounder is detected, where `devices` is a `PounderDevices` structure /// containing all of the pounder hardware interfaces in a disabled state. pub fn setup( - mut core: rtic::export::Peripherals, + mut core: rtic::Peripherals, device: stm32h7xx_hal::stm32::Peripherals, ) -> (StabilizerDevices, Option) { let pwr = device.PWR.constrain(); @@ -139,7 +139,17 @@ pub fn setup( init_log(logger).unwrap(); } - let mut delay = hal::delay::Delay::new(core.SYST, ccdr.clocks); + // Set up the system timer for RTIC scheduling. + { + let tim15 = + device + .TIM15 + .timer(10.khz(), ccdr.peripheral.TIM15, &ccdr.clocks); + system_timer::SystemTimer::initialize(tim15); + } + + let mut delay = + asm_delay::AsmDelay::new(asm_delay::bitrate::MegaHertz(2 * 400)); let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 9a4279bee..0ae4c3557 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -51,4 +51,4 @@ pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The MQTT broker IPv4 address -pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10]; +pub const MQTT_BROKER: [u8; 4] = [10, 35, 16, 10]; diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index cc7a34ab6..668305e66 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -13,6 +13,7 @@ pub mod design_parameters; mod digital_input_stamper; mod eeprom; pub mod pounder; +mod system_timer; mod timers; pub use adc::{Adc0Input, Adc1Input}; @@ -21,6 +22,7 @@ pub use cycle_counter::CycleCounter; pub use dac::{Dac0Output, Dac1Output}; pub use digital_input_stamper::InputStamper; pub use pounder::DdsOutput; +pub use system_timer::SystemTimer; // Type alias for the analog front-end (AFE) for ADC0. pub type AFE0 = afe::ProgrammableGainAmplifier< diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs new file mode 100644 index 000000000..85938ba7e --- /dev/null +++ b/src/hardware/system_timer.rs @@ -0,0 +1,68 @@ +use hal::prelude::*; +use stm32h7xx_hal as hal; + +static mut OVERFLOWS: u32 = 0; + +pub struct SystemTimer {} + +impl SystemTimer { + pub fn initialize(mut timer: hal::timer::Timer) { + timer.pause(); + // Have the system timer operate at a tick rate of 10KHz (100uS per tick). With this + // configuration and a 65535 period, we get an overflow once every 6.5 seconds. + timer.set_tick_freq(10.khz()); + timer.apply_freq(); + + timer.resume(); + } + + pub fn ticks_from_secs(secs: u32) -> i32 { + (secs * 10_000) as i32 + } +} + +impl rtic::Monotonic for SystemTimer { + type Instant = i32; + + fn ratio() -> rtic::Fraction { + rtic::Fraction { + numerator: 1, + denominator: 40000, + } + } + + fn now() -> i32 { + let regs = unsafe { &*hal::device::TIM15::ptr() }; + + loop { + // Check for overflows + if regs.sr.read().uif().bit_is_set() { + regs.sr.modify(|_, w| w.uif().clear_bit()); + unsafe { + OVERFLOWS += 1; + } + } + + let current_value = regs.cnt.read().bits(); + + // If the overflow is still unset, return our latest count, as it indicates we weren't + // pre-empted. + if regs.sr.read().uif().bit_is_clear() { + unsafe { + return (OVERFLOWS * 65535 + current_value) as i32; + } + } + } + } + + unsafe fn reset() { + // Note: The timer must be safely configured in `SystemTimer::initialize()`. + let regs = &*hal::device::TIM15::ptr(); + + regs.cnt.reset(); + } + + fn zero() -> i32 { + 0 + } +} From 854f45ae15f6f5084dfa6274ee8886d7c431b14e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 15 Apr 2021 15:08:57 +0200 Subject: [PATCH 02/34] Updating tick rates --- src/bin/dual-iir.rs | 12 ++++++------ src/hardware/system_timer.rs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index c4ef154ab..e8f12a045 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -103,7 +103,7 @@ const APP: () = { stabilizer.dacs.1.start(); // Start sampling ADCs. - //stabilizer.adc_dac_timer.start(); + stabilizer.adc_dac_timer.start(); c.schedule.telemetry(c.start).unwrap(); @@ -231,16 +231,16 @@ const APP: () = { // Serialize telemetry outside of a critical section to prevent blocking the processing // task. - let _telemetry = miniconf::serde_json_core::to_string::< + let telemetry = miniconf::serde_json_core::to_string::< heapless::consts::U256, _, >(&telemetry) .unwrap(); - //c.resources.mqtt_interface.client(|client| { - // // TODO: Incorporate current MQTT prefix instead of hard-coded value. - // client.publish("dt/sinara/dual-iir/telemetry", telemetry.as_bytes(), minimq::QoS::AtMostOnce, &[]).ok() - //}); + c.resources.mqtt_interface.client(|client| { + // TODO: Incorporate current MQTT prefix instead of hard-coded value. + client.publish("dt/sinara/dual-iir/telemetry", telemetry.as_bytes(), minimq::QoS::AtMostOnce, &[]).ok() + }); // Schedule the telemetry task in the future. c.schedule diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 85938ba7e..b964a6781 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -10,14 +10,14 @@ impl SystemTimer { timer.pause(); // Have the system timer operate at a tick rate of 10KHz (100uS per tick). With this // configuration and a 65535 period, we get an overflow once every 6.5 seconds. - timer.set_tick_freq(10.khz()); + timer.set_tick_freq(1.mhz()); timer.apply_freq(); timer.resume(); } pub fn ticks_from_secs(secs: u32) -> i32 { - (secs * 10_000) as i32 + (secs * 1_000_000) as i32 } } @@ -27,7 +27,7 @@ impl rtic::Monotonic for SystemTimer { fn ratio() -> rtic::Fraction { rtic::Fraction { numerator: 1, - denominator: 40000, + denominator: 400, } } From 403ff16dde8eca1f7a14b8b49d65e4cf1e7e2532 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 15 Apr 2021 15:44:03 +0200 Subject: [PATCH 03/34] Resetting IP --- src/hardware/design_parameters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 0ae4c3557..9a4279bee 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -51,4 +51,4 @@ pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The MQTT broker IPv4 address -pub const MQTT_BROKER: [u8; 4] = [10, 35, 16, 10]; +pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10]; From afcf058590931327f621870ed04904ff92ef8f10 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 20 Apr 2021 14:12:47 +0200 Subject: [PATCH 04/34] Refactoring telemetry to support binaries --- src/bin/dual-iir.rs | 25 +--- src/bin/lockin-internal.rs | 143 ---------------------- src/bin/{lockin-external.rs => lockin.rs} | 73 ++++++++++- src/net/mod.rs | 3 + src/net/telemetry.rs | 18 +++ 5 files changed, 92 insertions(+), 170 deletions(-) delete mode 100644 src/bin/lockin-internal.rs rename src/bin/{lockin-external.rs => lockin.rs} (75%) create mode 100644 src/net/telemetry.rs diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 83cf76407..9f4898992 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -5,7 +5,7 @@ use stabilizer::{hardware, net}; use miniconf::{minimq, Miniconf}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use dsp::iir; use hardware::{ @@ -20,13 +20,6 @@ const SCALE: f32 = i16::MAX as _; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; -#[derive(Serialize, Clone)] -pub struct Telemetry { - latest_samples: [i16; 2], - latest_outputs: [i16; 2], - digital_inputs: [bool; 2], -} - #[derive(Clone, Copy, Debug, Deserialize, Miniconf)] pub struct Settings { afe: [AfeGain; 2], @@ -48,17 +41,7 @@ impl Default for Settings { } } -impl Default for Telemetry { - fn default() -> Self { - Self { - latest_samples: [0, 0], - latest_outputs: [0, 0], - digital_inputs: [false, false], - } - } -} - -#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = crate::hardware::SystemTimer)] +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)] const APP: () = { struct Resources { afes: (AFE0, AFE1), @@ -66,7 +49,7 @@ const APP: () = { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), mqtt_config: MiniconfInterface, - telemetry: Telemetry, + telemetry: net::Telemetry, settings: Settings, // Format: iir_state[ch][cascade-no][coeff] @@ -107,7 +90,7 @@ const APP: () = { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, - telemetry: Telemetry::default(), + telemetry: net::Telemetry::default(), digital_inputs: stabilizer.digital_inputs, mqtt_config, settings: Settings::default(), diff --git a/src/bin/lockin-internal.rs b/src/bin/lockin-internal.rs deleted file mode 100644 index ee9da2fbf..000000000 --- a/src/bin/lockin-internal.rs +++ /dev/null @@ -1,143 +0,0 @@ -#![deny(warnings)] -#![no_std] -#![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}; - -// 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; 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)] -const APP: () = { - struct Resources { - afes: (AFE0, AFE1), - adc: Adc1Input, - dacs: (Dac0Output, Dac1Output), - - lockin: Lockin, - } - - #[init] - fn init(c: init::Context) -> init::LateResources { - // Configure the microcontroller - let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - - // Enable ADC/DAC events - stabilizer.adcs.1.start(); - stabilizer.dacs.0.start(); - stabilizer.dacs.1.start(); - - // Start sampling ADCs. - stabilizer.adc_dac_timer.start(); - - init::LateResources { - lockin: Lockin::default(), - afes: stabilizer.afes, - adc: stabilizer.adcs.1, - dacs: stabilizer.dacs, - } - } - - /// Main DSP processing routine. - /// - /// See `dual-iir` for general notes on processing time and timing. - /// - /// 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; - let adc_samples = c.resources.adc.acquire_buffer(); - let dac_samples = [ - c.resources.dacs.0.acquire_buffer(), - c.resources.dacs.1.acquire_buffer(), - ]; - - // Reference phase and frequency are known. - 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 (complex conjugate) - let harmonic: i32 = -1; - - // Demodulation LO phase offset - 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.wrapping_mul(harmonic)); - - let output: Complex = adc_samples - .iter() - // Zip in the LO phase. - .zip(Accu::new(sample_phase, sample_frequency)) - // Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter) - .map(|(&sample, phase)| { - let s = (sample as i16 as i32) << 16; - lockin.update(s, phase, time_constant) - }) - // Decimate - .last() - .unwrap() - * 2; // Full scale assuming the 2f component is gone. - - // 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 { - cortex_m::asm::wfi(); - } - } - - #[task(binds = ETH, priority = 1)] - fn eth(_: eth::Context) { - unsafe { stm32h7xx_hal::ethernet::interrupt_handler() } - } - - #[task(binds = SPI2, priority = 3)] - fn spi2(_: spi2::Context) { - panic!("ADC0 input overrun"); - } - - #[task(binds = SPI3, priority = 3)] - fn spi3(_: spi3::Context) { - panic!("ADC1 input overrun"); - } - - #[task(binds = SPI4, priority = 3)] - fn spi4(_: spi4::Context) { - panic!("DAC0 output error"); - } - - #[task(binds = SPI5, priority = 3)] - fn spi5(_: spi5::Context) { - panic!("DAC1 output error"); - } - - extern "C" { - // hw interrupt handlers for RTIC to use for scheduling tasks - // one per priority - fn DCMI(); - fn JPEG(); - fn SDMMC(); - } -}; diff --git a/src/bin/lockin-external.rs b/src/bin/lockin.rs similarity index 75% rename from src/bin/lockin-external.rs rename to src/bin/lockin.rs index 082b0d23f..f2f71816d 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin.rs @@ -2,6 +2,7 @@ #![no_std] #![no_main] +use embedded_hal::digital::v2::InputPin; use generic_array::typenum::U4; use serde::Deserialize; @@ -12,10 +13,11 @@ use stabilizer::net; use stabilizer::hardware::{ design_parameters, setup, Adc0Input, Adc1Input, AfeGain, Dac0Output, - Dac1Output, InputStamper, AFE0, AFE1, + Dac1Output, DigitalInput0, DigitalInput1, InputStamper, SystemTimer, AFE0, + AFE1, }; -use miniconf::Miniconf; +use miniconf::{minimq, Miniconf}; use stabilizer::net::{Action, MiniconfInterface}; #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] @@ -36,6 +38,7 @@ pub struct Settings { lockin_phase: i32, output_conf: [Conf; 2], + telemetry_period_secs: u16, } impl Default for Settings { @@ -50,11 +53,12 @@ impl Default for Settings { lockin_phase: 0, // Demodulation LO phase offset output_conf: [Conf::Quadrature; 2], + telemetry_period_secs: 10, } } } -#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)] const APP: () = { struct Resources { afes: (AFE0, AFE1), @@ -62,13 +66,15 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), mqtt_config: MiniconfInterface, settings: Settings, + telemetry: net::Telemetry, + digital_inputs: (DigitalInput0, DigitalInput1), timestamper: InputStamper, pll: RPLL, lockin: Lockin, } - #[init(spawn=[settings_update])] + #[init(spawn=[settings_update, telemetry])] fn init(c: init::Context) -> init::LateResources { // Configure the microcontroller let (mut stabilizer, _pounder) = setup(c.core, c.device); @@ -91,8 +97,9 @@ const APP: () = { + design_parameters::SAMPLE_BUFFER_SIZE_LOG2, ); - // Spawn a settings update for default settings. + // Spawn a settings and telemetry update for default settings. c.spawn.settings_update().unwrap(); + c.spawn.telemetry().unwrap(); // Enable ADC/DAC events stabilizer.adcs.0.start(); @@ -113,8 +120,10 @@ const APP: () = { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, + digital_inputs: stabilizer.digital_inputs, mqtt_config, timestamper: stabilizer.timestamper, + telemetry: net::Telemetry::default(), settings, @@ -130,7 +139,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, lockin, timestamper, pll, settings], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), @@ -193,6 +202,14 @@ const APP: () = { dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000; dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000; } + + // Update telemetry measurements. + // TODO: Should we report these as voltages? + c.resources.telemetry.latest_samples = + [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; + + c.resources.telemetry.latest_outputs = + [dac_samples[0][0] as i16, dac_samples[1][0] as i16]; } #[idle(resources=[mqtt_config], spawn=[settings_update])] @@ -222,6 +239,50 @@ const APP: () = { c.resources.settings.lock(|current| *current = *settings); } + #[task(priority = 1, resources=[mqtt_config, digital_inputs, settings, telemetry], schedule=[telemetry])] + fn telemetry(mut c: telemetry::Context) { + let mut telemetry = + c.resources.telemetry.lock(|telemetry| telemetry.clone()); + + telemetry.digital_inputs = [ + c.resources.digital_inputs.0.is_high().unwrap(), + c.resources.digital_inputs.1.is_high().unwrap(), + ]; + + // Serialize telemetry outside of a critical section to prevent blocking the processing + // task. + let telemetry = miniconf::serde_json_core::to_string::< + heapless::consts::U256, + _, + >(&telemetry) + .unwrap(); + + c.resources.mqtt_config.mqtt.client(|client| { + // TODO: Incorporate current MQTT prefix instead of hard-coded value. + client + .publish( + "dt/sinara/dual-iir/telemetry", + telemetry.as_bytes(), + minimq::QoS::AtMostOnce, + &[], + ) + .ok() + }); + + let telemetry_period = c + .resources + .settings + .lock(|settings| settings.telemetry_period_secs); + + // Schedule the telemetry task in the future. + c.schedule + .telemetry( + c.scheduled + + SystemTimer::ticks_from_secs(telemetry_period as u32), + ) + .unwrap(); + } + #[task(binds = ETH, priority = 1)] fn eth(_: eth::Context) { unsafe { stm32h7xx_hal::ethernet::interrupt_handler() } diff --git a/src/net/mod.rs b/src/net/mod.rs index 13b514cbd..2a4ba63d1 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -7,6 +7,9 @@ use core::fmt::Write; use heapless::{consts, String}; use miniconf::minimq; +mod telemetry; +pub use telemetry::Telemetry; + /// Potential actions for firmware to take. pub enum Action { /// Indicates that firmware can sleep for the next event. diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs new file mode 100644 index 000000000..5c948d58f --- /dev/null +++ b/src/net/telemetry.rs @@ -0,0 +1,18 @@ +use serde::Serialize; + +#[derive(Serialize, Clone)] +pub struct Telemetry { + pub latest_samples: [i16; 2], + pub latest_outputs: [i16; 2], + pub digital_inputs: [bool; 2], +} + +impl Default for Telemetry { + fn default() -> Self { + Self { + latest_samples: [0, 0], + latest_outputs: [0, 0], + digital_inputs: [false, false], + } + } +} From 3ae319d1ac242d250e7b69715b3879a7bb286d7a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 20 Apr 2021 14:13:07 +0200 Subject: [PATCH 05/34] Updating readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index f91976a4b..80cda1d4a 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,7 @@ to implement different use cases. Several applications are provides by default * anti-windup * derivative kick avoidance -### Lockin external - -### Lockin internal +### Lockin ## Minimal bootstrapping documentation From e746e2a12c8f5e04b5b0682b436692649340007c Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 29 Apr 2021 15:55:36 +0200 Subject: [PATCH 06/34] Adding WIP telemetry --- src/bin/dual-iir.rs | 7 ++++--- src/bin/lockin.rs | 7 ++++--- src/net/mod.rs | 2 +- src/net/telemetry.rs | 31 ++++++++++++++++++++++++++++--- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 4ceb04c22..f043a403a 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -57,7 +57,7 @@ const APP: () = { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), mqtt: MqttInterface, - telemetry: net::Telemetry, + telemetry: net::TelemetryBuffer, settings: Settings, #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -99,7 +99,7 @@ const APP: () = { dacs: stabilizer.dacs, mqtt, digital_inputs: stabilizer.digital_inputs, - telemetry: net::Telemetry::default(), + telemetry: net::TelemetryBuffer::default(), settings: Settings::default(), } } @@ -197,7 +197,8 @@ const APP: () = { let telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); - c.resources.mqtt.publish_telemetry(&telemetry); + let gains = c.resources.settings.lock(|settings| settings.afe.clone()); + c.resources.mqtt.publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 6e0b4c9d1..13408ff0d 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -66,7 +66,7 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), mqtt: MqttInterface, settings: Settings, - telemetry: net::Telemetry, + telemetry: net::TelemetryBuffer, digital_inputs: (DigitalInput0, DigitalInput1), timestamper: InputStamper, @@ -123,7 +123,7 @@ const APP: () = { mqtt, digital_inputs: stabilizer.digital_inputs, timestamper: stabilizer.timestamper, - telemetry: net::Telemetry::default(), + telemetry: net::TelemetryBuffer::default(), settings, @@ -245,7 +245,8 @@ const APP: () = { c.resources.digital_inputs.1.is_high().unwrap(), ]; - c.resources.mqtt.publish_telemetry(&telemetry); + let gains = c.resources.settings.lock(|settings| settings.afe.clone()); + c.resources.mqtt.publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/net/mod.rs b/src/net/mod.rs index 6a3665270..f6dbe2856 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -15,7 +15,7 @@ use messages::{MqttMessage, SettingsResponse, SettingsResponseCode}; pub use mqtt_interface::MqttInterface; mod telemetry; -pub use telemetry::Telemetry; +pub use telemetry::{Telemetry, TelemetryBuffer}; /// Potential actions for firmware to take. pub enum Action { diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 5c948d58f..f5736a63b 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -1,13 +1,22 @@ use serde::Serialize; -#[derive(Serialize, Clone)] -pub struct Telemetry { +use crate::hardware::AfeGain; + +#[derive(Copy, Clone)] +pub struct TelemetryBuffer { pub latest_samples: [i16; 2], pub latest_outputs: [i16; 2], pub digital_inputs: [bool; 2], } -impl Default for Telemetry { +#[derive(Serialize)] +pub struct Telemetry { + input_levels: [f32; 2], + output_levels: [f32; 2], + digital_inputs: [bool; 2] +} + +impl Default for TelemetryBuffer { fn default() -> Self { Self { latest_samples: [0, 0], @@ -16,3 +25,19 @@ impl Default for Telemetry { } } } + +impl TelemetryBuffer { + pub fn to_telemetry(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { + let in0_volts = self.latest_samples[0] as f32 / (i16::MAX as f32 * 5.0 * afe0.to_multiplier() as f32) * 4.096; + let in1_volts = self.latest_samples[1] as f32 / (i16::MAX as f32 * 5.0 * afe1.to_multiplier() as f32) * 4.096; + + let out0_volts = self.latest_outputs[0] as f32 / (i16::MAX as f32) * 10.24; + let out1_volts = self.latest_outputs[1] as f32 / (i16::MAX as f32) * 10.24; + + Telemetry { + input_levels: [in0_volts, in1_volts], + output_levels: [out0_volts, out1_volts], + digital_inputs: self.digital_inputs, + } + } +} From 4169cd82515e38bbeae5033ae91f56047077bbb8 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 29 Apr 2021 16:22:06 +0200 Subject: [PATCH 07/34] Adding AFE functions --- src/hardware/afe.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/hardware/afe.rs b/src/hardware/afe.rs index 9e3674bbd..836a3d09b 100644 --- a/src/hardware/afe.rs +++ b/src/hardware/afe.rs @@ -20,6 +20,18 @@ pub struct ProgrammableGainAmplifier { a1: A1, } +impl Gain { + /// Get the AFE gain as a multiplying integer. + pub fn to_multiplier(&self) -> u8 { + match self { + Gain::G1 => 1, + Gain::G2 => 2, + Gain::G5 => 5, + Gain::G10 => 10, + } + } +} + impl TryFrom for Gain { type Error = (); From b35250efbf839c87d91216b674182e045d0bbdda Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 29 Apr 2021 17:39:19 +0200 Subject: [PATCH 08/34] Fixing ADC/voltage conversion functions --- src/bin/dual-iir.rs | 6 ++++-- src/bin/lockin.rs | 7 ++++--- src/net/telemetry.rs | 22 ++++++++++++++++------ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index f043a403a..1416e837f 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -160,7 +160,7 @@ const APP: () = { [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; c.resources.telemetry.latest_outputs = - [dac_samples[0][0] as i16, dac_samples[1][0] as i16]; + [dac_samples[0][0], dac_samples[1][0]]; c.resources.telemetry.digital_inputs = [ c.resources.digital_inputs.0.is_high().unwrap(), @@ -198,7 +198,9 @@ const APP: () = { c.resources.telemetry.lock(|telemetry| telemetry.clone()); let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - c.resources.mqtt.publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + c.resources + .mqtt + .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 13408ff0d..69df37cd9 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -204,12 +204,11 @@ const APP: () = { } // Update telemetry measurements. - // TODO: Should we report these as voltages? c.resources.telemetry.latest_samples = [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; c.resources.telemetry.latest_outputs = - [dac_samples[0][0] as i16, dac_samples[1][0] as i16]; + [dac_samples[0][0], dac_samples[1][0]]; } #[idle(resources=[mqtt], spawn=[settings_update])] @@ -246,7 +245,9 @@ const APP: () = { ]; let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - c.resources.mqtt.publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + c.resources + .mqtt + .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index f5736a63b..dccf3ac6e 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -5,7 +5,7 @@ use crate::hardware::AfeGain; #[derive(Copy, Clone)] pub struct TelemetryBuffer { pub latest_samples: [i16; 2], - pub latest_outputs: [i16; 2], + pub latest_outputs: [u16; 2], pub digital_inputs: [bool; 2], } @@ -13,7 +13,7 @@ pub struct TelemetryBuffer { pub struct Telemetry { input_levels: [f32; 2], output_levels: [f32; 2], - digital_inputs: [bool; 2] + digital_inputs: [bool; 2], } impl Default for TelemetryBuffer { @@ -28,11 +28,21 @@ impl Default for TelemetryBuffer { impl TelemetryBuffer { pub fn to_telemetry(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { - let in0_volts = self.latest_samples[0] as f32 / (i16::MAX as f32 * 5.0 * afe0.to_multiplier() as f32) * 4.096; - let in1_volts = self.latest_samples[1] as f32 / (i16::MAX as f32 * 5.0 * afe1.to_multiplier() as f32) * 4.096; + let in0_volts = + (self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0 + * 5.0 + / afe0.to_multiplier() as f32; + let in1_volts = + (self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0 + * 5.0 + / afe1.to_multiplier() as f32; - let out0_volts = self.latest_outputs[0] as f32 / (i16::MAX as f32) * 10.24; - let out1_volts = self.latest_outputs[1] as f32 / (i16::MAX as f32) * 10.24; + let out0_volts = (10.24 * 2.0) + * (self.latest_outputs[0] as f32 / (u16::MAX as f32)) + - 10.24; + let out1_volts = (10.24 * 2.0) + * (self.latest_outputs[1] as f32 / (u16::MAX as f32)) + - 10.24; Telemetry { input_levels: [in0_volts, in1_volts], From 4888f18f880bf5b3e2673c5120ffae9f237d82db Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 4 May 2021 19:52:41 +0200 Subject: [PATCH 09/34] Adding rework to network module --- src/net/mod.rs | 20 +++++++----- src/net/mqtt_interface.rs | 66 +++++++-------------------------------- src/net/shared.rs | 57 +++++++++++++++++++++++++++++++++ src/net/stack_manager.rs | 44 ++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 62 deletions(-) create mode 100644 src/net/shared.rs create mode 100644 src/net/stack_manager.rs diff --git a/src/net/mod.rs b/src/net/mod.rs index 9b4d22de3..01270178c 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -11,19 +11,23 @@ use core::fmt::Write; mod messages; mod mqtt_interface; +mod shared; +mod stack_manager; use messages::{MqttMessage, SettingsResponse}; -pub use mqtt_interface::MqttInterface; +pub use mqtt_interface::MiniconfClient; +pub use stack_manager::NetworkProcessor; + +pub use shared::NetworkManager; + +use crate::hardware::NetworkStack; +pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>; mod telemetry; pub use telemetry::{Telemetry, TelemetryBuffer}; -/// Potential actions for firmware to take. -pub enum Action { - /// Indicates that firmware can sleep for the next event. - Sleep, - - /// Indicates that settings have updated and firmware needs to propogate changes. - UpdateSettings, +pub enum UpdateState { + NoChange, + Updated, } /// Get the MQTT prefix of a device. diff --git a/src/net/mqtt_interface.rs b/src/net/mqtt_interface.rs index cef26dd0b..02a6ba63e 100644 --- a/src/net/mqtt_interface.rs +++ b/src/net/mqtt_interface.rs @@ -1,27 +1,22 @@ -use crate::hardware::{ - design_parameters::MQTT_BROKER, CycleCounter, EthernetPhy, NetworkStack, -}; +use crate::hardware::design_parameters::MQTT_BROKER; use heapless::{consts, String}; -use super::{Action, MqttMessage, SettingsResponse}; +use super::{UpdateState, MqttMessage, SettingsResponse, NetworkReference}; /// MQTT settings interface. -pub struct MqttInterface +pub struct MiniconfClient where S: miniconf::Miniconf + Default + Clone, { default_response_topic: String, - mqtt: minimq::MqttClient, + mqtt: minimq::MqttClient, settings: S, - clock: CycleCounter, - phy: EthernetPhy, - network_was_reset: bool, subscribed: bool, settings_prefix: String, } -impl MqttInterface +impl MiniconfClient where S: miniconf::Miniconf + Default + Clone, { @@ -31,14 +26,10 @@ where /// * `stack` - The network stack to use for communication. /// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning. /// * `prefix` - The MQTT device prefix to use for this device. - /// * `phy` - The PHY driver for querying the link state. - /// * `clock` - The clock to utilize for querying the current system time. pub fn new( - stack: NetworkStack, + stack: NetworkReference, client_id: &str, prefix: &str, - phy: EthernetPhy, - clock: CycleCounter, ) -> Self { let mqtt = minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) @@ -54,10 +45,7 @@ where mqtt, settings: S::default(), settings_prefix, - clock, - phy, default_response_topic: response_topic, - network_was_reset: false, subscribed: false, } } @@ -66,30 +54,7 @@ where /// /// # Returns /// An option containing an action that should be completed as a result of network servicing. - pub fn update(&mut self) -> Option { - // First, service the network stack to process any inbound and outbound traffic. - let sleep = match self.mqtt.network_stack.poll(self.clock.current_ms()) - { - Ok(updated) => !updated, - Err(err) => { - log::info!("Network error: {:?}", err); - false - } - }; - - // If the PHY indicates there's no more ethernet link, reset the DHCP server in the network - // stack. - match self.phy.poll_link() { - true => self.network_was_reset = false, - - // Only reset the network stack once per link reconnection. This prevents us from - // sending an excessive number of DHCP requests. - false if !self.network_was_reset => { - self.network_was_reset = true; - self.mqtt.network_stack.handle_link_reset(); - } - _ => {}, - }; + pub fn update(&mut self) -> UpdateState { let mqtt_connected = match self.mqtt.is_connected() { Ok(connected) => connected, @@ -165,26 +130,19 @@ where .ok(); }) { // If settings updated, - Ok(_) => { - if update { - Some(Action::UpdateSettings) - } else if sleep { - Some(Action::Sleep) - } else { - None - } - } + Ok(_) if update => UpdateState::Updated, + Ok(_) => UpdateState::NoChange, Err(minimq::Error::Disconnected) => { self.subscribed = false; - None + UpdateState::NoChange } Err(minimq::Error::Network( smoltcp_nal::NetworkError::NoIpAddress, - )) => None, + )) => UpdateState::NoChange, Err(error) => { log::info!("Unexpected error: {:?}", error); - None + UpdateState::NoChange } } } diff --git a/src/net/shared.rs b/src/net/shared.rs new file mode 100644 index 000000000..c8f0b771e --- /dev/null +++ b/src/net/shared.rs @@ -0,0 +1,57 @@ +use shared_bus::{AtomicCheckMutex, BusMutex}; +use minimq::embedded_nal; +use smoltcp_nal::smoltcp; + +use crate::hardware::NetworkStack; + +pub struct NetworkStackProxy<'a, S> { + mutex: &'a AtomicCheckMutex +} + +impl<'a> NetworkStackProxy<'a, NetworkStack> { + pub fn poll(&mut self, now: u32) -> Result { + self.mutex.lock(|stack| stack.poll(now)) + } + pub fn handle_link_reset(&mut self) { + self.mutex.lock(|stack| stack.handle_link_reset()) + } +} + +macro_rules! forward { + ($func:ident($($v:ident: $IT:ty),*) -> $T:ty) => { + fn $func(&self, $($v: $IT),*) -> $T { + self.mutex.lock(|stack| stack.$func($($v),*)) + } + } +} + +impl<'a, S> embedded_nal::TcpStack for NetworkStackProxy<'a, S> +where + S: embedded_nal::TcpStack +{ + type TcpSocket = S::TcpSocket; + type Error = S::Error; + + forward! {open(mode: embedded_nal::Mode) -> Result} + forward! {connect(socket: S::TcpSocket, remote: embedded_nal::SocketAddr) -> Result} + forward! {is_connected(socket: &S::TcpSocket) -> Result} + forward! {write(socket: &mut S::TcpSocket, buffer: &[u8]) -> embedded_nal::nb::Result} + forward! {read(socket: &mut S::TcpSocket, buffer: &mut [u8]) -> embedded_nal::nb::Result} + forward! {close(socket: S::TcpSocket) -> Result<(), S::Error>} +} + +pub struct NetworkManager { + mutex: AtomicCheckMutex +} + +impl NetworkManager { + pub fn new(stack: NetworkStack) -> Self { + Self { mutex: AtomicCheckMutex::create(stack) } + } + + pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> { + NetworkStackProxy { + mutex: &self.mutex + } + } +} diff --git a/src/net/stack_manager.rs b/src/net/stack_manager.rs new file mode 100644 index 000000000..32841881e --- /dev/null +++ b/src/net/stack_manager.rs @@ -0,0 +1,44 @@ +use super::{UpdateState, NetworkReference}; + +use crate::hardware::{EthernetPhy, CycleCounter}; + +pub struct NetworkProcessor { + stack: NetworkReference, + phy: EthernetPhy, + clock: CycleCounter, + network_was_reset: bool, +} + +impl NetworkProcessor { + pub fn new(stack: NetworkReference, phy: EthernetPhy, clock: CycleCounter) -> Self { + Self { stack, phy, clock, network_was_reset: false } + } + + pub fn update(&mut self) -> UpdateState { + // Service the network stack to process any inbound and outbound traffic. + let result = match self.stack.poll(self.clock.current_ms()) { + Ok(true) => UpdateState::Updated, + Ok(false) => UpdateState::NoChange, + Err(err) => { + log::info!("Network error: {:?}", err); + UpdateState::Updated + } + }; + + // If the PHY indicates there's no more ethernet link, reset the DHCP server in the network + // stack. + match self.phy.poll_link() { + true => self.network_was_reset = false, + + // Only reset the network stack once per link reconnection. This prevents us from + // sending an excessive number of DHCP requests. + false if !self.network_was_reset => { + self.network_was_reset = true; + self.stack.handle_link_reset(); + } + _ => {}, + }; + + result + } +} From 06b328ff52696bd82eb703b7d29d2b4b46fcc54f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 14:42:17 +0200 Subject: [PATCH 10/34] Adding WIP updates for telemetry --- Cargo.lock | 11 +++++++ Cargo.toml | 1 + src/bin/dual-iir.rs | 79 +++++++++++++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae9ef69e7..41d1676b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -708,6 +708,16 @@ dependencies = [ "syn", ] +[[package]] +name = "shared-bus" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b60428415b23ed3f0e3abc776e10e548cf2cbb4288e73d5d181a02b5a90b95" +dependencies = [ + "cortex-m 0.6.7", + "embedded-hal", +] + [[package]] name = "smoltcp" version = "0.7.1" @@ -753,6 +763,7 @@ dependencies = [ "panic-semihosting", "paste", "serde", + "shared-bus", "smoltcp-nal", "stm32h7xx-hal", ] diff --git a/Cargo.toml b/Cargo.toml index 226e4d4e1..b6279a83d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ dsp = { path = "dsp" } ad9959 = { path = "ad9959" } generic-array = "0.14" miniconf = "0.1.0" +shared-bus = {version = "0.2.2", features = ["cortex-m"] } [dependencies.mcp23017] git = "https://github.com/mrd0ll4r/mcp23017.git" diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index c0232367c..7762d5b51 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -13,7 +13,7 @@ use hardware::{ DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; -use net::{Action, MqttInterface}; +use net::{UpdateState, MiniconfClient, NetworkProcessor}; const SCALE: f32 = i16::MAX as _; @@ -49,6 +49,11 @@ impl Default for Settings { } } +struct NetworkUsers { + miniconf: MiniconfClient, + processor: NetworkProcessor, +} + #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)] const APP: () = { struct Resources { @@ -56,8 +61,8 @@ const APP: () = { digital_inputs: (DigitalInput0, DigitalInput1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - mqtt: MqttInterface, - telemetry: net::TelemetryBuffer, + network: NetworkUsers, + settings: Settings, #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -69,16 +74,31 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - let mqtt = MqttInterface::new( - stabilizer.net.stack, - "", - &net::get_device_prefix( - env!("CARGO_BIN_NAME"), - stabilizer.net.mac_address, - ), - stabilizer.net.phy, - stabilizer.cycle_counter, - ); + let network = { + let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stabilizer.net.stack)).unwrap(); + + let processor = NetworkProcessor::new( + stack_manager.acquire_stack(), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); + + let settings = MqttInterface::new( + stack_manager.acquire_stack(), + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + ); + + // TODO: Add telemetry client + + NetworkUsers { + miniconf: settings, + processor, + } + }; // Spawn a settings update for default settings. c.spawn.settings_update().unwrap(); @@ -93,11 +113,12 @@ const APP: () = { // Start sampling ADCs. stabilizer.adc_dac_timer.start(); + init::LateResources { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, - mqtt, + network, digital_inputs: stabilizer.digital_inputs, telemetry: net::TelemetryBuffer::default(), settings: Settings::default(), @@ -168,23 +189,25 @@ const APP: () = { ]; } - #[idle(resources=[mqtt], spawn=[settings_update])] + #[idle(resources=[network], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { loop { - match c.resources.mqtt.lock(|mqtt| mqtt.update()) { - Some(Action::Sleep) => cortex_m::asm::wfi(), - Some(Action::UpdateSettings) => { - c.spawn.settings_update().unwrap() - } - _ => {} + // Update the smoltcp network stack. + let poll_result = c.resources.network.lock(|network| network.processor.poll()); + + // Service the MQTT configuration client. + if c.resources.miniconf_client.lock(|client| client.update()) == UpdateStatus::Updated { + c.spawn.settings_update().unwrap() + } else if poll_result == UpdateStatus::NoChange { + cortex_m::asm::wfi(); } } } - #[task(priority = 1, resources=[mqtt, afes, settings])] + #[task(priority = 1, resources=[network, afes, settings])] fn settings_update(mut c: settings_update::Context) { // Update the IIR channels. - let settings = c.resources.mqtt.settings(); + let settings = c.resources.network.miniconf.settings(); c.resources.settings.lock(|current| *current = *settings); // Update AFEs @@ -192,15 +215,17 @@ const APP: () = { c.resources.afes.1.set_gain(settings.afe[1]); } - #[task(priority = 1, resources=[mqtt, settings, telemetry], schedule=[telemetry])] + #[task(priority = 1, resources=[network, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { let telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - c.resources - .mqtt - .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + + // TODO: Publish telemetry through the telemetry client here. + //c.resources + // .mqtt + // .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources From 4a656eedd24fae09f17c23d4d2a2b3d57c81191c Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 15:39:33 +0200 Subject: [PATCH 11/34] Adding refactor for telemetry --- src/bin/dual-iir.rs | 36 ++++++++++------- src/bin/lockin.rs | 83 ++++++++++++++++++++++++++------------- src/net/mod.rs | 19 ++++++--- src/net/mqtt_interface.rs | 9 +---- src/net/shared.rs | 26 ++++++------ src/net/stack_manager.rs | 25 ++++++++---- 6 files changed, 121 insertions(+), 77 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 7762d5b51..f83ad1c31 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -13,7 +13,10 @@ use hardware::{ DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; -use net::{UpdateState, MiniconfClient, NetworkProcessor}; +use net::{ + MiniconfClient, NetworkManager, NetworkProcessor, NetworkUsers, + TelemetryBuffer, UpdateState, +}; const SCALE: f32 = i16::MAX as _; @@ -49,11 +52,6 @@ impl Default for Settings { } } -struct NetworkUsers { - miniconf: MiniconfClient, - processor: NetworkProcessor, -} - #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = stabilizer::hardware::SystemTimer)] const APP: () = { struct Resources { @@ -61,9 +59,10 @@ const APP: () = { digital_inputs: (DigitalInput0, DigitalInput1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - network: NetworkUsers, + network: NetworkUsers, settings: Settings, + telemetry: TelemetryBuffer, #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] iir_state: [[iir::Vec5; IIR_CASCADE_LENGTH]; 2], @@ -75,7 +74,8 @@ const APP: () = { let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); let network = { - let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stabilizer.net.stack)).unwrap(); + let stack = stabilizer.net.stack; + let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)).unwrap(); let processor = NetworkProcessor::new( stack_manager.acquire_stack(), @@ -83,7 +83,7 @@ const APP: () = { stabilizer.cycle_counter, ); - let settings = MqttInterface::new( + let settings = MiniconfClient::new( stack_manager.acquire_stack(), "", &net::get_device_prefix( @@ -113,7 +113,6 @@ const APP: () = { // Start sampling ADCs. stabilizer.adc_dac_timer.start(); - init::LateResources { afes: stabilizer.afes, adcs: stabilizer.adcs, @@ -193,12 +192,19 @@ const APP: () = { fn idle(mut c: idle::Context) -> ! { loop { // Update the smoltcp network stack. - let poll_result = c.resources.network.lock(|network| network.processor.poll()); + let poll_result = c + .resources + .network + .lock(|network| network.processor.update()); // Service the MQTT configuration client. - if c.resources.miniconf_client.lock(|client| client.update()) == UpdateStatus::Updated { + if c.resources + .network + .lock(|network| network.miniconf.update()) + == UpdateState::Updated + { c.spawn.settings_update().unwrap() - } else if poll_result == UpdateStatus::NoChange { + } else if poll_result == UpdateState::NoChange { cortex_m::asm::wfi(); } } @@ -217,10 +223,10 @@ const APP: () = { #[task(priority = 1, resources=[network, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { - let telemetry = + let _telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); - let gains = c.resources.settings.lock(|settings| settings.afe.clone()); + let _gains = c.resources.settings.lock(|settings| settings.afe.clone()); // TODO: Publish telemetry through the telemetry client here. //c.resources diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index fdeb83d05..ed7f4f301 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -18,7 +18,9 @@ use stabilizer::hardware::{ }; use miniconf::Miniconf; -use stabilizer::net::{Action, MqttInterface}; +use stabilizer::net::{ + MiniconfClient, NetworkManager, NetworkProcessor, NetworkUsers, UpdateState, +}; #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { @@ -64,7 +66,7 @@ const APP: () = { afes: (AFE0, AFE1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - mqtt: MqttInterface, + network: NetworkUsers, settings: Settings, telemetry: net::TelemetryBuffer, digital_inputs: (DigitalInput0, DigitalInput1), @@ -79,16 +81,32 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = setup(c.core, c.device); - let mqtt = MqttInterface::new( - stabilizer.net.stack, - "", - &net::get_device_prefix( - env!("CARGO_BIN_NAME"), - stabilizer.net.mac_address, - ), - stabilizer.net.phy, - stabilizer.cycle_counter, - ); + let network = { + let stack = stabilizer.net.stack; + let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)).unwrap(); + + let processor = NetworkProcessor::new( + stack_manager.acquire_stack(), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); + + let settings = MiniconfClient::new( + stack_manager.acquire_stack(), + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + ); + + // TODO: Add telemetry client + + NetworkUsers { + miniconf: settings, + processor, + } + }; let settings = Settings::default(); @@ -120,7 +138,7 @@ const APP: () = { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, - mqtt, + network, digital_inputs: stabilizer.digital_inputs, timestamper: stabilizer.timestamper, telemetry: net::TelemetryBuffer::default(), @@ -211,22 +229,31 @@ const APP: () = { [dac_samples[0][0], dac_samples[1][0]]; } - #[idle(resources=[mqtt], spawn=[settings_update])] + #[idle(resources=[network], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { loop { - match c.resources.mqtt.lock(|mqtt| mqtt.update()) { - Some(Action::Sleep) => cortex_m::asm::wfi(), - Some(Action::UpdateSettings) => { - c.spawn.settings_update().unwrap() - } - _ => {} + // Update the smoltcp network stack. + let poll_result = c + .resources + .network + .lock(|network| network.processor.update()); + + // Service the MQTT configuration client. + if c.resources + .network + .lock(|network| network.miniconf.update()) + == UpdateState::Updated + { + c.spawn.settings_update().unwrap() + } else if poll_result == UpdateState::NoChange { + cortex_m::asm::wfi(); } } } - #[task(priority = 1, resources=[mqtt, settings, afes])] + #[task(priority = 1, resources=[network, settings, afes])] fn settings_update(mut c: settings_update::Context) { - let settings = c.resources.mqtt.settings(); + let settings = c.resources.network.miniconf.settings(); c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); @@ -234,7 +261,7 @@ const APP: () = { c.resources.settings.lock(|current| *current = *settings); } - #[task(priority = 1, resources=[mqtt, digital_inputs, settings, telemetry], schedule=[telemetry])] + #[task(priority = 1, resources=[network, digital_inputs, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { let mut telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); @@ -244,10 +271,12 @@ const APP: () = { c.resources.digital_inputs.1.is_high().unwrap(), ]; - let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - c.resources - .mqtt - .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + let _gains = c.resources.settings.lock(|settings| settings.afe.clone()); + + // TODO: Publish telemetry. + //c.resources + // .mqtt + // .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/net/mod.rs b/src/net/mod.rs index 01270178c..960150175 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -13,23 +13,30 @@ mod messages; mod mqtt_interface; mod shared; mod stack_manager; +mod telemetry; + +use crate::hardware::NetworkStack; use messages::{MqttMessage, SettingsResponse}; -pub use mqtt_interface::MiniconfClient; -pub use stack_manager::NetworkProcessor; +use miniconf::Miniconf; +pub use mqtt_interface::MiniconfClient; pub use shared::NetworkManager; +pub use stack_manager::NetworkProcessor; +pub use telemetry::{Telemetry, TelemetryBuffer}; -use crate::hardware::NetworkStack; pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>; -mod telemetry; -pub use telemetry::{Telemetry, TelemetryBuffer}; - +#[derive(Copy, Clone, PartialEq)] pub enum UpdateState { NoChange, Updated, } +pub struct NetworkUsers { + pub miniconf: MiniconfClient, + pub processor: NetworkProcessor, +} + /// Get the MQTT prefix of a device. /// /// # Args diff --git a/src/net/mqtt_interface.rs b/src/net/mqtt_interface.rs index 02a6ba63e..6e9cf060f 100644 --- a/src/net/mqtt_interface.rs +++ b/src/net/mqtt_interface.rs @@ -2,7 +2,7 @@ use crate::hardware::design_parameters::MQTT_BROKER; use heapless::{consts, String}; -use super::{UpdateState, MqttMessage, SettingsResponse, NetworkReference}; +use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; /// MQTT settings interface. pub struct MiniconfClient @@ -26,11 +26,7 @@ where /// * `stack` - The network stack to use for communication. /// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning. /// * `prefix` - The MQTT device prefix to use for this device. - pub fn new( - stack: NetworkReference, - client_id: &str, - prefix: &str, - ) -> Self { + pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self { let mqtt = minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) .unwrap(); @@ -55,7 +51,6 @@ where /// # Returns /// An option containing an action that should be completed as a result of network servicing. pub fn update(&mut self) -> UpdateState { - let mqtt_connected = match self.mqtt.is_connected() { Ok(connected) => connected, Err(minimq::Error::Network( diff --git a/src/net/shared.rs b/src/net/shared.rs index c8f0b771e..e0be260a7 100644 --- a/src/net/shared.rs +++ b/src/net/shared.rs @@ -1,19 +1,15 @@ -use shared_bus::{AtomicCheckMutex, BusMutex}; use minimq::embedded_nal; -use smoltcp_nal::smoltcp; +use shared_bus::{AtomicCheckMutex, BusMutex}; use crate::hardware::NetworkStack; pub struct NetworkStackProxy<'a, S> { - mutex: &'a AtomicCheckMutex + mutex: &'a AtomicCheckMutex, } -impl<'a> NetworkStackProxy<'a, NetworkStack> { - pub fn poll(&mut self, now: u32) -> Result { - self.mutex.lock(|stack| stack.poll(now)) - } - pub fn handle_link_reset(&mut self) { - self.mutex.lock(|stack| stack.handle_link_reset()) +impl<'a, S> NetworkStackProxy<'a, S> { + pub fn lock R>(&mut self, f: F) -> R { + self.mutex.lock(|stack| f(stack)) } } @@ -27,7 +23,7 @@ macro_rules! forward { impl<'a, S> embedded_nal::TcpStack for NetworkStackProxy<'a, S> where - S: embedded_nal::TcpStack + S: embedded_nal::TcpStack, { type TcpSocket = S::TcpSocket; type Error = S::Error; @@ -41,17 +37,17 @@ where } pub struct NetworkManager { - mutex: AtomicCheckMutex + mutex: AtomicCheckMutex, } impl NetworkManager { pub fn new(stack: NetworkStack) -> Self { - Self { mutex: AtomicCheckMutex::create(stack) } + Self { + mutex: AtomicCheckMutex::create(stack), + } } pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> { - NetworkStackProxy { - mutex: &self.mutex - } + NetworkStackProxy { mutex: &self.mutex } } } diff --git a/src/net/stack_manager.rs b/src/net/stack_manager.rs index 32841881e..d2ec9ab0c 100644 --- a/src/net/stack_manager.rs +++ b/src/net/stack_manager.rs @@ -1,6 +1,6 @@ -use super::{UpdateState, NetworkReference}; +use super::{NetworkReference, UpdateState}; -use crate::hardware::{EthernetPhy, CycleCounter}; +use crate::hardware::{CycleCounter, EthernetPhy}; pub struct NetworkProcessor { stack: NetworkReference, @@ -10,13 +10,24 @@ pub struct NetworkProcessor { } impl NetworkProcessor { - pub fn new(stack: NetworkReference, phy: EthernetPhy, clock: CycleCounter) -> Self { - Self { stack, phy, clock, network_was_reset: false } + pub fn new( + stack: NetworkReference, + phy: EthernetPhy, + clock: CycleCounter, + ) -> Self { + Self { + stack, + phy, + clock, + network_was_reset: false, + } } pub fn update(&mut self) -> UpdateState { // Service the network stack to process any inbound and outbound traffic. - let result = match self.stack.poll(self.clock.current_ms()) { + let now = self.clock.current_ms(); + + let result = match self.stack.lock(|stack| stack.poll(now)) { Ok(true) => UpdateState::Updated, Ok(false) => UpdateState::NoChange, Err(err) => { @@ -34,9 +45,9 @@ impl NetworkProcessor { // sending an excessive number of DHCP requests. false if !self.network_was_reset => { self.network_was_reset = true; - self.stack.handle_link_reset(); + self.stack.lock(|stack| stack.handle_link_reset()); } - _ => {}, + _ => {} }; result From 8144b3acf2b05561b780a6f673ca55ef11b5e153 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 16:16:54 +0200 Subject: [PATCH 12/34] Updating constructors --- Cargo.lock | 1 + Cargo.toml | 1 + src/bin/dual-iir.rs | 52 ++++++++------------------- src/bin/lockin.rs | 51 ++++++++------------------- src/net/mod.rs | 83 ++++++++++++++++++++++++++++++++++++++++---- src/net/telemetry.rs | 36 +++++++++++++++++++ 6 files changed, 145 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41d1676b2..b7bfa7362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,6 +763,7 @@ dependencies = [ "panic-semihosting", "paste", "serde", + "serde-json-core", "shared-bus", "smoltcp-nal", "stm32h7xx-hal", diff --git a/Cargo.toml b/Cargo.toml index b6279a83d..eefadff42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ ad9959 = { path = "ad9959" } generic-array = "0.14" miniconf = "0.1.0" shared-bus = {version = "0.2.2", features = ["cortex-m"] } +serde-json-core = "0.3" [dependencies.mcp23017] git = "https://github.com/mrd0ll4r/mcp23017.git" diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index f83ad1c31..f02efda97 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -13,10 +13,7 @@ use hardware::{ DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; -use net::{ - MiniconfClient, NetworkManager, NetworkProcessor, NetworkUsers, - TelemetryBuffer, UpdateState, -}; +use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState}; const SCALE: f32 = i16::MAX as _; @@ -59,7 +56,7 @@ const APP: () = { digital_inputs: (DigitalInput0, DigitalInput1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - network: NetworkUsers, + network: NetworkUsers, settings: Settings, telemetry: TelemetryBuffer, @@ -73,32 +70,13 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - let network = { - let stack = stabilizer.net.stack; - let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)).unwrap(); - - let processor = NetworkProcessor::new( - stack_manager.acquire_stack(), - stabilizer.net.phy, - stabilizer.cycle_counter, - ); - - let settings = MiniconfClient::new( - stack_manager.acquire_stack(), - "", - &net::get_device_prefix( - env!("CARGO_BIN_NAME"), - stabilizer.net.mac_address, - ), - ); - - // TODO: Add telemetry client - - NetworkUsers { - miniconf: settings, - processor, - } - }; + let network = NetworkUsers::new( + stabilizer.net.stack, + stabilizer.net.phy, + stabilizer.cycle_counter, + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ); // Spawn a settings update for default settings. c.spawn.settings_update().unwrap(); @@ -223,15 +201,15 @@ const APP: () = { #[task(priority = 1, resources=[network, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { - let _telemetry = + let telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); - let _gains = c.resources.settings.lock(|settings| settings.afe.clone()); + let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - // TODO: Publish telemetry through the telemetry client here. - //c.resources - // .mqtt - // .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + c.resources + .network + .telemetry + .publish(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index ed7f4f301..de0857f4c 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -18,9 +18,7 @@ use stabilizer::hardware::{ }; use miniconf::Miniconf; -use stabilizer::net::{ - MiniconfClient, NetworkManager, NetworkProcessor, NetworkUsers, UpdateState, -}; +use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState}; #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { @@ -66,9 +64,9 @@ const APP: () = { afes: (AFE0, AFE1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - network: NetworkUsers, + network: NetworkUsers, settings: Settings, - telemetry: net::TelemetryBuffer, + telemetry: TelemetryBuffer, digital_inputs: (DigitalInput0, DigitalInput1), timestamper: InputStamper, @@ -81,32 +79,13 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = setup(c.core, c.device); - let network = { - let stack = stabilizer.net.stack; - let stack_manager = cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)).unwrap(); - - let processor = NetworkProcessor::new( - stack_manager.acquire_stack(), - stabilizer.net.phy, - stabilizer.cycle_counter, - ); - - let settings = MiniconfClient::new( - stack_manager.acquire_stack(), - "", - &net::get_device_prefix( - env!("CARGO_BIN_NAME"), - stabilizer.net.mac_address, - ), - ); - - // TODO: Add telemetry client - - NetworkUsers { - miniconf: settings, - processor, - } - }; + let network = NetworkUsers::new( + stabilizer.net.stack, + stabilizer.net.phy, + stabilizer.cycle_counter, + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ); let settings = Settings::default(); @@ -271,12 +250,12 @@ const APP: () = { c.resources.digital_inputs.1.is_high().unwrap(), ]; - let _gains = c.resources.settings.lock(|settings| settings.afe.clone()); + let gains = c.resources.settings.lock(|settings| settings.afe.clone()); - // TODO: Publish telemetry. - //c.resources - // .mqtt - // .publish_telemetry(&telemetry.to_telemetry(gains[0], gains[1])); + c.resources + .network + .telemetry + .publish(&telemetry.to_telemetry(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/net/mod.rs b/src/net/mod.rs index 960150175..41eee4538 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,3 +1,4 @@ +use core::fmt::Write; ///! Stabilizer network management module ///! ///! # Design @@ -6,8 +7,8 @@ ///! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines ///! related to Stabilizer networking operations. use heapless::{consts, String}; - -use core::fmt::Write; +use miniconf::Miniconf; +use serde::Serialize; mod messages; mod mqtt_interface; @@ -15,14 +16,13 @@ mod shared; mod stack_manager; mod telemetry; -use crate::hardware::NetworkStack; +use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack}; use messages::{MqttMessage, SettingsResponse}; -use miniconf::Miniconf; pub use mqtt_interface::MiniconfClient; pub use shared::NetworkManager; pub use stack_manager::NetworkProcessor; -pub use telemetry::{Telemetry, TelemetryBuffer}; +pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient}; pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>; @@ -32,9 +32,80 @@ pub enum UpdateState { Updated, } -pub struct NetworkUsers { +pub struct NetworkUsers { pub miniconf: MiniconfClient, pub processor: NetworkProcessor, + pub telemetry: TelemetryClient, +} + +impl NetworkUsers +where + S: Default + Clone + Miniconf, + T: Serialize, +{ + pub fn new( + stack: NetworkStack, + phy: EthernetPhy, + cycle_counter: CycleCounter, + app: &str, + mac: smoltcp_nal::smoltcp::wire::EthernetAddress, + ) -> Self { + let stack_manager = + cortex_m::singleton!(: NetworkManager = NetworkManager::new(stack)) + .unwrap(); + + let processor = NetworkProcessor::new( + stack_manager.acquire_stack(), + phy, + cycle_counter, + ); + + let prefix = get_device_prefix(app, mac); + + let settings = MiniconfClient::new( + stack_manager.acquire_stack(), + &get_client_id(app, "settings", mac), + &prefix, + ); + + let telemetry = TelemetryClient::new( + stack_manager.acquire_stack(), + &get_client_id(app, "tlm", mac), + &prefix, + ); + + NetworkUsers { + miniconf: settings, + processor, + telemetry, + } + } +} + +fn get_client_id( + app: &str, + client: &str, + mac: smoltcp_nal::smoltcp::wire::EthernetAddress, +) -> String { + let mac_string = { + let mut mac_string: String = String::new(); + let mac = mac.as_bytes(); + + // Note(unwrap): 32-bytes is guaranteed to be valid for any mac address, as the address has + // a fixed length. + write!( + &mut mac_string, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ) + .unwrap(); + + mac_string + }; + + let mut identifier = String::new(); + write!(&mut identifier, "{}-{}-{}", app, mac_string, client).unwrap(); + identifier } /// Get the MQTT prefix of a device. diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index dccf3ac6e..1c22968ed 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -1,5 +1,10 @@ +use heapless::{consts, String, Vec}; use serde::Serialize; +use super::NetworkReference; +use crate::hardware::design_parameters::MQTT_BROKER; +use minimq::QoS; + use crate::hardware::AfeGain; #[derive(Copy, Clone)] @@ -51,3 +56,34 @@ impl TelemetryBuffer { } } } + +pub struct TelemetryClient { + mqtt: minimq::MqttClient, + telemetry_topic: String, + _telemetry: core::marker::PhantomData, +} + +impl TelemetryClient { + pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self { + let mqtt = + minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) + .unwrap(); + + let mut telemetry_topic: String = String::from(prefix); + telemetry_topic.push_str("/telemetry").unwrap(); + + Self { + mqtt, + telemetry_topic, + _telemetry: core::marker::PhantomData::default(), + } + } + + pub fn publish(&mut self, telemetry: &T) { + let telemetry: Vec = + serde_json_core::to_vec(telemetry).unwrap(); + self.mqtt + .publish(&self.telemetry_topic, &telemetry, QoS::AtMostOnce, &[]) + .ok(); + } +} From 0c6935587e7ae6946d1e590cc3e0ef0333124e83 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 16:46:53 +0200 Subject: [PATCH 13/34] Adding updated telemetry implementation --- src/bin/dual-iir.rs | 18 ++-------- src/bin/lockin.rs | 18 ++-------- src/hardware/configuration.rs | 57 ++++++++++++++++++++++--------- src/hardware/design_parameters.rs | 4 +-- src/net/mod.rs | 13 +++++++ src/net/telemetry.rs | 11 ++++++ 6 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index f02efda97..a69892435 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -169,21 +169,9 @@ const APP: () = { #[idle(resources=[network], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { loop { - // Update the smoltcp network stack. - let poll_result = c - .resources - .network - .lock(|network| network.processor.update()); - - // Service the MQTT configuration client. - if c.resources - .network - .lock(|network| network.miniconf.update()) - == UpdateState::Updated - { - c.spawn.settings_update().unwrap() - } else if poll_result == UpdateState::NoChange { - cortex_m::asm::wfi(); + match c.resources.network.lock(|net| net.update()) { + UpdateState::Updated => c.spawn.settings_update().unwrap(), + UpdateState::NoChange => cortex_m::asm::wfi(), } } } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index de0857f4c..b2c6d65e2 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -211,21 +211,9 @@ const APP: () = { #[idle(resources=[network], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { loop { - // Update the smoltcp network stack. - let poll_result = c - .resources - .network - .lock(|network| network.processor.update()); - - // Service the MQTT configuration client. - if c.resources - .network - .lock(|network| network.miniconf.update()) - == UpdateState::Updated - { - c.spawn.settings_update().unwrap() - } else if poll_result == UpdateState::NoChange { - cortex_m::asm::wfi(); + match c.resources.network.lock(|net| net.update()) { + UpdateState::Updated => c.spawn.settings_update().unwrap(), + UpdateState::NoChange => cortex_m::asm::wfi(), } } } diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 1fb7ab84a..4ab2abc98 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -7,6 +7,9 @@ use stm32h7xx_hal::{ prelude::*, }; +const NUM_SOCKETS: usize = 4; + +use heapless::{consts, Vec}; use smoltcp_nal::smoltcp; use embedded_hal::digital::v2::{InputPin, OutputPin}; @@ -19,13 +22,13 @@ use super::{ pub struct NetStorage { pub ip_addrs: [smoltcp::wire::IpCidr; 1], - pub sockets: [Option>; 2], + pub sockets: + [Option>; NUM_SOCKETS + 1], + pub socket_storage: [SocketStorage; NUM_SOCKETS], pub neighbor_cache: [Option<(smoltcp::wire::IpAddress, smoltcp::iface::Neighbor)>; 8], pub routes_cache: [Option<(smoltcp::wire::IpCidr, smoltcp::iface::Route)>; 8], - pub tx_storage: [u8; 4096], - pub rx_storage: [u8; 4096], pub dhcp_rx_metadata: [smoltcp::socket::RawPacketMetadata; 1], pub dhcp_tx_metadata: [smoltcp::socket::RawPacketMetadata; 1], @@ -33,6 +36,21 @@ pub struct NetStorage { pub dhcp_rx_storage: [u8; 600], } +#[derive(Copy, Clone)] +pub struct SocketStorage { + rx_storage: [u8; 4096], + tx_storage: [u8; 4096], +} + +impl SocketStorage { + const fn new() -> Self { + Self { + rx_storage: [0; 4096], + tx_storage: [0; 4096], + } + } +} + impl NetStorage { pub fn new() -> Self { NetStorage { @@ -42,9 +60,8 @@ impl NetStorage { )], neighbor_cache: [None; 8], routes_cache: [None; 8], - sockets: [None, None], - tx_storage: [0; 4096], - rx_storage: [0; 4096], + sockets: [None, None, None, None, None], + socket_storage: [SocketStorage::new(); NUM_SOCKETS], dhcp_tx_storage: [0; 600], dhcp_rx_storage: [0; 600], dhcp_rx_metadata: [smoltcp::socket::RawPacketMetadata::EMPTY; 1], @@ -572,19 +589,25 @@ pub fn setup( let mut sockets = smoltcp::socket::SocketSet::new(&mut store.sockets[..]); - let tcp_socket = { - let rx_buffer = smoltcp::socket::TcpSocketBuffer::new( - &mut store.rx_storage[..], - ); - let tx_buffer = smoltcp::socket::TcpSocketBuffer::new( - &mut store.tx_storage[..], - ); + let mut handles: Vec = + Vec::new(); + for storage in store.socket_storage.iter_mut() { + let tcp_socket = { + let rx_buffer = smoltcp::socket::TcpSocketBuffer::new( + &mut storage.rx_storage[..], + ); + let tx_buffer = smoltcp::socket::TcpSocketBuffer::new( + &mut storage.tx_storage[..], + ); + + smoltcp::socket::TcpSocket::new(rx_buffer, tx_buffer) + }; + let handle = sockets.add(tcp_socket); - smoltcp::socket::TcpSocket::new(rx_buffer, tx_buffer) - }; + handles.push(handle).unwrap(); + } - let handle = sockets.add(tcp_socket); - (sockets, [handle]) + (sockets, handles) }; let dhcp_client = { diff --git a/src/hardware/design_parameters.rs b/src/hardware/design_parameters.rs index 9a4279bee..b3e1fb1c4 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -43,7 +43,7 @@ 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/128 MHz ~ 800 KHz -pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7; +pub const ADC_SAMPLE_TICKS_LOG2: u8 = 12; pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; // The desired ADC sample processing buffer size. @@ -51,4 +51,4 @@ pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The MQTT broker IPv4 address -pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10]; +pub const MQTT_BROKER: [u8; 4] = [10, 35, 16, 10]; diff --git a/src/net/mod.rs b/src/net/mod.rs index 41eee4538..f5e5c6c68 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -80,6 +80,19 @@ where telemetry, } } + + pub fn update(&mut self) -> UpdateState { + // Poll for incoming data. + let poll_result = self.processor.update(); + + // Update the MQTT clients. + self.telemetry.update(); + + match self.miniconf.update() { + UpdateState::Updated => UpdateState::Updated, + UpdateState::NoChange => poll_result, + } + } } fn get_client_id( diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 1c22968ed..37c705b74 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -86,4 +86,15 @@ impl TelemetryClient { .publish(&self.telemetry_topic, &telemetry, QoS::AtMostOnce, &[]) .ok(); } + + pub fn update(&mut self) { + match self.mqtt.poll(|_client, _topic, _message, _properties| {}) { + Err(minimq::Error::Network( + smoltcp_nal::NetworkError::NoIpAddress, + )) => {} + + Err(error) => log::info!("Unexpected error: {:?}", error), + _ => {} + } + } } From 8efd7d44178e04063e2d04c51c9b8932c7050ddf Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 16:49:11 +0200 Subject: [PATCH 14/34] Fixing design parameters file --- 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 b3e1fb1c4..9a4279bee 100644 --- a/src/hardware/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -43,7 +43,7 @@ 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/128 MHz ~ 800 KHz -pub const ADC_SAMPLE_TICKS_LOG2: u8 = 12; +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. @@ -51,4 +51,4 @@ pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3; pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The MQTT broker IPv4 address -pub const MQTT_BROKER: [u8; 4] = [10, 35, 16, 10]; +pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10]; From 740e41d7cb05c3fa3922cad9585e8cf1ae4f517f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 5 May 2021 16:50:51 +0200 Subject: [PATCH 15/34] Renaming files --- src/net/{mqtt_interface.rs => miniconf_client.rs} | 0 src/net/mod.rs | 8 ++++---- src/net/{stack_manager.rs => network_processor.rs} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/net/{mqtt_interface.rs => miniconf_client.rs} (100%) rename src/net/{stack_manager.rs => network_processor.rs} (100%) diff --git a/src/net/mqtt_interface.rs b/src/net/miniconf_client.rs similarity index 100% rename from src/net/mqtt_interface.rs rename to src/net/miniconf_client.rs diff --git a/src/net/mod.rs b/src/net/mod.rs index f5e5c6c68..3a5ea66c1 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -11,17 +11,17 @@ use miniconf::Miniconf; use serde::Serialize; mod messages; -mod mqtt_interface; +mod miniconf_client; mod shared; -mod stack_manager; +mod network_processor; mod telemetry; use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack}; use messages::{MqttMessage, SettingsResponse}; -pub use mqtt_interface::MiniconfClient; +pub use miniconf_client::MiniconfClient; pub use shared::NetworkManager; -pub use stack_manager::NetworkProcessor; +pub use network_processor::NetworkProcessor; pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient}; pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>; diff --git a/src/net/stack_manager.rs b/src/net/network_processor.rs similarity index 100% rename from src/net/stack_manager.rs rename to src/net/network_processor.rs From 4cbc826f00937c623a33a4c5a72e98269cc94374 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 12:33:07 +0200 Subject: [PATCH 16/34] Adding documentation --- src/bin/dual-iir.rs | 3 +- src/bin/lockin.rs | 2 +- src/hardware/afe.rs | 12 ++--- src/hardware/system_timer.rs | 67 +++++++++++++++++++++----- src/net/miniconf_client.rs | 16 ++++++- src/net/mod.rs | 28 ++++++++++- src/net/network_processor.rs | 24 +++++++++- src/net/shared.rs | 46 ++++++++++++++++-- src/net/telemetry.rs | 93 +++++++++++++++++++++++++++++++----- 9 files changed, 250 insertions(+), 41 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index a69892435..8c617ccbd 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -153,7 +153,6 @@ const APP: () = { } // Update telemetry measurements. - // TODO: Should we report these as voltages? c.resources.telemetry.latest_samples = [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; @@ -197,7 +196,7 @@ const APP: () = { c.resources .network .telemetry - .publish(&telemetry.to_telemetry(gains[0], gains[1])); + .publish(&telemetry.finalize(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index b2c6d65e2..f4a009fb4 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -243,7 +243,7 @@ const APP: () = { c.resources .network .telemetry - .publish(&telemetry.to_telemetry(gains[0], gains[1])); + .publish(&telemetry.finalize(gains[0], gains[1])); let telemetry_period = c .resources diff --git a/src/hardware/afe.rs b/src/hardware/afe.rs index 836a3d09b..ec633a793 100644 --- a/src/hardware/afe.rs +++ b/src/hardware/afe.rs @@ -21,13 +21,13 @@ pub struct ProgrammableGainAmplifier { } impl Gain { - /// Get the AFE gain as a multiplying integer. - pub fn to_multiplier(&self) -> u8 { + /// Get the AFE gain as a numerical value. + pub fn as_multiplier (self) -> f32 { match self { - Gain::G1 => 1, - Gain::G2 => 2, - Gain::G5 => 5, - Gain::G10 => 10, + Gain::G1 => 1.0, + Gain::G2 => 2.0, + Gain::G5 => 5.0, + Gain::G10 => 10.0, } } } diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 0d243e30d..34011a120 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -1,11 +1,33 @@ +///! System timer used for RTIC scheduling +///! +///! # Design +///! The SystemTimer is an RTIC monotonic timer that can be used for scheduling tasks in RTIC. +///! This timer is used in place of the cycle counter to allow the timer to tick at a slower rate +///! than the CPU clock. This allows for longer scheduling periods with less resolution. This is +///! needed for infrequent (e.g. multiple second) telemetry periods. +///! +///! # Limitations +///! This implementation relies on sufficient timer polling to not miss timer counter overflows. If +///! the timer is not polled often enough, it's possible that an overflow would be missed and time +///! would "halt" for a shore period of time. This could be fixed in the future by instead +///! listening for the overflow interrupt instead of polling the overflow state. use hal::prelude::*; use stm32h7xx_hal as hal; +// A global buffer indicating how many times the internal counter has overflowed. static mut OVERFLOWS: u32 = 0; +/// System timer used for implementing RTIC scheduling. +/// +/// # Note +/// The system timer must be initialized before being used. pub struct SystemTimer {} impl SystemTimer { + /// Initialize the system timer. + /// + /// # Args + /// * `timer` - The hardware timer used for implementing the RTIC monotonic. pub fn initialize(mut timer: hal::timer::Timer) { timer.pause(); // Have the system timer operate at a tick rate of 10KHz (100uS per tick). With this @@ -16,52 +38,73 @@ impl SystemTimer { timer.resume(); } + /// Convert a provided number of seconds into timer ticks. pub fn ticks_from_secs(secs: u32) -> i32 { (secs * 10_000) as i32 } } impl rtic::Monotonic for SystemTimer { + // Instants are stored in 32-bit signed integers. With a 10KHz tick rate, this means an + // instant can store up to ~59 hours of time before overflowing. type Instant = i32; fn ratio() -> rtic::Fraction { rtic::Fraction { + // At 10KHz with a 400MHz CPU clock, the CPU clock runs 40,000 times faster than + // the system timer. numerator: 40_000, denominator: 1, } } + /// Get the current time instant. + /// + /// # Note + /// The time will overflow into -59 hours after the first 59 hours. This time value is intended + /// for use in calculating time delta, and should not be used for timestamping purposes due to + /// roll-over. fn now() -> i32 { + // Note(unsafe): Multiple interrupt contexts have access to the underlying timer, so care + // is taken when reading and modifying register values. let regs = unsafe { &*hal::device::TIM15::ptr() }; loop { - // Check for overflows - if regs.sr.read().uif().bit_is_set() { - regs.sr.modify(|_, w| w.uif().clear_bit()); - unsafe { - OVERFLOWS += 1; + // Checking for overflows of the current counter must be performed atomically. Any + // other task that is accessing the current time could potentially race for the + // registers. Note that this is only required for writing to global state (e.g. timer + // registers and overflow counter) + cortex_m::interrupt::free(|_cs| { + // Check for overflows and clear the overflow bit atomically. This must be done in + // a critical section to prevent race conditions on the status register. + if regs.sr.read().uif().bit_is_set() { + regs.sr.modify(|_, w| w.uif().clear_bit()); + unsafe { + OVERFLOWS += 1; + } } - } - let current_value = regs.cnt.read().bits(); + let current_value = regs.cnt.read().bits(); - // If the overflow is still unset, return our latest count, as it indicates we weren't - // pre-empted. - if regs.sr.read().uif().bit_is_clear() { - unsafe { - return (OVERFLOWS * 65535 + current_value) as i32; + // Check that an overflow didn't occur since we just cleared the overflow bit. If + // it did, loop around and retry. + if regs.sr.read().uif().bit_is_clear() { + return (overflows * 65535 + current_value) as i32; } } } } + /// Reset the timer count. unsafe fn reset() { // Note: The timer must be safely configured in `SystemTimer::initialize()`. let regs = &*hal::device::TIM15::ptr(); + OVERFLOWS = 0; regs.cnt.reset(); } + /// Get a timestamp correlating to zero time. fn zero() -> i32 { 0 } diff --git a/src/net/miniconf_client.rs b/src/net/miniconf_client.rs index e7fec737a..cc802c6a4 100644 --- a/src/net/miniconf_client.rs +++ b/src/net/miniconf_client.rs @@ -1,7 +1,18 @@ -use crate::hardware::design_parameters::MQTT_BROKER; - +///! Stabilizer Run-time Settings Client +///! +///! # Design +///! Stabilizer allows for settings to be configured at run-time via MQTT using miniconf. +///! Settings are written in serialized JSON form to the settings path associated with the setting. +///! +///! # Limitations +///! The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to +///connect to it when errors occur. +///! +///! Respones to settings updates are sent without quality-of-service guarantees, so there's no +///! guarantee that the requestee will be informed that settings have been applied. use heapless::{consts, String}; +use crate::hardware::design_parameters::MQTT_BROKER; use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; /// MQTT settings interface. @@ -144,6 +155,7 @@ where } } + /// Get the current settings from miniconf. pub fn settings(&self) -> &S { &self.settings } diff --git a/src/net/mod.rs b/src/net/mod.rs index 3a5ea66c1..4b5b0d376 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,4 +1,3 @@ -use core::fmt::Write; ///! Stabilizer network management module ///! ///! # Design @@ -10,6 +9,8 @@ use heapless::{consts, String}; use miniconf::Miniconf; use serde::Serialize; +use core::fmt::Write; + mod messages; mod miniconf_client; mod shared; @@ -32,6 +33,7 @@ pub enum UpdateState { Updated, } +/// A structure of Stabilizer's default network users. pub struct NetworkUsers { pub miniconf: MiniconfClient, pub processor: NetworkProcessor, @@ -43,6 +45,17 @@ where S: Default + Clone + Miniconf, T: Serialize, { + /// Construct Stabilizer's default network users. + /// + /// # Args + /// * `stack` - The network stack that will be used to share with all network users. + /// * `phy` - The ethernet PHY connecting the network. + /// * `cycle_counter` - The clock used for measuring time in the network. + /// * `app` - The name of the application. + /// * `mac` - The MAC address of the network. + /// + /// # Returns + /// A new struct of network users. pub fn new( stack: NetworkStack, phy: EthernetPhy, @@ -81,6 +94,10 @@ where } } + /// Update and process all of the network users state. + /// + /// # Returns + /// An indication if any of the network users indicated a state change. pub fn update(&mut self) -> UpdateState { // Poll for incoming data. let poll_result = self.processor.update(); @@ -95,6 +112,15 @@ where } } +/// Get an MQTT client ID for a client. +/// +/// # Args +/// * `app` - The name of the application +/// * `client` - The unique tag of the client +/// * `mac` - The MAC address of the device. +/// +/// # Returns +/// A client ID that may be used for MQTT client identification. fn get_client_id( app: &str, client: &str, diff --git a/src/net/network_processor.rs b/src/net/network_processor.rs index d2ec9ab0c..a64d6e7a8 100644 --- a/src/net/network_processor.rs +++ b/src/net/network_processor.rs @@ -1,7 +1,12 @@ +///! Task to process network hardware. +///! +///! # Design +///! The network processir is a small taks to regularly process incoming data over ethernet, handle +///! the ethernet PHY state, and reset the network as appropriate. use super::{NetworkReference, UpdateState}; - use crate::hardware::{CycleCounter, EthernetPhy}; +/// Processor for managing network hardware. pub struct NetworkProcessor { stack: NetworkReference, phy: EthernetPhy, @@ -10,6 +15,15 @@ pub struct NetworkProcessor { } impl NetworkProcessor { + /// Construct a new network processor. + /// + /// # Args + /// * `stack` - A reference to the shared network stack + /// * `phy` - The ethernet PHY used for the network. + /// * `clock` - The clock used for providing time to the network. + /// + /// # Returns + /// The newly constructed processor. pub fn new( stack: NetworkReference, phy: EthernetPhy, @@ -23,6 +37,14 @@ impl NetworkProcessor { } } + /// Process and update the state of the network. + /// + /// # Note + /// This function should be called regularly before other network tasks to update the state of + /// all relevant network sockets. + /// + /// # Returns + /// An update state corresponding with any changes in the underlying network. pub fn update(&mut self) -> UpdateState { // Service the network stack to process any inbound and outbound traffic. let now = self.clock.current_ms(); diff --git a/src/net/shared.rs b/src/net/shared.rs index e0be260a7..488f4d6b6 100644 --- a/src/net/shared.rs +++ b/src/net/shared.rs @@ -1,18 +1,50 @@ +///! Network Stack Sharing Utilities +///! +///! # Design +///! This module provides a mechanism for sharing a single network stack safely between drivers +///that may or may not execute in multiple contexts. The design copies that of `shared-bus`. +///! +///! Specifically, the network stack is stored in a global static singleton and proxies to the +///! underlying stack are handed out. The proxies provide an identical API for the +///! `embedded_nal::TcpStack` stack trait, so they can be provided direclty to drivers that require +///! a network stack. +///! +///! In order to ensure that pre-emption does not occur while accessing the same network stack from +///! multiple interrupt contexts, the proxy uses an atomic boolean check - if the flag indicates the +///! stack is in use, the proxy will generate a panic. The actual synchronization mechanism (mutex) +///! leverages RTIC resource allocation. All devices that use the underlying network stack must be +///! placed in a single RTIC resource, which will cause RTIC to prevent contention for the +///! underlying network stack. use minimq::embedded_nal; use shared_bus::{AtomicCheckMutex, BusMutex}; use crate::hardware::NetworkStack; +/// A manager for a shared network stack. +pub struct NetworkManager { + mutex: AtomicCheckMutex, +} + +/// A basic proxy that references a shared network stack. pub struct NetworkStackProxy<'a, S> { mutex: &'a AtomicCheckMutex, } impl<'a, S> NetworkStackProxy<'a, S> { + /// Using the proxy, access the underlying network stack directly. + /// + /// # Args + /// * `f` - A closure which will be provided the network stack as an argument. + /// + /// # Returns + /// Any value returned by the provided closure pub fn lock R>(&mut self, f: F) -> R { self.mutex.lock(|stack| f(stack)) } } +// A simple forwarding macro taken from the `embedded-nal` to forward the embedded-nal API into the +// proxy structure. macro_rules! forward { ($func:ident($($v:ident: $IT:ty),*) -> $T:ty) => { fn $func(&self, $($v: $IT),*) -> $T { @@ -21,6 +53,7 @@ macro_rules! forward { } } +// Implement a TCP stack for the proxy if the underlying network stack implements it. impl<'a, S> embedded_nal::TcpStack for NetworkStackProxy<'a, S> where S: embedded_nal::TcpStack, @@ -36,17 +69,22 @@ where forward! {close(socket: S::TcpSocket) -> Result<(), S::Error>} } -pub struct NetworkManager { - mutex: AtomicCheckMutex, -} - impl NetworkManager { + /// Construct a new manager for a shared network stack + /// + /// # Args + /// * `stack` - The network stack that is being shared. pub fn new(stack: NetworkStack) -> Self { Self { mutex: AtomicCheckMutex::create(stack), } } + /// Acquire a proxy to the shared network stack. + /// + /// # Returns + /// A proxy that can be used in place of the network stack. Note the requirements of + /// concurrency listed in the description of this file for usage. pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> { NetworkStackProxy { mutex: &self.mutex } } diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 37c705b74..b727e2363 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -1,19 +1,50 @@ +///! Stabilizer Telemetry Capabilities +///! +///! # Design +///! Telemetry is reported regularly using an MQTT client. All telemetry is reported in SI units +///! using standard JSON format. +///! +///! In order to report ADC/DAC codes generated during the DSP routines, a telemetry buffer is +///! employed to track the latest codes. Converting these codes to SI units would result in +///! repetitive and unnecessary calculations within the DSP routine, slowing it down and limiting +///! sampling frequency. Instead, the raw codes are stored and the telemetry is generated as +///! required immediately before transmission. This ensures that any slower computation required +///! for unit conversion can be off-loaded to lower priority tasks. use heapless::{consts, String, Vec}; use serde::Serialize; +use minimq::QoS; use super::NetworkReference; -use crate::hardware::design_parameters::MQTT_BROKER; -use minimq::QoS; +use crate::hardware::{AfeGain, design_parameters::MQTT_BROKER}; -use crate::hardware::AfeGain; +/// The telemetry client for reporting telemetry data over MQTT. +pub struct TelemetryClient { + mqtt: minimq::MqttClient, + telemetry_topic: String, + _telemetry: core::marker::PhantomData, +} +/// The telemetry buffer is used for storing sample values during execution. +/// +/// # Note +/// These values can be converted to SI units immediately before reporting to save processing time. +/// This allows for the DSP process to continually update the values without incurring significant +/// run-time overhead during conversion to SI units. #[derive(Copy, Clone)] pub struct TelemetryBuffer { + /// The latest input sample on ADC0/ADC1. pub latest_samples: [i16; 2], + /// The latest output code on DAC0/DAC1. pub latest_outputs: [u16; 2], + /// The latest digital input states during processing. pub digital_inputs: [bool; 2], } +/// The telemetry structure is data that is ultimately reported as telemetry over MQTT. +/// +/// # Note +/// This structure should be generated on-demand by the buffer when required to minimize conversion +/// overhead. #[derive(Serialize)] pub struct Telemetry { input_levels: [f32; 2], @@ -32,16 +63,37 @@ impl Default for TelemetryBuffer { } impl TelemetryBuffer { - pub fn to_telemetry(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { + /// Convert the telemetry buffer to finalized, SI-unit telemetry for reporting. + /// + /// # Args + /// * `afe0` - The current AFE configuration for channel 0. + /// * `afe1` - The current AFE configuration for channel 1. + /// + /// # Returns + /// The finalized telemetry structure that can be serialized and reported. + pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { + + // The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a + // dynamic range across signed integers. Additionally, the signal is fully differential, so + // the differential voltage is measured at the ADC. Thus, the single-ended signal is + // measured at the input is half of the ADC-reported measurement. As a pre-filter, the + // input signal has a fixed gain of 1/5 through a static input active filter. Finally, at + // the very front-end of the signal, there's an analog input multiplier that is + // configurable by the user. let in0_volts = (self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0 * 5.0 - / afe0.to_multiplier() as f32; + / afe0.as_multiplier(); let in1_volts = (self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0 * 5.0 - / afe1.to_multiplier() as f32; + / afe1.as_multiplier(); + // The output voltage is generated by the DAC with an output range of +/- 4.096 V. This + // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned + // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of + // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, + // and at a max DAC code, there is an output of 10.24 V. let out0_volts = (10.24 * 2.0) * (self.latest_outputs[0] as f32 / (u16::MAX as f32)) - 10.24; @@ -57,13 +109,16 @@ impl TelemetryBuffer { } } -pub struct TelemetryClient { - mqtt: minimq::MqttClient, - telemetry_topic: String, - _telemetry: core::marker::PhantomData, -} - impl TelemetryClient { + /// Construct a new telemetry client. + /// + /// # Args + /// * `stack` - A reference to the (shared) underlying network stack. + /// * `client_id` - The MQTT client ID of the telemetry client. + /// * `prefix` - The device prefix to use for MQTT telemetry reporting. + /// + /// # Returns + /// A new telemetry client. pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self { let mqtt = minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) @@ -79,6 +134,14 @@ impl TelemetryClient { } } + /// Publish telemetry over MQTT + /// + /// # Note + /// Telemetry is reported in a "best-effort" fashion. Failure to transmit telemetry will cause + /// it to be silently dropped. + /// + /// # Args + /// * `telemetry` - The telemetry to report pub fn publish(&mut self, telemetry: &T) { let telemetry: Vec = serde_json_core::to_vec(telemetry).unwrap(); @@ -87,6 +150,12 @@ impl TelemetryClient { .ok(); } + /// Update the telemetry client + /// + /// # Note + /// This function is provided to force the underlying MQTT state machine to process incoming + /// and outgoing messages. Without this, the client will never connect to the broker. This + /// should be called regularly. pub fn update(&mut self) { match self.mqtt.poll(|_client, _topic, _message, _properties| {}) { Err(minimq::Error::Network( From 949ea9c1b37608dfc3c3146cdfbb4197f63d118a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 12:35:04 +0200 Subject: [PATCH 17/34] Fixing build, formatting --- src/hardware/afe.rs | 2 +- src/hardware/system_timer.rs | 2 +- src/net/miniconf_client.rs | 2 +- src/net/mod.rs | 4 ++-- src/net/telemetry.rs | 5 ++--- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/hardware/afe.rs b/src/hardware/afe.rs index ec633a793..962c4170c 100644 --- a/src/hardware/afe.rs +++ b/src/hardware/afe.rs @@ -22,7 +22,7 @@ pub struct ProgrammableGainAmplifier { impl Gain { /// Get the AFE gain as a numerical value. - pub fn as_multiplier (self) -> f32 { + pub fn as_multiplier(self) -> f32 { match self { Gain::G1 => 1.0, Gain::G2 => 2.0, diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 34011a120..4b0ba0a77 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -91,7 +91,7 @@ impl rtic::Monotonic for SystemTimer { if regs.sr.read().uif().bit_is_clear() { return (overflows * 65535 + current_value) as i32; } - } + }) } } diff --git a/src/net/miniconf_client.rs b/src/net/miniconf_client.rs index cc802c6a4..9c6bbf5be 100644 --- a/src/net/miniconf_client.rs +++ b/src/net/miniconf_client.rs @@ -12,8 +12,8 @@ ///! guarantee that the requestee will be informed that settings have been applied. use heapless::{consts, String}; -use crate::hardware::design_parameters::MQTT_BROKER; use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState}; +use crate::hardware::design_parameters::MQTT_BROKER; /// MQTT settings interface. pub struct MiniconfClient diff --git a/src/net/mod.rs b/src/net/mod.rs index 4b5b0d376..fb7d0f3ca 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -13,16 +13,16 @@ use core::fmt::Write; mod messages; mod miniconf_client; -mod shared; mod network_processor; +mod shared; mod telemetry; use crate::hardware::{CycleCounter, EthernetPhy, NetworkStack}; use messages::{MqttMessage, SettingsResponse}; pub use miniconf_client::MiniconfClient; -pub use shared::NetworkManager; pub use network_processor::NetworkProcessor; +pub use shared::NetworkManager; pub use telemetry::{Telemetry, TelemetryBuffer, TelemetryClient}; pub type NetworkReference = shared::NetworkStackProxy<'static, NetworkStack>; diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index b727e2363..57fc19228 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -11,11 +11,11 @@ ///! required immediately before transmission. This ensures that any slower computation required ///! for unit conversion can be off-loaded to lower priority tasks. use heapless::{consts, String, Vec}; -use serde::Serialize; use minimq::QoS; +use serde::Serialize; use super::NetworkReference; -use crate::hardware::{AfeGain, design_parameters::MQTT_BROKER}; +use crate::hardware::{design_parameters::MQTT_BROKER, AfeGain}; /// The telemetry client for reporting telemetry data over MQTT. pub struct TelemetryClient { @@ -72,7 +72,6 @@ impl TelemetryBuffer { /// # Returns /// The finalized telemetry structure that can be serialized and reported. pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { - // The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a // dynamic range across signed integers. Additionally, the signal is fully differential, so // the differential voltage is measured at the ADC. Thus, the single-ended signal is From 81a292a77edfd985e975eb5426abe62e498d3a08 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 12:45:13 +0200 Subject: [PATCH 18/34] Fixing system timer --- src/hardware/system_timer.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 4b0ba0a77..715cb9fa3 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -74,7 +74,7 @@ impl rtic::Monotonic for SystemTimer { // other task that is accessing the current time could potentially race for the // registers. Note that this is only required for writing to global state (e.g. timer // registers and overflow counter) - cortex_m::interrupt::free(|_cs| { + if let Some(time) = cortex_m::interrupt::free(|_cs| { // Check for overflows and clear the overflow bit atomically. This must be done in // a critical section to prevent race conditions on the status register. if regs.sr.read().uif().bit_is_set() { @@ -89,9 +89,15 @@ impl rtic::Monotonic for SystemTimer { // Check that an overflow didn't occur since we just cleared the overflow bit. If // it did, loop around and retry. if regs.sr.read().uif().bit_is_clear() { - return (overflows * 65535 + current_value) as i32; + // Note(unsafe): We are in a critical section, so it is safe to read the + // global variable. + unsafe { Some((OVERFLOWS * 65535 + current_value) as i32) } + } else { + None } - }) + }) { + return time; + } } } From 8c581ea0a6e07b5f26d24775b7498f9c6b8aa5a0 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 13:02:39 +0200 Subject: [PATCH 19/34] Merging lockin app functions --- src/bin/lockin.rs | 80 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index f4a009fb4..0d7e92159 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -20,6 +20,13 @@ use stabilizer::hardware::{ use miniconf::Miniconf; use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState}; +// A constant sinusoid to send on the DAC output. +// Full-scale gives a +/- 10.24V amplitude waveform. Scale it down to give +/- 1V. +const ONE: i16 = ((1.0 / 10.24) * u16::MAX as f32) as _; +const SQRT2: i16 = (ONE as f32 * 0.707) as _; +const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] = + [ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2]; + #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { PowerPhase, @@ -27,9 +34,16 @@ enum Conf { Quadrature, } +#[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)] +enum LockinMode { + Internal, + External, +} + #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] pub struct Settings { afe: [AfeGain; 2], + lockin_mode: LockinMode, pll_tc: [u8; 2], @@ -46,6 +60,8 @@ impl Default for Settings { Self { afe: [AfeGain::G1; 2], + lockin_mode: LockinMode::External, + pll_tc: [21, 21], // frequency and phase settling time (log2 counter cycles) lockin_tc: 6, // lockin lowpass time constant @@ -151,21 +167,49 @@ const APP: () = { let lockin = c.resources.lockin; let settings = c.resources.settings; - let timestamp = - c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows. - let (pll_phase, pll_frequency) = c.resources.pll.update( - timestamp.map(|t| t as i32), - settings.pll_tc[0], - settings.pll_tc[1], - ); + let mut pll_frequency = 0; + + let (sample_phase, sample_frequency) = match settings.lockin_mode { + LockinMode::External => { + let timestamp = + c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows. + let (pll_phase, frequency) = c.resources.pll.update( + timestamp.map(|t| t as i32), + settings.pll_tc[0], + settings.pll_tc[1], + ); + + pll_frequency = frequency; + + let sample_frequency = ((pll_frequency + >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) + as i32) + .wrapping_mul(settings.lockin_harmonic); + let sample_phase = settings.lockin_phase.wrapping_add( + pll_phase.wrapping_mul(settings.lockin_harmonic), + ); + + (sample_phase, sample_frequency) + } - let sample_frequency = ((pll_frequency - >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) - as i32) - .wrapping_mul(settings.lockin_harmonic); - let sample_phase = settings - .lockin_phase - .wrapping_add(pll_phase.wrapping_mul(settings.lockin_harmonic)); + LockinMode::Internal => { + // Reference phase and frequency are known. + let pll_phase = 0i32; + let pll_frequency = + 1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2); + + // Demodulation LO phase offset + let phase_offset: i32 = 1 << 30; + + let sample_frequency = (pll_frequency as i32) + .wrapping_mul(settings.lockin_harmonic); + let sample_phase = phase_offset.wrapping_add( + pll_phase.wrapping_mul(settings.lockin_harmonic), + ); + + (sample_phase, sample_frequency) + } + }; let output: Complex = adc_samples[0] .iter() @@ -196,7 +240,13 @@ const APP: () = { // Convert to DAC data. for i in 0..dac_samples[0].len() { - dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000; + // When operating in internal lockin mode, DAC0 is always used for generating the + // reference signal. + if settings.lockin_mode == LockinMode::Internal { + dac_samples[0][i] = DAC_SEQUENCE[i] as u16 ^ 0x8000; + } else { + dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000; + } dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000; } From 80b8716be46a9d64bb905385cec30958722a69b2 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 14:40:28 +0200 Subject: [PATCH 20/34] Finalizing merge --- .github/workflows/release.yml | 3 +-- src/bin/lockin.rs | 48 ++++++++++++++++------------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5be1311d5..475c05ee0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,8 +24,7 @@ jobs: - run: > zip bin.zip target/*/release/dual-iir - target/*/release/lockin-external - target/*/release/lockin-internal + target/*/release/lockin - id: create_release uses: actions/create-release@v1 env: diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 0d7e92159..05002a52c 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -22,16 +22,20 @@ use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState}; // A constant sinusoid to send on the DAC output. // Full-scale gives a +/- 10.24V amplitude waveform. Scale it down to give +/- 1V. -const ONE: i16 = ((1.0 / 10.24) * u16::MAX as f32) as _; +const ONE: i16 = ((1.0 / 10.24) * i16::MAX as f32) as _; const SQRT2: i16 = (ONE as f32 * 0.707) as _; const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] = [ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2]; #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { - PowerPhase, - FrequencyDiscriminator, + Magnitude, + Phase, + PllFrequency, + LogPower, + InPhase, Quadrature, + Modulation, } #[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)] @@ -68,7 +72,7 @@ impl Default for Settings { lockin_harmonic: -1, // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate) lockin_phase: 0, // Demodulation LO phase offset - output_conf: [Conf::Quadrature; 2], + output_conf: [Conf::InPhase, Conf::Quadrature], telemetry_period_secs: 10, } } @@ -159,7 +163,7 @@ const APP: () = { c.resources.adcs.1.acquire_buffer(), ]; - let dac_samples = [ + let mut dac_samples = [ c.resources.dacs.0.acquire_buffer(), c.resources.dacs.1.acquire_buffer(), ]; @@ -225,29 +229,21 @@ const APP: () = { .unwrap() * 2; // Full scale assuming the 2f component is gone. - let output = [ - match settings.output_conf[0] { - Conf::PowerPhase => output.abs_sqr() as _, - Conf::FrequencyDiscriminator => (output.log2() << 24) as _, - Conf::Quadrature => output.re, - }, - match settings.output_conf[1] { - Conf::PowerPhase => output.arg(), - Conf::FrequencyDiscriminator => pll_frequency as _, - Conf::Quadrature => output.im, - }, - ]; - // Convert to DAC data. - for i in 0..dac_samples[0].len() { - // When operating in internal lockin mode, DAC0 is always used for generating the - // reference signal. - if settings.lockin_mode == LockinMode::Internal { - dac_samples[0][i] = DAC_SEQUENCE[i] as u16 ^ 0x8000; - } else { - dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000; + for (channel, samples) in dac_samples.iter_mut().enumerate() { + for (i, sample) in samples.iter_mut().enumerate() { + let value = match settings.output_conf[channel] { + Conf::Magnitude => output.abs_sqr() as i32 >> 16, + Conf::Phase => output.arg() >> 16, + Conf::LogPower => (output.log2() << 24) as i32 >> 16, + Conf::PllFrequency => pll_frequency as i32 >> 16, + Conf::InPhase => output.re >> 16, + Conf::Quadrature => output.im >> 16, + Conf::Modulation => DAC_SEQUENCE[i] as i32, + }; + + *sample = value as u16 ^ 0x8000; } - dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000; } // Update telemetry measurements. From 03adb72aab218ad480bc521256f92ffd3fc5572e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 16:23:41 +0200 Subject: [PATCH 21/34] Fixing clippy --- src/bin/dual-iir.rs | 6 +++--- src/bin/lockin.rs | 6 +++--- src/net/shared.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 8c617ccbd..0a881d2b3 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -188,10 +188,10 @@ const APP: () = { #[task(priority = 1, resources=[network, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { - let telemetry = - c.resources.telemetry.lock(|telemetry| telemetry.clone()); + let telemetry: TelemetryBuffer = + c.resources.telemetry.lock(|telemetry| *telemetry); - let gains = c.resources.settings.lock(|settings| settings.afe.clone()); + let gains: [AfeGain; 2] = c.resources.settings.lock(|settings| settings.afe); c.resources .network diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 05002a52c..fba92ed8c 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -276,15 +276,15 @@ const APP: () = { #[task(priority = 1, resources=[network, digital_inputs, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { - let mut telemetry = - c.resources.telemetry.lock(|telemetry| telemetry.clone()); + let mut telemetry: TelemetryBuffer = + c.resources.telemetry.lock(|telemetry| *telemetry); telemetry.digital_inputs = [ c.resources.digital_inputs.0.is_high().unwrap(), c.resources.digital_inputs.1.is_high().unwrap(), ]; - let gains = c.resources.settings.lock(|settings| settings.afe.clone()); + let gains: [AfeGain; 2] = c.resources.settings.lock(|settings| settings.afe); c.resources .network diff --git a/src/net/shared.rs b/src/net/shared.rs index 488f4d6b6..e09489b05 100644 --- a/src/net/shared.rs +++ b/src/net/shared.rs @@ -85,7 +85,7 @@ impl NetworkManager { /// # Returns /// A proxy that can be used in place of the network stack. Note the requirements of /// concurrency listed in the description of this file for usage. - pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> { + pub fn acquire_stack(&'_ self) -> NetworkStackProxy<'_, NetworkStack> { NetworkStackProxy { mutex: &self.mutex } } } From eeee5af2961bf8b58b08aa1cc84a8939ad99c7cb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 16:32:57 +0200 Subject: [PATCH 22/34] Updating dependencies --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- src/net/miniconf_client.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7bfa7362..06309e32e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,7 +426,7 @@ dependencies = [ [[package]] name = "minimq" version = "0.2.0" -source = "git+https://github.com/quartiq/minimq.git?rev=b3f364d#b3f364d55dea35da6572f78ddb91c87bfbb453bf" +source = "git+https://github.com/quartiq/minimq.git?rev=d2ec3e8#d2ec3e8351fa403ea96defd98c0b4410cbaa18a4" dependencies = [ "bit_field", "embedded-nal", @@ -732,7 +732,7 @@ dependencies = [ [[package]] name = "smoltcp-nal" version = "0.1.0" -source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=8468f11#8468f11abacd7aba82454e6904df19c1d1ab91bb" +source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=4a1711c#4a1711c54cdf79f5ee8c1c99a1e8984f5944270c" dependencies = [ "embedded-nal", "heapless 0.6.1", diff --git a/Cargo.toml b/Cargo.toml index eefadff42..3d57a4788 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,11 +62,11 @@ rev = "c6f2b28" [dependencies.smoltcp-nal] git = "https://github.com/quartiq/smoltcp-nal.git" -rev = "8468f11" +rev = "4a1711c" [dependencies.minimq] git = "https://github.com/quartiq/minimq.git" -rev = "b3f364d" +rev = "d2ec3e8" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/net/miniconf_client.rs b/src/net/miniconf_client.rs index 9c6bbf5be..b809d7363 100644 --- a/src/net/miniconf_client.rs +++ b/src/net/miniconf_client.rs @@ -140,7 +140,7 @@ where // If settings updated, Ok(_) if update => UpdateState::Updated, Ok(_) => UpdateState::NoChange, - Err(minimq::Error::Disconnected) => { + Err(minimq::Error::SessionReset) => { self.subscribed = false; UpdateState::NoChange } From e07f0a4e2a9182d665acbfc81831cfff57bebe85 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 16:37:36 +0200 Subject: [PATCH 23/34] Formatting --- src/bin/dual-iir.rs | 3 ++- src/bin/lockin.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 0a881d2b3..f681c9a37 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -191,7 +191,8 @@ const APP: () = { let telemetry: TelemetryBuffer = c.resources.telemetry.lock(|telemetry| *telemetry); - let gains: [AfeGain; 2] = c.resources.settings.lock(|settings| settings.afe); + let gains: [AfeGain; 2] = + c.resources.settings.lock(|settings| settings.afe); c.resources .network diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index fba92ed8c..d16731d86 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -284,7 +284,8 @@ const APP: () = { c.resources.digital_inputs.1.is_high().unwrap(), ]; - let gains: [AfeGain; 2] = c.resources.settings.lock(|settings| settings.afe); + let gains: [AfeGain; 2] = + c.resources.settings.lock(|settings| settings.afe); c.resources .network From ff79e0a8ac0ebf74be43011a497de8e0b7ec8b31 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 17:10:38 +0200 Subject: [PATCH 24/34] Fixing merge --- src/bin/lockin.rs | 57 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index d16731d86..4a3494bd5 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -2,7 +2,6 @@ #![no_std] #![no_main] -use embedded_hal::digital::v2::InputPin; use generic_array::typenum::U4; use serde::Deserialize; @@ -31,7 +30,7 @@ const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] = enum Conf { Magnitude, Phase, - PllFrequency, + ReferenceFrequency, LogPower, InPhase, Quadrature, @@ -171,50 +170,38 @@ const APP: () = { let lockin = c.resources.lockin; let settings = c.resources.settings; - let mut pll_frequency = 0; - - let (sample_phase, sample_frequency) = match settings.lockin_mode { + let (reference_phase, reference_frequency) = match settings.lockin_mode + { LockinMode::External => { let timestamp = c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows. - let (pll_phase, frequency) = c.resources.pll.update( + let (pll_phase, pll_frequency) = c.resources.pll.update( timestamp.map(|t| t as i32), settings.pll_tc[0], settings.pll_tc[1], ); - - pll_frequency = frequency; - - let sample_frequency = ((pll_frequency - >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) - as i32) - .wrapping_mul(settings.lockin_harmonic); - let sample_phase = settings.lockin_phase.wrapping_add( - pll_phase.wrapping_mul(settings.lockin_harmonic), - ); - - (sample_phase, sample_frequency) + ( + pll_phase, + (pll_frequency + >> design_parameters::SAMPLE_BUFFER_SIZE_LOG2) + as i32, + ) } - LockinMode::Internal => { // Reference phase and frequency are known. - let pll_phase = 0i32; - let pll_frequency = - 1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2); - - // Demodulation LO phase offset - let phase_offset: i32 = 1 << 30; - - let sample_frequency = (pll_frequency as i32) - .wrapping_mul(settings.lockin_harmonic); - let sample_phase = phase_offset.wrapping_add( - pll_phase.wrapping_mul(settings.lockin_harmonic), - ); - - (sample_phase, sample_frequency) + ( + 1i32 << 30, + 1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2), + ) } }; + let sample_frequency = + reference_frequency.wrapping_mul(settings.lockin_harmonic); + let sample_phase = settings.lockin_phase.wrapping_add( + reference_phase.wrapping_mul(settings.lockin_harmonic), + ); + let output: Complex = adc_samples[0] .iter() // Zip in the LO phase. @@ -236,7 +223,9 @@ const APP: () = { Conf::Magnitude => output.abs_sqr() as i32 >> 16, Conf::Phase => output.arg() >> 16, Conf::LogPower => (output.log2() << 24) as i32 >> 16, - Conf::PllFrequency => pll_frequency as i32 >> 16, + Conf::ReferenceFrequency => { + reference_frequency as i32 >> 16 + } Conf::InPhase => output.re >> 16, Conf::Quadrature => output.im >> 16, Conf::Modulation => DAC_SEQUENCE[i] as i32, From 89eaefd8d59fd95f047e8952b0642e117583face Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 6 May 2021 17:14:17 +0200 Subject: [PATCH 25/34] Updating delay --- src/bin/lockin.rs | 1 + src/hardware/configuration.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 4a3494bd5..756e8ed82 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -2,6 +2,7 @@ #![no_std] #![no_main] +use embedded_hal::digital::v2::InputPin; use generic_array::typenum::U4; use serde::Deserialize; diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 4ab2abc98..dfaf4dff1 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -167,8 +167,9 @@ pub fn setup( system_timer::SystemTimer::initialize(tim15); } - let mut delay = - asm_delay::AsmDelay::new(asm_delay::bitrate::MegaHertz(2 * 400)); + let mut delay = asm_delay::AsmDelay::new(asm_delay::bitrate::MegaHertz( + ccdr.clocks.c_ck().0 / 1_000_000, + )); let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); From 7b76b1f14cf0117e38d1a178686614dfd50be7d6 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 13:02:14 +0200 Subject: [PATCH 26/34] Updating after review --- src/bin/dual-iir.rs | 26 +++++++++++++----------- src/bin/lockin.rs | 13 ++++++------ src/hardware/system_timer.rs | 22 +++++++++----------- src/net/telemetry.rs | 39 ++++++++++++++++++------------------ 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index f681c9a37..d5205d8ef 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -26,7 +26,9 @@ pub struct Settings { iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], allow_hold: bool, force_hold: bool, - telemetry_period_secs: u16, + + // The telemetry period in seconds. + telemetry_period: u16, } impl Default for Settings { @@ -44,7 +46,7 @@ impl Default for Settings { allow_hold: false, // Force suppress filter output updates. force_hold: false, - telemetry_period_secs: 10, + telemetry_period: 10, } } } @@ -130,9 +132,13 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; + let digital_inputs = [ + c.resources.digital_inputs.0.is_high().unwrap(), + c.resources.digital_inputs.1.is_high().unwrap(), + ]; + let hold = c.resources.settings.force_hold - || (c.resources.digital_inputs.1.is_high().unwrap() - && c.resources.settings.allow_hold); + || (digital_inputs[1] && c.resources.settings.allow_hold); for channel in 0..adc_samples.len() { for sample in 0..adc_samples[0].len() { @@ -153,16 +159,12 @@ const APP: () = { } // Update telemetry measurements. - c.resources.telemetry.latest_samples = + c.resources.telemetry.adcs = [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; - c.resources.telemetry.latest_outputs = - [dac_samples[0][0], dac_samples[1][0]]; + c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]]; - c.resources.telemetry.digital_inputs = [ - c.resources.digital_inputs.0.is_high().unwrap(), - c.resources.digital_inputs.1.is_high().unwrap(), - ]; + c.resources.telemetry.digital_inputs = digital_inputs; } #[idle(resources=[network], spawn=[settings_update])] @@ -202,7 +204,7 @@ const APP: () = { let telemetry_period = c .resources .settings - .lock(|settings| settings.telemetry_period_secs); + .lock(|settings| settings.telemetry_period); // Schedule the telemetry task in the future. c.schedule diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 756e8ed82..10534baf3 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -56,7 +56,9 @@ pub struct Settings { lockin_phase: i32, output_conf: [Conf; 2], - telemetry_period_secs: u16, + + // The telemetry period in seconds. + telemetry_period: u16, } impl Default for Settings { @@ -73,7 +75,7 @@ impl Default for Settings { lockin_phase: 0, // Demodulation LO phase offset output_conf: [Conf::InPhase, Conf::Quadrature], - telemetry_period_secs: 10, + telemetry_period: 10, } } } @@ -237,11 +239,10 @@ const APP: () = { } // Update telemetry measurements. - c.resources.telemetry.latest_samples = + c.resources.telemetry.adcs = [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; - c.resources.telemetry.latest_outputs = - [dac_samples[0][0], dac_samples[1][0]]; + c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]]; } #[idle(resources=[network], spawn=[settings_update])] @@ -285,7 +286,7 @@ const APP: () = { let telemetry_period = c .resources .settings - .lock(|settings| settings.telemetry_period_secs); + .lock(|settings| settings.telemetry_period); // Schedule the telemetry task in the future. c.schedule diff --git a/src/hardware/system_timer.rs b/src/hardware/system_timer.rs index 715cb9fa3..7b445c4fe 100644 --- a/src/hardware/system_timer.rs +++ b/src/hardware/system_timer.rs @@ -69,12 +69,12 @@ impl rtic::Monotonic for SystemTimer { // is taken when reading and modifying register values. let regs = unsafe { &*hal::device::TIM15::ptr() }; - loop { - // Checking for overflows of the current counter must be performed atomically. Any - // other task that is accessing the current time could potentially race for the - // registers. Note that this is only required for writing to global state (e.g. timer - // registers and overflow counter) - if let Some(time) = cortex_m::interrupt::free(|_cs| { + cortex_m::interrupt::free(|_cs| { + loop { + // Checking for overflows of the current counter must be performed atomically. Any + // other task that is accessing the current time could potentially race for the + // registers. Note that this is only required for writing to global state (e.g. timer + // registers and overflow counter) // Check for overflows and clear the overflow bit atomically. This must be done in // a critical section to prevent race conditions on the status register. if regs.sr.read().uif().bit_is_set() { @@ -91,14 +91,12 @@ impl rtic::Monotonic for SystemTimer { if regs.sr.read().uif().bit_is_clear() { // Note(unsafe): We are in a critical section, so it is safe to read the // global variable. - unsafe { Some((OVERFLOWS * 65535 + current_value) as i32) } - } else { - None + return unsafe { + ((OVERFLOWS << 16) + current_value) as i32 + }; } - }) { - return time; } - } + }) } /// Reset the timer count. diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 57fc19228..cf14f4f62 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -33,9 +33,9 @@ pub struct TelemetryClient { #[derive(Copy, Clone)] pub struct TelemetryBuffer { /// The latest input sample on ADC0/ADC1. - pub latest_samples: [i16; 2], + pub adcs: [i16; 2], /// The latest output code on DAC0/DAC1. - pub latest_outputs: [u16; 2], + pub dacs: [u16; 2], /// The latest digital input states during processing. pub digital_inputs: [bool; 2], } @@ -47,16 +47,16 @@ pub struct TelemetryBuffer { /// overhead. #[derive(Serialize)] pub struct Telemetry { - input_levels: [f32; 2], - output_levels: [f32; 2], + adcs: [f32; 2], + dacs: [f32; 2], digital_inputs: [bool; 2], } impl Default for TelemetryBuffer { fn default() -> Self { Self { - latest_samples: [0, 0], - latest_outputs: [0, 0], + adcs: [0, 0], + dacs: [0, 0], digital_inputs: [false, false], } } @@ -79,30 +79,29 @@ impl TelemetryBuffer { // input signal has a fixed gain of 1/5 through a static input active filter. Finally, at // the very front-end of the signal, there's an analog input multiplier that is // configurable by the user. + let adc_volts_per_lsb = 5.0 * 4.096 / 2.0 / i16::MAX as f32; let in0_volts = - (self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0 - * 5.0 - / afe0.as_multiplier(); + (adc_volts_per_lsb * self.adcs[0] as f32) / afe0.as_multiplier(); let in1_volts = - (self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0 - * 5.0 - / afe1.as_multiplier(); + (adc_volts_per_lsb * self.adcs[1] as f32) / afe1.as_multiplier(); // The output voltage is generated by the DAC with an output range of +/- 4.096 V. This // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, // and at a max DAC code, there is an output of 10.24 V. - let out0_volts = (10.24 * 2.0) - * (self.latest_outputs[0] as f32 / (u16::MAX as f32)) - - 10.24; - let out1_volts = (10.24 * 2.0) - * (self.latest_outputs[1] as f32 / (u16::MAX as f32)) - - 10.24; + // + // Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger + // than full-scale. + let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32; + let dac_offset = -10.24; + + let out0_volts = dac_volts_per_lsb * self.dacs[0] as f32 + dac_offset; + let out1_volts = dac_volts_per_lsb * self.dacs[1] as f32 + dac_offset; Telemetry { - input_levels: [in0_volts, in1_volts], - output_levels: [out0_volts, out1_volts], + adcs: [in0_volts, in1_volts], + dacs: [out0_volts, out1_volts], digital_inputs: self.digital_inputs, } } From d68fa87fece7389e0eab52f97337a4ae03e30398 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 13:04:25 +0200 Subject: [PATCH 27/34] Simplifying settings lock --- src/bin/dual-iir.rs | 11 ++++------- src/bin/lockin.rs | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index d5205d8ef..e952ae61f 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -193,19 +193,16 @@ const APP: () = { let telemetry: TelemetryBuffer = c.resources.telemetry.lock(|telemetry| *telemetry); - let gains: [AfeGain; 2] = - c.resources.settings.lock(|settings| settings.afe); + let (gains, telemetry_period) = c + .resources + .settings + .lock(|settings| (settings.afe, settings.telemetry_period)); c.resources .network .telemetry .publish(&telemetry.finalize(gains[0], gains[1])); - let telemetry_period = c - .resources - .settings - .lock(|settings| settings.telemetry_period); - // Schedule the telemetry task in the future. c.schedule .telemetry( diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 10534baf3..73c3a9241 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -275,19 +275,16 @@ const APP: () = { c.resources.digital_inputs.1.is_high().unwrap(), ]; - let gains: [AfeGain; 2] = - c.resources.settings.lock(|settings| settings.afe); + let (gains, telemetry_period) = c + .resources + .settings + .lock(|settings| (settings.afe, settings.telemetry_period)); c.resources .network .telemetry .publish(&telemetry.finalize(gains[0], gains[1])); - let telemetry_period = c - .resources - .settings - .lock(|settings| settings.telemetry_period); - // Schedule the telemetry task in the future. c.schedule .telemetry( From b73a4d9e5974d53db921759bf3370bbe8b9921eb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 13:50:34 +0200 Subject: [PATCH 28/34] Adding adc/dac code conversion utilities --- src/bin/dual-iir.rs | 11 ++++++----- src/bin/lockin.rs | 13 +++++++------ src/hardware/adc.rs | 22 ++++++++++++++++++++++ src/hardware/dac.rs | 33 +++++++++++++++++++++++++++++++++ src/hardware/mod.rs | 4 ++-- src/net/telemetry.rs | 42 ++++++++++-------------------------------- 6 files changed, 80 insertions(+), 45 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index e952ae61f..2820184d8 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -9,8 +9,8 @@ use serde::Deserialize; use dsp::iir; use hardware::{ - Adc0Input, Adc1Input, AfeGain, Dac0Output, Dac1Output, DigitalInput0, - DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, + Adc0Input, Adc1Input, AdcSample, AfeGain, Dac0Output, Dac1Output, DacCode, + DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState}; @@ -154,15 +154,16 @@ const APP: () = { // The truncation introduces 1/2 LSB distortion. let y = unsafe { y.to_int_unchecked::() }; // Convert to DAC code - dac_samples[channel][sample] = y as u16 ^ 0x8000; + dac_samples[channel][sample] = DacCode::from(y).0; } } // Update telemetry measurements. c.resources.telemetry.adcs = - [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; + [AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])]; - c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]]; + c.resources.telemetry.dacs = + [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])]; c.resources.telemetry.digital_inputs = digital_inputs; } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 73c3a9241..f4f86d719 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -12,9 +12,9 @@ use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; use stabilizer::net; use stabilizer::hardware::{ - design_parameters, setup, Adc0Input, Adc1Input, AfeGain, Dac0Output, - Dac1Output, DigitalInput0, DigitalInput1, InputStamper, SystemTimer, AFE0, - AFE1, + design_parameters, setup, Adc0Input, Adc1Input, AdcSample, AfeGain, + Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1, + InputStamper, SystemTimer, AFE0, AFE1, }; use miniconf::Miniconf; @@ -234,15 +234,16 @@ const APP: () = { Conf::Modulation => DAC_SEQUENCE[i] as i32, }; - *sample = value as u16 ^ 0x8000; + *sample = DacCode::from(value as i16).0; } } // Update telemetry measurements. c.resources.telemetry.adcs = - [adc_samples[0][0] as i16, adc_samples[1][0] as i16]; + [AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])]; - c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]]; + c.resources.telemetry.dacs = + [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])]; } #[idle(resources=[network], spawn=[settings_update])] diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 3f2fa5d97..081d80136 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -84,6 +84,28 @@ use hal::dma::{ MemoryToPeripheral, PeripheralToMemory, Transfer, }; +/// A type representing an ADC sample. +#[derive(Copy, Clone)] +pub struct AdcSample(pub u16); + +impl Into for AdcSample { + /// Convert raw ADC codes to/from voltage levels. + /// + /// # Note + /// + /// This does not account for the programmable gain amplifier at the signal input. + fn into(self) -> f32 { + // The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a + // dynamic range across signed integers. Additionally, the signal is fully differential, so + // the differential voltage is measured at the ADC. Thus, the single-ended signal is + // measured at the input is half of the ADC-reported measurement. As a pre-filter, the + // input signal has a fixed gain of 1/5 through a static input active filter. + let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / i16::MAX as f32; + + (self.0 as i16) as f32 * adc_volts_per_lsb + } +} + // The following data is written by the timer ADC sample trigger into the SPI CR1 to start the // transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is // initialized during setup. diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index d41ae8c1f..e4e5947c4 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -69,6 +69,39 @@ use hal::dma::{ static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 3]; 2]; +/// Custom type for referencing DAC output codes. +/// The internal integer is the raw code written to the DAC output register. +#[derive(Copy, Clone)] +pub struct DacCode(pub u16); + +impl Into for DacCode { + fn into(self) -> f32 { + // The output voltage is generated by the DAC with an output range of +/- 4.096 V. This + // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned + // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of + // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, + // and at a max DAC code, there is an output of 10.24 V. + // + // Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger + // than full-scale. + let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32; + + (self.0 as f32) * dac_volts_per_lsb - 10.24 + } +} + +impl From for DacCode { + /// Generate a DAC code from a 16-bit signed value. + /// + /// # Note + /// This is provided as a means to convert between the DACs internal 16-bit, unsigned register + /// and a 2s-complement integer that is convenient when using the DAC in the bipolar + /// configuration. + fn from(value: i16) -> Self { + Self(value as u16 ^ 0x8000) + } +} + macro_rules! dac_output { ($name:ident, $index:literal, $data_stream:ident, $spi:ident, $trigger_channel:ident, $dma_req:ident) => { diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index a43964202..a741cb355 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -19,10 +19,10 @@ pub mod pounder; mod system_timer; mod timers; -pub use adc::{Adc0Input, Adc1Input}; +pub use adc::{Adc0Input, Adc1Input, AdcSample}; pub use afe::Gain as AfeGain; pub use cycle_counter::CycleCounter; -pub use dac::{Dac0Output, Dac1Output}; +pub use dac::{Dac0Output, Dac1Output, DacCode}; pub use digital_input_stamper::InputStamper; pub use pounder::DdsOutput; pub use system_timer::SystemTimer; diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index cf14f4f62..974e0eb44 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -15,7 +15,9 @@ use minimq::QoS; use serde::Serialize; use super::NetworkReference; -use crate::hardware::{design_parameters::MQTT_BROKER, AfeGain}; +use crate::hardware::{ + design_parameters::MQTT_BROKER, AdcSample, AfeGain, DacCode, +}; /// The telemetry client for reporting telemetry data over MQTT. pub struct TelemetryClient { @@ -33,9 +35,9 @@ pub struct TelemetryClient { #[derive(Copy, Clone)] pub struct TelemetryBuffer { /// The latest input sample on ADC0/ADC1. - pub adcs: [i16; 2], + pub adcs: [AdcSample; 2], /// The latest output code on DAC0/DAC1. - pub dacs: [u16; 2], + pub dacs: [DacCode; 2], /// The latest digital input states during processing. pub digital_inputs: [bool; 2], } @@ -55,8 +57,8 @@ pub struct Telemetry { impl Default for TelemetryBuffer { fn default() -> Self { Self { - adcs: [0, 0], - dacs: [0, 0], + adcs: [AdcSample(0), AdcSample(0)], + dacs: [DacCode(0), DacCode(0)], digital_inputs: [false, false], } } @@ -72,36 +74,12 @@ impl TelemetryBuffer { /// # Returns /// The finalized telemetry structure that can be serialized and reported. pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry { - // The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a - // dynamic range across signed integers. Additionally, the signal is fully differential, so - // the differential voltage is measured at the ADC. Thus, the single-ended signal is - // measured at the input is half of the ADC-reported measurement. As a pre-filter, the - // input signal has a fixed gain of 1/5 through a static input active filter. Finally, at - // the very front-end of the signal, there's an analog input multiplier that is - // configurable by the user. - let adc_volts_per_lsb = 5.0 * 4.096 / 2.0 / i16::MAX as f32; - let in0_volts = - (adc_volts_per_lsb * self.adcs[0] as f32) / afe0.as_multiplier(); - let in1_volts = - (adc_volts_per_lsb * self.adcs[1] as f32) / afe1.as_multiplier(); - - // The output voltage is generated by the DAC with an output range of +/- 4.096 V. This - // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned - // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of - // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, - // and at a max DAC code, there is an output of 10.24 V. - // - // Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger - // than full-scale. - let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32; - let dac_offset = -10.24; - - let out0_volts = dac_volts_per_lsb * self.dacs[0] as f32 + dac_offset; - let out1_volts = dac_volts_per_lsb * self.dacs[1] as f32 + dac_offset; + let in0_volts = Into::::into(self.adcs[0]) / afe0.as_multiplier(); + let in1_volts = Into::::into(self.adcs[1]) / afe1.as_multiplier(); Telemetry { adcs: [in0_volts, in1_volts], - dacs: [out0_volts, out1_volts], + dacs: [self.dacs[0].into(), self.dacs[1].into()], digital_inputs: self.digital_inputs, } } From 923790b0b90f2aa9de2a2dc896e3bff4c183a254 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 14:02:25 +0200 Subject: [PATCH 29/34] Updating float conversion --- src/hardware/dac.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index e4e5947c4..3afa06c1b 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -80,13 +80,14 @@ impl Into for DacCode { // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, - // and at a max DAC code, there is an output of 10.24 V. - // - // Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger - // than full-scale. - let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32; + // and at a max DAC code, there is an output of (slightly less than) 10.24 V. - (self.0 as f32) * dac_volts_per_lsb - 10.24 + let dac_volts_per_lsb = 10.24 * 2.0 / u16::MAX as f32; + + // Note that the bipolar table is an offset-binary code, but it is much more logical and + // correct to treat it as a twos-complement value. TO do that, we XOR the most significant + // bit to convert it. + (self.0 ^ 0x8000) as i16 as f32 * dac_volts_per_lsb } } From 60b1b112b185b69dfd1ff4e8f7385166f1b3116d Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 14:11:25 +0200 Subject: [PATCH 30/34] Renaming AdcSample -> AdcCode --- src/bin/dual-iir.rs | 4 ++-- src/bin/lockin.rs | 4 ++-- src/hardware/adc.rs | 4 ++-- src/hardware/mod.rs | 2 +- src/net/telemetry.rs | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 2820184d8..a2c219e80 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -9,7 +9,7 @@ use serde::Deserialize; use dsp::iir; use hardware::{ - Adc0Input, Adc1Input, AdcSample, AfeGain, Dac0Output, Dac1Output, DacCode, + Adc0Input, Adc1Input, AdcCode, AfeGain, Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; @@ -160,7 +160,7 @@ const APP: () = { // Update telemetry measurements. c.resources.telemetry.adcs = - [AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])]; + [AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])]; c.resources.telemetry.dacs = [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])]; diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index f4f86d719..7e29c62c3 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -12,7 +12,7 @@ use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; use stabilizer::net; use stabilizer::hardware::{ - design_parameters, setup, Adc0Input, Adc1Input, AdcSample, AfeGain, + design_parameters, setup, Adc0Input, Adc1Input, AdcCode, AfeGain, Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1, InputStamper, SystemTimer, AFE0, AFE1, }; @@ -240,7 +240,7 @@ const APP: () = { // Update telemetry measurements. c.resources.telemetry.adcs = - [AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])]; + [AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])]; c.resources.telemetry.dacs = [DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])]; diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 081d80136..aa7bcadd8 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -86,9 +86,9 @@ use hal::dma::{ /// A type representing an ADC sample. #[derive(Copy, Clone)] -pub struct AdcSample(pub u16); +pub struct AdcCode(pub u16); -impl Into for AdcSample { +impl Into for AdcCode { /// Convert raw ADC codes to/from voltage levels. /// /// # Note diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index a741cb355..352c70d36 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -19,7 +19,7 @@ pub mod pounder; mod system_timer; mod timers; -pub use adc::{Adc0Input, Adc1Input, AdcSample}; +pub use adc::{Adc0Input, Adc1Input, AdcCode}; pub use afe::Gain as AfeGain; pub use cycle_counter::CycleCounter; pub use dac::{Dac0Output, Dac1Output, DacCode}; diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs index 974e0eb44..b59b2227e 100644 --- a/src/net/telemetry.rs +++ b/src/net/telemetry.rs @@ -16,7 +16,7 @@ use serde::Serialize; use super::NetworkReference; use crate::hardware::{ - design_parameters::MQTT_BROKER, AdcSample, AfeGain, DacCode, + design_parameters::MQTT_BROKER, AdcCode, AfeGain, DacCode, }; /// The telemetry client for reporting telemetry data over MQTT. @@ -35,7 +35,7 @@ pub struct TelemetryClient { #[derive(Copy, Clone)] pub struct TelemetryBuffer { /// The latest input sample on ADC0/ADC1. - pub adcs: [AdcSample; 2], + pub adcs: [AdcCode; 2], /// The latest output code on DAC0/DAC1. pub dacs: [DacCode; 2], /// The latest digital input states during processing. @@ -57,7 +57,7 @@ pub struct Telemetry { impl Default for TelemetryBuffer { fn default() -> Self { Self { - adcs: [AdcSample(0), AdcSample(0)], + adcs: [AdcCode(0), AdcCode(0)], dacs: [DacCode(0), DacCode(0)], digital_inputs: [false, false], } From 6e94ffc13881527c757298aade412e65e719d526 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 7 May 2021 14:23:03 +0200 Subject: [PATCH 31/34] Update src/hardware/dac.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- src/hardware/dac.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 3afa06c1b..3df70e863 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -82,7 +82,7 @@ impl Into for DacCode { // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, // and at a max DAC code, there is an output of (slightly less than) 10.24 V. - let dac_volts_per_lsb = 10.24 * 2.0 / u16::MAX as f32; + let dac_volts_per_lsb = 4.096 * 2.5 / (1u16 << 15) as f32; // Note that the bipolar table is an offset-binary code, but it is much more logical and // correct to treat it as a twos-complement value. TO do that, we XOR the most significant From fcda2d5bd115e636d8240f2083a4e5d3b34ae4fb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 10 May 2021 10:57:50 +0200 Subject: [PATCH 32/34] Addressing review feedback --- src/hardware/configuration.rs | 14 ++++++++------ src/net/mod.rs | 36 ++--------------------------------- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index dfaf4dff1..c8104f6e0 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -22,6 +22,8 @@ use super::{ pub struct NetStorage { pub ip_addrs: [smoltcp::wire::IpCidr; 1], + + // Note: There is an additional socket set item required for the DHCP socket. pub sockets: [Option>; NUM_SOCKETS + 1], pub socket_storage: [SocketStorage; NUM_SOCKETS], @@ -38,15 +40,15 @@ pub struct NetStorage { #[derive(Copy, Clone)] pub struct SocketStorage { - rx_storage: [u8; 4096], - tx_storage: [u8; 4096], + rx_storage: [u8; 1024], + tx_storage: [u8; 1024], } impl SocketStorage { const fn new() -> Self { Self { - rx_storage: [0; 4096], - tx_storage: [0; 4096], + rx_storage: [0; 1024], + tx_storage: [0; 1024], } } } @@ -167,8 +169,8 @@ pub fn setup( system_timer::SystemTimer::initialize(tim15); } - let mut delay = asm_delay::AsmDelay::new(asm_delay::bitrate::MegaHertz( - ccdr.clocks.c_ck().0 / 1_000_000, + let mut delay = asm_delay::AsmDelay::new(asm_delay::bitrate::Hertz( + ccdr.clocks.c_ck().0, )); let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); diff --git a/src/net/mod.rs b/src/net/mod.rs index fb7d0f3ca..38499ca1f 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -126,24 +126,8 @@ fn get_client_id( client: &str, mac: smoltcp_nal::smoltcp::wire::EthernetAddress, ) -> String { - let mac_string = { - let mut mac_string: String = String::new(); - let mac = mac.as_bytes(); - - // Note(unwrap): 32-bytes is guaranteed to be valid for any mac address, as the address has - // a fixed length. - write!( - &mut mac_string, - "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] - ) - .unwrap(); - - mac_string - }; - let mut identifier = String::new(); - write!(&mut identifier, "{}-{}-{}", app, mac_string, client).unwrap(); + write!(&mut identifier, "{}-{}-{}", app, mac, client).unwrap(); identifier } @@ -159,26 +143,10 @@ pub fn get_device_prefix( app: &str, mac: smoltcp_nal::smoltcp::wire::EthernetAddress, ) -> String { - let mac_string = { - let mut mac_string: String = String::new(); - let mac = mac.as_bytes(); - - // Note(unwrap): 32-bytes is guaranteed to be valid for any mac address, as the address has - // a fixed length. - write!( - &mut mac_string, - "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] - ) - .unwrap(); - - mac_string - }; - // Note(unwrap): The mac address + binary name must be short enough to fit into this string. If // they are defined too long, this will panic and the device will fail to boot. let mut prefix: String = String::new(); - write!(&mut prefix, "dt/sinara/{}/{}", app, mac_string).unwrap(); + write!(&mut prefix, "dt/sinara/{}/{}", app, mac).unwrap(); prefix } From fa886d2eacf3b59856766aca0476f0797c0bfc9f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 10 May 2021 11:10:26 +0200 Subject: [PATCH 33/34] Cleaning up conversion + comments --- src/bin/dual-iir.rs | 3 +-- src/bin/lockin.rs | 3 +-- src/hardware/adc.rs | 8 ++------ src/hardware/dac.rs | 18 +++--------------- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index a2c219e80..29eb4747a 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -26,8 +26,6 @@ pub struct Settings { iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], allow_hold: bool, force_hold: bool, - - // The telemetry period in seconds. telemetry_period: u16, } @@ -46,6 +44,7 @@ impl Default for Settings { allow_hold: false, // Force suppress filter output updates. force_hold: false, + // The default telemetry period in seconds. telemetry_period: 10, } } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 7e29c62c3..944b87ac7 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -56,8 +56,6 @@ pub struct Settings { lockin_phase: i32, output_conf: [Conf; 2], - - // The telemetry period in seconds. telemetry_period: u16, } @@ -75,6 +73,7 @@ impl Default for Settings { lockin_phase: 0, // Demodulation LO phase offset output_conf: [Conf::InPhase, Conf::Quadrature], + // The default telemetry period in seconds. telemetry_period: 10, } } diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index aa7bcadd8..29eb2903e 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -92,14 +92,10 @@ impl Into for AdcCode { /// Convert raw ADC codes to/from voltage levels. /// /// # Note - /// /// This does not account for the programmable gain amplifier at the signal input. fn into(self) -> f32 { - // The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a - // dynamic range across signed integers. Additionally, the signal is fully differential, so - // the differential voltage is measured at the ADC. Thus, the single-ended signal is - // measured at the input is half of the ADC-reported measurement. As a pre-filter, the - // input signal has a fixed gain of 1/5 through a static input active filter. + // The ADC has a differential input with a range of +/- 4.096 V and 16-bit resolution. + // The gain into the two inputs is 1/5. let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / i16::MAX as f32; (self.0 as i16) as f32 * adc_volts_per_lsb diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 3df70e863..a8a8af948 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -76,28 +76,16 @@ pub struct DacCode(pub u16); impl Into for DacCode { fn into(self) -> f32 { - // The output voltage is generated by the DAC with an output range of +/- 4.096 V. This - // signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned - // integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of - // the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V, - // and at a max DAC code, there is an output of (slightly less than) 10.24 V. - + // The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096 + // V with 16-bit resolution. The anti-aliasing filter has an additional gain of 2.5. let dac_volts_per_lsb = 4.096 * 2.5 / (1u16 << 15) as f32; - // Note that the bipolar table is an offset-binary code, but it is much more logical and - // correct to treat it as a twos-complement value. TO do that, we XOR the most significant - // bit to convert it. (self.0 ^ 0x8000) as i16 as f32 * dac_volts_per_lsb } } impl From for DacCode { - /// Generate a DAC code from a 16-bit signed value. - /// - /// # Note - /// This is provided as a means to convert between the DACs internal 16-bit, unsigned register - /// and a 2s-complement integer that is convenient when using the DAC in the bipolar - /// configuration. + /// Encode signed 16-bit values into DAC offset binary for a bipolar output configuration. fn from(value: i16) -> Self { Self(value as u16 ^ 0x8000) } From 81bc569f0e153cb142ee18bd279262084757433f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 10 May 2021 11:40:36 +0200 Subject: [PATCH 34/34] Simplifying unit conversions --- src/hardware/adc.rs | 2 +- src/hardware/dac.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 29eb2903e..92890975d 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -96,7 +96,7 @@ impl Into for AdcCode { fn into(self) -> f32 { // The ADC has a differential input with a range of +/- 4.096 V and 16-bit resolution. // The gain into the two inputs is 1/5. - let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / i16::MAX as f32; + let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / (1u16 << 15) as f32; (self.0 as i16) as f32 * adc_volts_per_lsb } diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index a8a8af948..56f3033a4 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -80,14 +80,14 @@ impl Into for DacCode { // V with 16-bit resolution. The anti-aliasing filter has an additional gain of 2.5. let dac_volts_per_lsb = 4.096 * 2.5 / (1u16 << 15) as f32; - (self.0 ^ 0x8000) as i16 as f32 * dac_volts_per_lsb + (self.0 as i16).wrapping_add(i16::MIN) as f32 * dac_volts_per_lsb } } impl From for DacCode { /// Encode signed 16-bit values into DAC offset binary for a bipolar output configuration. fn from(value: i16) -> Self { - Self(value as u16 ^ 0x8000) + Self(value.wrapping_add(i16::MIN) as u16) } }