Merge pull request #263 from quartiq/rj/deglitch-misc
deglitch timer input, miscellaneous changes
This commit is contained in:
commit
ae43f60d60
2
.github/bors.toml
vendored
2
.github/bors.toml
vendored
@ -3,5 +3,5 @@ delete_merged_branches = true
|
||||
status = [
|
||||
"style",
|
||||
"test (stable)",
|
||||
"compile (stable, false)",
|
||||
"compile (stable)",
|
||||
]
|
||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -35,22 +35,18 @@ jobs:
|
||||
|
||||
compile:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.optional }}
|
||||
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain: [stable]
|
||||
features: ['']
|
||||
optional: [false]
|
||||
include:
|
||||
- toolchain: beta
|
||||
features: ''
|
||||
optional: false
|
||||
- toolchain: stable
|
||||
features: pounder_v1_1
|
||||
optional: false
|
||||
- toolchain: nightly
|
||||
features: nightly
|
||||
optional: true
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
@ -1,4 +1,4 @@
|
||||
use core::f32::consts::PI;
|
||||
use core::f64::consts::PI;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Generic vector for integer IIR filter.
|
||||
@ -19,7 +19,7 @@ impl Vec5 {
|
||||
///
|
||||
/// # Returns
|
||||
/// 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.
|
||||
let f = f * 2. * PI;
|
||||
let f2 = f * f * 0.5;
|
||||
@ -27,10 +27,10 @@ impl Vec5 {
|
||||
let fsin = f * (1. - f2 / 3.);
|
||||
let alpha = fsin / (2. * q);
|
||||
// IIR uses Q2.30 fixed point
|
||||
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f32;
|
||||
let b0 = (k / 2. * (1. - fcos) / a0) as _;
|
||||
let a1 = (2. * fcos / a0) as _;
|
||||
let a2 = ((alpha - 1.) / a0) as _;
|
||||
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64;
|
||||
let b0 = (k / 2. * (1. - fcos) / a0 + 0.5) as _;
|
||||
let a1 = (2. * fcos / a0 + 0.5) as _;
|
||||
let a2 = ((alpha - 1.) / a0 + 0.5) as _;
|
||||
|
||||
Self([b0, 2 * b0, b0, a1, a2])
|
||||
}
|
||||
@ -97,7 +97,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,6 @@ mod test {
|
||||
|
||||
struct Harness {
|
||||
rpll: RPLL,
|
||||
dt2: u8,
|
||||
shift_frequency: u8,
|
||||
shift_phase: u8,
|
||||
noise: i32,
|
||||
@ -109,7 +108,6 @@ mod test {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rpll: RPLL::new(8),
|
||||
dt2: 8,
|
||||
shift_frequency: 9,
|
||||
shift_phase: 8,
|
||||
noise: 0,
|
||||
@ -122,7 +120,7 @@ mod test {
|
||||
}
|
||||
|
||||
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_phase + 1);
|
||||
|
||||
@ -130,7 +128,7 @@ mod test {
|
||||
let mut f = Vec::<f32>::new();
|
||||
for _ in 0..n {
|
||||
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);
|
||||
let timestamp = self.next_noisy;
|
||||
let p_noise = self.rng.gen_range(-self.noise..=self.noise);
|
||||
@ -151,23 +149,23 @@ mod test {
|
||||
// phase error
|
||||
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;
|
||||
// relative frequency error
|
||||
f.push(
|
||||
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
|
||||
self.time = self.time.wrapping_add(1 << self.dt2);
|
||||
self.time = self.time.wrapping_add(1 << self.rpll.dt2);
|
||||
}
|
||||
(y, f)
|
||||
}
|
||||
|
||||
fn measure(&mut self, n: usize, limits: [f32; 4]) {
|
||||
let t_settle = (1 << self.shift_frequency - self.dt2 + 4)
|
||||
+ (1 << self.shift_phase - self.dt2 + 4);
|
||||
let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4)
|
||||
+ (1 << self.shift_phase - self.rpll.dt2 + 4);
|
||||
self.run(t_settle);
|
||||
|
||||
let (y, f) = self.run(n);
|
||||
@ -268,4 +266,18 @@ mod test {
|
||||
|
||||
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;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use stabilizer::{hardware, hardware::design_parameters};
|
||||
|
||||
use rtic::cyccnt::{Instant, U32Ext};
|
||||
|
||||
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 dsp::{iir_int, lockin::Lockin, rpll::RPLL, Accu};
|
||||
use hardware::{
|
||||
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)]
|
||||
const APP: () = {
|
||||
struct Resources {
|
||||
@ -36,12 +19,6 @@ const APP: () = {
|
||||
dacs: (Dac0Output, Dac1Output),
|
||||
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,
|
||||
pll: RPLL,
|
||||
lockin: Lockin,
|
||||
@ -52,7 +29,10 @@ const APP: () = {
|
||||
// Configure the microcontroller
|
||||
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(
|
||||
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.
|
||||
/// 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.
|
||||
#[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) {
|
||||
let adc_samples = [
|
||||
c.resources.adcs.0.acquire_buffer(),
|
||||
@ -104,30 +84,30 @@ const APP: () = {
|
||||
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 timestamp = c
|
||||
.resources
|
||||
.timestamper
|
||||
.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);
|
||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
||||
timestamp,
|
||||
22, // frequency settling time (log2 counter cycles), TODO: expose
|
||||
22, // phase settling time, TODO: expose
|
||||
21, // frequency settling time (log2 counter cycles), TODO: expose
|
||||
21, // phase settling time, TODO: expose
|
||||
);
|
||||
|
||||
// Harmonic index of the LO: -1 to _de_modulate the fundamental (complex conjugate)
|
||||
let harmonic: i32 = -1; // TODO: expose
|
||||
// Demodulation LO phase offset
|
||||
|
||||
// Demodulation LO phase offset
|
||||
let phase_offset: i32 = 0; // TODO: expose
|
||||
|
||||
let sample_frequency = ((pll_frequency
|
||||
// .wrapping_add(1 << SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias
|
||||
>> SAMPLE_BUFFER_SIZE_LOG2) as i32)
|
||||
// .wrapping_add(1 << design_parameters::SAMPLE_BUFFER_SIZE_LOG2 - 1) // half-up rounding bias
|
||||
>> design_parameters::SAMPLE_BUFFER_SIZE_LOG2)
|
||||
as i32)
|
||||
.wrapping_mul(harmonic);
|
||||
let sample_phase =
|
||||
phase_offset.wrapping_add(pll_phase.wrapping_mul(harmonic));
|
||||
@ -142,187 +122,26 @@ const APP: () = {
|
||||
.last()
|
||||
.unwrap();
|
||||
|
||||
// convert i/q to power/phase,
|
||||
let power_phase = true; // TODO: expose
|
||||
|
||||
let mut output = if power_phase {
|
||||
let conf = "frequency_discriminator";
|
||||
let output = match conf {
|
||||
// Convert from IQ to power and phase.
|
||||
[output.abs_sqr() as _, output.arg() as _]
|
||||
} else {
|
||||
[output.0 as _, output.1 as _]
|
||||
"power_phase" => [output.abs_sqr(), output.arg()],
|
||||
"frequency_discriminator" => [pll_frequency as i32, output.arg()],
|
||||
_ => [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.
|
||||
for i in 0..dac_samples[0].len() {
|
||||
unsafe {
|
||||
dac_samples[0][i] =
|
||||
output[0].to_int_unchecked::<i16>() as u16 ^ 0x8000;
|
||||
dac_samples[1][i] =
|
||||
output[1].to_int_unchecked::<i16>() as u16 ^ 0x8000;
|
||||
}
|
||||
dac_samples[0][i] = (output[0] >> 16) as u16 ^ 0x8000;
|
||||
dac_samples[1][i] = (output[1] >> 16) as u16 ^ 0x8000;
|
||||
}
|
||||
}
|
||||
|
||||
#[idle(resources=[net_interface, iir_state, iir_ch, afes])]
|
||||
fn idle(mut c: 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();
|
||||
|
||||
#[idle(resources=[afes])]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
loop {
|
||||
let tick = Instant::now() > next_ms;
|
||||
|
||||
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();
|
||||
}
|
||||
// TODO: Implement network interface.
|
||||
cortex_m::asm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,13 @@
|
||||
|
||||
use dsp::{iir_int, lockin::Lockin, Accu};
|
||||
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.
|
||||
// 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; SAMPLE_BUFFER_SIZE] =
|
||||
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)]
|
||||
@ -83,7 +83,8 @@ const APP: () = {
|
||||
|
||||
// Reference phase and frequency are known.
|
||||
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
|
||||
let harmonic: i32 = -1;
|
||||
|
@ -74,9 +74,9 @@
|
||||
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
|
||||
use stm32h7xx_hal as hal;
|
||||
|
||||
use crate::SAMPLE_BUFFER_SIZE;
|
||||
|
||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
||||
use super::timers;
|
||||
|
||||
use hal::dma::{
|
||||
config::Priority,
|
||||
dma::{DMAReq, DmaConfig},
|
||||
|
@ -1,14 +1,6 @@
|
||||
///! Stabilizer hardware configuration
|
||||
///!
|
||||
///! 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 stm32h7xx_hal::{
|
||||
@ -157,7 +149,8 @@ pub fn setup(
|
||||
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||
|
||||
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,
|
||||
// it generates a trigger whenever it is enabled.
|
||||
@ -181,7 +174,8 @@ pub fn setup(
|
||||
|
||||
let mut shadow_sampling_timer =
|
||||
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
|
||||
// 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 =
|
||||
design_parameters::DDS_REF_CLK.into();
|
||||
|
||||
let ad9959 = ad9959::Ad9959::new(
|
||||
let mut ad9959 = ad9959::Ad9959::new(
|
||||
qspi_interface,
|
||||
reset_pin,
|
||||
&mut io_update,
|
||||
@ -614,6 +608,8 @@ pub fn setup(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ad9959.self_test().unwrap();
|
||||
|
||||
// Return IO_Update
|
||||
gpiog.pg7 = io_update.into_analog();
|
||||
|
||||
@ -726,7 +722,8 @@ pub fn setup(
|
||||
let sample_frequency = {
|
||||
let timer_frequency: hal::time::Hertz =
|
||||
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;
|
||||
@ -761,22 +758,13 @@ pub fn setup(
|
||||
// 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
|
||||
// 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.start();
|
||||
|
||||
// We want the pounder timestamp timer to overflow once per batch.
|
||||
let tick_ratio = {
|
||||
let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0
|
||||
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());
|
||||
// Set the timer to wrap at the u16 boundary to meet the PLL periodicity.
|
||||
// Scale and wrap before or after the PLL.
|
||||
timestamp_timer.set_period_ticks(u16::MAX);
|
||||
let tim8_channels = timestamp_timer.channels();
|
||||
|
||||
pounder::timestamp::Timestamper::new(
|
||||
|
@ -52,9 +52,9 @@
|
||||
///! served promptly after the transfer completes.
|
||||
use stm32h7xx_hal as hal;
|
||||
|
||||
use crate::SAMPLE_BUFFER_SIZE;
|
||||
|
||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
||||
use super::timers;
|
||||
|
||||
use hal::dma::{
|
||||
dma::{DMAReq, DmaConfig},
|
||||
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).
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
|
||||
// capture source.
|
||||
let input_capture =
|
||||
let mut input_capture =
|
||||
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 {
|
||||
capture_channel: input_capture,
|
||||
_di0_trigger: trigger,
|
||||
|
@ -11,10 +11,10 @@ mod adc;
|
||||
mod afe;
|
||||
mod configuration;
|
||||
mod dac;
|
||||
mod design_parameters;
|
||||
pub mod design_parameters;
|
||||
mod digital_input_stamper;
|
||||
mod eeprom;
|
||||
mod pounder;
|
||||
pub mod pounder;
|
||||
mod timers;
|
||||
|
||||
pub use adc::{Adc0Input, Adc1Input};
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod attenuators;
|
||||
pub mod attenuators;
|
||||
mod dds_output;
|
||||
pub mod hrtimer;
|
||||
mod rf_power;
|
||||
|
@ -26,7 +26,7 @@ use stm32h7xx_hal as hal;
|
||||
|
||||
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
|
||||
// working data provided to the application. These buffers must exist in a DMA-accessible memory
|
||||
|
@ -48,6 +48,13 @@ pub enum SlaveMode {
|
||||
Trigger = 0b0110,
|
||||
}
|
||||
|
||||
/// Optional input capture preconditioning filter configurations.
|
||||
#[allow(dead_code)]
|
||||
pub enum InputFilter {
|
||||
Div1N1 = 0b0000,
|
||||
Div1N8 = 0b0011,
|
||||
}
|
||||
|
||||
macro_rules! timer_channels {
|
||||
($name:ident, $TY:ident, $size:ty) => {
|
||||
paste::paste! {
|
||||
@ -334,6 +341,18 @@ macro_rules! timer_channels {
|
||||
let regs = unsafe { &*<$TY>::ptr() };
|
||||
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
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -6,13 +6,3 @@ extern crate log;
|
||||
|
||||
pub mod hardware;
|
||||
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
Block a user