Adding initial take at scan mode signal generation
This commit is contained in:
parent
9d34e755d8
commit
986e7cc457
|
@ -16,6 +16,7 @@ use stabilizer::{
|
||||||
embedded_hal::digital::v2::InputPin,
|
embedded_hal::digital::v2::InputPin,
|
||||||
hal,
|
hal,
|
||||||
input_stamper::InputStamper,
|
input_stamper::InputStamper,
|
||||||
|
signal_generator,
|
||||||
system_timer::SystemTimer,
|
system_timer::SystemTimer,
|
||||||
DigitalInput0, DigitalInput1, AFE0, AFE1,
|
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)]
|
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
||||||
enum Conf {
|
enum Conf {
|
||||||
Magnitude,
|
Magnitude,
|
||||||
|
@ -102,6 +96,7 @@ const APP: () = {
|
||||||
telemetry: TelemetryBuffer,
|
telemetry: TelemetryBuffer,
|
||||||
digital_inputs: (DigitalInput0, DigitalInput1),
|
digital_inputs: (DigitalInput0, DigitalInput1),
|
||||||
generator: BlockGenerator,
|
generator: BlockGenerator,
|
||||||
|
signal_generator: signal_generator::Generator,
|
||||||
|
|
||||||
timestamper: InputStamper,
|
timestamper: InputStamper,
|
||||||
pll: RPLL,
|
pll: RPLL,
|
||||||
|
@ -161,6 +156,14 @@ const APP: () = {
|
||||||
digital_inputs: stabilizer.digital_inputs,
|
digital_inputs: stabilizer.digital_inputs,
|
||||||
timestamper: stabilizer.timestamper,
|
timestamper: stabilizer.timestamper,
|
||||||
telemetry: TelemetryBuffer::default(),
|
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,
|
settings,
|
||||||
generator,
|
generator,
|
||||||
|
@ -177,7 +180,7 @@ const APP: () = {
|
||||||
/// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal.
|
/// 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.
|
/// 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.
|
/// 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)]
|
#[inline(never)]
|
||||||
#[link_section = ".itcm.process"]
|
#[link_section = ".itcm.process"]
|
||||||
fn process(mut c: process::Context) {
|
fn process(mut c: process::Context) {
|
||||||
|
@ -190,6 +193,7 @@ const APP: () = {
|
||||||
ref mut pll,
|
ref mut pll,
|
||||||
ref mut timestamper,
|
ref mut timestamper,
|
||||||
ref mut generator,
|
ref mut generator,
|
||||||
|
ref mut signal_generator,
|
||||||
} = c.resources;
|
} = c.resources;
|
||||||
|
|
||||||
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
||||||
|
@ -246,7 +250,7 @@ const APP: () = {
|
||||||
|
|
||||||
// Convert to DAC data.
|
// Convert to DAC data.
|
||||||
for (channel, samples) in dac_samples.iter_mut().enumerate() {
|
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] {
|
let value = match settings.output_conf[channel] {
|
||||||
Conf::Magnitude => output.abs_sqr() as i32 >> 16,
|
Conf::Magnitude => output.abs_sqr() as i32 >> 16,
|
||||||
Conf::Phase => output.arg() >> 16,
|
Conf::Phase => output.arg() >> 16,
|
||||||
|
@ -256,7 +260,11 @@ const APP: () = {
|
||||||
}
|
}
|
||||||
Conf::InPhase => output.re >> 16,
|
Conf::InPhase => output.re >> 16,
|
||||||
Conf::Quadrature => output.im >> 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;
|
*sample = DacCode::from(value as i16).0;
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub mod design_parameters;
|
||||||
pub mod input_stamper;
|
pub mod input_stamper;
|
||||||
pub mod pounder;
|
pub mod pounder;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
pub mod signal_generator;
|
||||||
pub mod system_timer;
|
pub mod system_timer;
|
||||||
|
|
||||||
mod eeprom;
|
mod eeprom;
|
||||||
|
|
|
@ -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<InternalConf> 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<InternalConf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue