Adding adc/dac code conversion utilities

This commit is contained in:
Ryan Summers 2021-05-07 13:50:34 +02:00
parent d68fa87fec
commit b73a4d9e59
6 changed files with 80 additions and 45 deletions

View File

@ -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;
} }

View File

@ -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])]

View File

@ -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.

View File

@ -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) => {

View File

@ -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;

View File

@ -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,
} }
} }