Refactoring signal generation to utilize static tuning words

This commit is contained in:
Ryan Summers 2021-07-16 12:55:11 +02:00
parent 9635aeccb1
commit b319fe2c6b
4 changed files with 127 additions and 68 deletions

View File

@ -160,16 +160,14 @@ const APP: () = {
signal_generator: signal_generator::SignalGenerator::new( signal_generator: signal_generator::SignalGenerator::new(
signal_generator::Config { signal_generator::Config {
// Same frequency as batch size. // Same frequency as batch size.
frequency: ((u32::MAX as u64 + 1u64) frequency: (1u64
/ design_parameters::SAMPLE_BUFFER_SIZE as u64) << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2))
as u32, as u32,
// Equal symmetry
phase_symmetry: 0,
// 1V Amplitude // 1V Amplitude
amplitude: ((1.0 / 10.24) * i16::MAX as f32) as i16, amplitude: DacCode::from(1.0).into(),
signal: signal_generator::Signal::Cosine,
signal: signal_generator::SignalConfig::Cosine,
}, },
), ),
@ -269,9 +267,6 @@ const APP: () = {
Conf::InPhase => output.re >> 16, Conf::InPhase => output.re >> 16,
Conf::Quadrature => output.im >> 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 => { Conf::Modulation => {
signal_generator.next().unwrap() as i32 signal_generator.next().unwrap() as i32
} }

View File

@ -83,18 +83,45 @@ use hal::dma::{
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct AdcCode(pub u16); pub struct AdcCode(pub u16);
#[allow(clippy::from_over_into)] impl From<u16> for AdcCode {
impl Into<f32> for AdcCode { /// Construct an ADC code from a provided binary (ADC-formatted) code.
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<i16> 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<AdcCode> for i16 {
/// Get a stabilizer-defined code from the ADC code.
fn from(code: AdcCode) -> i16 {
code.0 as i16
}
}
impl From<AdcCode> for u16 {
/// Get an ADC-frmatted binary value from the code.
fn from(code: AdcCode) -> u16 {
code.0
}
}
impl From<AdcCode> for f32 {
/// Convert raw ADC codes to/from voltage levels. /// Convert raw ADC codes to/from voltage levels.
/// ///
/// # Note /// # Note
/// This does not account for the programmable gain amplifier at the signal input. /// 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 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. // The gain into the two inputs is 1/5.
let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / (1u16 << 15) as f32; let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / (1u16 << 15) as f32;
(self.0 as i16) as f32 * adc_volts_per_lsb i16::from(code) as f32 * adc_volts_per_lsb
} }
} }

View File

@ -116,6 +116,13 @@ impl From<i16> for DacCode {
} }
} }
impl From<u16> for DacCode {
/// Create a dac code from the provided DAC output code.
fn from(value: u16) -> Self {
Self(value)
}
}
macro_rules! dac_output { macro_rules! dac_output {
($name:ident, $index:literal, $data_stream:ident, ($name:ident, $index:literal, $data_stream:ident,
$spi:ident, $trigger_channel:ident, $dma_req:ident) => { $spi:ident, $trigger_channel:ident, $dma_req:ident) => {

View File

@ -31,9 +31,11 @@ impl Default for BasicConfig {
impl From<BasicConfig> for Config { impl From<BasicConfig> for Config {
fn from(config: BasicConfig) -> Config { fn from(config: BasicConfig) -> Config {
// Calculate the frequency tuning word // Calculate the frequency tuning word
let frequency: u32 = let frequency: u32 = {
(config.frequency * ADC_SAMPLE_TICKS as f32 / 100.0_e6 let conversion_factor =
* (u32::MAX as u64 + 1u64) as f32) as u32; ADC_SAMPLE_TICKS as f32 / 100.0e6 * (1u64 << 32) as f32;
(config.frequency * conversion_factor) as u32
};
// Clamp amplitude and symmetry. // Clamp amplitude and symmetry.
let amplitude = if config.amplitude > 10.24 { let amplitude = if config.amplitude > 10.24 {
@ -44,18 +46,52 @@ impl From<BasicConfig> for Config {
config.amplitude config.amplitude
}; };
let asymmetry = if config.asymmetry < -1.0 { let symmetry = {
-1.0 let asymmetry = if config.asymmetry < -1.0 {
} else if config.asymmetry > 1.0 { -1.0
1.0 } else if config.asymmetry > 1.0 {
} else { 1.0
config.asymmetry } 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 { Config {
signal: config.signal,
amplitude: DacCode::from(amplitude).into(), amplitude: DacCode::from(amplitude).into(),
phase_symmetry: (asymmetry * i32::MAX as f32) as i32, signal: signal_config,
frequency, frequency,
} }
} }
@ -63,20 +99,32 @@ impl From<BasicConfig> for Config {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Config { pub struct Config {
// The type of signal being generated /// The type of signal being generated
pub signal: Signal, pub signal: SignalConfig,
// The full-scale output code of the signal /// The full-scale output code of the signal
pub amplitude: i16, pub amplitude: i16,
// The 32-bit representation of the phase symmetry. That is, with a 50% symmetry, this is equal /// The frequency tuning word of the signal. Phase is incremented by this amount
// to 0.
pub phase_symmetry: i32,
// The frequency tuning word of the signal. Phase is incremented by this amount
pub frequency: u32, 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)] #[derive(Debug)]
pub struct SignalGenerator { pub struct SignalGenerator {
phase_accumulator: u32, phase_accumulator: u32,
@ -153,48 +201,30 @@ impl core::iter::Iterator for SignalGenerator {
let phase = self.increment(); let phase = self.increment();
let amplitude = match self.config.signal { let amplitude = match self.config.signal {
Signal::Cosine => (dsp::cossin(phase).0 >> 16) as i16, SignalConfig::Cosine => (dsp::cossin(phase).0 >> 16) as i16,
Signal::Square => { SignalConfig::Square { symmetry } => {
if phase < self.config.phase_symmetry { if phase < symmetry {
i16::MAX i16::MAX
} else { } else {
i16::MIN i16::MIN
} }
} }
Signal::Triangle => { SignalConfig::Triangle {
if phase < self.config.phase_symmetry { symmetry,
let duration_of_phase = tuning_word,
(self.config.phase_symmetry.wrapping_sub(i32::MIN) } => {
>> 16) as u16; if phase < symmetry {
let phase_progress = let segment_turns =
(phase.wrapping_sub(i32::MIN) >> 16) as u16; (phase.wrapping_sub(i32::MIN) >> 16) as u16;
i16::MIN.wrapping_add(
if duration_of_phase == 0 { (tuning_word[0] * segment_turns as u32) as i16,
i16::MIN )
} else {
i16::MIN.wrapping_add(
(u16::MAX as u32 * phase_progress as u32
/ duration_of_phase as u32)
as i16,
)
}
} else { } else {
let duration_of_phase = let segment_turns =
(i32::MAX.wrapping_sub(self.config.phase_symmetry) (phase.wrapping_sub(symmetry) >> 16) as u16;
>> 16) as u16; i16::MAX.wrapping_sub(
let phase_progress = (phase (tuning_word[1] * segment_turns as u32) as i16,
.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,
)
}
} }
} }
}; };