Merge pull request #263 from quartiq/rj/deglitch-misc

deglitch timer input, miscellaneous changes
This commit is contained in:
Robert Jördens 2021-02-09 15:39:24 +01:00 committed by GitHub
commit ae43f60d60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 115 additions and 276 deletions

2
.github/bors.toml vendored
View File

@ -3,5 +3,5 @@ delete_merged_branches = true
status = [ status = [
"style", "style",
"test (stable)", "test (stable)",
"compile (stable, false)", "compile (stable)",
] ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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