Adding adc/dac code conversion utilities
This commit is contained in:
parent
d68fa87fec
commit
b73a4d9e59
|
@ -9,8 +9,8 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use dsp::iir;
|
use dsp::iir;
|
||||||
use hardware::{
|
use hardware::{
|
||||||
Adc0Input, Adc1Input, AfeGain, Dac0Output, Dac1Output, DigitalInput0,
|
Adc0Input, Adc1Input, AdcSample, AfeGain, Dac0Output, Dac1Output, DacCode,
|
||||||
DigitalInput1, InputPin, SystemTimer, AFE0, AFE1,
|
DigitalInput0, DigitalInput1, InputPin, SystemTimer, AFE0, AFE1,
|
||||||
};
|
};
|
||||||
|
|
||||||
use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState};
|
use net::{NetworkUsers, Telemetry, TelemetryBuffer, UpdateState};
|
||||||
|
@ -154,15 +154,16 @@ const APP: () = {
|
||||||
// The truncation introduces 1/2 LSB distortion.
|
// The truncation introduces 1/2 LSB distortion.
|
||||||
let y = unsafe { y.to_int_unchecked::<i16>() };
|
let y = unsafe { y.to_int_unchecked::<i16>() };
|
||||||
// Convert to DAC code
|
// Convert to DAC code
|
||||||
dac_samples[channel][sample] = y as u16 ^ 0x8000;
|
dac_samples[channel][sample] = DacCode::from(y).0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
c.resources.telemetry.adcs =
|
||||||
[adc_samples[0][0] as i16, adc_samples[1][0] as i16];
|
[AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]];
|
c.resources.telemetry.dacs =
|
||||||
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.digital_inputs = digital_inputs;
|
c.resources.telemetry.digital_inputs = digital_inputs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
|
||||||
use stabilizer::net;
|
use stabilizer::net;
|
||||||
|
|
||||||
use stabilizer::hardware::{
|
use stabilizer::hardware::{
|
||||||
design_parameters, setup, Adc0Input, Adc1Input, AfeGain, Dac0Output,
|
design_parameters, setup, Adc0Input, Adc1Input, AdcSample, AfeGain,
|
||||||
Dac1Output, DigitalInput0, DigitalInput1, InputStamper, SystemTimer, AFE0,
|
Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1,
|
||||||
AFE1,
|
InputStamper, SystemTimer, AFE0, AFE1,
|
||||||
};
|
};
|
||||||
|
|
||||||
use miniconf::Miniconf;
|
use miniconf::Miniconf;
|
||||||
|
@ -234,15 +234,16 @@ const APP: () = {
|
||||||
Conf::Modulation => DAC_SEQUENCE[i] as i32,
|
Conf::Modulation => DAC_SEQUENCE[i] as i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
*sample = value as u16 ^ 0x8000;
|
*sample = DacCode::from(value as i16).0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
c.resources.telemetry.adcs =
|
||||||
[adc_samples[0][0] as i16, adc_samples[1][0] as i16];
|
[AdcSample(adc_samples[0][0]), AdcSample(adc_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.dacs = [dac_samples[0][0], dac_samples[1][0]];
|
c.resources.telemetry.dacs =
|
||||||
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[network], spawn=[settings_update])]
|
#[idle(resources=[network], spawn=[settings_update])]
|
||||||
|
|
|
@ -84,6 +84,28 @@ use hal::dma::{
|
||||||
MemoryToPeripheral, PeripheralToMemory, Transfer,
|
MemoryToPeripheral, PeripheralToMemory, Transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A type representing an ADC sample.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct AdcSample(pub u16);
|
||||||
|
|
||||||
|
impl Into<f32> for AdcSample {
|
||||||
|
/// 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 {
|
||||||
|
// The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a
|
||||||
|
// dynamic range across signed integers. Additionally, the signal is fully differential, so
|
||||||
|
// the differential voltage is measured at the ADC. Thus, the single-ended signal is
|
||||||
|
// measured at the input is half of the ADC-reported measurement. As a pre-filter, the
|
||||||
|
// input signal has a fixed gain of 1/5 through a static input active filter.
|
||||||
|
let adc_volts_per_lsb = 5.0 / 2.0 * 4.096 / i16::MAX as f32;
|
||||||
|
|
||||||
|
(self.0 as i16) as f32 * adc_volts_per_lsb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the
|
// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the
|
||||||
// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is
|
// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is
|
||||||
// initialized during setup.
|
// initialized during setup.
|
||||||
|
|
|
@ -69,6 +69,39 @@ use hal::dma::{
|
||||||
static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] =
|
static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] =
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 3]; 2];
|
[[[0; SAMPLE_BUFFER_SIZE]; 3]; 2];
|
||||||
|
|
||||||
|
/// Custom type for referencing DAC output codes.
|
||||||
|
/// The internal integer is the raw code written to the DAC output register.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct DacCode(pub u16);
|
||||||
|
|
||||||
|
impl Into<f32> for DacCode {
|
||||||
|
fn into(self) -> f32 {
|
||||||
|
// The output voltage is generated by the DAC with an output range of +/- 4.096 V. This
|
||||||
|
// signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned
|
||||||
|
// integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of
|
||||||
|
// the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V,
|
||||||
|
// and at a max DAC code, there is an output of 10.24 V.
|
||||||
|
//
|
||||||
|
// Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger
|
||||||
|
// than full-scale.
|
||||||
|
let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32;
|
||||||
|
|
||||||
|
(self.0 as f32) * dac_volts_per_lsb - 10.24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i16> for DacCode {
|
||||||
|
/// Generate a DAC code from a 16-bit signed value.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This is provided as a means to convert between the DACs internal 16-bit, unsigned register
|
||||||
|
/// and a 2s-complement integer that is convenient when using the DAC in the bipolar
|
||||||
|
/// configuration.
|
||||||
|
fn from(value: i16) -> Self {
|
||||||
|
Self(value as u16 ^ 0x8000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) => {
|
||||||
|
|
|
@ -19,10 +19,10 @@ pub mod pounder;
|
||||||
mod system_timer;
|
mod system_timer;
|
||||||
mod timers;
|
mod timers;
|
||||||
|
|
||||||
pub use adc::{Adc0Input, Adc1Input};
|
pub use adc::{Adc0Input, Adc1Input, AdcSample};
|
||||||
pub use afe::Gain as AfeGain;
|
pub use afe::Gain as AfeGain;
|
||||||
pub use cycle_counter::CycleCounter;
|
pub use cycle_counter::CycleCounter;
|
||||||
pub use dac::{Dac0Output, Dac1Output};
|
pub use dac::{Dac0Output, Dac1Output, DacCode};
|
||||||
pub use digital_input_stamper::InputStamper;
|
pub use digital_input_stamper::InputStamper;
|
||||||
pub use pounder::DdsOutput;
|
pub use pounder::DdsOutput;
|
||||||
pub use system_timer::SystemTimer;
|
pub use system_timer::SystemTimer;
|
||||||
|
|
|
@ -15,7 +15,9 @@ use minimq::QoS;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::NetworkReference;
|
use super::NetworkReference;
|
||||||
use crate::hardware::{design_parameters::MQTT_BROKER, AfeGain};
|
use crate::hardware::{
|
||||||
|
design_parameters::MQTT_BROKER, AdcSample, AfeGain, DacCode,
|
||||||
|
};
|
||||||
|
|
||||||
/// The telemetry client for reporting telemetry data over MQTT.
|
/// The telemetry client for reporting telemetry data over MQTT.
|
||||||
pub struct TelemetryClient<T: Serialize> {
|
pub struct TelemetryClient<T: Serialize> {
|
||||||
|
@ -33,9 +35,9 @@ pub struct TelemetryClient<T: Serialize> {
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TelemetryBuffer {
|
pub struct TelemetryBuffer {
|
||||||
/// The latest input sample on ADC0/ADC1.
|
/// The latest input sample on ADC0/ADC1.
|
||||||
pub adcs: [i16; 2],
|
pub adcs: [AdcSample; 2],
|
||||||
/// The latest output code on DAC0/DAC1.
|
/// The latest output code on DAC0/DAC1.
|
||||||
pub dacs: [u16; 2],
|
pub dacs: [DacCode; 2],
|
||||||
/// The latest digital input states during processing.
|
/// The latest digital input states during processing.
|
||||||
pub digital_inputs: [bool; 2],
|
pub digital_inputs: [bool; 2],
|
||||||
}
|
}
|
||||||
|
@ -55,8 +57,8 @@ pub struct Telemetry {
|
||||||
impl Default for TelemetryBuffer {
|
impl Default for TelemetryBuffer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
adcs: [0, 0],
|
adcs: [AdcSample(0), AdcSample(0)],
|
||||||
dacs: [0, 0],
|
dacs: [DacCode(0), DacCode(0)],
|
||||||
digital_inputs: [false, false],
|
digital_inputs: [false, false],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,36 +74,12 @@ impl TelemetryBuffer {
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// The finalized telemetry structure that can be serialized and reported.
|
/// The finalized telemetry structure that can be serialized and reported.
|
||||||
pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry {
|
pub fn finalize(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry {
|
||||||
// The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a
|
let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
|
||||||
// dynamic range across signed integers. Additionally, the signal is fully differential, so
|
let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
|
||||||
// the differential voltage is measured at the ADC. Thus, the single-ended signal is
|
|
||||||
// measured at the input is half of the ADC-reported measurement. As a pre-filter, the
|
|
||||||
// input signal has a fixed gain of 1/5 through a static input active filter. Finally, at
|
|
||||||
// the very front-end of the signal, there's an analog input multiplier that is
|
|
||||||
// configurable by the user.
|
|
||||||
let adc_volts_per_lsb = 5.0 * 4.096 / 2.0 / i16::MAX as f32;
|
|
||||||
let in0_volts =
|
|
||||||
(adc_volts_per_lsb * self.adcs[0] as f32) / afe0.as_multiplier();
|
|
||||||
let in1_volts =
|
|
||||||
(adc_volts_per_lsb * self.adcs[1] as f32) / afe1.as_multiplier();
|
|
||||||
|
|
||||||
// The output voltage is generated by the DAC with an output range of +/- 4.096 V. This
|
|
||||||
// signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned
|
|
||||||
// integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of
|
|
||||||
// the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V,
|
|
||||||
// and at a max DAC code, there is an output of 10.24 V.
|
|
||||||
//
|
|
||||||
// Note: The MAX code corresponding to +VREF is not programmable, as it is 1 bit larger
|
|
||||||
// than full-scale.
|
|
||||||
let dac_volts_per_lsb = 10.24 * 2.0 / (u16::MAX as u32 + 1) as f32;
|
|
||||||
let dac_offset = -10.24;
|
|
||||||
|
|
||||||
let out0_volts = dac_volts_per_lsb * self.dacs[0] as f32 + dac_offset;
|
|
||||||
let out1_volts = dac_volts_per_lsb * self.dacs[1] as f32 + dac_offset;
|
|
||||||
|
|
||||||
Telemetry {
|
Telemetry {
|
||||||
adcs: [in0_volts, in1_volts],
|
adcs: [in0_volts, in1_volts],
|
||||||
dacs: [out0_volts, out1_volts],
|
dacs: [self.dacs[0].into(), self.dacs[1].into()],
|
||||||
digital_inputs: self.digital_inputs,
|
digital_inputs: self.digital_inputs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue