From b319fe2c6b60719d1cea13e9655fafe5d585f326 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Fri, 16 Jul 2021 12:55:11 +0200 Subject: [PATCH] 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, + ) } } };