Merge #352
352: Refactoring lockin binaries r=ryan-summers a=ryan-summers This PR merges `lockin-external` and `lockin-internal` into a single binary. This has also refactored the output configuration to make it more configurable. This was needed to work well with the reference signal generation, as that output should necessarily just override a different setting. Co-authored-by: Ryan Summers <ryan.summers@vertigo-designs.com> Co-authored-by: Robert Jördens <rj@quartiq.de>
This commit is contained in:
commit
bde0b0a4be
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -24,8 +24,7 @@ jobs:
|
||||
- run: >
|
||||
zip bin.zip
|
||||
target/*/release/dual-iir
|
||||
target/*/release/lockin-external
|
||||
target/*/release/lockin-internal
|
||||
target/*/release/lockin
|
||||
- id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
@ -29,9 +29,7 @@ to implement different use cases. Several applications are provides by default
|
||||
* anti-windup
|
||||
* derivative kick avoidance
|
||||
|
||||
### Lockin external
|
||||
|
||||
### Lockin internal
|
||||
### Lockin
|
||||
|
||||
## Minimal bootstrapping documentation
|
||||
|
||||
|
@ -1,143 +0,0 @@
|
||||
#![deny(warnings)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use dsp::{Accu, Complex, ComplexExt, Lockin};
|
||||
use generic_array::typenum::U2;
|
||||
use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1};
|
||||
use stabilizer::{hardware, hardware::design_parameters};
|
||||
|
||||
// A constant sinusoid to send on the DAC output.
|
||||
// Full-scale gives a +/- 10V amplitude waveform. Scale it down to give +/- 1V.
|
||||
const ONE: i16 = (0.1 * u16::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];
|
||||
|
||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
const APP: () = {
|
||||
struct Resources {
|
||||
afes: (AFE0, AFE1),
|
||||
adc: Adc1Input,
|
||||
dacs: (Dac0Output, Dac1Output),
|
||||
|
||||
lockin: Lockin<U2>,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(c: init::Context) -> init::LateResources {
|
||||
// Configure the microcontroller
|
||||
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
||||
|
||||
// Enable ADC/DAC events
|
||||
stabilizer.adcs.1.start();
|
||||
stabilizer.dacs.0.start();
|
||||
stabilizer.dacs.1.start();
|
||||
|
||||
// Start sampling ADCs.
|
||||
stabilizer.adc_dac_timer.start();
|
||||
|
||||
init::LateResources {
|
||||
lockin: Lockin::default(),
|
||||
afes: stabilizer.afes,
|
||||
adc: stabilizer.adcs.1,
|
||||
dacs: stabilizer.dacs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Main DSP processing routine.
|
||||
///
|
||||
/// See `dual-iir` for general notes on processing time and timing.
|
||||
///
|
||||
/// This is an implementation of an internal-reference lockin on the ADC1 signal.
|
||||
/// The reference at f_sample/8 is output on DAC0 and the phase of the demodulated
|
||||
/// signal on DAC1.
|
||||
#[task(binds=DMA1_STR4, resources=[adc, dacs, lockin], priority=2)]
|
||||
fn process(c: process::Context) {
|
||||
let lockin = c.resources.lockin;
|
||||
let adc_samples = c.resources.adc.acquire_buffer();
|
||||
let dac_samples = [
|
||||
c.resources.dacs.0.acquire_buffer(),
|
||||
c.resources.dacs.1.acquire_buffer(),
|
||||
];
|
||||
|
||||
// Reference phase and frequency are known.
|
||||
let pll_phase = 0i32;
|
||||
let pll_frequency =
|
||||
1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2);
|
||||
|
||||
// Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
|
||||
let harmonic: i32 = -1;
|
||||
|
||||
// Demodulation LO phase offset
|
||||
let phase_offset: i32 = 1 << 30;
|
||||
|
||||
// Log2 lowpass time constant.
|
||||
let time_constant: u8 = 8;
|
||||
|
||||
let sample_frequency = (pll_frequency as i32).wrapping_mul(harmonic);
|
||||
let sample_phase =
|
||||
phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic));
|
||||
|
||||
let output: Complex<i32> = adc_samples
|
||||
.iter()
|
||||
// Zip in the LO phase.
|
||||
.zip(Accu::new(sample_phase, sample_frequency))
|
||||
// Convert to signed, MSB align the ADC sample, update the Lockin (demodulate, filter)
|
||||
.map(|(&sample, phase)| {
|
||||
let s = (sample as i16 as i32) << 16;
|
||||
lockin.update(s, phase, time_constant)
|
||||
})
|
||||
// Decimate
|
||||
.last()
|
||||
.unwrap()
|
||||
* 2; // Full scale assuming the 2f component is gone.
|
||||
|
||||
// Convert to DAC data.
|
||||
for (i, data) in DAC_SEQUENCE.iter().enumerate() {
|
||||
// DAC0 always generates a fixed sinusoidal output.
|
||||
dac_samples[0][i] = *data as u16 ^ 0x8000;
|
||||
dac_samples[1][i] = (output.arg() >> 16) as u16 ^ 0x8000;
|
||||
}
|
||||
}
|
||||
|
||||
#[idle(resources=[afes])]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
loop {
|
||||
cortex_m::asm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = ETH, priority = 1)]
|
||||
fn eth(_: eth::Context) {
|
||||
unsafe { stm32h7xx_hal::ethernet::interrupt_handler() }
|
||||
}
|
||||
|
||||
#[task(binds = SPI2, priority = 3)]
|
||||
fn spi2(_: spi2::Context) {
|
||||
panic!("ADC0 input overrun");
|
||||
}
|
||||
|
||||
#[task(binds = SPI3, priority = 3)]
|
||||
fn spi3(_: spi3::Context) {
|
||||
panic!("ADC1 input overrun");
|
||||
}
|
||||
|
||||
#[task(binds = SPI4, priority = 3)]
|
||||
fn spi4(_: spi4::Context) {
|
||||
panic!("DAC0 output error");
|
||||
}
|
||||
|
||||
#[task(binds = SPI5, priority = 3)]
|
||||
fn spi5(_: spi5::Context) {
|
||||
panic!("DAC1 output error");
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// hw interrupt handlers for RTIC to use for scheduling tasks
|
||||
// one per priority
|
||||
fn DCMI();
|
||||
fn JPEG();
|
||||
fn SDMMC();
|
||||
}
|
||||
};
|
@ -18,16 +18,34 @@ use stabilizer::hardware::{
|
||||
use miniconf::Miniconf;
|
||||
use stabilizer::net::{Action, MqttInterface};
|
||||
|
||||
// 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)]
|
||||
enum Conf {
|
||||
PowerPhase,
|
||||
FrequencyDiscriminator,
|
||||
Magnitude,
|
||||
Phase,
|
||||
ReferenceFrequency,
|
||||
LogPower,
|
||||
InPhase,
|
||||
Quadrature,
|
||||
Modulation,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Miniconf, Deserialize, PartialEq)]
|
||||
enum LockinMode {
|
||||
Internal,
|
||||
External,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Miniconf)]
|
||||
pub struct Settings {
|
||||
afe: [AfeGain; 2],
|
||||
lockin_mode: LockinMode,
|
||||
|
||||
pll_tc: [u8; 2],
|
||||
|
||||
@ -43,13 +61,15 @@ impl Default for Settings {
|
||||
Self {
|
||||
afe: [AfeGain::G1; 2],
|
||||
|
||||
lockin_mode: LockinMode::External,
|
||||
|
||||
pll_tc: [21, 21], // frequency and phase settling time (log2 counter cycles)
|
||||
|
||||
lockin_tc: 6, // lockin lowpass time constant
|
||||
lockin_harmonic: -1, // Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
|
||||
lockin_phase: 0, // Demodulation LO phase offset
|
||||
|
||||
output_conf: [Conf::Quadrature; 2],
|
||||
output_conf: [Conf::InPhase, Conf::Quadrature],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,7 +157,7 @@ const APP: () = {
|
||||
c.resources.adcs.1.acquire_buffer(),
|
||||
];
|
||||
|
||||
let dac_samples = [
|
||||
let mut dac_samples = [
|
||||
c.resources.dacs.0.acquire_buffer(),
|
||||
c.resources.dacs.1.acquire_buffer(),
|
||||
];
|
||||
@ -145,6 +165,9 @@ const APP: () = {
|
||||
let lockin = c.resources.lockin;
|
||||
let settings = c.resources.settings;
|
||||
|
||||
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
||||
{
|
||||
LockinMode::External => {
|
||||
let timestamp =
|
||||
c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
|
||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
||||
@ -152,14 +175,27 @@ const APP: () = {
|
||||
settings.pll_tc[0],
|
||||
settings.pll_tc[1],
|
||||
);
|
||||
|
||||
let sample_frequency = ((pll_frequency
|
||||
(
|
||||
pll_phase,
|
||||
(pll_frequency
|
||||
>> design_parameters::SAMPLE_BUFFER_SIZE_LOG2)
|
||||
as i32)
|
||||
.wrapping_mul(settings.lockin_harmonic);
|
||||
let sample_phase = settings
|
||||
.lockin_phase
|
||||
.wrapping_add(pll_phase.wrapping_mul(settings.lockin_harmonic));
|
||||
as i32,
|
||||
)
|
||||
}
|
||||
LockinMode::Internal => {
|
||||
// Reference phase and frequency are known.
|
||||
(
|
||||
1i32 << 30,
|
||||
1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let sample_frequency =
|
||||
reference_frequency.wrapping_mul(settings.lockin_harmonic);
|
||||
let sample_phase = settings.lockin_phase.wrapping_add(
|
||||
reference_phase.wrapping_mul(settings.lockin_harmonic),
|
||||
);
|
||||
|
||||
let output: Complex<i32> = adc_samples[0]
|
||||
.iter()
|
||||
@ -175,23 +211,23 @@ const APP: () = {
|
||||
.unwrap()
|
||||
* 2; // Full scale assuming the 2f component is gone.
|
||||
|
||||
let output = [
|
||||
match settings.output_conf[0] {
|
||||
Conf::PowerPhase => output.abs_sqr() as _,
|
||||
Conf::FrequencyDiscriminator => (output.log2() << 24) as _,
|
||||
Conf::Quadrature => output.re,
|
||||
},
|
||||
match settings.output_conf[1] {
|
||||
Conf::PowerPhase => output.arg(),
|
||||
Conf::FrequencyDiscriminator => pll_frequency as _,
|
||||
Conf::Quadrature => output.im,
|
||||
},
|
||||
];
|
||||
|
||||
// Convert to DAC data.
|
||||
for i in 0..dac_samples[0].len() {
|
||||
dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000;
|
||||
dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000;
|
||||
for (channel, samples) in dac_samples.iter_mut().enumerate() {
|
||||
for (i, sample) in samples.iter_mut().enumerate() {
|
||||
let value = match settings.output_conf[channel] {
|
||||
Conf::Magnitude => output.abs_sqr() as i32 >> 16,
|
||||
Conf::Phase => output.arg() >> 16,
|
||||
Conf::LogPower => (output.log2() << 24) as i32 >> 16,
|
||||
Conf::ReferenceFrequency => {
|
||||
reference_frequency as i32 >> 16
|
||||
}
|
||||
Conf::InPhase => output.re >> 16,
|
||||
Conf::Quadrature => output.im >> 16,
|
||||
Conf::Modulation => DAC_SEQUENCE[i] as i32,
|
||||
};
|
||||
|
||||
*sample = value as u16 ^ 0x8000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user