Simplifying calculation for signals
This commit is contained in:
parent
6d8273ec42
commit
2f6e2a5ef5
|
@ -120,6 +120,16 @@ pub struct Settings {
|
||||||
/// # Value
|
/// # Value
|
||||||
/// See [StreamTarget#miniconf]
|
/// See [StreamTarget#miniconf]
|
||||||
stream_target: StreamTarget,
|
stream_target: StreamTarget,
|
||||||
|
|
||||||
|
/// Specifies the config for signal generators to add on to DAC0/DAC1 outputs.
|
||||||
|
///
|
||||||
|
/// # Path
|
||||||
|
/// `signal_generator/<n>`
|
||||||
|
///
|
||||||
|
/// * <n> specifies which channel to configure. <n> := [0, 1]
|
||||||
|
///
|
||||||
|
/// # Value
|
||||||
|
/// See [signal_generator::BasicConfig#miniconf]
|
||||||
signal_generator: [signal_generator::BasicConfig; 2],
|
signal_generator: [signal_generator::BasicConfig; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ use stabilizer::{
|
||||||
adc::{Adc0Input, Adc1Input, AdcCode},
|
adc::{Adc0Input, Adc1Input, AdcCode},
|
||||||
afe::Gain,
|
afe::Gain,
|
||||||
dac::{Dac0Output, Dac1Output, DacCode},
|
dac::{Dac0Output, Dac1Output, DacCode},
|
||||||
design_parameters,
|
|
||||||
embedded_hal::digital::v2::InputPin,
|
embedded_hal::digital::v2::InputPin,
|
||||||
hal,
|
hal,
|
||||||
input_stamper::InputStamper,
|
input_stamper::InputStamper,
|
||||||
|
@ -259,6 +258,23 @@ const APP: () = {
|
||||||
// Enable the timestamper.
|
// Enable the timestamper.
|
||||||
stabilizer.timestamper.start();
|
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 {
|
init::LateResources {
|
||||||
afes: stabilizer.afes,
|
afes: stabilizer.afes,
|
||||||
adcs: stabilizer.adcs,
|
adcs: stabilizer.adcs,
|
||||||
|
@ -268,17 +284,7 @@ const APP: () = {
|
||||||
timestamper: stabilizer.timestamper,
|
timestamper: stabilizer.timestamper,
|
||||||
telemetry: TelemetryBuffer::default(),
|
telemetry: TelemetryBuffer::default(),
|
||||||
signal_generator: signal_generator::SignalGenerator::new(
|
signal_generator: signal_generator::SignalGenerator::new(
|
||||||
signal_generator::Config {
|
signal_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,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::hardware::{dac::DacCode, design_parameters::ADC_SAMPLE_TICKS};
|
||||||
use miniconf::Miniconf;
|
use miniconf::Miniconf;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Types of signals that can be generated.
|
||||||
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
||||||
pub enum Signal {
|
pub enum Signal {
|
||||||
Cosine,
|
Cosine,
|
||||||
|
@ -9,11 +10,27 @@ pub enum Signal {
|
||||||
Triangle,
|
Triangle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Basic configuration for a generated signal.
|
||||||
|
///
|
||||||
|
/// # Miniconf
|
||||||
|
/// `{"signal": <signal>, "frequency", 1000.0, "symmetry": 0.5, "amplitude": 1.0}`
|
||||||
|
///
|
||||||
|
/// Where `<signal>` 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)]
|
#[derive(Copy, Clone, Debug, Miniconf, Deserialize)]
|
||||||
pub struct BasicConfig {
|
pub struct BasicConfig {
|
||||||
pub frequency: f32,
|
/// The signal type that should be generated. See [Signal] variants.
|
||||||
pub asymmetry: f32,
|
|
||||||
pub signal: Signal,
|
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,
|
pub amplitude: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +38,7 @@ impl Default for BasicConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
frequency: 1.0e3,
|
frequency: 1.0e3,
|
||||||
asymmetry: 0.0,
|
symmetry: 0.5,
|
||||||
signal: Signal::Cosine,
|
signal: Signal::Cosine,
|
||||||
amplitude: 0.0,
|
amplitude: 0.0,
|
||||||
}
|
}
|
||||||
|
@ -30,11 +47,29 @@ 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 words
|
||||||
let frequency: u32 = {
|
let frequency_tuning_word: [u32; 2] = {
|
||||||
let conversion_factor =
|
let conversion_factor =
|
||||||
ADC_SAMPLE_TICKS as f32 / 100.0e6 * (1u64 << 32) as f32;
|
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.
|
// Clamp amplitude and symmetry.
|
||||||
|
@ -46,53 +81,10 @@ impl From<BasicConfig> for Config {
|
||||||
config.amplitude
|
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 {
|
Config {
|
||||||
amplitude: DacCode::from(amplitude).into(),
|
amplitude: DacCode::from(amplitude).into(),
|
||||||
signal: signal_config,
|
signal: config.signal,
|
||||||
frequency,
|
frequency_tuning_word,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,29 +92,13 @@ 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: SignalConfig,
|
pub signal: Signal,
|
||||||
|
|
||||||
/// The full-scale output code of the signal
|
/// The full-scale output code of the signal
|
||||||
pub amplitude: i16,
|
pub amplitude: i16,
|
||||||
|
|
||||||
/// 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,
|
pub frequency_tuning_word: [u32; 2],
|
||||||
}
|
|
||||||
|
|
||||||
#[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)]
|
||||||
|
@ -166,37 +142,26 @@ impl core::iter::Iterator for SignalGenerator {
|
||||||
|
|
||||||
/// Get the next value in the generator sequence.
|
/// Get the next value in the generator sequence.
|
||||||
fn next(&mut self) -> Option<i16> {
|
fn next(&mut self) -> Option<i16> {
|
||||||
self.phase_accumulator =
|
self.phase_accumulator = self.phase_accumulator.wrapping_add(
|
||||||
self.phase_accumulator.wrapping_add(self.config.frequency);
|
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 phase = self.phase_accumulator as i32;
|
||||||
|
|
||||||
let amplitude = match self.config.signal {
|
let amplitude: i16 = match self.config.signal {
|
||||||
SignalConfig::Cosine => (dsp::cossin(phase).0 >> 16) as i16,
|
Signal::Cosine => (dsp::cossin(phase).0 >> 16) as i16,
|
||||||
SignalConfig::Square { symmetry } => {
|
Signal::Square => {
|
||||||
if phase < symmetry {
|
if phase.is_negative() {
|
||||||
i16::MAX
|
i16::MAX
|
||||||
} else {
|
} else {
|
||||||
i16::MIN
|
i16::MIN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalConfig::Triangle {
|
Signal::Triangle => i16::MAX - (phase.abs() >> 15) as i16,
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate the final output result as an i16.
|
// Calculate the final output result as an i16.
|
||||||
|
|
Loading…
Reference in New Issue