Refactoring signal generation to utilize static tuning words
This commit is contained in:
parent
9635aeccb1
commit
b319fe2c6b
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue