From 986e7cc45790eec28e29ed387b722f4c8466e772 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 21 Jun 2021 16:59:38 +0200 Subject: [PATCH 01/14] Adding initial take at scan mode signal generation --- src/bin/lockin.rs | 28 +++-- src/hardware/mod.rs | 1 + src/hardware/signal_generator.rs | 178 +++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 src/hardware/signal_generator.rs diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index bc1d39a..24a18be 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -16,6 +16,7 @@ use stabilizer::{ embedded_hal::digital::v2::InputPin, hal, input_stamper::InputStamper, + signal_generator, system_timer::SystemTimer, DigitalInput0, DigitalInput1, AFE0, AFE1, }, @@ -28,13 +29,6 @@ use stabilizer::{ }, }; -// 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) * 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 { Magnitude, @@ -102,6 +96,7 @@ const APP: () = { telemetry: TelemetryBuffer, digital_inputs: (DigitalInput0, DigitalInput1), generator: BlockGenerator, + signal_generator: signal_generator::Generator, timestamper: InputStamper, pll: RPLL, @@ -161,6 +156,14 @@ const APP: () = { digital_inputs: stabilizer.digital_inputs, timestamper: stabilizer.timestamper, telemetry: TelemetryBuffer::default(), + signal_generator: signal_generator::Generator::new( + signal_generator::Config { + period: design_parameters::SAMPLE_BUFFER_SIZE as u32, + symmetry: 0.5, + amplitude: 1.0, + signal: signal_generator::Signal::Triangle, + }, + ), settings, generator, @@ -177,7 +180,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, telemetry, generator], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry, generator, signal_generator], priority=2)] #[inline(never)] #[link_section = ".itcm.process"] fn process(mut c: process::Context) { @@ -190,6 +193,7 @@ const APP: () = { ref mut pll, ref mut timestamper, ref mut generator, + ref mut signal_generator, } = c.resources; let (reference_phase, reference_frequency) = match settings.lockin_mode @@ -246,7 +250,7 @@ const APP: () = { // Convert to DAC data. for (channel, samples) in dac_samples.iter_mut().enumerate() { - for (i, sample) in samples.iter_mut().enumerate() { + for sample in samples.iter_mut() { let value = match settings.output_conf[channel] { Conf::Magnitude => output.abs_sqr() as i32 >> 16, Conf::Phase => output.arg() >> 16, @@ -256,7 +260,11 @@ const APP: () = { } Conf::InPhase => output.re >> 16, Conf::Quadrature => output.im >> 16, - Conf::Modulation => DAC_SEQUENCE[i] as i32, + + // Note: Because the signal generator has a period equal to one batch size, + // it's okay to only update it when outputting the modulation waveform, as + // it will perfectly wrap back to zero phase for each batch. + Conf::Modulation => signal_generator.next() as i32, }; *sample = DacCode::from(value as i16).0; diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index 422f7b9..07f1b3d 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -10,6 +10,7 @@ pub mod design_parameters; pub mod input_stamper; pub mod pounder; pub mod setup; +pub mod signal_generator; pub mod system_timer; mod eeprom; diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs new file mode 100644 index 0000000..52aa341 --- /dev/null +++ b/src/hardware/signal_generator.rs @@ -0,0 +1,178 @@ +#[derive(Copy, Clone, Debug)] +pub enum Signal { + Sine, + Square, + Triangle, +} + +#[derive(Copy, Clone, Debug)] +pub struct Config { + // TODO: Should period be specified in Hz? + pub period: u32, + pub symmetry: f32, + pub signal: Signal, + pub amplitude: f32, +} + +impl Default for Config { + fn default() -> Self { + Self { + period: 10, + symmetry: 0.5, + signal: Signal::Sine, + amplitude: 1.0, + } + } +} + +impl Into for Config { + fn into(self) -> InternalConf { + // Clamp amplitude and symmetry. + let amplitude = if self.amplitude > 10.24 { + 10.24 + } else { + self.amplitude + }; + + let symmetry = if self.symmetry < 0.0 { + 0.0 + } else if self.symmetry > 1.0 { + 1.0 + } else { + self.symmetry + }; + + InternalConf { + signal: self.signal, + period: self.period, + amplitude: ((amplitude / 10.24) * i16::MAX as f32) as i16, + phase_symmetry: ((symmetry - 0.5) * i32::MAX as f32) as i32, + } + } +} + +#[derive(Copy, Clone, Debug)] +struct InternalConf { + period: u32, + signal: Signal, + amplitude: i16, + + // The 32-bit representation of the phase symmetry. That is, with a 50% symmetry, this is equal + // to 0. + phase_symmetry: i32, +} + +#[derive(Debug)] +pub struct Generator { + index: u32, + config: InternalConf, + pending_config: Option, +} + +impl Default for Generator { + fn default() -> Self { + Self { + config: Config::default().into(), + index: 0, + pending_config: None, + } + } +} + +impl Generator { + /// Construct a new signal generator with some specific config. + /// + /// # Args + /// * `config` - The config to use for generating signals. + /// + /// # Returns + /// The generator + pub fn new(config: Config) -> Self { + Self { + config: config.into(), + pending_config: None, + index: 0, + } + } + + /// Generate a sequence of new values. + /// + /// # Args + /// * `samples` - The location to store generated values into. + pub fn generate(&mut self, samples: &mut [i16]) { + for sample in samples.iter_mut() { + *sample = self.next(); + } + } + + /// Get the next value in the generator sequence. + pub fn next(&mut self) -> i16 { + // When phase wraps, apply any new settings. + if self.pending_config.is_some() && self.index == 0 { + self.config = self.pending_config.take().unwrap(); + } + + let phase_step = u32::MAX / self.config.period; + + // Note: We allow phase to silently wrap here intentionally, as it will wrap to negative. + // This is acceptable with phase, since it is perfectly periodic. + let phase = (self.index * phase_step) as i32; + + let amplitude = match self.config.signal { + Signal::Sine => (dsp::cossin(phase).1 >> 16) as i16, + Signal::Square => { + if phase < self.config.phase_symmetry { + i16::MAX + } else { + i16::MIN + } + } + Signal::Triangle => { + if phase < self.config.phase_symmetry { + let rise_period: u32 = + (self.config.phase_symmetry - i32::MIN) as u32 + / phase_step; + + if rise_period == 0 { + i16::MIN + } else { + i16::MIN + + (self.index * u16::MAX as u32 / rise_period) + as i16 + } + } else { + let fall_period: u32 = (i32::MAX as u32 + - self.config.phase_symmetry as u32) + / phase_step; + let index: u32 = (phase - self.config.phase_symmetry) + as u32 + / phase_step; + + if fall_period == 0 { + i16::MAX + } else { + i16::MAX + - (index * u16::MAX as u32 / fall_period) as i16 + } + } + } + }; + + // Update the current index. + self.index = (self.index + 1) % self.config.period; + + // Calculate the final output result as an i16. + let result = amplitude as i32 * self.config.amplitude as i32; + + // Note: We downshift by 15-bits to preserve only one of the sign bits. + (result >> 15) as i16 + } + + /// Update waveform generation settings. + /// + /// # Note + /// Changes will not take effect until the current waveform period elapses. + pub fn update_waveform(&mut self, new_config: Config) { + self.pending_config = Some(new_config.into()); + } +} From 45638caa434a6e51704e8d1a316b30d1e7ae04f1 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 21 Jun 2021 20:57:36 +0200 Subject: [PATCH 02/14] Simplifying and correcting triangle wave generation --- src/hardware/signal_generator.rs | 40 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 52aa341..0ca57a2 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -46,7 +46,8 @@ impl Into for Config { signal: self.signal, period: self.period, amplitude: ((amplitude / 10.24) * i16::MAX as f32) as i16, - phase_symmetry: ((symmetry - 0.5) * i32::MAX as f32) as i32, + phase_symmetry: (2.0 * (symmetry - 0.5) * i32::MAX as f32) as i32, + phase_step: ((u32::MAX as u64 + 1u64) / self.period as u64) as u32, } } } @@ -60,6 +61,8 @@ struct InternalConf { // The 32-bit representation of the phase symmetry. That is, with a 50% symmetry, this is equal // to 0. phase_symmetry: i32, + + phase_step: u32, } #[derive(Debug)] @@ -112,11 +115,7 @@ impl Generator { self.config = self.pending_config.take().unwrap(); } - let phase_step = u32::MAX / self.config.period; - - // Note: We allow phase to silently wrap here intentionally, as it will wrap to negative. - // This is acceptable with phase, since it is perfectly periodic. - let phase = (self.index * phase_step) as i32; + let phase = (self.index * self.config.phase_step) as i32; let amplitude = match self.config.signal { Signal::Sine => (dsp::cossin(phase).1 >> 16) as i16, @@ -129,37 +128,32 @@ impl Generator { } Signal::Triangle => { if phase < self.config.phase_symmetry { - let rise_period: u32 = - (self.config.phase_symmetry - i32::MIN) as u32 - / phase_step; + let duration_of_phase = (self.config.phase_symmetry.wrapping_sub(i32::MIN) >> 16) as u16; + let phase_progress = (phase.wrapping_sub(i32::MIN) >> 16) as u16; - if rise_period == 0 { + if duration_of_phase == 0 { i16::MIN } else { - i16::MIN - + (self.index * u16::MAX as u32 / rise_period) - as i16 + i16::MIN.wrapping_add((u16::MAX as u32 * phase_progress as u32 / + duration_of_phase as u32) as i16) } } else { - let fall_period: u32 = (i32::MAX as u32 - - self.config.phase_symmetry as u32) - / phase_step; - let index: u32 = (phase - self.config.phase_symmetry) - as u32 - / phase_step; - if fall_period == 0 { + let duration_of_phase = (i32::MAX.wrapping_sub(self.config.phase_symmetry) >> 16) as u16; + let phase_progress = (phase.wrapping_sub(self.config.phase_symmetry) >> 16) as + u16; + + if duration_of_phase == 0 { i16::MAX } else { - i16::MAX - - (index * u16::MAX as u32 / fall_period) as i16 + i16::MAX.wrapping_sub((u16::MAX as u32 * phase_progress as u32 / duration_of_phase as u32) as i16) } } } }; // Update the current index. - self.index = (self.index + 1) % self.config.period; + self.index = self.index.wrapping_add(1) % self.config.period; // Calculate the final output result as an i16. let result = amplitude as i32 * self.config.amplitude as i32; From 9cca1497b7590b82f80740c69cd498e42fa0fbf7 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 28 Jun 2021 13:16:54 +0200 Subject: [PATCH 03/14] Adding signal generator control to dual-iir --- src/bin/dual-iir.rs | 65 +++++++++++++++++++++++--------- src/hardware/signal_generator.rs | 18 +++++++-- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index efec9c1..ba0a508 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -16,6 +16,7 @@ use stabilizer::{ hal, system_timer::SystemTimer, DigitalInput0, DigitalInput1, AFE0, AFE1, + signal_generator::SignalGenerator, }, net::{ data_stream::{BlockGenerator, StreamTarget}, @@ -39,6 +40,13 @@ pub struct Settings { force_hold: bool, telemetry_period: u16, stream_target: StreamTarget, + signal_generator: signal_generator::Config; + output_mode: [OutputMode; 2], +} + +pub struct OutputMode { + IirFilter, + SignalGenerator, } impl Default for Settings { @@ -59,6 +67,8 @@ impl Default for Settings { // The default telemetry period in seconds. telemetry_period: 10, + signal_generator: signal_generator::Config::default(), + stream_target: StreamTarget::default(), } } @@ -172,24 +182,42 @@ const APP: () = { fence(Ordering::SeqCst); for channel in 0..adc_samples.len() { - adc_samples[channel] - .iter() - .zip(dac_samples[channel].iter_mut()) - .map(|(ai, di)| { - let x = f32::from(*ai as i16); - let y = settings.iir_ch[channel] + match settings.output_mode[channel] { + OutputMode::IirFilter => { + adc_samples[channel] .iter() - .zip(iir_state[channel].iter_mut()) - .fold(x, |yi, (ch, state)| { - ch.update(state, yi, hold) - }); - // Note(unsafe): The filter limits must ensure that the value is in range. - // The truncation introduces 1/2 LSB distortion. - let y: i16 = unsafe { y.to_int_unchecked() }; - // Convert to DAC code - *di = DacCode::from(y).0; - }) - .last(); + .zip(dac_samples[channel].iter_mut()) + .map(|(ai, di)| { + let x = f32::from(*ai as i16); + let y = settings.iir_ch[channel] + .iter() + .zip(iir_state[channel].iter_mut()) + .fold(x, |yi, (ch, state)| { + ch.update(state, yi, hold) + }); + // Note(unsafe): The filter limits must ensure that the value is in range. + // The truncation introduces 1/2 LSB distortion. + let y: i16 = unsafe { y.to_int_unchecked() }; + // Convert to DAC code + *di = DacCode::from(y).0; + }) + .last(); + } + OutputMode::SignalGenerator => { + // Do not generate the samples twice, or we may mess up phasing of the + // signal generator. Instead, copy the previously-generated signal. + // TODO: Is there a nicer way we can handle this edge case? + if (channel == 1) && settings.output_mode[0] == OutputMode::SignalGenerator { + adc_samples[1].copy_from_slice(adc_samples[0]); + } else { + signal_generator.generate(&mut adc_samples[channel]); + } + } + } + } + + if !settings.output_mode.iter().any(|&mode| mode == OutputMode::SignalGenerator) { + signal_generator.skip(adc_samples[0].len()); } // Stream the data. @@ -230,6 +258,9 @@ const APP: () = { c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); + // Update the signal generator + c.resources.signal_generator.update_waveform(settings.signal_generator); + let target = settings.stream_target.into(); c.resources.network.direct_stream(target); } diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 0ca57a2..7cbe331 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -66,13 +66,13 @@ struct InternalConf { } #[derive(Debug)] -pub struct Generator { +pub struct SignalGenerator { index: u32, config: InternalConf, pending_config: Option, } -impl Default for Generator { +impl Default for SignalGenerator { fn default() -> Self { Self { config: Config::default().into(), @@ -82,7 +82,7 @@ impl Default for Generator { } } -impl Generator { +impl SignalGenerator { /// Construct a new signal generator with some specific config. /// /// # Args @@ -108,6 +108,18 @@ impl Generator { } } + /// Skip `count` elements of the generator + pub fn skip(&mut self, count: usize) { + let index = self.index.wrapping_add(count); + + // If we skip past the period of the signal, apply any pending config. + if index > self.config.period && let Some(config) = self.pendig_config.take() { + self.config = config; + } + + self.index = index % self.config.period; + } + /// Get the next value in the generator sequence. pub fn next(&mut self) -> i16 { // When phase wraps, apply any new settings. From 68859c387aa822375cec9c99a9ca7d4d3fa07ebe Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 28 Jun 2021 13:40:59 +0200 Subject: [PATCH 04/14] Adding initial working proof of concept --- src/bin/dual-iir.rs | 41 ++++++++++++++++++-------- src/bin/lockin.rs | 4 +-- src/hardware/signal_generator.rs | 50 ++++++++++++++++++++++---------- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index ba0a508..eb35e00 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -14,9 +14,9 @@ use stabilizer::{ dac::{Dac0Output, Dac1Output, DacCode}, embedded_hal::digital::v2::InputPin, hal, + signal_generator::{self, SignalGenerator}, system_timer::SystemTimer, DigitalInput0, DigitalInput1, AFE0, AFE1, - signal_generator::SignalGenerator, }, net::{ data_stream::{BlockGenerator, StreamTarget}, @@ -40,11 +40,12 @@ pub struct Settings { force_hold: bool, telemetry_period: u16, stream_target: StreamTarget, - signal_generator: signal_generator::Config; + signal_generator: signal_generator::Config, output_mode: [OutputMode; 2], } -pub struct OutputMode { +#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Miniconf)] +pub enum OutputMode { IirFilter, SignalGenerator, } @@ -70,6 +71,7 @@ impl Default for Settings { signal_generator: signal_generator::Config::default(), stream_target: StreamTarget::default(), + output_mode: [OutputMode::IirFilter, OutputMode::IirFilter], } } } @@ -83,6 +85,7 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), network: NetworkUsers, generator: BlockGenerator, + signal_generator: SignalGenerator, settings: Settings, telemetry: TelemetryBuffer, @@ -123,6 +126,8 @@ const APP: () = { // Start sampling ADCs. stabilizer.adc_dac_timer.start(); + let settings = Settings::default(); + init::LateResources { afes: stabilizer.afes, adcs: stabilizer.adcs, @@ -131,7 +136,8 @@ const APP: () = { network, digital_inputs: stabilizer.digital_inputs, telemetry: TelemetryBuffer::default(), - settings: Settings::default(), + settings, + signal_generator: SignalGenerator::new(settings.signal_generator), } } @@ -151,7 +157,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, digital_inputs, dacs, iir_state, settings, telemetry, generator], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, signal_generator, telemetry, generator], priority=2)] #[inline(never)] #[link_section = ".itcm.process"] fn process(mut c: process::Context) { @@ -163,6 +169,7 @@ const APP: () = { ref mut iir_state, ref mut telemetry, ref mut generator, + ref mut signal_generator, } = c.resources; let digital_inputs = [ @@ -207,17 +214,25 @@ const APP: () = { // Do not generate the samples twice, or we may mess up phasing of the // signal generator. Instead, copy the previously-generated signal. // TODO: Is there a nicer way we can handle this edge case? - if (channel == 1) && settings.output_mode[0] == OutputMode::SignalGenerator { - adc_samples[1].copy_from_slice(adc_samples[0]); + if (channel == 1) + && settings.output_mode[0] + == OutputMode::SignalGenerator + { + *dac_samples[1] = *dac_samples[0]; } else { - signal_generator.generate(&mut adc_samples[channel]); + signal_generator + .generate(&mut dac_samples[channel][..]); } } } } - if !settings.output_mode.iter().any(|&mode| mode == OutputMode::SignalGenerator) { - signal_generator.skip(adc_samples[0].len()); + if !settings + .output_mode + .iter() + .any(|&mode| mode == OutputMode::SignalGenerator) + { + signal_generator.skip(adc_samples[0].len() as u32); } // Stream the data. @@ -248,7 +263,7 @@ const APP: () = { } } - #[task(priority = 1, resources=[network, afes, settings])] + #[task(priority = 1, resources=[network, afes, settings, signal_generator])] fn settings_update(mut c: settings_update::Context) { // Update the IIR channels. let settings = c.resources.network.miniconf.settings(); @@ -259,7 +274,9 @@ const APP: () = { c.resources.afes.1.set_gain(settings.afe[1]); // Update the signal generator - c.resources.signal_generator.update_waveform(settings.signal_generator); + c.resources.signal_generator.lock(|generator| { + generator.update_waveform(settings.signal_generator) + }); let target = settings.stream_target.into(); c.resources.network.direct_stream(target); diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 24a18be..681b5ad 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -96,7 +96,7 @@ const APP: () = { telemetry: TelemetryBuffer, digital_inputs: (DigitalInput0, DigitalInput1), generator: BlockGenerator, - signal_generator: signal_generator::Generator, + signal_generator: signal_generator::SignalGenerator, timestamper: InputStamper, pll: RPLL, @@ -156,7 +156,7 @@ const APP: () = { digital_inputs: stabilizer.digital_inputs, timestamper: stabilizer.timestamper, telemetry: TelemetryBuffer::default(), - signal_generator: signal_generator::Generator::new( + signal_generator: signal_generator::SignalGenerator::new( signal_generator::Config { period: design_parameters::SAMPLE_BUFFER_SIZE as u32, symmetry: 0.5, diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 7cbe331..4fc4893 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -1,11 +1,15 @@ -#[derive(Copy, Clone, Debug)] +use crate::hardware::dac::DacCode; +use miniconf::Miniconf; +use serde::Deserialize; + +#[derive(Copy, Clone, Debug, Deserialize, Miniconf)] pub enum Signal { Sine, Square, Triangle, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Miniconf, Deserialize)] pub struct Config { // TODO: Should period be specified in Hz? pub period: u32, @@ -102,19 +106,21 @@ impl SignalGenerator { /// /// # Args /// * `samples` - The location to store generated values into. - pub fn generate(&mut self, samples: &mut [i16]) { + pub fn generate(&mut self, samples: &mut [u16]) { for sample in samples.iter_mut() { - *sample = self.next(); + *sample = DacCode::from(self.next()).0; } } /// Skip `count` elements of the generator - pub fn skip(&mut self, count: usize) { + pub fn skip(&mut self, count: u32) { let index = self.index.wrapping_add(count); // If we skip past the period of the signal, apply any pending config. - if index > self.config.period && let Some(config) = self.pendig_config.take() { - self.config = config; + if index > self.config.period { + if let Some(config) = self.pending_config.take() { + self.config = config; + } } self.index = index % self.config.period; @@ -140,25 +146,37 @@ impl SignalGenerator { } Signal::Triangle => { if phase < self.config.phase_symmetry { - let duration_of_phase = (self.config.phase_symmetry.wrapping_sub(i32::MIN) >> 16) as u16; - let phase_progress = (phase.wrapping_sub(i32::MIN) >> 16) as u16; + let duration_of_phase = + (self.config.phase_symmetry.wrapping_sub(i32::MIN) + >> 16) as u16; + let phase_progress = + (phase.wrapping_sub(i32::MIN) >> 16) as u16; if duration_of_phase == 0 { i16::MIN } else { - i16::MIN.wrapping_add((u16::MAX as u32 * phase_progress as u32 / - duration_of_phase as u32) as i16) + i16::MIN.wrapping_add( + (u16::MAX as u32 * phase_progress as u32 + / duration_of_phase as u32) + as i16, + ) } } else { - - let duration_of_phase = (i32::MAX.wrapping_sub(self.config.phase_symmetry) >> 16) as u16; - let phase_progress = (phase.wrapping_sub(self.config.phase_symmetry) >> 16) as - u16; + let duration_of_phase = + (i32::MAX.wrapping_sub(self.config.phase_symmetry) + >> 16) as u16; + let phase_progress = (phase + .wrapping_sub(self.config.phase_symmetry) + >> 16) as u16; if duration_of_phase == 0 { i16::MAX } else { - i16::MAX.wrapping_sub((u16::MAX as u32 * phase_progress as u32 / duration_of_phase as u32) as i16) + i16::MAX.wrapping_sub( + (u16::MAX as u32 * phase_progress as u32 + / duration_of_phase as u32) + as i16, + ) } } } From 24c0075b7a942bcd32a3f27eace9abe93a7ac5ac Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 28 Jun 2021 14:11:52 +0200 Subject: [PATCH 05/14] Adding signal generator refactor --- src/bin/dual-iir.rs | 4 +- src/bin/lockin.rs | 13 ++++- src/hardware/signal_generator.rs | 97 +++++++++++++++++++------------- 3 files changed, 69 insertions(+), 45 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index eb35e00..fcb31fe 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -40,7 +40,7 @@ pub struct Settings { force_hold: bool, telemetry_period: u16, stream_target: StreamTarget, - signal_generator: signal_generator::Config, + signal_generator: signal_generator::BasicConfig, output_mode: [OutputMode; 2], } @@ -68,7 +68,7 @@ impl Default for Settings { // The default telemetry period in seconds. telemetry_period: 10, - signal_generator: signal_generator::Config::default(), + signal_generator: signal_generator::BasicConfig::default(), stream_target: StreamTarget::default(), output_mode: [OutputMode::IirFilter, OutputMode::IirFilter], diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 681b5ad..0a4cd01 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -158,9 +158,16 @@ const APP: () = { telemetry: TelemetryBuffer::default(), signal_generator: signal_generator::SignalGenerator::new( signal_generator::Config { - period: design_parameters::SAMPLE_BUFFER_SIZE as u32, - symmetry: 0.5, - amplitude: 1.0, + // Same frequency as batch size. + // TODO: Is this off-by-one? + frequency_tuning_word: u32::MAX + / design_parameters::SAMPLE_BUFFER_SIZE as u32, + + // Equal symmetry + phase_symmetry: 0, + + // 1V Amplitude + amplitude: ((1.0 / 10.24) * i16::MAX as f32) as i16, signal: signal_generator::Signal::Triangle, }, ), diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 4fc4893..f2bd3cb 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -1,4 +1,4 @@ -use crate::hardware::dac::DacCode; +use crate::hardware::{dac::DacCode, design_parameters::ADC_SAMPLE_TICKS}; use miniconf::Miniconf; use serde::Deserialize; @@ -10,18 +10,17 @@ pub enum Signal { } #[derive(Copy, Clone, Debug, Miniconf, Deserialize)] -pub struct Config { - // TODO: Should period be specified in Hz? - pub period: u32, +pub struct BasicConfig { + pub frequency: f32, pub symmetry: f32, pub signal: Signal, pub amplitude: f32, } -impl Default for Config { +impl Default for BasicConfig { fn default() -> Self { Self { - period: 10, + frequency: 1.0e3, symmetry: 0.5, signal: Signal::Sine, amplitude: 1.0, @@ -29,8 +28,12 @@ impl Default for Config { } } -impl Into for Config { - fn into(self) -> InternalConf { +impl Into for BasicConfig { + fn into(self) -> Config { + // Calculate the number of output codes in the signal period. + let period = + (100.0_e6 / (self.frequency * ADC_SAMPLE_TICKS as f32)) as u32; + // Clamp amplitude and symmetry. let amplitude = if self.amplitude > 10.24 { 10.24 @@ -46,41 +49,44 @@ impl Into for Config { self.symmetry }; - InternalConf { + Config { signal: self.signal, - period: self.period, amplitude: ((amplitude / 10.24) * i16::MAX as f32) as i16, phase_symmetry: (2.0 * (symmetry - 0.5) * i32::MAX as f32) as i32, - phase_step: ((u32::MAX as u64 + 1u64) / self.period as u64) as u32, + frequency_tuning_word: ((u32::MAX as u64 + 1u64) / period as u64) + as u32, } } } #[derive(Copy, Clone, Debug)] -struct InternalConf { - period: u32, - signal: Signal, - amplitude: i16, +pub struct Config { + // The type of signal being generated + pub signal: Signal, + + // The full-scale output code of the signal + pub amplitude: i16, // The 32-bit representation of the phase symmetry. That is, with a 50% symmetry, this is equal // to 0. - phase_symmetry: i32, + pub phase_symmetry: i32, - phase_step: u32, + // The frequency tuning word of the signal. Phase is incremented by this amount + pub frequency_tuning_word: u32, } #[derive(Debug)] pub struct SignalGenerator { - index: u32, - config: InternalConf, - pending_config: Option, + phase_accumulator: u32, + config: Config, + pending_config: Option, } impl Default for SignalGenerator { fn default() -> Self { Self { - config: Config::default().into(), - index: 0, + config: BasicConfig::default().into(), + phase_accumulator: 0, pending_config: None, } } @@ -94,11 +100,11 @@ impl SignalGenerator { /// /// # Returns /// The generator - pub fn new(config: Config) -> Self { + pub fn new(config: impl Into) -> Self { Self { config: config.into(), pending_config: None, - index: 0, + phase_accumulator: 0, } } @@ -112,28 +118,42 @@ impl SignalGenerator { } } - /// Skip `count` elements of the generator - pub fn skip(&mut self, count: u32) { - let index = self.index.wrapping_add(count); + // Increment the phase of the signal. + // + // # Note + // This handles automatically applying pending configurations on phase wrap. + // + // # Returns + // The new phase to use + fn increment(&mut self) -> i32 { + let (phase, overflow) = self + .phase_accumulator + .overflowing_add(self.config.frequency_tuning_word); - // If we skip past the period of the signal, apply any pending config. - if index > self.config.period { + self.phase_accumulator = phase; + + // Special case: If the FTW is specified as zero, we would otherwise never update the + // settings. Perform a check here for this corner case. + if overflow || self.config.frequency_tuning_word == 0 { if let Some(config) = self.pending_config.take() { self.config = config; + self.phase_accumulator = 0; } } - self.index = index % self.config.period; + self.phase_accumulator as i32 + } + + /// Skip `count` elements of the generator + pub fn skip(&mut self, count: u32) { + for _ in 0..count { + self.increment(); + } } /// Get the next value in the generator sequence. pub fn next(&mut self) -> i16 { - // When phase wraps, apply any new settings. - if self.pending_config.is_some() && self.index == 0 { - self.config = self.pending_config.take().unwrap(); - } - - let phase = (self.index * self.config.phase_step) as i32; + let phase = self.increment(); let amplitude = match self.config.signal { Signal::Sine => (dsp::cossin(phase).1 >> 16) as i16, @@ -182,9 +202,6 @@ impl SignalGenerator { } }; - // Update the current index. - self.index = self.index.wrapping_add(1) % self.config.period; - // Calculate the final output result as an i16. let result = amplitude as i32 * self.config.amplitude as i32; @@ -196,7 +213,7 @@ impl SignalGenerator { /// /// # Note /// Changes will not take effect until the current waveform period elapses. - pub fn update_waveform(&mut self, new_config: Config) { + pub fn update_waveform(&mut self, new_config: impl Into) { self.pending_config = Some(new_config.into()); } } From 386259bf6aed168d87ece56e5a7c2b06266287d9 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 29 Jun 2021 13:23:42 +0200 Subject: [PATCH 06/14] Updating after review --- src/bin/dual-iir.rs | 87 +++++++++++-------------------- src/bin/lockin.rs | 12 +++-- src/hardware/dac.rs | 31 +++++++++-- src/hardware/signal_generator.rs | 89 ++++++++++++++------------------ 4 files changed, 104 insertions(+), 115 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index fcb31fe..a27fc3d 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -40,14 +40,7 @@ pub struct Settings { force_hold: bool, telemetry_period: u16, stream_target: StreamTarget, - signal_generator: signal_generator::BasicConfig, - output_mode: [OutputMode; 2], -} - -#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Miniconf)] -pub enum OutputMode { - IirFilter, - SignalGenerator, + signal_generator: [signal_generator::BasicConfig; 2], } impl Default for Settings { @@ -68,10 +61,9 @@ impl Default for Settings { // The default telemetry period in seconds. telemetry_period: 10, - signal_generator: signal_generator::BasicConfig::default(), + signal_generator: [signal_generator::BasicConfig::default(); 2], stream_target: StreamTarget::default(), - output_mode: [OutputMode::IirFilter, OutputMode::IirFilter], } } } @@ -85,7 +77,7 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), network: NetworkUsers, generator: BlockGenerator, - signal_generator: SignalGenerator, + signal_generator: [SignalGenerator; 2], settings: Settings, telemetry: TelemetryBuffer, @@ -137,7 +129,10 @@ const APP: () = { digital_inputs: stabilizer.digital_inputs, telemetry: TelemetryBuffer::default(), settings, - signal_generator: SignalGenerator::new(settings.signal_generator), + signal_generator: [ + SignalGenerator::new(settings.signal_generator[0]), + SignalGenerator::new(settings.signal_generator[1]), + ], } } @@ -189,50 +184,29 @@ const APP: () = { fence(Ordering::SeqCst); for channel in 0..adc_samples.len() { - match settings.output_mode[channel] { - OutputMode::IirFilter => { - adc_samples[channel] + adc_samples[channel] + .iter() + .zip(dac_samples[channel].iter_mut()) + .zip(&mut signal_generator[channel]) + .map(|((ai, di), signal)| { + let x = f32::from(*ai as i16); + let y = settings.iir_ch[channel] .iter() - .zip(dac_samples[channel].iter_mut()) - .map(|(ai, di)| { - let x = f32::from(*ai as i16); - let y = settings.iir_ch[channel] - .iter() - .zip(iir_state[channel].iter_mut()) - .fold(x, |yi, (ch, state)| { - ch.update(state, yi, hold) - }); - // Note(unsafe): The filter limits must ensure that the value is in range. - // The truncation introduces 1/2 LSB distortion. - let y: i16 = unsafe { y.to_int_unchecked() }; - // Convert to DAC code - *di = DacCode::from(y).0; - }) - .last(); - } - OutputMode::SignalGenerator => { - // Do not generate the samples twice, or we may mess up phasing of the - // signal generator. Instead, copy the previously-generated signal. - // TODO: Is there a nicer way we can handle this edge case? - if (channel == 1) - && settings.output_mode[0] - == OutputMode::SignalGenerator - { - *dac_samples[1] = *dac_samples[0]; - } else { - signal_generator - .generate(&mut dac_samples[channel][..]); - } - } - } - } + .zip(iir_state[channel].iter_mut()) + .fold(x, |yi, (ch, state)| { + ch.update(state, yi, hold) + }); - if !settings - .output_mode - .iter() - .any(|&mode| mode == OutputMode::SignalGenerator) - { - signal_generator.skip(adc_samples[0].len() as u32); + // Note(unsafe): The filter limits must ensure that the value is in range. + // The truncation introduces 1/2 LSB distortion. + let y: i16 = unsafe { y.to_int_unchecked() }; + + let y = y.saturating_add(signal); + + // Convert to DAC code + *di = DacCode::from(y).0; + }) + .last(); } // Stream the data. @@ -273,9 +247,10 @@ const APP: () = { c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); - // Update the signal generator + // Update the signal generators c.resources.signal_generator.lock(|generator| { - generator.update_waveform(settings.signal_generator) + generator[0].update_waveform(settings.signal_generator[0]); + generator[1].update_waveform(settings.signal_generator[1]); }); let target = settings.stream_target.into(); diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 0a4cd01..d0af31e 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -159,16 +159,16 @@ const APP: () = { signal_generator: signal_generator::SignalGenerator::new( signal_generator::Config { // Same frequency as batch size. - // TODO: Is this off-by-one? - frequency_tuning_word: u32::MAX - / design_parameters::SAMPLE_BUFFER_SIZE as u32, + frequency: ((u32::MAX as u64 + 1u64) + / design_parameters::SAMPLE_BUFFER_SIZE as u64) + as u32, // Equal symmetry phase_symmetry: 0, // 1V Amplitude amplitude: ((1.0 / 10.24) * i16::MAX as f32) as i16, - signal: signal_generator::Signal::Triangle, + signal: signal_generator::Signal::Cosine, }, ), @@ -271,7 +271,9 @@ const APP: () = { // Note: Because the signal generator has a period equal to one batch size, // it's okay to only update it when outputting the modulation waveform, as // it will perfectly wrap back to zero phase for each batch. - Conf::Modulation => signal_generator.next() as i32, + Conf::Modulation => { + signal_generator.next().unwrap() as i32 + } }; *sample = DacCode::from(value as i16).0; diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 9b42e37..9f9a330 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -73,14 +73,37 @@ static mut DAC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2]; #[derive(Copy, Clone)] pub struct DacCode(pub u16); -#[allow(clippy::from_over_into)] -impl Into for DacCode { - fn into(self) -> f32 { +impl From for DacCode { + fn from(voltage: f32) -> DacCode { + // 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_range = 4.096 * 2.5; + + let voltage = if voltage > dac_range { + dac_range + } else if voltage < -1. * dac_range { + -1. * dac_range + } else { + voltage + }; + + DacCode::from((voltage / dac_range * i16::MAX as f32) as i16) + } +} + +impl From for f32 { + fn from(code: DacCode) -> f32 { // 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; - (self.0 as i16).wrapping_add(i16::MIN) as f32 * dac_volts_per_lsb + (code.0 as i16).wrapping_add(i16::MIN) as f32 * dac_volts_per_lsb + } +} + +impl From for i16 { + fn from(code: DacCode) -> i16 { + (code.0 as i16).wrapping_sub(i16::MIN) } } diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index f2bd3cb..c7bbed0 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -4,7 +4,7 @@ use serde::Deserialize; #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] pub enum Signal { - Sine, + Cosine, Square, Triangle, } @@ -12,7 +12,7 @@ pub enum Signal { #[derive(Copy, Clone, Debug, Miniconf, Deserialize)] pub struct BasicConfig { pub frequency: f32, - pub symmetry: f32, + pub asymmetry: f32, pub signal: Signal, pub amplitude: f32, } @@ -21,40 +21,42 @@ impl Default for BasicConfig { fn default() -> Self { Self { frequency: 1.0e3, - symmetry: 0.5, - signal: Signal::Sine, - amplitude: 1.0, + asymmetry: 0.0, + signal: Signal::Cosine, + amplitude: 0.0, } } } -impl Into for BasicConfig { - fn into(self) -> Config { - // Calculate the number of output codes in the signal period. - let period = - (100.0_e6 / (self.frequency * ADC_SAMPLE_TICKS as f32)) as u32; +impl From for Config { + fn from(config: BasicConfig) -> Config { + // Calculate the frequency tuning word + let frequency: u32 = + (config.frequency * ADC_SAMPLE_TICKS as f32 / 100.0_e6 + * (u32::MAX as u64 + 1u64) as f32) as u32; // Clamp amplitude and symmetry. - let amplitude = if self.amplitude > 10.24 { + let amplitude = if config.amplitude > 10.24 { 10.24 + } else if config.amplitude < 0.0 { + 0.0 } else { - self.amplitude + config.amplitude }; - let symmetry = if self.symmetry < 0.0 { - 0.0 - } else if self.symmetry > 1.0 { + let asymmetry = if config.asymmetry < -1.0 { + -1.0 + } else if config.asymmetry > 1.0 { 1.0 } else { - self.symmetry + config.asymmetry }; Config { - signal: self.signal, - amplitude: ((amplitude / 10.24) * i16::MAX as f32) as i16, - phase_symmetry: (2.0 * (symmetry - 0.5) * i32::MAX as f32) as i32, - frequency_tuning_word: ((u32::MAX as u64 + 1u64) / period as u64) - as u32, + signal: config.signal, + amplitude: DacCode::from(amplitude).into(), + phase_symmetry: (asymmetry * i32::MAX as f32) as i32, + frequency, } } } @@ -72,7 +74,7 @@ pub struct Config { pub phase_symmetry: i32, // The frequency tuning word of the signal. Phase is incremented by this amount - pub frequency_tuning_word: u32, + pub frequency: u32, } #[derive(Debug)] @@ -108,16 +110,6 @@ impl SignalGenerator { } } - /// Generate a sequence of new values. - /// - /// # Args - /// * `samples` - The location to store generated values into. - pub fn generate(&mut self, samples: &mut [u16]) { - for sample in samples.iter_mut() { - *sample = DacCode::from(self.next()).0; - } - } - // Increment the phase of the signal. // // # Note @@ -128,13 +120,13 @@ impl SignalGenerator { fn increment(&mut self) -> i32 { let (phase, overflow) = self .phase_accumulator - .overflowing_add(self.config.frequency_tuning_word); + .overflowing_add(self.config.frequency); self.phase_accumulator = phase; // Special case: If the FTW is specified as zero, we would otherwise never update the // settings. Perform a check here for this corner case. - if overflow || self.config.frequency_tuning_word == 0 { + if overflow || self.config.frequency == 0 { if let Some(config) = self.pending_config.take() { self.config = config; self.phase_accumulator = 0; @@ -144,19 +136,24 @@ impl SignalGenerator { self.phase_accumulator as i32 } - /// Skip `count` elements of the generator - pub fn skip(&mut self, count: u32) { - for _ in 0..count { - self.increment(); - } + /// Update waveform generation settings. + /// + /// # Note + /// Changes will not take effect until the current waveform period elapses. + pub fn update_waveform(&mut self, new_config: impl Into) { + self.pending_config = Some(new_config.into()); } +} + +impl core::iter::Iterator for SignalGenerator { + type Item = i16; /// Get the next value in the generator sequence. - pub fn next(&mut self) -> i16 { + fn next(&mut self) -> Option { let phase = self.increment(); let amplitude = match self.config.signal { - Signal::Sine => (dsp::cossin(phase).1 >> 16) as i16, + Signal::Cosine => (dsp::cossin(phase).0 >> 16) as i16, Signal::Square => { if phase < self.config.phase_symmetry { i16::MAX @@ -206,14 +203,6 @@ impl SignalGenerator { let result = amplitude as i32 * self.config.amplitude as i32; // Note: We downshift by 15-bits to preserve only one of the sign bits. - (result >> 15) as i16 - } - - /// Update waveform generation settings. - /// - /// # Note - /// Changes will not take effect until the current waveform period elapses. - pub fn update_waveform(&mut self, new_config: impl Into) { - self.pending_config = Some(new_config.into()); + Some((result >> 15) as i16) } } From b319fe2c6b60719d1cea13e9655fafe5d585f326 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 16 Jul 2021 12:55:11 +0200 Subject: [PATCH 07/14] Refactoring signal generation to utilize static tuning words --- src/bin/lockin.rs | 15 ++-- src/hardware/adc.rs | 35 +++++++- src/hardware/dac.rs | 7 ++ src/hardware/signal_generator.rs | 138 +++++++++++++++++++------------ 4 files changed, 127 insertions(+), 68 deletions(-) diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 6b7510b..ae2d611 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -160,16 +160,14 @@ const APP: () = { signal_generator: signal_generator::SignalGenerator::new( signal_generator::Config { // Same frequency as batch size. - frequency: ((u32::MAX as u64 + 1u64) - / design_parameters::SAMPLE_BUFFER_SIZE as u64) + frequency: (1u64 + << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2)) as u32, - // Equal symmetry - phase_symmetry: 0, - // 1V Amplitude - amplitude: ((1.0 / 10.24) * i16::MAX as f32) as i16, - signal: signal_generator::Signal::Cosine, + amplitude: DacCode::from(1.0).into(), + + signal: signal_generator::SignalConfig::Cosine, }, ), @@ -269,9 +267,6 @@ const APP: () = { Conf::InPhase => output.re >> 16, Conf::Quadrature => output.im >> 16, - // Note: Because the signal generator has a period equal to one batch size, - // it's okay to only update it when outputting the modulation waveform, as - // it will perfectly wrap back to zero phase for each batch. Conf::Modulation => { signal_generator.next().unwrap() as i32 } diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index 9b1a833..fd131ee 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -83,18 +83,45 @@ use hal::dma::{ #[derive(Copy, Clone)] pub struct AdcCode(pub u16); -#[allow(clippy::from_over_into)] -impl Into for AdcCode { +impl From for AdcCode { + /// Construct an ADC code from a provided binary (ADC-formatted) code. + fn from(value: u16) -> Self { + Self(value) + } +} + +impl From for AdcCode { + /// Construct an ADC code from the stabilizer-defined code (i16 full range). + fn from(value: i16) -> Self { + Self(value as u16) + } +} + +impl From for i16 { + /// Get a stabilizer-defined code from the ADC code. + fn from(code: AdcCode) -> i16 { + code.0 as i16 + } +} + +impl From for u16 { + /// Get an ADC-frmatted binary value from the code. + fn from(code: AdcCode) -> u16 { + code.0 + } +} + +impl From for f32 { /// 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 { + fn from(code: AdcCode) -> 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 / (1u16 << 15) as f32; - (self.0 as i16) as f32 * adc_volts_per_lsb + i16::from(code) as f32 * adc_volts_per_lsb } } diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 90b75c7..b40c5c6 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -116,6 +116,13 @@ impl From for DacCode { } } +impl From for DacCode { + /// Create a dac code from the provided DAC output code. + fn from(value: u16) -> Self { + Self(value) + } +} + macro_rules! dac_output { ($name:ident, $index:literal, $data_stream:ident, $spi:ident, $trigger_channel:ident, $dma_req:ident) => { diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index c7bbed0..33f3d80 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -31,9 +31,11 @@ impl Default for BasicConfig { impl From for Config { fn from(config: BasicConfig) -> Config { // Calculate the frequency tuning word - let frequency: u32 = - (config.frequency * ADC_SAMPLE_TICKS as f32 / 100.0_e6 - * (u32::MAX as u64 + 1u64) as f32) as u32; + let frequency: u32 = { + let conversion_factor = + ADC_SAMPLE_TICKS as f32 / 100.0e6 * (1u64 << 32) as f32; + (config.frequency * conversion_factor) as u32 + }; // Clamp amplitude and symmetry. let amplitude = if config.amplitude > 10.24 { @@ -44,18 +46,52 @@ impl From for Config { config.amplitude }; - let asymmetry = if config.asymmetry < -1.0 { - -1.0 - } else if config.asymmetry > 1.0 { - 1.0 - } else { - config.asymmetry + let symmetry = { + let asymmetry = if config.asymmetry < -1.0 { + -1.0 + } else if config.asymmetry > 1.0 { + 1.0 + } else { + config.asymmetry + }; + + (asymmetry * i32::MAX as f32) as i32 + }; + + let signal_config = match config.signal { + Signal::Cosine => SignalConfig::Cosine, + Signal::Square => SignalConfig::Square { symmetry }, + Signal::Triangle => { + let tuning_word = { + let segment_one_turns = + symmetry.wrapping_sub(i32::MIN) >> 16; + let segment_one_tw = if segment_one_turns > 0 { + u16::MAX as u32 / segment_one_turns as u32 + } else { + 0 + }; + + let segment_two_turns = + i32::MAX.wrapping_sub(symmetry) >> 16; + let segment_two_tw = if segment_two_turns > 0 { + u16::MAX as u32 / segment_two_turns as u32 + } else { + 0 + }; + + [segment_one_tw, segment_two_tw] + }; + + SignalConfig::Triangle { + symmetry, + tuning_word, + } + } }; Config { - signal: config.signal, amplitude: DacCode::from(amplitude).into(), - phase_symmetry: (asymmetry * i32::MAX as f32) as i32, + signal: signal_config, frequency, } } @@ -63,20 +99,32 @@ impl From for Config { #[derive(Copy, Clone, Debug)] pub struct Config { - // The type of signal being generated - pub signal: Signal, + /// The type of signal being generated + pub signal: SignalConfig, - // The full-scale output code of the signal + /// The full-scale output code of the signal pub amplitude: i16, - // The 32-bit representation of the phase symmetry. That is, with a 50% symmetry, this is equal - // to 0. - pub phase_symmetry: i32, - - // The frequency tuning word of the signal. Phase is incremented by this amount + /// The frequency tuning word of the signal. Phase is incremented by this amount pub frequency: u32, } +#[derive(Copy, Clone, Debug)] +pub enum SignalConfig { + Cosine, + Square { + /// The phase symmetry cross-over of the waveform. + symmetry: i32, + }, + Triangle { + /// The phase symmetry cross-over of the waveform. + symmetry: i32, + /// The amplitude added for each phase turn increment (different words for different + /// phases). + tuning_word: [u32; 2], + }, +} + #[derive(Debug)] pub struct SignalGenerator { phase_accumulator: u32, @@ -153,48 +201,30 @@ impl core::iter::Iterator for SignalGenerator { let phase = self.increment(); let amplitude = match self.config.signal { - Signal::Cosine => (dsp::cossin(phase).0 >> 16) as i16, - Signal::Square => { - if phase < self.config.phase_symmetry { + SignalConfig::Cosine => (dsp::cossin(phase).0 >> 16) as i16, + SignalConfig::Square { symmetry } => { + if phase < symmetry { i16::MAX } else { i16::MIN } } - Signal::Triangle => { - if phase < self.config.phase_symmetry { - let duration_of_phase = - (self.config.phase_symmetry.wrapping_sub(i32::MIN) - >> 16) as u16; - let phase_progress = + SignalConfig::Triangle { + symmetry, + tuning_word, + } => { + if phase < symmetry { + let segment_turns = (phase.wrapping_sub(i32::MIN) >> 16) as u16; - - if duration_of_phase == 0 { - i16::MIN - } else { - i16::MIN.wrapping_add( - (u16::MAX as u32 * phase_progress as u32 - / duration_of_phase as u32) - as i16, - ) - } + i16::MIN.wrapping_add( + (tuning_word[0] * segment_turns as u32) as i16, + ) } else { - let duration_of_phase = - (i32::MAX.wrapping_sub(self.config.phase_symmetry) - >> 16) as u16; - let phase_progress = (phase - .wrapping_sub(self.config.phase_symmetry) - >> 16) as u16; - - if duration_of_phase == 0 { - i16::MAX - } else { - i16::MAX.wrapping_sub( - (u16::MAX as u32 * phase_progress as u32 - / duration_of_phase as u32) - as i16, - ) - } + let segment_turns = + (phase.wrapping_sub(symmetry) >> 16) as u16; + i16::MAX.wrapping_sub( + (tuning_word[1] * segment_turns as u32) as i16, + ) } } }; From e1064bf403d165e2f7a65db1fda6543060299d90 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 16 Jul 2021 12:58:59 +0200 Subject: [PATCH 08/14] Allowing signal generator to update any time --- src/hardware/signal_generator.rs | 38 ++++---------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 33f3d80..30ef03a 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -129,7 +129,6 @@ pub enum SignalConfig { pub struct SignalGenerator { phase_accumulator: u32, config: Config, - pending_config: Option, } impl Default for SignalGenerator { @@ -137,7 +136,6 @@ impl Default for SignalGenerator { Self { config: BasicConfig::default().into(), phase_accumulator: 0, - pending_config: None, } } } @@ -153,43 +151,13 @@ impl SignalGenerator { pub fn new(config: impl Into) -> Self { Self { config: config.into(), - pending_config: None, phase_accumulator: 0, } } - // Increment the phase of the signal. - // - // # Note - // This handles automatically applying pending configurations on phase wrap. - // - // # Returns - // The new phase to use - fn increment(&mut self) -> i32 { - let (phase, overflow) = self - .phase_accumulator - .overflowing_add(self.config.frequency); - - self.phase_accumulator = phase; - - // Special case: If the FTW is specified as zero, we would otherwise never update the - // settings. Perform a check here for this corner case. - if overflow || self.config.frequency == 0 { - if let Some(config) = self.pending_config.take() { - self.config = config; - self.phase_accumulator = 0; - } - } - - self.phase_accumulator as i32 - } - /// Update waveform generation settings. - /// - /// # Note - /// Changes will not take effect until the current waveform period elapses. pub fn update_waveform(&mut self, new_config: impl Into) { - self.pending_config = Some(new_config.into()); + self.config = new_config.into(); } } @@ -198,7 +166,9 @@ impl core::iter::Iterator for SignalGenerator { /// Get the next value in the generator sequence. fn next(&mut self) -> Option { - let phase = self.increment(); + self.phase_accumulator = + self.phase_accumulator.wrapping_add(self.config.frequency); + let phase = self.phase_accumulator as i32; let amplitude = match self.config.signal { SignalConfig::Cosine => (dsp::cossin(phase).0 >> 16) as i16, From 2f6e2a5ef533e37ffd0868dd21ecd15eedb2eb3f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 12:46:06 +0200 Subject: [PATCH 09/14] Simplifying calculation for signals --- src/bin/dual-iir.rs | 10 ++ src/bin/lockin.rs | 30 +++--- src/hardware/signal_generator.rs | 151 ++++++++++++------------------- 3 files changed, 86 insertions(+), 105 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 56cd7bb..16f7894 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -120,6 +120,16 @@ pub struct Settings { /// # Value /// See [StreamTarget#miniconf] stream_target: StreamTarget, + + /// Specifies the config for signal generators to add on to DAC0/DAC1 outputs. + /// + /// # Path + /// `signal_generator/` + /// + /// * specifies which channel to configure. := [0, 1] + /// + /// # Value + /// See [signal_generator::BasicConfig#miniconf] signal_generator: [signal_generator::BasicConfig; 2], } diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 3b07341..bfd78e0 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -40,7 +40,6 @@ use stabilizer::{ adc::{Adc0Input, Adc1Input, AdcCode}, afe::Gain, dac::{Dac0Output, Dac1Output, DacCode}, - design_parameters, embedded_hal::digital::v2::InputPin, hal, input_stamper::InputStamper, @@ -259,6 +258,23 @@ const APP: () = { // Enable the timestamper. stabilizer.timestamper.start(); + let signal_config = { + let frequency_tuning_word = + (1u64 << (32 - configuration::SAMPLE_BUFFER_SIZE_LOG2)) as u32; + + signal_generator::Config { + // Same frequency as batch size. + frequency_tuning_word: [ + frequency_tuning_word, + frequency_tuning_word, + ], + // 1V Amplitude + amplitude: DacCode::from(1.0).into(), + + signal: signal_generator::Signal::Cosine, + } + }; + init::LateResources { afes: stabilizer.afes, adcs: stabilizer.adcs, @@ -268,17 +284,7 @@ const APP: () = { timestamper: stabilizer.timestamper, telemetry: TelemetryBuffer::default(), signal_generator: signal_generator::SignalGenerator::new( - signal_generator::Config { - // Same frequency as batch size. - frequency: (1u64 - << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2)) - as u32, - - // 1V Amplitude - amplitude: DacCode::from(1.0).into(), - - signal: signal_generator::SignalConfig::Cosine, - }, + signal_config, ), settings, diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 30ef03a..019d56d 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -2,6 +2,7 @@ use crate::hardware::{dac::DacCode, design_parameters::ADC_SAMPLE_TICKS}; use miniconf::Miniconf; use serde::Deserialize; +/// Types of signals that can be generated. #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] pub enum Signal { Cosine, @@ -9,11 +10,27 @@ pub enum Signal { Triangle, } +/// Basic configuration for a generated signal. +/// +/// # Miniconf +/// `{"signal": , "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}` +/// +/// Where `` may be any of [Signal] variants, `frequency` specifies the signal frequency +/// in Hertz, `symmetry` specifies the normalized signal symmetry which ranges from 0 - 1.0, and +/// `amplitude` specifies the signal amplitude in Volts. #[derive(Copy, Clone, Debug, Miniconf, Deserialize)] pub struct BasicConfig { - pub frequency: f32, - pub asymmetry: f32, + /// The signal type that should be generated. See [Signal] variants. pub signal: Signal, + + /// The frequency of the generated signal in Hertz. + pub frequency: f32, + + /// The normalized symmetry of the signal. At 0% symmetry, the first half phase does not exist. + /// At 25% symmetry, the first half-phase lasts for 25% of the signal period. + pub symmetry: f32, + + /// The amplitude of the output signal in volts. pub amplitude: f32, } @@ -21,7 +38,7 @@ impl Default for BasicConfig { fn default() -> Self { Self { frequency: 1.0e3, - asymmetry: 0.0, + symmetry: 0.5, signal: Signal::Cosine, amplitude: 0.0, } @@ -30,11 +47,29 @@ impl Default for BasicConfig { impl From for Config { fn from(config: BasicConfig) -> Config { - // Calculate the frequency tuning word - let frequency: u32 = { + // Calculate the frequency tuning words + let frequency_tuning_word: [u32; 2] = { let conversion_factor = ADC_SAMPLE_TICKS as f32 / 100.0e6 * (1u64 << 32) as f32; - (config.frequency * conversion_factor) as u32 + + if config.symmetry <= 0.0 { + [ + i32::MAX as u32, + (config.frequency * conversion_factor) as u32, + ] + } else if config.symmetry >= 1.0 { + [ + (config.frequency * conversion_factor) as u32, + i32::MAX as u32, + ] + } else { + [ + (config.frequency * conversion_factor / config.symmetry) + as u32, + (config.frequency * conversion_factor + / (1.0 - config.symmetry)) as u32, + ] + } }; // Clamp amplitude and symmetry. @@ -46,53 +81,10 @@ impl From for Config { config.amplitude }; - let symmetry = { - let asymmetry = if config.asymmetry < -1.0 { - -1.0 - } else if config.asymmetry > 1.0 { - 1.0 - } else { - config.asymmetry - }; - - (asymmetry * i32::MAX as f32) as i32 - }; - - let signal_config = match config.signal { - Signal::Cosine => SignalConfig::Cosine, - Signal::Square => SignalConfig::Square { symmetry }, - Signal::Triangle => { - let tuning_word = { - let segment_one_turns = - symmetry.wrapping_sub(i32::MIN) >> 16; - let segment_one_tw = if segment_one_turns > 0 { - u16::MAX as u32 / segment_one_turns as u32 - } else { - 0 - }; - - let segment_two_turns = - i32::MAX.wrapping_sub(symmetry) >> 16; - let segment_two_tw = if segment_two_turns > 0 { - u16::MAX as u32 / segment_two_turns as u32 - } else { - 0 - }; - - [segment_one_tw, segment_two_tw] - }; - - SignalConfig::Triangle { - symmetry, - tuning_word, - } - } - }; - Config { amplitude: DacCode::from(amplitude).into(), - signal: signal_config, - frequency, + signal: config.signal, + frequency_tuning_word, } } } @@ -100,29 +92,13 @@ impl From for Config { #[derive(Copy, Clone, Debug)] pub struct Config { /// The type of signal being generated - pub signal: SignalConfig, + pub signal: Signal, /// The full-scale output code of the signal pub amplitude: i16, /// The frequency tuning word of the signal. Phase is incremented by this amount - pub frequency: u32, -} - -#[derive(Copy, Clone, Debug)] -pub enum SignalConfig { - Cosine, - Square { - /// The phase symmetry cross-over of the waveform. - symmetry: i32, - }, - Triangle { - /// The phase symmetry cross-over of the waveform. - symmetry: i32, - /// The amplitude added for each phase turn increment (different words for different - /// phases). - tuning_word: [u32; 2], - }, + pub frequency_tuning_word: [u32; 2], } #[derive(Debug)] @@ -166,37 +142,26 @@ impl core::iter::Iterator for SignalGenerator { /// Get the next value in the generator sequence. fn next(&mut self) -> Option { - self.phase_accumulator = - self.phase_accumulator.wrapping_add(self.config.frequency); + self.phase_accumulator = self.phase_accumulator.wrapping_add( + if (self.phase_accumulator as i32).is_negative() { + self.config.frequency_tuning_word[0] + } else { + self.config.frequency_tuning_word[1] + }, + ); + let phase = self.phase_accumulator as i32; - let amplitude = match self.config.signal { - SignalConfig::Cosine => (dsp::cossin(phase).0 >> 16) as i16, - SignalConfig::Square { symmetry } => { - if phase < symmetry { + let amplitude: i16 = match self.config.signal { + Signal::Cosine => (dsp::cossin(phase).0 >> 16) as i16, + Signal::Square => { + if phase.is_negative() { i16::MAX } else { i16::MIN } } - SignalConfig::Triangle { - symmetry, - tuning_word, - } => { - if phase < symmetry { - let segment_turns = - (phase.wrapping_sub(i32::MIN) >> 16) as u16; - i16::MIN.wrapping_add( - (tuning_word[0] * segment_turns as u32) as i16, - ) - } else { - let segment_turns = - (phase.wrapping_sub(symmetry) >> 16) as u16; - i16::MAX.wrapping_sub( - (tuning_word[1] * segment_turns as u32) as i16, - ) - } - } + Signal::Triangle => i16::MAX - (phase.abs() >> 15) as i16, }; // Calculate the final output result as an i16. From e1cfeff65f747bc78eeff2af7bfad5c51572e5ff Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 13:01:31 +0200 Subject: [PATCH 10/14] Updating config to reject out-of-bounds amplitudes --- src/bin/dual-iir.rs | 20 ++++++++++++---- src/bin/lockin.rs | 7 ++++-- src/hardware/dac.rs | 22 ++++++++++------- src/hardware/signal_generator.rs | 41 +++++++++++++++++--------------- 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 16f7894..411c7de 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -29,7 +29,10 @@ #![no_std] #![no_main] -use core::sync::atomic::{fence, Ordering}; +use core::{ + convert::TryInto, + sync::atomic::{fence, Ordering}, +}; use mutex_trait::prelude::*; @@ -220,8 +223,12 @@ const APP: () = { telemetry: TelemetryBuffer::default(), settings, signal_generator: [ - SignalGenerator::new(settings.signal_generator[0]), - SignalGenerator::new(settings.signal_generator[1]), + SignalGenerator::new( + settings.signal_generator[0].try_into().unwrap(), + ), + SignalGenerator::new( + settings.signal_generator[1].try_into().unwrap(), + ), ], } } @@ -339,8 +346,11 @@ const APP: () = { // Update the signal generators c.resources.signal_generator.lock(|generator| { - generator[0].update_waveform(settings.signal_generator[0]); - generator[1].update_waveform(settings.signal_generator[1]); + for i in 0..2 { + if let Ok(config) = settings.signal_generator[i].try_into() { + generator[i].update_waveform(config); + } + } }); let target = settings.stream_target.into(); diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index bfd78e0..af7a747 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -28,7 +28,10 @@ #![no_std] #![no_main] -use core::sync::atomic::{fence, Ordering}; +use core::{ + convert::TryFrom, + sync::atomic::{fence, Ordering}, +}; use mutex_trait::prelude::*; @@ -269,7 +272,7 @@ const APP: () = { frequency_tuning_word, ], // 1V Amplitude - amplitude: DacCode::from(1.0).into(), + amplitude: DacCode::try_from(1.0).unwrap().into(), signal: signal_generator::Signal::Cosine, } diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index b40c5c6..5e77080 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -57,6 +57,8 @@ use mutex_trait::Mutex; use super::design_parameters::{SampleBuffer, SAMPLE_BUFFER_SIZE}; use super::timers; +use core::convert::TryFrom; + use hal::dma::{ dma::{DMAReq, DmaConfig}, traits::TargetAddress, @@ -75,21 +77,23 @@ static mut DAC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2]; #[derive(Copy, Clone)] pub struct DacCode(pub u16); -impl From for DacCode { - fn from(voltage: f32) -> DacCode { +impl TryFrom for DacCode { + type Error = (); + + fn try_from(voltage: f32) -> Result { // 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_range = 4.096 * 2.5; - let voltage = if voltage > dac_range { - dac_range + if voltage > dac_range { + Err(()) } else if voltage < -1. * dac_range { - -1. * dac_range + Err(()) } else { - voltage - }; - - DacCode::from((voltage / dac_range * i16::MAX as f32) as i16) + Ok(DacCode::from( + (voltage / dac_range * i16::MAX as f32) as i16, + )) + } } } diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 019d56d..5d3ea2e 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -1,4 +1,5 @@ use crate::hardware::{dac::DacCode, design_parameters::ADC_SAMPLE_TICKS}; +use core::convert::{TryFrom, TryInto}; use miniconf::Miniconf; use serde::Deserialize; @@ -45,8 +46,17 @@ impl Default for BasicConfig { } } -impl From for Config { - fn from(config: BasicConfig) -> Config { +/// Represents the errors that can occur when attempting to configure the signal generator. +#[derive(Copy, Clone, Debug)] +pub enum Error { + /// The provided amplitude is out-of-range. + InvalidAmplitude, +} + +impl TryFrom for Config { + type Error = Error; + + fn try_from(config: BasicConfig) -> Result { // Calculate the frequency tuning words let frequency_tuning_word: [u32; 2] = { let conversion_factor = @@ -72,20 +82,13 @@ impl From for Config { } }; - // Clamp amplitude and symmetry. - let amplitude = if config.amplitude > 10.24 { - 10.24 - } else if config.amplitude < 0.0 { - 0.0 - } else { - config.amplitude - }; - - Config { - amplitude: DacCode::from(amplitude).into(), + Ok(Config { + amplitude: DacCode::try_from(config.amplitude) + .or(Err(Error::InvalidAmplitude))? + .into(), signal: config.signal, frequency_tuning_word, - } + }) } } @@ -110,7 +113,7 @@ pub struct SignalGenerator { impl Default for SignalGenerator { fn default() -> Self { Self { - config: BasicConfig::default().into(), + config: BasicConfig::default().try_into().unwrap(), phase_accumulator: 0, } } @@ -124,16 +127,16 @@ impl SignalGenerator { /// /// # Returns /// The generator - pub fn new(config: impl Into) -> Self { + pub fn new(config: Config) -> Self { Self { - config: config.into(), + config, phase_accumulator: 0, } } /// Update waveform generation settings. - pub fn update_waveform(&mut self, new_config: impl Into) { - self.config = new_config.into(); + pub fn update_waveform(&mut self, new_config: Config) { + self.config = new_config; } } From 19bcc87c7f3afe8ae68bb3d1eac9732713a5867a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 13:05:15 +0200 Subject: [PATCH 11/14] Adding error message --- src/bin/dual-iir.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 411c7de..57872fc 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -347,8 +347,13 @@ const APP: () = { // Update the signal generators c.resources.signal_generator.lock(|generator| { for i in 0..2 { - if let Ok(config) = settings.signal_generator[i].try_into() { - generator[i].update_waveform(config); + match settings.signal_generator[i].try_into() { + Ok(config) => generator[i].update_waveform(config), + Err(err) => log::error!( + "Failed to update signal generation on DAC{}: {:?}", + i, + err + ), } } }); From 1c605623c2edf40c3f78e6d574bbf5943e30b9fd Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 13:30:12 +0200 Subject: [PATCH 12/14] fixing clippy --- src/bin/dual-iir.rs | 10 +++++++--- src/hardware/dac.rs | 4 +--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 57872fc..5c8a00b 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -346,9 +346,13 @@ const APP: () = { // Update the signal generators c.resources.signal_generator.lock(|generator| { - for i in 0..2 { - match settings.signal_generator[i].try_into() { - Ok(config) => generator[i].update_waveform(config), + for (i, (ref mut generator, &config)) in generator + .iter_mut() + .zip(settings.signal_generator.iter()) + .enumerate() + { + match config.try_into() { + Ok(config) => generator.update_waveform(config), Err(err) => log::error!( "Failed to update signal generation on DAC{}: {:?}", i, diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 5e77080..6ef427c 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -85,9 +85,7 @@ impl TryFrom for DacCode { // V with 16-bit resolution. The anti-aliasing filter has an additional gain of 2.5. let dac_range = 4.096 * 2.5; - if voltage > dac_range { - Err(()) - } else if voltage < -1. * dac_range { + if voltage > dac_range || voltage < -1. * dac_range { Err(()) } else { Ok(DacCode::from( From e2d2ce0752de09c2e303beff87a5042dd5bda8f7 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 14:12:48 +0200 Subject: [PATCH 13/14] Apply suggestions from code review 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 +- src/hardware/signal_generator.rs | 29 +++++++++++------------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/hardware/dac.rs b/src/hardware/dac.rs index 6ef427c..b5391c6 100644 --- a/src/hardware/dac.rs +++ b/src/hardware/dac.rs @@ -89,7 +89,7 @@ impl TryFrom for DacCode { Err(()) } else { Ok(DacCode::from( - (voltage / dac_range * i16::MAX as f32) as i16, + (voltage * (i16::MAX as f32 / dac_range)) as i16, )) } } diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index 5d3ea2e..f61b4ed 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -27,8 +27,9 @@ pub struct BasicConfig { /// The frequency of the generated signal in Hertz. pub frequency: f32, - /// The normalized symmetry of the signal. At 0% symmetry, the first half phase does not exist. - /// At 25% symmetry, the first half-phase lasts for 25% of the signal period. + /// The normalized symmetry of the signal. At 0% symmetry, the duration of the first half oscillation is minimal. + /// At 25% symmetry, the first half oscillation lasts for 25% of the signal period. For square wave output this + //// symmetry is the duty cycle. pub symmetry: f32, /// The amplitude of the output signal in volts. @@ -59,25 +60,17 @@ impl TryFrom for Config { fn try_from(config: BasicConfig) -> Result { // Calculate the frequency tuning words let frequency_tuning_word: [u32; 2] = { - let conversion_factor = - ADC_SAMPLE_TICKS as f32 / 100.0e6 * (1u64 << 32) as f32; + const LSB_PER_HERTZ: f32 = ((1u64 + ADC_SAMPLE_TICKS_LOG2) << 32) as f32 / 100.0e6; + let ftw = config.frequency * LSB_PER_HERTZ; if config.symmetry <= 0.0 { - [ - i32::MAX as u32, - (config.frequency * conversion_factor) as u32, - ] + [1u32 << 31, ftw as u32] } else if config.symmetry >= 1.0 { - [ - (config.frequency * conversion_factor) as u32, - i32::MAX as u32, - ] + [ftw as u32, 1u32 << 31] } else { [ - (config.frequency * conversion_factor / config.symmetry) - as u32, - (config.frequency * conversion_factor - / (1.0 - config.symmetry)) as u32, + (ftw / config.symmetry) as u32, + (ftw / (1.0 - config.symmetry)) as u32, ] } }; @@ -161,7 +154,7 @@ impl core::iter::Iterator for SignalGenerator { if phase.is_negative() { i16::MAX } else { - i16::MIN + -i16::MAX } } Signal::Triangle => i16::MAX - (phase.abs() >> 15) as i16, @@ -171,6 +164,6 @@ impl core::iter::Iterator for SignalGenerator { let result = amplitude as i32 * self.config.amplitude as i32; // Note: We downshift by 15-bits to preserve only one of the sign bits. - Some((result >> 15) as i16) + Some(((result + (1 << 14)) >> 15) as i16) } } From 1c32695a493a0c867918b38284baa4147d3c7679 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 19 Jul 2021 14:37:34 +0200 Subject: [PATCH 14/14] Fixing math --- src/bin/dual-iir.rs | 26 ++++++++++++-------------- src/hardware/signal_generator.rs | 5 +++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 5c8a00b..39cdefe 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -345,22 +345,20 @@ const APP: () = { c.resources.afes.1.set_gain(settings.afe[1]); // Update the signal generators - c.resources.signal_generator.lock(|generator| { - for (i, (ref mut generator, &config)) in generator - .iter_mut() - .zip(settings.signal_generator.iter()) - .enumerate() - { - match config.try_into() { - Ok(config) => generator.update_waveform(config), - Err(err) => log::error!( - "Failed to update signal generation on DAC{}: {:?}", - i, - err - ), + for (i, &config) in settings.signal_generator.iter().enumerate() { + match config.try_into() { + Ok(config) => { + c.resources + .signal_generator + .lock(|generator| generator[i].update_waveform(config)); } + Err(err) => log::error!( + "Failed to update signal generation on DAC{}: {:?}", + i, + err + ), } - }); + } let target = settings.stream_target.into(); c.resources.network.direct_stream(target); diff --git a/src/hardware/signal_generator.rs b/src/hardware/signal_generator.rs index f61b4ed..2eade09 100644 --- a/src/hardware/signal_generator.rs +++ b/src/hardware/signal_generator.rs @@ -1,4 +1,4 @@ -use crate::hardware::{dac::DacCode, design_parameters::ADC_SAMPLE_TICKS}; +use crate::{configuration::ADC_SAMPLE_TICKS_LOG2, hardware::dac::DacCode}; use core::convert::{TryFrom, TryInto}; use miniconf::Miniconf; use serde::Deserialize; @@ -60,7 +60,8 @@ impl TryFrom for Config { fn try_from(config: BasicConfig) -> Result { // Calculate the frequency tuning words let frequency_tuning_word: [u32; 2] = { - const LSB_PER_HERTZ: f32 = ((1u64 + ADC_SAMPLE_TICKS_LOG2) << 32) as f32 / 100.0e6; + const LSB_PER_HERTZ: f32 = + (1u64 << (31 + ADC_SAMPLE_TICKS_LOG2)) as f32 / 100.0e6; let ftw = config.frequency * LSB_PER_HERTZ; if config.symmetry <= 0.0 {