From 5ea3f5067275dd9b9f5737147b4582b9c3dbc0be Mon Sep 17 00:00:00 2001 From: mwojcik Date: Wed, 5 Jun 2024 17:29:13 +0800 Subject: [PATCH] mcu-contrib: break apart stabilizer and stabilizer-pounder --- hydra/mcu.json | 1 + mcu-contrib/default.nix | 14 +- mcu-contrib/pounder-725.diff | 797 ----------------------------------- 3 files changed, 8 insertions(+), 804 deletions(-) delete mode 100644 mcu-contrib/pounder-725.diff diff --git a/hydra/mcu.json b/hydra/mcu.json index ea340cc..6a004a6 100644 --- a/hydra/mcu.json +++ b/hydra/mcu.json @@ -15,6 +15,7 @@ "mozillaOverlay": { "type": "git", "value": "https://github.com/mozilla/nixpkgs-mozilla.git", "emailresponsible": false }, "nixScripts": { "type": "git", "value": "https://git.m-labs.hk/M-Labs/nix-scripts.git", "emailresponsible": false }, "stabilizerSrc": { "type": "git", "value": "https://github.com/quartiq/stabilizer.git main", "emailresponsible": false }, + "stabilizerPounderSrc": { "type": "git", "value": "https://github.com/spaqin/stabilizer.git pounder-telemetry", "emailresponsible": false }, "saymaMmcSrc": { "type": "git", "value": "https://github.com/sinara-hw/openMMC.git sayma-devel", "emailresponsible": false } } }, diff --git a/mcu-contrib/default.nix b/mcu-contrib/default.nix index 728fa24..abd3029 100644 --- a/mcu-contrib/default.nix +++ b/mcu-contrib/default.nix @@ -63,18 +63,18 @@ in binaryName = name; cargoDepsName = "stabilizer"; src = ; - patchPhase = '' - patch -p1 < ${./pounder-725.diff} - ''; doCheck = false; } // value))) { dual-iir = {}; - dual-iir-pounder_v1_0 = { - binaryName = "dual-iir"; - extraCargoBuildArgs = "--features pounder_v1_0"; - }; lockin = {}; } // + { stabilizer-pounder-dual-iir = buildStm32Firmware { + name = "stabilizer-pounder-dual-iir"; + binaryName = "dual-iir"; + cargoDepsName = "stabilizer"; + src = ; + doCheck = false; + }; } // { # openMMC build system breaks if host compiler is not available, so do not use stdenvNoCC here sayma-mmc = pkgs.stdenv.mkDerivation { diff --git a/mcu-contrib/pounder-725.diff b/mcu-contrib/pounder-725.diff deleted file mode 100644 index ea91140..0000000 --- a/mcu-contrib/pounder-725.diff +++ /dev/null @@ -1,797 +0,0 @@ -diff --git a/Cargo.lock b/Cargo.lock -index 7c796acf..cee5ba60 100644 ---- a/Cargo.lock -+++ b/Cargo.lock -@@ -983,9 +983,9 @@ dependencies = [ - - [[package]] - name = "serde" --version = "1.0.203" -+version = "1.0.202" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" -+checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" - dependencies = [ - "serde_derive", - ] -@@ -1003,9 +1003,9 @@ dependencies = [ - - [[package]] - name = "serde_derive" --version = "1.0.203" -+version = "1.0.202" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" -+checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" - dependencies = [ - "proc-macro2", - "quote", -diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs -index 025f7d4f..59578cce 100644 ---- a/ad9959/src/lib.rs -+++ b/ad9959/src/lib.rs -@@ -2,8 +2,24 @@ - - use bit_field::BitField; - use bitflags::bitflags; -+use core::ops::Range; - use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin}; - -+/// The minimum reference clock input frequency with REFCLK multiplier disabled. -+const MIN_REFCLK_FREQUENCY: f32 = 1e6; -+/// The minimum reference clock input frequency with REFCLK multiplier enabled. -+const MIN_MULTIPLIED_REFCLK_FREQUENCY: f32 = 10e6; -+/// The system clock frequency range with high gain configured for the internal VCO. -+const HIGH_GAIN_VCO_RANGE: Range = Range { -+ start: 255e6, -+ end: 500e6, -+}; -+/// The system clock frequency range with low gain configured for the internal VCO. -+const LOW_GAIN_VCO_RANGE: Range = Range { -+ start: 100e6, -+ end: 160e6, -+}; -+ - /// A device driver for the AD9959 direct digital synthesis (DDS) chip. - /// - /// This chip provides four independently controllable digital-to-analog output sinusoids with -@@ -218,23 +234,17 @@ impl Ad9959 { - reference_clock_frequency: f32, - multiplier: u8, - ) -> Result { -+ let frequency = -+ validate_clocking(reference_clock_frequency, multiplier)?; - self.reference_clock_frequency = reference_clock_frequency; - -- if multiplier != 1 && !(4..=20).contains(&multiplier) { -- return Err(Error::Bounds); -- } -- -- let frequency = multiplier as f32 * self.reference_clock_frequency; -- if frequency > 500_000_000.0f32 { -- return Err(Error::Frequency); -- } -- - // TODO: Update / disable any enabled channels? - let mut fr1: [u8; 3] = [0, 0, 0]; - self.read(Register::FR1, &mut fr1)?; - fr1[0].set_bits(2..=6, multiplier); - -- let vco_range = frequency > 255e6; -+ let vco_range = HIGH_GAIN_VCO_RANGE.contains(&frequency) -+ || frequency == HIGH_GAIN_VCO_RANGE.end; - fr1[0].set_bit(7, vco_range); - - self.write(Register::FR1, &fr1)?; -@@ -365,9 +375,7 @@ impl Ad9959 { - channel: Channel, - phase_turns: f32, - ) -> Result { -- let phase_offset: u16 = -- (phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16; -- -+ let phase_offset = phase_to_pow(phase_turns)?; - self.modify_channel( - channel, - Register::CPOW0, -@@ -513,6 +521,108 @@ impl Ad9959 { - } - } - -+/// Validate the internal system clock configuration of the chip. -+/// -+/// Arguments: -+/// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core. -+/// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20. -+/// -+/// Returns: -+/// The system clock frequency to be configured. -+pub fn validate_clocking( -+ reference_clock_frequency: f32, -+ multiplier: u8, -+) -> Result { -+ // The REFCLK frequency must be at least 1 MHz with REFCLK multiplier disabled. -+ if reference_clock_frequency < MIN_REFCLK_FREQUENCY { -+ return Err(Error::Bounds); -+ } -+ // If the REFCLK multiplier is enabled, the multiplier (FR1[22:18]) must be between 4 to 20. -+ // Alternatively, the clock multiplier can be disabled. The multiplication factor is 1. -+ if multiplier != 1 && !(4..=20).contains(&multiplier) { -+ return Err(Error::Bounds); -+ } -+ // If the REFCLK multiplier is enabled, the REFCLK frequency must be at least 10 MHz. -+ if multiplier != 1 -+ && reference_clock_frequency < MIN_MULTIPLIED_REFCLK_FREQUENCY -+ { -+ return Err(Error::Bounds); -+ } -+ let frequency = multiplier as f32 * reference_clock_frequency; -+ // SYSCLK frequency between 255 MHz and 500 MHz (inclusive) is valid with high range VCO -+ if HIGH_GAIN_VCO_RANGE.contains(&frequency) -+ || frequency == HIGH_GAIN_VCO_RANGE.end -+ { -+ return Ok(frequency); -+ } -+ -+ // SYSCLK frequency between 100 MHz and 160 MHz (inclusive) is valid with low range VCO -+ if LOW_GAIN_VCO_RANGE.contains(&frequency) -+ || frequency == LOW_GAIN_VCO_RANGE.end -+ { -+ return Ok(frequency); -+ } -+ -+ // When the REFCLK multiplier is disabled, SYSCLK frequency can go below 100 MHz -+ if multiplier == 1 && (0.0..=LOW_GAIN_VCO_RANGE.start).contains(&frequency) -+ { -+ return Ok(frequency); -+ } -+ -+ Err(Error::Frequency) -+} -+ -+/// Convert and validate frequency into frequency tuning word. -+/// -+/// Arguments: -+/// * `dds_frequency` - The DDS frequency to be converted and validated. -+/// * `system_clock_frequency` - The system clock frequency of the AD9959 core. -+/// -+/// Returns: -+/// The corresponding frequency tuning word. -+pub fn frequency_to_ftw( -+ dds_frequency: f32, -+ system_clock_frequency: f32, -+) -> Result { -+ // Output frequency should not exceed the Nyquist's frequency. -+ if !(0.0..=(system_clock_frequency / 2.0)).contains(&dds_frequency) { -+ return Err(Error::Bounds); -+ } -+ // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the -+ // frequency tuning word and f_s is the system clock rate. -+ Ok(((dds_frequency / system_clock_frequency) * (1u64 << 32) as f32) as u32) -+} -+ -+/// Convert phase into phase offset word. -+/// -+/// Arguments: -+/// * `phase_turns` - The normalized number of phase turns of a DDS channel. -+/// -+/// Returns: -+/// The corresponding phase offset word. -+pub fn phase_to_pow(phase_turns: f32) -> Result { -+ Ok((phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16) -+} -+ -+/// Convert amplitude into amplitude control register values. -+/// -+/// Arguments: -+/// * `amplitude` - The normalized amplitude of a DDS channel. -+/// -+/// Returns: -+/// The corresponding value in the amplitude control register. -+pub fn amplitude_to_acr(amplitude: f32) -> Result { -+ if !(0.0..=1.0).contains(&litude) { -+ return Err(Error::Bounds); -+ } -+ -+ let acr: u32 = *0u32 -+ .set_bits(0..=9, ((amplitude * (1 << 10) as f32) as u32) & 0x3FF) -+ .set_bit(12, amplitude != 1.0); -+ -+ Ok(acr as u32) -+} -+ - /// Represents a means of serializing a DDS profile for writing to a stream. - pub struct ProfileSerializer { - // heapless::Vec, especially its extend_from_slice() is slow -@@ -568,6 +678,39 @@ impl ProfileSerializer { - } - } - -+ /// Update the system clock configuration. -+ /// -+ /// # Args -+ /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core. -+ /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20. -+ /// -+ /// # Limitations -+ /// The correctness of the FR1 register setting code rely on FR1\[0:17\] staying 0. -+ pub fn set_system_clock( -+ &mut self, -+ reference_clock_frequency: f32, -+ multiplier: u8, -+ ) -> Result { -+ let frequency = reference_clock_frequency * multiplier as f32; -+ -+ // The enabled channel will be updated after clock reconfig -+ let mut fr1 = [0u8; 3]; -+ -+ // The ad9959 crate does not modify FR1[0:17]. These bits keep their default value. -+ // These bits by default are 0. -+ // Reading the register then update is not possible to implement in a serializer, where -+ // many QSPI writes are performed in burst. Switching between read and write requires -+ // breaking the QSPI indirect write mode and switch into the QSPI indirect read mode. -+ fr1[0].set_bits(2..=6, multiplier); -+ -+ // Frequencies within the VCO forbidden range (160e6, 255e6) are already rejected. -+ let vco_range = HIGH_GAIN_VCO_RANGE.contains(&frequency); -+ fr1[0].set_bit(7, vco_range); -+ -+ self.add_write(Register::FR1, &fr1); -+ Ok(frequency) -+ } -+ - /// Add a register write to the serialization data. - fn add_write(&mut self, register: Register, value: &[u8]) { - let data = &mut self.data[self.index..]; -diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs -index a4a04be8..83dcdefb 100644 ---- a/src/bin/dual-iir.rs -+++ b/src/bin/dual-iir.rs -@@ -47,6 +47,8 @@ use stabilizer::{ - dac::{Dac0Output, Dac1Output, DacCode}, - hal, - signal_generator::{self, SignalGenerator}, -+ pounder::{ClockConfig, PounderConfig}, -+ setup::PounderDevices as Pounder, - timers::SamplingTimer, - DigitalInput0, DigitalInput1, SerialTerminal, SystemTimer, Systick, - UsbDevice, AFE0, AFE1, -@@ -179,6 +181,16 @@ pub struct DualIir { - /// See [signal_generator::BasicConfig#miniconf] - #[tree(depth = 2)] - signal_generator: [signal_generator::BasicConfig; 2], -+ -+ /// Specifies the config for pounder DDS clock configuration, DDS channels & attenuations -+ /// -+ /// # Path -+ /// `pounder` -+ /// -+ /// # Value -+ /// See [PounderConfig#miniconf] -+ #[tree] -+ pounder: Option, - } - - impl Default for DualIir { -@@ -206,6 +218,8 @@ impl Default for DualIir { - signal_generator: [signal_generator::BasicConfig::default(); 2], - - stream_target: StreamTarget::default(), -+ -+ pounder: None.into(), - } - } - } -@@ -222,6 +236,7 @@ mod app { - active_settings: DualIir, - telemetry: TelemetryBuffer, - signal_generator: [SignalGenerator; 2], -+ pounder: Option, - } - - #[local] -@@ -233,6 +248,7 @@ mod app { - adcs: (Adc0Input, Adc1Input), - dacs: (Dac0Output, Dac1Output), - iir_state: [[[f32; 4]; IIR_CASCADE_LENGTH]; 2], -+ dds_clock_state: Option, - generator: FrameGenerator, - cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor, - } -@@ -242,7 +258,7 @@ mod app { - let clock = SystemTimer::new(|| Systick::now().ticks()); - - // Configure the microcontroller -- let (stabilizer, _pounder) = hardware::setup::setup::( -+ let (mut stabilizer, pounder) = hardware::setup::setup::( - c.core, - c.device, - clock, -@@ -261,6 +277,13 @@ mod app { - - let generator = network.configure_streaming(StreamFormat::AdcDacData); - -+ let dds_clock_state = pounder.as_ref().map(|_| ClockConfig::default()); -+ if pounder.is_some() { -+ stabilizer.settings.dual_iir -+ .pounder -+ .replace(PounderConfig::default()); -+ } -+ - let shared = Shared { - usb: stabilizer.usb, - network, -@@ -279,6 +302,7 @@ mod app { - ), - ], - settings: stabilizer.settings, -+ pounder - }; - - let mut local = Local { -@@ -289,6 +313,7 @@ mod app { - adcs: stabilizer.adcs, - dacs: stabilizer.dacs, - iir_state: [[[0.; 4]; IIR_CASCADE_LENGTH]; 2], -+ dds_clock_state, - generator, - cpu_temp_sensor: stabilizer.temperature_sensor, - }; -@@ -458,7 +483,7 @@ mod app { - } - } - -- #[task(priority = 1, local=[afes], shared=[network, settings, active_settings, signal_generator])] -+ #[task(priority = 1, local=[afes, dds_clock_state], shared=[network, settings, active_settings, signal_generator, pounder])] - async fn settings_update(mut c: settings_update::Context) { - c.shared.settings.lock(|settings| { - c.local.afes.0.set_gain(settings.dual_iir.afe[0]); -@@ -482,6 +507,17 @@ mod app { - ), - } - } -+ // Update Pounder configurations -+ c.shared.pounder.lock(|pounder| { -+ if let Some(pounder) = pounder { -+ let pounder_settings = settings.dual_iir.pounder.as_ref().unwrap(); -+ // let mut clocking = c.local.dds_clock_state; -+ pounder.update_dds( -+ *pounder_settings, -+ c.local.dds_clock_state.as_mut().unwrap(), -+ ); -+ } -+ }); - - let target = settings.dual_iir.stream_target.into(); - c.shared.network.lock(|net| net.direct_stream(target)); -@@ -492,22 +528,31 @@ mod app { - }); - } - -- #[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])] -+ #[task(priority = 1, shared=[network, settings, telemetry, pounder], local=[cpu_temp_sensor])] - async fn telemetry(mut c: telemetry::Context) { - loop { - let telemetry: TelemetryBuffer = - c.shared.telemetry.lock(|telemetry| *telemetry); - -- let (gains, telemetry_period) = -+ let (gains, telemetry_period, pounder_config) = - c.shared.settings.lock(|settings| { -- (settings.dual_iir.afe, settings.dual_iir.telemetry_period) -+ ( -+ settings.dual_iir.afe, -+ settings.dual_iir.telemetry_period, -+ settings.dual_iir.pounder -+ ) - }); - -+ let pounder_telemetry = c.shared.pounder.lock(|pounder| { -+ pounder.as_mut().map(|pdr| pdr.get_telemetry(pounder_config.unwrap())) -+ }); -+ - c.shared.network.lock(|net| { - net.telemetry.publish(&telemetry.finalize( - gains[0], - gains[1], - c.local.cpu_temp_sensor.get_temperature().unwrap(), -+ pounder_telemetry, - )) - }); - -diff --git a/src/hardware/pounder/attenuators.rs b/src/hardware/pounder/attenuators.rs -index cfd08b7f..2570f506 100644 ---- a/src/hardware/pounder/attenuators.rs -+++ b/src/hardware/pounder/attenuators.rs -@@ -52,10 +52,9 @@ pub trait AttenuatorInterface { - fn get_attenuation(&mut self, channel: Channel) -> Result { - let mut channels = [0_u8; 4]; - -- // Reading the data always shifts data out of the staging registers, so we perform a -- // duplicate write-back to ensure the staging register is always equal to the output -- // register. -- self.transfer_attenuators(&mut channels)?; -+ // Reading the data always shifts data out of the staging registers, so a duplicate -+ // write-back will be performed to ensure the staging register is always equal to the -+ // output register. - self.transfer_attenuators(&mut channels)?; - - // The attenuation code is stored in the upper 6 bits of the register, where each LSB -@@ -66,6 +65,9 @@ pub trait AttenuatorInterface { - // care) would contain erroneous data. - let attenuation_code = (!channels[channel as usize]) >> 2; - -+ // The write-back transfer is performed. Staging register is now restored. -+ self.transfer_attenuators(&mut channels)?; -+ - // Convert the desired channel code into dB of attenuation. - Ok(attenuation_code as f32 / 2.0) - } -diff --git a/src/hardware/pounder/dds_output.rs b/src/hardware/pounder/dds_output.rs -index 3ae1ce90..cd978b01 100644 ---- a/src/hardware/pounder/dds_output.rs -+++ b/src/hardware/pounder/dds_output.rs -@@ -55,7 +55,7 @@ - use log::warn; - use stm32h7xx_hal as hal; - --use super::{hrtimer::HighResTimerE, QspiInterface}; -+use super::{hrtimer::HighResTimerE, Profile, QspiInterface}; - use ad9959::{Channel, Mode, ProfileSerializer}; - - /// The DDS profile update stream. -@@ -157,6 +157,46 @@ impl<'a> ProfileBuilder<'a> { - self - } - -+ /// Update a number of channels with fully defined profile settings. -+ /// -+ /// # Args -+ /// * `channels` - A set of channels to apply the configuration to. -+ /// * `profile` - The complete DDS profile, which defines the frequency tuning word, -+ /// amplitude control register & the phase offset word of the channels. -+ /// # Note -+ /// The ACR should be stored in the 3 LSB of the word. -+ /// If amplitude scaling is to be used, the "Amplitude multiplier enable" bit must be set. -+ #[inline] -+ pub fn update_channels_with_profile( -+ &mut self, -+ channels: Channel, -+ profile: Profile, -+ ) -> &mut Self { -+ self.serializer.update_channels( -+ channels, -+ Some(profile.frequency_tuning_word), -+ Some(profile.phase_offset), -+ Some(profile.amplitude_control), -+ ); -+ self -+ } -+ -+ /// Update the system clock configuration. -+ /// -+ /// # Args -+ /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core. -+ /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20. -+ #[inline] -+ pub fn set_system_clock( -+ &mut self, -+ reference_clock_frequency: f32, -+ multiplier: u8, -+ ) -> Result<&mut Self, ad9959::Error> { -+ self.serializer -+ .set_system_clock(reference_clock_frequency, multiplier)?; -+ Ok(self) -+ } -+ - /// Write the profile to the DDS asynchronously. - #[allow(dead_code)] - #[inline] -diff --git a/src/hardware/pounder/mod.rs b/src/hardware/pounder/mod.rs -index 5bc7e9ff..5b8d5d30 100644 ---- a/src/hardware/pounder/mod.rs -+++ b/src/hardware/pounder/mod.rs -@@ -1,10 +1,17 @@ - use self::attenuators::AttenuatorInterface; - - use super::hal; --use crate::hardware::{shared_adc::AdcChannel, I2c1Proxy}; -+use crate::hardware::{setup, shared_adc::AdcChannel, I2c1Proxy}; -+use crate::net::telemetry::PounderTelemetry; -+use ad9959::{ -+ amplitude_to_acr, frequency_to_ftw, phase_to_pow, validate_clocking, -+}; - use embedded_hal::blocking::spi::Transfer; - use enum_iterator::Sequence; -+use miniconf::Tree; -+use rf_power::PowerMeasurementInterface; - use serde::{Deserialize, Serialize}; -+use stm32h7xx_hal::time::MegaHertz; - - pub mod attenuators; - pub mod dds_output; -@@ -120,40 +127,99 @@ impl From for GpioPin { - } - } - --#[derive(Serialize, Deserialize, Copy, Clone, Debug)] --pub struct DdsChannelState { -- pub phase_offset: f32, -+#[derive(Serialize, Deserialize, Copy, Clone, Debug, Tree)] -+pub struct DdsChannelConfig { - pub frequency: f32, -+ pub phase_offset: f32, - pub amplitude: f32, -- pub enabled: bool, - } - --#[derive(Serialize, Deserialize, Copy, Clone, Debug)] --pub struct ChannelState { -- pub parameters: DdsChannelState, -- pub attenuation: f32, -+impl Default for DdsChannelConfig { -+ fn default() -> Self { -+ Self { -+ frequency: 0.0, -+ phase_offset: 0.0, -+ amplitude: 0.0, -+ } -+ } - } - --#[derive(Serialize, Deserialize, Copy, Clone, Debug)] --pub struct InputChannelState { -- pub attenuation: f32, -- pub power: f32, -- pub mixer: DdsChannelState, -+/// Represents a fully defined DDS profile, with parameters expressed in machine units -+pub struct Profile { -+ /// A 32-bits representation of DDS frequency in relation to the system clock frequency. -+ /// This value corresponds to the AD9959 CFTW0 register, which specifies the frequency -+ /// of DDS channels. -+ pub frequency_tuning_word: u32, -+ /// The DDS phase offset. It corresponds to the AD9959 CPOW0 register, which specifies -+ /// the phase offset of DDS channels. -+ pub phase_offset: u16, -+ /// Control amplitudes of DDS channels. It corresponds to the AD9959 ACR register, which -+ /// controls the amplitude scaling factor of DDS channels. -+ pub amplitude_control: u32, -+} -+ -+impl TryFrom<(ClockConfig, ChannelConfig)> for Profile { -+ type Error = ad9959::Error; -+ -+ fn try_from( -+ (clocking, channel): (ClockConfig, ChannelConfig), -+ ) -> Result { -+ let system_clock_frequency = -+ clocking.reference_clock * clocking.multiplier as f32; -+ Ok(Profile { -+ frequency_tuning_word: frequency_to_ftw( -+ channel.dds.frequency, -+ system_clock_frequency, -+ )?, -+ phase_offset: phase_to_pow(channel.dds.phase_offset)?, -+ amplitude_control: amplitude_to_acr(channel.dds.amplitude)?, -+ }) -+ } - } - --#[derive(Serialize, Deserialize, Copy, Clone, Debug)] --pub struct OutputChannelState { -+#[derive(Serialize, Deserialize, Copy, Clone, Debug, Tree)] -+pub struct ChannelConfig { -+ #[tree] -+ pub dds: DdsChannelConfig, - pub attenuation: f32, -- pub channel: DdsChannelState, - } - --#[derive(Serialize, Deserialize, Copy, Clone, Debug)] --pub struct DdsClockConfig { -+impl Default for ChannelConfig { -+ fn default() -> Self { -+ ChannelConfig { -+ dds: DdsChannelConfig::default(), -+ attenuation: 31.5, -+ } -+ } -+} -+ -+#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Tree)] -+pub struct ClockConfig { - pub multiplier: u8, - pub reference_clock: f32, - pub external_clock: bool, - } - -+impl Default for ClockConfig { -+ fn default() -> Self { -+ Self { -+ multiplier: 5, -+ reference_clock: MegaHertz::MHz(100).to_Hz() as f32, -+ external_clock: false, -+ } -+ } -+} -+ -+#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Tree)] -+pub struct PounderConfig { -+ #[tree] -+ pub clock: ClockConfig, -+ #[tree(depth = 2)] -+ pub in_channel: [ChannelConfig; 2], -+ #[tree(depth = 2)] -+ pub out_channel: [ChannelConfig; 2], -+} -+ - impl From for ad9959::Channel { - /// Translate pounder channels to DDS output channels. - fn from(other: Channel) -> Self { -@@ -585,3 +651,78 @@ impl rf_power::PowerMeasurementInterface for PounderDevices { - Ok(adc_scale * 2.048) - } - } -+ -+impl setup::PounderDevices { -+ pub fn update_dds( -+ &mut self, -+ settings: PounderConfig, -+ clocking: &mut ClockConfig, -+ ) { -+ if *clocking != settings.clock { -+ match validate_clocking( -+ settings.clock.reference_clock, -+ settings.clock.multiplier, -+ ) { -+ Ok(_frequency) => { -+ self.pounder -+ .set_ext_clk(settings.clock.external_clock) -+ .unwrap(); -+ -+ self.dds_output -+ .builder() -+ .set_system_clock( -+ settings.clock.reference_clock, -+ settings.clock.multiplier, -+ ) -+ .unwrap() -+ .write(); -+ -+ *clocking = settings.clock; -+ } -+ Err(err) => { -+ log::error!("Invalid AD9959 clocking parameters: {:?}", err) -+ } -+ } -+ } -+ -+ for (channel_config, pounder_channel) in settings -+ .in_channel -+ .iter() -+ .chain(settings.out_channel.iter()) -+ .zip([Channel::In0, Channel::In1, Channel::Out0, Channel::Out1]) -+ { -+ match Profile::try_from((*clocking, *channel_config)) { -+ Ok(dds_profile) => { -+ self.dds_output -+ .builder() -+ .update_channels_with_profile( -+ pounder_channel.into(), -+ dds_profile, -+ ) -+ .write(); -+ -+ if let Err(err) = self.pounder.set_attenuation( -+ pounder_channel, -+ channel_config.attenuation, -+ ) { -+ log::error!("Invalid attenuation settings: {:?}", err) -+ } -+ } -+ Err(err) => { -+ log::error!("Invalid AD9959 profile settings: {:?}", err) -+ } -+ } -+ } -+ } -+ -+ pub fn get_telemetry(&mut self, config: PounderConfig) -> PounderTelemetry { -+ PounderTelemetry { -+ temperature: self.pounder.lm75.read_temperature().unwrap(), -+ input_power: [ -+ self.pounder.measure_power(Channel::In0).unwrap(), -+ self.pounder.measure_power(Channel::In1).unwrap(), -+ ], -+ config, -+ } -+ } -+} -diff --git a/src/net/mod.rs b/src/net/mod.rs -index a10b7cdb..efa2b8c8 100644 ---- a/src/net/mod.rs -+++ b/src/net/mod.rs -@@ -33,14 +33,14 @@ pub type NetworkReference = - - pub struct MqttStorage { - telemetry: [u8; 2048], -- settings: [u8; 1024], -+ settings: [u8; 1536], - } - - impl Default for MqttStorage { - fn default() -> Self { - Self { - telemetry: [0u8; 2048], -- settings: [0u8; 1024], -+ settings: [0u8; 1536], - } - } - } -diff --git a/src/net/telemetry.rs b/src/net/telemetry.rs -index 4aa82601..3dc3086c 100644 ---- a/src/net/telemetry.rs -+++ b/src/net/telemetry.rs -@@ -16,7 +16,7 @@ use minimq::{DeferredPublication, Publication}; - use serde::Serialize; - - use super::NetworkReference; --use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode, SystemTimer}; -+use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode, SystemTimer, pounder::PounderConfig}; - - /// Default metadata message if formatting errors occur. - const DEFAULT_METADATA: &str = "{\"message\":\"Truncated: See USB terminal\"}"; -@@ -68,6 +68,26 @@ pub struct Telemetry { - - /// The CPU temperature in degrees Celsius. - pub cpu_temp: f32, -+ -+ /// Measurements related to Pounder -+ pub pounder: Option, -+} -+ -+/// The structure that holds the telemetry related to Pounder. -+/// -+/// # Note -+/// This structure should be generated on-demand by the buffer when required to minimize conversion -+/// overhead. -+#[derive(Copy, Clone, Serialize)] -+pub struct PounderTelemetry { -+ /// The Pounder temperature in degrees Celsius -+ pub temperature: f32, -+ -+ /// The detected RF power into IN channels -+ pub input_power: [f32; 2], -+ -+ /// The configuration of the clock and DDS channels -+ pub config: PounderConfig, - } - - impl Default for TelemetryBuffer { -@@ -87,10 +107,17 @@ impl TelemetryBuffer { - /// * `afe0` - The current AFE configuration for channel 0. - /// * `afe1` - The current AFE configuration for channel 1. - /// * `cpu_temp` - The current CPU temperature. -+ /// * `pounder` - The current Pounder telemetry. - /// - /// # Returns - /// The finalized telemetry structure that can be serialized and reported. -- pub fn finalize(self, afe0: Gain, afe1: Gain, cpu_temp: f32) -> Telemetry { -+ pub fn finalize( -+ self, -+ afe0: Gain, -+ afe1: Gain, -+ cpu_temp: f32, -+ pounder: Option, -+ ) -> Telemetry { - let in0_volts = Into::::into(self.adcs[0]) / afe0.as_multiplier(); - let in1_volts = Into::::into(self.adcs[1]) / afe1.as_multiplier(); - -@@ -99,6 +126,7 @@ impl TelemetryBuffer { - adcs: [in0_volts, in1_volts], - dacs: [self.dacs[0].into(), self.dacs[1].into()], - digital_inputs: self.digital_inputs, -+ pounder, - } - } - }