Merge pull request #263 from quartiq/rj/deglitch-misc
deglitch timer input, miscellaneous changes
This commit is contained in:
commit
ae43f60d60
|
@ -3,5 +3,5 @@ delete_merged_branches = true
|
||||||
status = [
|
status = [
|
||||||
"style",
|
"style",
|
||||||
"test (stable)",
|
"test (stable)",
|
||||||
"compile (stable, false)",
|
"compile (stable)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -35,22 +35,18 @@ jobs:
|
||||||
|
|
||||||
compile:
|
compile:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.optional }}
|
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
toolchain: [stable]
|
toolchain: [stable]
|
||||||
features: ['']
|
features: ['']
|
||||||
optional: [false]
|
|
||||||
include:
|
include:
|
||||||
- toolchain: beta
|
- toolchain: beta
|
||||||
features: ''
|
features: ''
|
||||||
optional: false
|
|
||||||
- toolchain: stable
|
- toolchain: stable
|
||||||
features: pounder_v1_1
|
features: pounder_v1_1
|
||||||
optional: false
|
|
||||||
- toolchain: nightly
|
- toolchain: nightly
|
||||||
features: nightly
|
features: nightly
|
||||||
optional: true
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use core::f32::consts::PI;
|
use core::f64::consts::PI;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Generic vector for integer IIR filter.
|
/// Generic vector for integer IIR filter.
|
||||||
|
@ -19,7 +19,7 @@ impl Vec5 {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
|
/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
|
||||||
pub fn lowpass(f: f32, q: f32, k: f32) -> Self {
|
pub fn lowpass(f: f64, q: f64, k: f64) -> Self {
|
||||||
// 3rd order Taylor approximation of sin and cos.
|
// 3rd order Taylor approximation of sin and cos.
|
||||||
let f = f * 2. * PI;
|
let f = f * 2. * PI;
|
||||||
let f2 = f * f * 0.5;
|
let f2 = f * f * 0.5;
|
||||||
|
@ -27,10 +27,10 @@ impl Vec5 {
|
||||||
let fsin = f * (1. - f2 / 3.);
|
let fsin = f * (1. - f2 / 3.);
|
||||||
let alpha = fsin / (2. * q);
|
let alpha = fsin / (2. * q);
|
||||||
// IIR uses Q2.30 fixed point
|
// IIR uses Q2.30 fixed point
|
||||||
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32;
|
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64;
|
||||||
let b0 = (k / 2. * (1. - fcos) / a0) as _;
|
let b0 = (k / 2. * (1. - fcos) / a0 + 0.5) as _;
|
||||||
let a1 = (2. * fcos / a0) as _;
|
let a1 = (2. * fcos / a0 + 0.5) as _;
|
||||||
let a2 = ((alpha - 1.) / a0) as _;
|
let a2 = ((alpha - 1.) / a0 + 0.5) as _;
|
||||||
|
|
||||||
Self([b0, 2 * b0, b0, a1, a2])
|
Self([b0, 2 * b0, b0, a1, a2])
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lowpass_gen() {
|
fn lowpass_gen() {
|
||||||
let ba = Vec5::lowpass(1e-3, 1. / 2f32.sqrt(), 2.);
|
let ba = Vec5::lowpass(1e-5, 1. / 2f64.sqrt(), 2.);
|
||||||
println!("{:?}", ba.0);
|
println!("{:?}", ba.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,6 @@ mod test {
|
||||||
|
|
||||||
struct Harness {
|
struct Harness {
|
||||||
rpll: RPLL,
|
rpll: RPLL,
|
||||||
dt2: u8,
|
|
||||||
shift_frequency: u8,
|
shift_frequency: u8,
|
||||||
shift_phase: u8,
|
shift_phase: u8,
|
||||||
noise: i32,
|
noise: i32,
|
||||||
|
@ -109,7 +108,6 @@ mod test {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
rpll: RPLL::new(8),
|
rpll: RPLL::new(8),
|
||||||
dt2: 8,
|
|
||||||
shift_frequency: 9,
|
shift_frequency: 9,
|
||||||
shift_phase: 8,
|
shift_phase: 8,
|
||||||
noise: 0,
|
noise: 0,
|
||||||
|
@ -122,7 +120,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, n: usize) -> (Vec<f32>, Vec<f32>) {
|
fn run(&mut self, n: usize) -> (Vec<f32>, Vec<f32>) {
|
||||||
assert!(self.period >= 1 << self.dt2);
|
assert!(self.period >= 1 << self.rpll.dt2);
|
||||||
assert!(self.period < 1 << self.shift_frequency);
|
assert!(self.period < 1 << self.shift_frequency);
|
||||||
assert!(self.period < 1 << self.shift_phase + 1);
|
assert!(self.period < 1 << self.shift_phase + 1);
|
||||||
|
|
||||||
|
@ -130,7 +128,7 @@ mod test {
|
||||||
let mut f = Vec::<f32>::new();
|
let mut f = Vec::<f32>::new();
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
let timestamp = if self.time - self.next_noisy >= 0 {
|
let timestamp = if self.time - self.next_noisy >= 0 {
|
||||||
assert!(self.time - self.next_noisy < 1 << self.dt2);
|
assert!(self.time - self.next_noisy < 1 << self.rpll.dt2);
|
||||||
self.next = self.next.wrapping_add(self.period);
|
self.next = self.next.wrapping_add(self.period);
|
||||||
let timestamp = self.next_noisy;
|
let timestamp = self.next_noisy;
|
||||||
let p_noise = self.rng.gen_range(-self.noise..=self.noise);
|
let p_noise = self.rng.gen_range(-self.noise..=self.noise);
|
||||||
|
@ -151,23 +149,23 @@ mod test {
|
||||||
// phase error
|
// phase error
|
||||||
y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32));
|
y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32));
|
||||||
|
|
||||||
let p_ref = 1 << 32 + self.dt2;
|
let p_ref = 1 << 32 + self.rpll.dt2;
|
||||||
let p_sig = fi as u64 * self.period as u64;
|
let p_sig = fi as u64 * self.period as u64;
|
||||||
// relative frequency error
|
// relative frequency error
|
||||||
f.push(
|
f.push(
|
||||||
p_sig.wrapping_sub(p_ref) as i64 as f32
|
p_sig.wrapping_sub(p_ref) as i64 as f32
|
||||||
/ 2f32.powi(32 + self.dt2 as i32),
|
/ 2f32.powi(32 + self.rpll.dt2 as i32),
|
||||||
);
|
);
|
||||||
|
|
||||||
// advance time
|
// advance time
|
||||||
self.time = self.time.wrapping_add(1 << self.dt2);
|
self.time = self.time.wrapping_add(1 << self.rpll.dt2);
|
||||||
}
|
}
|
||||||
(y, f)
|
(y, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(&mut self, n: usize, limits: [f32; 4]) {
|
fn measure(&mut self, n: usize, limits: [f32; 4]) {
|
||||||
let t_settle = (1 << self.shift_frequency - self.dt2 + 4)
|
let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4)
|
||||||
+ (1 << self.shift_phase - self.dt2 + 4);
|
+ (1 << self.shift_phase - self.rpll.dt2 + 4);
|
||||||
self.run(t_settle);
|
self.run(t_settle);
|
||||||
|
|
||||||
let (y, f) = self.run(n);
|
let (y, f) = self.run(n);
|
||||||
|
@ -268,4 +266,18 @@ mod test {
|
||||||
|
|
||||||
h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]);
|
h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn batch_fast_narrow() {
|
||||||
|
let mut h = Harness::default();
|
||||||
|
h.rpll.dt2 = 8 + 3;
|
||||||
|
h.period = 2431;
|
||||||
|
h.next = 35281;
|
||||||
|
h.next_noisy = h.next;
|
||||||
|
h.noise = 100;
|
||||||
|
h.shift_frequency = 23;
|
||||||
|
h.shift_phase = 23;
|
||||||
|
|
||||||
|
h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,30 +4,13 @@
|
||||||
|
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
#[macro_use]
|
use stabilizer::{hardware, hardware::design_parameters};
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use rtic::cyccnt::{Instant, U32Ext};
|
use dsp::{iir_int, lockin::Lockin, rpll::RPLL, Accu};
|
||||||
|
|
||||||
use heapless::{consts::*, String};
|
|
||||||
|
|
||||||
use stabilizer::{
|
|
||||||
hardware, server, ADC_SAMPLE_TICKS_LOG2, SAMPLE_BUFFER_SIZE_LOG2,
|
|
||||||
};
|
|
||||||
|
|
||||||
use dsp::{iir, iir_int, lockin::Lockin, rpll::RPLL, Accu};
|
|
||||||
use hardware::{
|
use hardware::{
|
||||||
Adc0Input, Adc1Input, Dac0Output, Dac1Output, InputStamper, AFE0, AFE1,
|
Adc0Input, Adc1Input, Dac0Output, Dac1Output, InputStamper, AFE0, AFE1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCALE: f32 = i16::MAX as _;
|
|
||||||
|
|
||||||
const TCP_RX_BUFFER_SIZE: usize = 8192;
|
|
||||||
const TCP_TX_BUFFER_SIZE: usize = 8192;
|
|
||||||
|
|
||||||
// The number of cascaded IIR biquads per channel. Select 1 or 2!
|
|
||||||
const IIR_CASCADE_LENGTH: usize = 1;
|
|
||||||
|
|
||||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
||||||
const APP: () = {
|
const APP: () = {
|
||||||
struct Resources {
|
struct Resources {
|
||||||
|
@ -36,12 +19,6 @@ const APP: () = {
|
||||||
dacs: (Dac0Output, Dac1Output),
|
dacs: (Dac0Output, Dac1Output),
|
||||||
net_interface: hardware::Ethernet,
|
net_interface: hardware::Ethernet,
|
||||||
|
|
||||||
// Format: iir_state[ch][cascade-no][coeff]
|
|
||||||
#[init([[iir::Vec5([0.; 5]); IIR_CASCADE_LENGTH]; 2])]
|
|
||||||
iir_state: [[iir::Vec5; IIR_CASCADE_LENGTH]; 2],
|
|
||||||
#[init([[iir::IIR::new(1./(1 << 16) as f32, -SCALE, SCALE); IIR_CASCADE_LENGTH]; 2])]
|
|
||||||
iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2],
|
|
||||||
|
|
||||||
timestamper: InputStamper,
|
timestamper: InputStamper,
|
||||||
pll: RPLL,
|
pll: RPLL,
|
||||||
lockin: Lockin,
|
lockin: Lockin,
|
||||||
|
@ -52,7 +29,10 @@ const APP: () = {
|
||||||
// Configure the microcontroller
|
// Configure the microcontroller
|
||||||
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device);
|
||||||
|
|
||||||
let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + SAMPLE_BUFFER_SIZE_LOG2);
|
let pll = RPLL::new(
|
||||||
|
design_parameters::ADC_SAMPLE_TICKS_LOG2
|
||||||
|
+ design_parameters::SAMPLE_BUFFER_SIZE_LOG2,
|
||||||
|
);
|
||||||
|
|
||||||
let lockin = Lockin::new(
|
let lockin = Lockin::new(
|
||||||
iir_int::Vec5::lowpass(1e-3, 0.707, 2.), // TODO: expose
|
iir_int::Vec5::lowpass(1e-3, 0.707, 2.), // TODO: expose
|
||||||
|
@ -92,7 +72,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, iir_state, iir_ch, lockin, timestamper, pll], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll], priority=2)]
|
||||||
fn process(c: process::Context) {
|
fn process(c: process::Context) {
|
||||||
let adc_samples = [
|
let adc_samples = [
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
c.resources.adcs.0.acquire_buffer(),
|
||||||
|
@ -104,30 +84,30 @@ const APP: () = {
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
c.resources.dacs.1.acquire_buffer(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let iir_ch = c.resources.iir_ch;
|
|
||||||
let iir_state = c.resources.iir_state;
|
|
||||||
let lockin = c.resources.lockin;
|
let lockin = c.resources.lockin;
|
||||||
|
|
||||||
let timestamp = c
|
let timestamp = c
|
||||||
.resources
|
.resources
|
||||||
.timestamper
|
.timestamper
|
||||||
.latest_timestamp()
|
.latest_timestamp()
|
||||||
.unwrap_or_else(|t| t) // Ignore timer capture overflows.
|
.unwrap_or(None) // Ignore data from timer capture overflows.
|
||||||
.map(|t| t as i32);
|
.map(|t| t as i32);
|
||||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
||||||
timestamp,
|
timestamp,
|
||||||
22, // frequency settling time (log2 counter cycles), TODO: expose
|
21, // frequency settling time (log2 counter cycles), TODO: expose
|
||||||
22, // phase settling time, TODO: expose
|
21, // phase settling time, TODO: expose
|
||||||
);
|
);
|
||||||
|
|
||||||
// Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
|
// Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
|
||||||
let harmonic: i32 = -1; // TODO: expose
|
let harmonic: i32 = -1; // TODO: expose
|
||||||
// Demodulation LO phase offset
|
|
||||||
|
// Demodulation LO phase offset
|
||||||
let phase_offset: i32 = 0; // TODO: expose
|
let phase_offset: i32 = 0; // TODO: expose
|
||||||
|
|
||||||
let sample_frequency = ((pll_frequency
|
let sample_frequency = ((pll_frequency
|
||||||
// .wrapping_add(1 << SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias
|
// .wrapping_add(1 << design_parameters::SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias
|
||||||
>> SAMPLE_BUFFER_SIZE_LOG2) as i32)
|
>> design_parameters::SAMPLE_BUFFER_SIZE_LOG2)
|
||||||
|
as i32)
|
||||||
.wrapping_mul(harmonic);
|
.wrapping_mul(harmonic);
|
||||||
let sample_phase =
|
let sample_phase =
|
||||||
phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic));
|
phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic));
|
||||||
|
@ -142,187 +122,26 @@ const APP: () = {
|
||||||
.last()
|
.last()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// convert i/q to power/phase,
|
let conf = "frequency_discriminator";
|
||||||
let power_phase = true; // TODO: expose
|
let output = match conf {
|
||||||
|
|
||||||
let mut output = if power_phase {
|
|
||||||
// Convert from IQ to power and phase.
|
// Convert from IQ to power and phase.
|
||||||
[output.abs_sqr() as _, output.arg() as _]
|
"power_phase" => [output.abs_sqr(), output.arg()],
|
||||||
} else {
|
"frequency_discriminator" => [pll_frequency as i32, output.arg()],
|
||||||
[output.0 as _, output.1 as _]
|
_ => [output.0, output.1],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter power and phase through IIR filters.
|
|
||||||
// Note: Normalization to be done in filters. Phase will wrap happily.
|
|
||||||
for j in 0..iir_state[0].len() {
|
|
||||||
for k in 0..output.len() {
|
|
||||||
output[k] =
|
|
||||||
iir_ch[k][j].update(&mut iir_state[k][j], output[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note(unsafe): range clipping to i16 is ensured by IIR filters above.
|
|
||||||
// Convert to DAC data.
|
// Convert to DAC data.
|
||||||
for i in 0..dac_samples[0].len() {
|
for i in 0..dac_samples[0].len() {
|
||||||
unsafe {
|
dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000;
|
||||||
dac_samples[0][i] =
|
dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000;
|
||||||
output[0].to_int_unchecked::<i16>() as u16 ^ 0x8000;
|
|
||||||
dac_samples[1][i] =
|
|
||||||
output[1].to_int_unchecked::<i16>() as u16 ^ 0x8000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[net_interface, iir_state, iir_ch, afes])]
|
#[idle(resources=[afes])]
|
||||||
fn idle(mut c: idle::Context) -> ! {
|
fn idle(_: idle::Context) -> ! {
|
||||||
let mut socket_set_entries: [_; 8] = Default::default();
|
|
||||||
let mut sockets =
|
|
||||||
smoltcp::socket::SocketSet::new(&mut socket_set_entries[..]);
|
|
||||||
|
|
||||||
let mut rx_storage = [0; TCP_RX_BUFFER_SIZE];
|
|
||||||
let mut tx_storage = [0; TCP_TX_BUFFER_SIZE];
|
|
||||||
let tcp_handle = {
|
|
||||||
let tcp_rx_buffer =
|
|
||||||
smoltcp::socket::TcpSocketBuffer::new(&mut rx_storage[..]);
|
|
||||||
let tcp_tx_buffer =
|
|
||||||
smoltcp::socket::TcpSocketBuffer::new(&mut tx_storage[..]);
|
|
||||||
let tcp_socket =
|
|
||||||
smoltcp::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
|
||||||
sockets.add(tcp_socket)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut server = server::Server::new();
|
|
||||||
|
|
||||||
let mut time = 0u32;
|
|
||||||
let mut next_ms = Instant::now();
|
|
||||||
|
|
||||||
// TODO: Replace with reference to CPU clock from CCDR.
|
|
||||||
next_ms += 400_000.cycles();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let tick = Instant::now() > next_ms;
|
// TODO: Implement network interface.
|
||||||
|
cortex_m::asm::wfi();
|
||||||
if tick {
|
|
||||||
next_ms += 400_000.cycles();
|
|
||||||
time += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let socket =
|
|
||||||
&mut *sockets.get::<smoltcp::socket::TcpSocket>(tcp_handle);
|
|
||||||
if socket.state() == smoltcp::socket::TcpState::CloseWait {
|
|
||||||
socket.close();
|
|
||||||
} else if !(socket.is_open() || socket.is_listening()) {
|
|
||||||
socket
|
|
||||||
.listen(1235)
|
|
||||||
.unwrap_or_else(|e| warn!("TCP listen error: {:?}", e));
|
|
||||||
} else {
|
|
||||||
server.poll(socket, |req| {
|
|
||||||
info!("Got request: {:?}", req);
|
|
||||||
stabilizer::route_request!(req,
|
|
||||||
readable_attributes: [
|
|
||||||
"stabilizer/iir/state": (|| {
|
|
||||||
let state = c.resources.iir_state.lock(|iir_state|
|
|
||||||
server::Status {
|
|
||||||
t: time,
|
|
||||||
x0: iir_state[0][0].0[0],
|
|
||||||
y0: iir_state[0][0].0[2],
|
|
||||||
x1: iir_state[1][0].0[0],
|
|
||||||
y1: iir_state[1][0].0[2],
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok::<server::Status, ()>(state)
|
|
||||||
}),
|
|
||||||
// "_b" means cascades 2nd IIR
|
|
||||||
"stabilizer/iir_b/state": (|| { let state = c.resources.iir_state.lock(|iir_state|
|
|
||||||
server::Status {
|
|
||||||
t: time,
|
|
||||||
x0: iir_state[0][IIR_CASCADE_LENGTH-1].0[0],
|
|
||||||
y0: iir_state[0][IIR_CASCADE_LENGTH-1].0[2],
|
|
||||||
x1: iir_state[1][IIR_CASCADE_LENGTH-1].0[0],
|
|
||||||
y1: iir_state[1][IIR_CASCADE_LENGTH-1].0[2],
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok::<server::Status, ()>(state)
|
|
||||||
}),
|
|
||||||
"stabilizer/afe0/gain": (|| c.resources.afes.0.get_gain()),
|
|
||||||
"stabilizer/afe1/gain": (|| c.resources.afes.1.get_gain())
|
|
||||||
],
|
|
||||||
|
|
||||||
modifiable_attributes: [
|
|
||||||
"stabilizer/iir0/state": server::IirRequest, (|req: server::IirRequest| {
|
|
||||||
c.resources.iir_ch.lock(|iir_ch| {
|
|
||||||
if req.channel > 1 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
|
|
||||||
iir_ch[req.channel as usize][0] = req.iir;
|
|
||||||
|
|
||||||
Ok::<server::IirRequest, ()>(req)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
"stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| {
|
|
||||||
c.resources.iir_ch.lock(|iir_ch| {
|
|
||||||
if req.channel > 1 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
|
|
||||||
iir_ch[req.channel as usize][0] = req.iir;
|
|
||||||
|
|
||||||
Ok::<server::IirRequest, ()>(req)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
"stabilizer/iir_b0/state": server::IirRequest, (|req: server::IirRequest| {
|
|
||||||
c.resources.iir_ch.lock(|iir_ch| {
|
|
||||||
if req.channel > 1 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
|
|
||||||
iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir;
|
|
||||||
|
|
||||||
Ok::<server::IirRequest, ()>(req)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
"stabilizer/iir_b1/state": server::IirRequest,(|req: server::IirRequest| {
|
|
||||||
c.resources.iir_ch.lock(|iir_ch| {
|
|
||||||
if req.channel > 1 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
|
|
||||||
iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir;
|
|
||||||
|
|
||||||
Ok::<server::IirRequest, ()>(req)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
"stabilizer/afe0/gain": hardware::AfeGain, (|gain| {
|
|
||||||
c.resources.afes.0.set_gain(gain);
|
|
||||||
Ok::<(), ()>(())
|
|
||||||
}),
|
|
||||||
"stabilizer/afe1/gain": hardware::AfeGain, (|gain| {
|
|
||||||
c.resources.afes.1.set_gain(gain);
|
|
||||||
Ok::<(), ()>(())
|
|
||||||
})
|
|
||||||
]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sleep = match c.resources.net_interface.poll(
|
|
||||||
&mut sockets,
|
|
||||||
smoltcp::time::Instant::from_millis(time as i64),
|
|
||||||
) {
|
|
||||||
Ok(changed) => !changed,
|
|
||||||
Err(smoltcp::Error::Unrecognized) => true,
|
|
||||||
Err(e) => {
|
|
||||||
info!("iface poll error: {:?}", e);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if sleep {
|
|
||||||
cortex_m::asm::wfi();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
use dsp::{iir_int, lockin::Lockin, Accu};
|
use dsp::{iir_int, lockin::Lockin, Accu};
|
||||||
use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1};
|
use hardware::{Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1};
|
||||||
use stabilizer::{hardware, SAMPLE_BUFFER_SIZE, SAMPLE_BUFFER_SIZE_LOG2};
|
use stabilizer::{hardware, hardware::design_parameters};
|
||||||
|
|
||||||
// A constant sinusoid to send on the DAC output.
|
// A constant sinusoid to send on the DAC output.
|
||||||
// Full-scale gives a +/- 10V amplitude waveform. Scale it down to give +/- 1V.
|
// Full-scale gives a +/- 10V amplitude waveform. Scale it down to give +/- 1V.
|
||||||
const ONE: i16 = (0.1 * u16::MAX as f32) as _;
|
const ONE: i16 = (0.1 * u16::MAX as f32) as _;
|
||||||
const SQRT2: i16 = (ONE as f32 * 0.707) as _;
|
const SQRT2: i16 = (ONE as f32 * 0.707) as _;
|
||||||
const DAC_SEQUENCE: [i16; SAMPLE_BUFFER_SIZE] =
|
const DAC_SEQUENCE: [i16; design_parameters::SAMPLE_BUFFER_SIZE] =
|
||||||
[ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2];
|
[ONE, SQRT2, 0, -SQRT2, -ONE, -SQRT2, 0, SQRT2];
|
||||||
|
|
||||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
||||||
|
@ -83,7 +83,8 @@ const APP: () = {
|
||||||
|
|
||||||
// Reference phase and frequency are known.
|
// Reference phase and frequency are known.
|
||||||
let pll_phase = 0;
|
let pll_phase = 0;
|
||||||
let pll_frequency = 1i32 << (32 - SAMPLE_BUFFER_SIZE_LOG2);
|
let pll_frequency =
|
||||||
|
1i32 << (32 - design_parameters::SAMPLE_BUFFER_SIZE_LOG2);
|
||||||
|
|
||||||
// Harmonic index of the LO: -1 to _de_modulate the fundamental
|
// Harmonic index of the LO: -1 to _de_modulate the fundamental
|
||||||
let harmonic: i32 = -1;
|
let harmonic: i32 = -1;
|
||||||
|
|
|
@ -74,9 +74,9 @@
|
||||||
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
|
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use crate::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
||||||
|
|
||||||
use super::timers;
|
use super::timers;
|
||||||
|
|
||||||
use hal::dma::{
|
use hal::dma::{
|
||||||
config::Priority,
|
config::Priority,
|
||||||
dma::{DMAReq, DmaConfig},
|
dma::{DMAReq, DmaConfig},
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
///! Stabilizer hardware configuration
|
///! Stabilizer hardware configuration
|
||||||
///!
|
///!
|
||||||
///! This file contains all of the hardware-specific configuration of Stabilizer.
|
///! This file contains all of the hardware-specific configuration of Stabilizer.
|
||||||
use crate::ADC_SAMPLE_TICKS;
|
|
||||||
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
|
||||||
use crate::SAMPLE_BUFFER_SIZE;
|
|
||||||
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
|
||||||
use core::convert::TryInto;
|
|
||||||
|
|
||||||
use smoltcp::{iface::Routes, wire::Ipv4Address};
|
use smoltcp::{iface::Routes, wire::Ipv4Address};
|
||||||
|
|
||||||
use stm32h7xx_hal::{
|
use stm32h7xx_hal::{
|
||||||
|
@ -157,7 +149,8 @@ pub fn setup(
|
||||||
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
||||||
sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32);
|
sampling_timer
|
||||||
|
.set_period_ticks((design_parameters::ADC_SAMPLE_TICKS - 1) as u32);
|
||||||
|
|
||||||
// The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
|
// The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
|
||||||
// it generates a trigger whenever it is enabled.
|
// it generates a trigger whenever it is enabled.
|
||||||
|
@ -181,7 +174,8 @@ pub fn setup(
|
||||||
|
|
||||||
let mut shadow_sampling_timer =
|
let mut shadow_sampling_timer =
|
||||||
timers::ShadowSamplingTimer::new(timer3);
|
timers::ShadowSamplingTimer::new(timer3);
|
||||||
shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1);
|
shadow_sampling_timer
|
||||||
|
.set_period_ticks(design_parameters::ADC_SAMPLE_TICKS - 1);
|
||||||
|
|
||||||
// The shadow sampling timer is a slave-mode timer to the sampling timer. It should
|
// The shadow sampling timer is a slave-mode timer to the sampling timer. It should
|
||||||
// always be in-sync - thus, we configure it to operate in slave mode using "Trigger
|
// always be in-sync - thus, we configure it to operate in slave mode using "Trigger
|
||||||
|
@ -603,7 +597,7 @@ pub fn setup(
|
||||||
let ref_clk: hal::time::Hertz =
|
let ref_clk: hal::time::Hertz =
|
||||||
design_parameters::DDS_REF_CLK.into();
|
design_parameters::DDS_REF_CLK.into();
|
||||||
|
|
||||||
let ad9959 = ad9959::Ad9959::new(
|
let mut ad9959 = ad9959::Ad9959::new(
|
||||||
qspi_interface,
|
qspi_interface,
|
||||||
reset_pin,
|
reset_pin,
|
||||||
&mut io_update,
|
&mut io_update,
|
||||||
|
@ -614,6 +608,8 @@ pub fn setup(
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
ad9959.self_test().unwrap();
|
||||||
|
|
||||||
// Return IO_Update
|
// Return IO_Update
|
||||||
gpiog.pg7 = io_update.into_analog();
|
gpiog.pg7 = io_update.into_analog();
|
||||||
|
|
||||||
|
@ -726,7 +722,8 @@ pub fn setup(
|
||||||
let sample_frequency = {
|
let sample_frequency = {
|
||||||
let timer_frequency: hal::time::Hertz =
|
let timer_frequency: hal::time::Hertz =
|
||||||
design_parameters::TIMER_FREQUENCY.into();
|
design_parameters::TIMER_FREQUENCY.into();
|
||||||
timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32
|
timer_frequency.0 as f32
|
||||||
|
/ design_parameters::ADC_SAMPLE_TICKS as f32
|
||||||
};
|
};
|
||||||
|
|
||||||
let sample_period = 1.0 / sample_frequency;
|
let sample_period = 1.0 / sample_frequency;
|
||||||
|
@ -761,22 +758,13 @@ pub fn setup(
|
||||||
// Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
|
// Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is
|
||||||
// output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
|
// output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for
|
||||||
// the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
|
// the timestamp timer. 31.25MHz corresponds with a 32ns tick rate.
|
||||||
|
// This is less than fCK_INT/3 of the timer as required for oversampling the trigger.
|
||||||
timestamp_timer.set_external_clock(timers::Prescaler::Div4);
|
timestamp_timer.set_external_clock(timers::Prescaler::Div4);
|
||||||
timestamp_timer.start();
|
timestamp_timer.start();
|
||||||
|
|
||||||
// We want the pounder timestamp timer to overflow once per batch.
|
// Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
|
||||||
let tick_ratio = {
|
// Scale and wrap before or after the PLL.
|
||||||
let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0
|
timestamp_timer.set_period_ticks(u16::MAX);
|
||||||
as f32
|
|
||||||
/ design_parameters::DDS_SYNC_CLK_DIV as f32;
|
|
||||||
sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32
|
|
||||||
};
|
|
||||||
|
|
||||||
let period = (tick_ratio
|
|
||||||
* ADC_SAMPLE_TICKS as f32
|
|
||||||
* SAMPLE_BUFFER_SIZE as f32) as u32
|
|
||||||
/ 4;
|
|
||||||
timestamp_timer.set_period_ticks((period - 1).try_into().unwrap());
|
|
||||||
let tim8_channels = timestamp_timer.channels();
|
let tim8_channels = timestamp_timer.channels();
|
||||||
|
|
||||||
pounder::timestamp::Timestamper::new(
|
pounder::timestamp::Timestamper::new(
|
||||||
|
|
|
@ -52,9 +52,9 @@
|
||||||
///! served promptly after the transfer completes.
|
///! served promptly after the transfer completes.
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use crate::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
||||||
|
|
||||||
use super::timers;
|
use super::timers;
|
||||||
|
|
||||||
use hal::dma::{
|
use hal::dma::{
|
||||||
dma::{DMAReq, DmaConfig},
|
dma::{DMAReq, DmaConfig},
|
||||||
traits::TargetAddress,
|
traits::TargetAddress,
|
||||||
|
|
|
@ -39,3 +39,13 @@ pub const DDS_SYSTEM_CLK: MegaHertz =
|
||||||
/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
|
/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk).
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const DDS_SYNC_CLK_DIV: u8 = 4;
|
pub const DDS_SYNC_CLK_DIV: u8 = 4;
|
||||||
|
|
||||||
|
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
||||||
|
// equal to 10ns per tick.
|
||||||
|
// Currently, the sample rate is equal to: Fsample = 100/128 MHz ~ 800 KHz
|
||||||
|
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 7;
|
||||||
|
pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2;
|
||||||
|
|
||||||
|
// The desired ADC sample processing buffer size.
|
||||||
|
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
||||||
|
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
||||||
|
|
|
@ -44,9 +44,13 @@ impl InputStamper {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
|
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
|
||||||
// capture source.
|
// capture source.
|
||||||
let input_capture =
|
let mut input_capture =
|
||||||
timer_channel.into_input_capture(timers::tim5::CaptureSource4::TI4);
|
timer_channel.into_input_capture(timers::tim5::CaptureSource4::TI4);
|
||||||
|
|
||||||
|
// Do not prescale the input capture signal - require 8 consecutive samples to record an
|
||||||
|
// incoming event - this prevents spurious glitches from triggering captures.
|
||||||
|
input_capture.configure_filter(timers::InputFilter::Div1N8);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
capture_channel: input_capture,
|
capture_channel: input_capture,
|
||||||
_di0_trigger: trigger,
|
_di0_trigger: trigger,
|
||||||
|
|
|
@ -11,10 +11,10 @@ mod adc;
|
||||||
mod afe;
|
mod afe;
|
||||||
mod configuration;
|
mod configuration;
|
||||||
mod dac;
|
mod dac;
|
||||||
mod design_parameters;
|
pub mod design_parameters;
|
||||||
mod digital_input_stamper;
|
mod digital_input_stamper;
|
||||||
mod eeprom;
|
mod eeprom;
|
||||||
mod pounder;
|
pub mod pounder;
|
||||||
mod timers;
|
mod timers;
|
||||||
|
|
||||||
pub use adc::{Adc0Input, Adc1Input};
|
pub use adc::{Adc0Input, Adc1Input};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod attenuators;
|
pub mod attenuators;
|
||||||
mod dds_output;
|
mod dds_output;
|
||||||
pub mod hrtimer;
|
pub mod hrtimer;
|
||||||
mod rf_power;
|
mod rf_power;
|
||||||
|
|
|
@ -26,7 +26,7 @@ use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer};
|
use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer};
|
||||||
|
|
||||||
use crate::{hardware::timers, SAMPLE_BUFFER_SIZE};
|
use crate::hardware::{design_parameters::SAMPLE_BUFFER_SIZE, timers};
|
||||||
|
|
||||||
// Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the
|
// Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the
|
||||||
// working data provided to the application. These buffers must exist in a DMA-accessible memory
|
// working data provided to the application. These buffers must exist in a DMA-accessible memory
|
||||||
|
|
|
@ -48,6 +48,13 @@ pub enum SlaveMode {
|
||||||
Trigger = 0b0110,
|
Trigger = 0b0110,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optional input capture preconditioning filter configurations.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum InputFilter {
|
||||||
|
Div1N1 = 0b0000,
|
||||||
|
Div1N8 = 0b0011,
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! timer_channels {
|
macro_rules! timer_channels {
|
||||||
($name:ident, $TY:ident, $size:ty) => {
|
($name:ident, $TY:ident, $size:ty) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
|
@ -334,6 +341,18 @@ macro_rules! timer_channels {
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
regs.sr.read().[< cc $index of >]().bit_is_set()
|
regs.sr.read().[< cc $index of >]().bit_is_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the input capture input pre-filter.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `filter` - The desired input filter stage configuration. Defaults to disabled.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn configure_filter(&mut self, filter: super::InputFilter) {
|
||||||
|
// Note(unsafe): This channel owns all access to the specific timer channel.
|
||||||
|
// Only atomic operations on completed on the timer registers.
|
||||||
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
|
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note(unsafe): This manually implements DMA support for input-capture channels. This
|
// Note(unsafe): This manually implements DMA support for input-capture channels. This
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -6,13 +6,3 @@ extern crate log;
|
||||||
|
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
|
||||||
// equal to 10ns per tick.
|
|
||||||
// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz
|
|
||||||
pub const ADC_SAMPLE_TICKS_LOG2: u8 = 8;
|
|
||||||
pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2;
|
|
||||||
|
|
||||||
// The desired ADC sample processing buffer size.
|
|
||||||
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
|
||||||
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
|
||||||
|
|
Loading…
Reference in New Issue