Merge #367
367: dma: don't swap buffers r=jordens a=jordens * This uses a new closure-based method to the DMA HAL implementation which gives access to the inactive buffer directly. * It removes changing addresses, the third buffer for DBM, the inactive address poisoning, and allows the cancellation of the redundant repeat memory barriers and compiler fences. * This is now around 20 instructions per buffer down from about 100 cycles before. * Also introduces a new `SampleBuffer` type alias. * The required unpacking of the resources structure is a bit annoying but could probably abstraced away. * Reduced pounder capture rate to the batch rate using the prescaler. * Removes the Pounder Timestamper DMA (close #260) TODO: * [x] Tested that dual-iir still works * [x] Tested that DMA overflows are signaled as panics (batch size 1 at full rate) * [x] Adapt `lockin` * [x] Tested on FLS without pounder timestamp DMA. Co-authored-by: Robert Jördens <rj@quartiq.de>
This commit is contained in:
commit
5e2c2c8f30
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -810,8 +810,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "stm32h7xx-hal"
|
name = "stm32h7xx-hal"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/quartiq/stm32h7xx-hal.git?rev=b0b8a93#b0b8a930b2c3bc5fcebc2e905b4c5e13360111a5"
|
||||||
checksum = "67034b80041bc33a48df1c1c435b6ae3bb18c35e42aa7e702ce8363b96793398"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bare-metal 1.0.0",
|
"bare-metal 1.0.0",
|
||||||
"cast",
|
"cast",
|
||||||
|
@ -52,9 +52,12 @@ mcp23017 = "1.0"
|
|||||||
git = "https://github.com/quartiq/rtt-logger.git"
|
git = "https://github.com/quartiq/rtt-logger.git"
|
||||||
rev = "70b0eb5"
|
rev = "70b0eb5"
|
||||||
|
|
||||||
|
# fast double buffered DMA without poisoning and buffer swapping
|
||||||
[dependencies.stm32h7xx-hal]
|
[dependencies.stm32h7xx-hal]
|
||||||
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
|
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
|
||||||
version = "0.9.0"
|
# version = "0.9.0"
|
||||||
|
git = "https://github.com/quartiq/stm32h7xx-hal.git"
|
||||||
|
rev = "b0b8a93"
|
||||||
|
|
||||||
# link.x section start/end
|
# link.x section start/end
|
||||||
[patch.crates-io.cortex-m-rt]
|
[patch.crates-io.cortex-m-rt]
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use stabilizer::{hardware, net};
|
use core::sync::atomic::{fence, Ordering};
|
||||||
|
|
||||||
|
use stabilizer::{flatten_closures, hardware, net};
|
||||||
|
|
||||||
use miniconf::Miniconf;
|
use miniconf::Miniconf;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -122,51 +124,63 @@ const APP: () = {
|
|||||||
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, digital_inputs, dacs, iir_state, settings, telemetry], priority=2)]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[link_section = ".itcm.process"]
|
#[link_section = ".itcm.process"]
|
||||||
fn process(c: process::Context) {
|
fn process(mut c: process::Context) {
|
||||||
let adc_samples = [
|
let process::Resources {
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
adcs: (ref mut adc0, ref mut adc1),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
dacs: (ref mut dac0, ref mut dac1),
|
||||||
];
|
ref digital_inputs,
|
||||||
|
ref settings,
|
||||||
let dac_samples = [
|
ref mut iir_state,
|
||||||
c.resources.dacs.0.acquire_buffer(),
|
ref mut telemetry,
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
} = c.resources;
|
||||||
];
|
|
||||||
|
|
||||||
let digital_inputs = [
|
let digital_inputs = [
|
||||||
c.resources.digital_inputs.0.is_high().unwrap(),
|
digital_inputs.0.is_high().unwrap(),
|
||||||
c.resources.digital_inputs.1.is_high().unwrap(),
|
digital_inputs.1.is_high().unwrap(),
|
||||||
];
|
];
|
||||||
|
telemetry.digital_inputs = digital_inputs;
|
||||||
|
|
||||||
let hold = c.resources.settings.force_hold
|
let hold =
|
||||||
|| (digital_inputs[1] && c.resources.settings.allow_hold);
|
settings.force_hold || (digital_inputs[1] && settings.allow_hold);
|
||||||
|
|
||||||
for channel in 0..adc_samples.len() {
|
flatten_closures!(with_buffer, adc0, adc1, dac0, dac1, {
|
||||||
for sample in 0..adc_samples[0].len() {
|
let adc_samples = [adc0, adc1];
|
||||||
let mut y = f32::from(adc_samples[channel][sample] as i16);
|
let dac_samples = [dac0, dac1];
|
||||||
for i in 0..c.resources.iir_state[channel].len() {
|
|
||||||
y = c.resources.settings.iir_ch[channel][i].update(
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
&mut c.resources.iir_state[channel][i],
|
fence(Ordering::SeqCst);
|
||||||
y,
|
|
||||||
hold,
|
for channel in 0..adc_samples.len() {
|
||||||
);
|
adc_samples[channel]
|
||||||
}
|
.iter()
|
||||||
// Note(unsafe): The filter limits ensure that the value is in range.
|
.zip(dac_samples[channel].iter_mut())
|
||||||
// The truncation introduces 1/2 LSB distortion.
|
.map(|(ai, di)| {
|
||||||
let y = unsafe { y.to_int_unchecked::<i16>() };
|
let x = f32::from(*ai as i16);
|
||||||
// Convert to DAC code
|
let y = settings.iir_ch[channel]
|
||||||
dac_samples[channel][sample] = DacCode::from(y).0;
|
.iter()
|
||||||
|
.zip(iir_state[channel].iter_mut())
|
||||||
|
.fold(x, |yi, (ch, state)| {
|
||||||
|
ch.update(state, yi, hold)
|
||||||
|
});
|
||||||
|
// Note(unsafe): The filter limits must ensure that the value is in range.
|
||||||
|
// The truncation introduces 1/2 LSB distortion.
|
||||||
|
let y: i16 = unsafe { y.to_int_unchecked() };
|
||||||
|
// Convert to DAC code
|
||||||
|
*di = DacCode::from(y).0;
|
||||||
|
})
|
||||||
|
.last();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
c.resources.telemetry.adcs =
|
telemetry.adcs =
|
||||||
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.dacs =
|
telemetry.dacs =
|
||||||
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
|
|
||||||
c.resources.telemetry.digital_inputs = digital_inputs;
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[network], spawn=[settings_update])]
|
#[idle(resources=[network], spawn=[settings_update])]
|
||||||
@ -224,22 +238,22 @@ const APP: () = {
|
|||||||
|
|
||||||
#[task(binds = SPI2, priority = 3)]
|
#[task(binds = SPI2, priority = 3)]
|
||||||
fn spi2(_: spi2::Context) {
|
fn spi2(_: spi2::Context) {
|
||||||
panic!("ADC0 input overrun");
|
panic!("ADC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI3, priority = 3)]
|
#[task(binds = SPI3, priority = 3)]
|
||||||
fn spi3(_: spi3::Context) {
|
fn spi3(_: spi3::Context) {
|
||||||
panic!("ADC1 input overrun");
|
panic!("ADC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI4, priority = 3)]
|
#[task(binds = SPI4, priority = 3)]
|
||||||
fn spi4(_: spi4::Context) {
|
fn spi4(_: spi4::Context) {
|
||||||
panic!("DAC0 output error");
|
panic!("DAC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI5, priority = 3)]
|
#[task(binds = SPI5, priority = 3)]
|
||||||
fn spi5(_: spi5::Context) {
|
fn spi5(_: spi5::Context) {
|
||||||
panic!("DAC1 output error");
|
panic!("DAC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
|
use core::sync::atomic::{fence, Ordering};
|
||||||
|
|
||||||
use embedded_hal::digital::v2::InputPin;
|
use embedded_hal::digital::v2::InputPin;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
|
use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL};
|
||||||
|
|
||||||
use stabilizer::net;
|
use stabilizer::{flatten_closures, hardware, net};
|
||||||
|
|
||||||
use stabilizer::hardware::{
|
use hardware::{
|
||||||
design_parameters, setup, Adc0Input, Adc1Input, AdcCode, AfeGain,
|
design_parameters, setup, Adc0Input, Adc1Input, AdcCode, AfeGain,
|
||||||
Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1,
|
Dac0Output, Dac1Output, DacCode, DigitalInput0, DigitalInput1,
|
||||||
InputStamper, SystemTimer, AFE0, AFE1,
|
InputStamper, SystemTimer, AFE0, AFE1,
|
||||||
@ -159,26 +161,22 @@ const APP: () = {
|
|||||||
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[adcs, dacs, lockin, timestamper, pll, settings, telemetry], priority=2)]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[link_section = ".itcm.process"]
|
#[link_section = ".itcm.process"]
|
||||||
fn process(c: process::Context) {
|
fn process(mut c: process::Context) {
|
||||||
let adc_samples = [
|
let process::Resources {
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
adcs: (ref mut adc0, ref mut adc1),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
dacs: (ref mut dac0, ref mut dac1),
|
||||||
];
|
ref settings,
|
||||||
|
ref mut telemetry,
|
||||||
let mut dac_samples = [
|
ref mut lockin,
|
||||||
c.resources.dacs.0.acquire_buffer(),
|
ref mut pll,
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
ref mut timestamper,
|
||||||
];
|
} = c.resources;
|
||||||
|
|
||||||
let lockin = c.resources.lockin;
|
|
||||||
let settings = c.resources.settings;
|
|
||||||
|
|
||||||
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
let (reference_phase, reference_frequency) = match settings.lockin_mode
|
||||||
{
|
{
|
||||||
LockinMode::External => {
|
LockinMode::External => {
|
||||||
let timestamp =
|
let timestamp = timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
|
||||||
c.resources.timestamper.latest_timestamp().unwrap_or(None); // Ignore data from timer capture overflows.
|
let (pll_phase, pll_frequency) = pll.update(
|
||||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
|
||||||
timestamp.map(|t| t as i32),
|
timestamp.map(|t| t as i32),
|
||||||
settings.pll_tc[0],
|
settings.pll_tc[0],
|
||||||
settings.pll_tc[1],
|
settings.pll_tc[1],
|
||||||
@ -205,45 +203,55 @@ const APP: () = {
|
|||||||
reference_phase.wrapping_mul(settings.lockin_harmonic),
|
reference_phase.wrapping_mul(settings.lockin_harmonic),
|
||||||
);
|
);
|
||||||
|
|
||||||
let output: Complex<i32> = adc_samples[0]
|
flatten_closures!(with_buffer, adc0, adc1, dac0, dac1, {
|
||||||
.iter()
|
let adc_samples = [adc0, adc1];
|
||||||
// Zip in the LO phase.
|
let mut dac_samples = [dac0, dac1];
|
||||||
.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, settings.lockin_tc)
|
|
||||||
})
|
|
||||||
// Decimate
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
* 2; // Full scale assuming the 2f component is gone.
|
|
||||||
|
|
||||||
// Convert to DAC data.
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
for (channel, samples) in dac_samples.iter_mut().enumerate() {
|
fence(Ordering::SeqCst);
|
||||||
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 = DacCode::from(value as i16).0;
|
let output: Complex<i32> = adc_samples[0]
|
||||||
|
.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, settings.lockin_tc)
|
||||||
|
})
|
||||||
|
// Decimate
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
* 2; // Full scale assuming the 2f component is gone.
|
||||||
|
|
||||||
|
// Convert to DAC data.
|
||||||
|
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 = DacCode::from(value as i16).0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// Update telemetry measurements.
|
||||||
|
telemetry.adcs =
|
||||||
|
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
||||||
|
|
||||||
// Update telemetry measurements.
|
telemetry.dacs =
|
||||||
c.resources.telemetry.adcs =
|
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
||||||
[AdcCode(adc_samples[0][0]), AdcCode(adc_samples[1][0])];
|
|
||||||
|
|
||||||
c.resources.telemetry.dacs =
|
// Preserve instruction and data ordering w.r.t. DMA flag access.
|
||||||
[DacCode(dac_samples[0][0]), DacCode(dac_samples[1][0])];
|
fence(Ordering::SeqCst);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle(resources=[network], spawn=[settings_update])]
|
#[idle(resources=[network], spawn=[settings_update])]
|
||||||
@ -305,22 +313,22 @@ const APP: () = {
|
|||||||
|
|
||||||
#[task(binds = SPI2, priority = 3)]
|
#[task(binds = SPI2, priority = 3)]
|
||||||
fn spi2(_: spi2::Context) {
|
fn spi2(_: spi2::Context) {
|
||||||
panic!("ADC0 input overrun");
|
panic!("ADC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI3, priority = 3)]
|
#[task(binds = SPI3, priority = 3)]
|
||||||
fn spi3(_: spi3::Context) {
|
fn spi3(_: spi3::Context) {
|
||||||
panic!("ADC1 input overrun");
|
panic!("ADC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI4, priority = 3)]
|
#[task(binds = SPI4, priority = 3)]
|
||||||
fn spi4(_: spi4::Context) {
|
fn spi4(_: spi4::Context) {
|
||||||
panic!("DAC0 output error");
|
panic!("DAC0 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = SPI5, priority = 3)]
|
#[task(binds = SPI5, priority = 3)]
|
||||||
fn spi5(_: spi5::Context) {
|
fn spi5(_: spi5::Context) {
|
||||||
panic!("DAC1 output error");
|
panic!("DAC1 SPI error");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -29,15 +29,9 @@
|
|||||||
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
||||||
///! and the ADC samples are available for processing.
|
///! and the ADC samples are available for processing.
|
||||||
///!
|
///!
|
||||||
///! The SPI peripheral internally has an 8- or 16-byte TX and RX FIFO, which corresponds to a 4- or
|
///! After a complete transfer of a batch of samples, the inactive buffer is available to the
|
||||||
///! 8-sample buffer for incoming ADC samples. During the handling of the DMA transfer completion,
|
///! user for processing. The processing must complete before the DMA transfer of the next batch
|
||||||
///! there is a small window where buffers are swapped over where it's possible that a sample could
|
///! completes.
|
||||||
///! be lost. In order to avoid this, the SPI RX FIFO is effectively used as a "sample overflow"
|
|
||||||
///! region and can buffer a number of samples until the next DMA transfer is configured. If a DMA
|
|
||||||
///! transfer is still not set in time, the SPI peripheral will generate an input-overrun interrupt.
|
|
||||||
///! This interrupt then serves as a means of detecting if samples have been lost, which will occur
|
|
||||||
///! whenever data processing takes longer than the collection period.
|
|
||||||
///!
|
|
||||||
///!
|
///!
|
||||||
///! ## Starting Data Collection
|
///! ## Starting Data Collection
|
||||||
///!
|
///!
|
||||||
@ -68,26 +62,26 @@
|
|||||||
///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
|
///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
|
||||||
///! value of 0 and ADC1's comparison to a counter value of 1.
|
///! value of 0 and ADC1's comparison to a counter value of 1.
|
||||||
///!
|
///!
|
||||||
///! In this implementation, single buffer mode DMA transfers are used because the SPI RX FIFO can
|
///! In this implementation, double buffer mode DMA transfers are used because the SPI RX FIFOs
|
||||||
///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because
|
///! have finite depth, FIFO access is slower than AXISRAM access, and because the single
|
||||||
///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless
|
///! buffer mode DMA disable/enable and buffer update sequence is slow.
|
||||||
///! double-buffered mode offers less overhead due to the DMA disable/enable procedure).
|
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::{SampleBuffer, 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},
|
||||||
traits::TargetAddress,
|
traits::TargetAddress,
|
||||||
MemoryToPeripheral, PeripheralToMemory, Transfer,
|
DMAError, MemoryToPeripheral, PeripheralToMemory, Transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A type representing an ADC sample.
|
/// A type representing an ADC sample.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct AdcCode(pub u16);
|
pub struct AdcCode(pub u16);
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<f32> for AdcCode {
|
impl Into<f32> for AdcCode {
|
||||||
/// Convert raw ADC codes to/from voltage levels.
|
/// Convert raw ADC codes to/from voltage levels.
|
||||||
///
|
///
|
||||||
@ -119,8 +113,7 @@ static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
|
|||||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||||
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
||||||
#[link_section = ".axisram.buffers"]
|
#[link_section = ".axisram.buffers"]
|
||||||
static mut ADC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] =
|
static mut ADC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
|
||||||
|
|
||||||
macro_rules! adc_input {
|
macro_rules! adc_input {
|
||||||
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
|
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
|
||||||
@ -192,12 +185,11 @@ macro_rules! adc_input {
|
|||||||
|
|
||||||
/// Represents data associated with ADC.
|
/// Represents data associated with ADC.
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
transfer: Transfer<
|
transfer: Transfer<
|
||||||
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
||||||
PeripheralToMemory,
|
PeripheralToMemory,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut SampleBuffer,
|
||||||
hal::dma::DBTransfer,
|
hal::dma::DBTransfer,
|
||||||
>,
|
>,
|
||||||
trigger_transfer: Transfer<
|
trigger_transfer: Transfer<
|
||||||
@ -316,6 +308,7 @@ macro_rules! adc_input {
|
|||||||
// data stream is used to trigger a transfer completion interrupt.
|
// data stream is used to trigger a transfer completion interrupt.
|
||||||
let data_config = DmaConfig::default()
|
let data_config = DmaConfig::default()
|
||||||
.memory_increment(true)
|
.memory_increment(true)
|
||||||
|
.double_buffer(true)
|
||||||
.transfer_complete_interrupt($index == 1)
|
.transfer_complete_interrupt($index == 1)
|
||||||
.priority(Priority::VeryHigh);
|
.priority(Priority::VeryHigh);
|
||||||
|
|
||||||
@ -333,17 +326,14 @@ macro_rules! adc_input {
|
|||||||
Transfer::init(
|
Transfer::init(
|
||||||
data_stream,
|
data_stream,
|
||||||
spi,
|
spi,
|
||||||
// Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral.
|
// Note(unsafe): The ADC_BUF[$index] is "owned" by this peripheral.
|
||||||
// It shall not be used anywhere else in the module.
|
// It shall not be used anywhere else in the module.
|
||||||
unsafe { &mut ADC_BUF[$index][0] },
|
unsafe { &mut ADC_BUF[$index][0] },
|
||||||
None,
|
unsafe { Some(&mut ADC_BUF[$index][1]) },
|
||||||
data_config,
|
data_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It
|
|
||||||
// shall not be used anywhere else in the module.
|
|
||||||
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
|
||||||
transfer: data_transfer,
|
transfer: data_transfer,
|
||||||
trigger_transfer,
|
trigger_transfer,
|
||||||
clear_transfer,
|
clear_transfer,
|
||||||
@ -364,27 +354,17 @@ macro_rules! adc_input {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with ADC samples.
|
/// Wait for the transfer of the currently active buffer to complete,
|
||||||
|
/// then call a function on the now inactive buffer and acknowledge the
|
||||||
|
/// transfer complete flag.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// NOTE(unsafe): Memory safety and access ordering is not guaranteed
|
||||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
/// (see the HAL DMA docs).
|
||||||
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
|
||||||
// Wait for the transfer to fully complete before continuing. Note: If a device
|
where
|
||||||
// hangs up, check that this conditional is passing correctly, as there is no
|
F: FnOnce(&mut SampleBuffer) -> R,
|
||||||
// time-out checks here in the interest of execution speed.
|
{
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
unsafe { self.transfer.next_dbm_transfer_with(|buf, _current| f(buf)) }
|
||||||
|
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
|
||||||
|
|
||||||
// Start the next transfer.
|
|
||||||
self.transfer.clear_interrupts();
|
|
||||||
let (prev_buffer, _, _) =
|
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
|
||||||
|
|
||||||
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
|
||||||
self.next_buffer.replace(prev_buffer);
|
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,11 +233,6 @@ pub fn setup(
|
|||||||
let dma_streams =
|
let dma_streams =
|
||||||
hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
|
hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1);
|
||||||
|
|
||||||
// Early, before the DMA1 peripherals (#272)
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
|
||||||
let dma2_streams =
|
|
||||||
hal::dma::dma::StreamsTuple::new(device.DMA2, ccdr.peripheral.DMA2);
|
|
||||||
|
|
||||||
// Configure timer 2 to trigger conversions for the ADC
|
// Configure timer 2 to trigger conversions for the ADC
|
||||||
let mut sampling_timer = {
|
let mut sampling_timer = {
|
||||||
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
||||||
@ -946,7 +941,6 @@ pub fn setup(
|
|||||||
|
|
||||||
pounder::timestamp::Timestamper::new(
|
pounder::timestamp::Timestamper::new(
|
||||||
timestamp_timer,
|
timestamp_timer,
|
||||||
dma2_streams.0,
|
|
||||||
tim8_channels.ch1,
|
tim8_channels.ch1,
|
||||||
&mut sampling_timer,
|
&mut sampling_timer,
|
||||||
etr_pin,
|
etr_pin,
|
||||||
|
@ -52,13 +52,13 @@
|
|||||||
///! served promptly after the transfer completes.
|
///! served promptly after the transfer completes.
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::design_parameters::SAMPLE_BUFFER_SIZE;
|
use super::design_parameters::{SampleBuffer, 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,
|
||||||
MemoryToPeripheral, Transfer,
|
DMAError, MemoryToPeripheral, Transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
|
// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
|
||||||
@ -66,14 +66,14 @@ use hal::dma::{
|
|||||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||||
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
// startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`.
|
||||||
#[link_section = ".axisram.buffers"]
|
#[link_section = ".axisram.buffers"]
|
||||||
static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] =
|
static mut DAC_BUF: [[SampleBuffer; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 3]; 2];
|
|
||||||
|
|
||||||
/// Custom type for referencing DAC output codes.
|
/// Custom type for referencing DAC output codes.
|
||||||
/// The internal integer is the raw code written to the DAC output register.
|
/// The internal integer is the raw code written to the DAC output register.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct DacCode(pub u16);
|
pub struct DacCode(pub u16);
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<f32> for DacCode {
|
impl Into<f32> for DacCode {
|
||||||
fn into(self) -> f32 {
|
fn into(self) -> f32 {
|
||||||
// The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
|
// The DAC output range in bipolar mode (including the external output op-amp) is +/- 4.096
|
||||||
@ -105,7 +105,7 @@ macro_rules! dac_output {
|
|||||||
_channel: timers::tim2::$trigger_channel,
|
_channel: timers::tim2::$trigger_channel,
|
||||||
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { _channel, spi }
|
Self { spi, _channel }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the SPI and begin operating in a DMA-driven transfer mode.
|
/// Start the SPI and begin operating in a DMA-driven transfer mode.
|
||||||
@ -137,13 +137,12 @@ macro_rules! dac_output {
|
|||||||
|
|
||||||
/// Represents data associated with DAC.
|
/// Represents data associated with DAC.
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
|
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
|
||||||
transfer: Transfer<
|
transfer: Transfer<
|
||||||
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
$spi,
|
$spi,
|
||||||
MemoryToPeripheral,
|
MemoryToPeripheral,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut SampleBuffer,
|
||||||
hal::dma::DBTransfer,
|
hal::dma::DBTransfer,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
@ -198,33 +197,26 @@ macro_rules! dac_output {
|
|||||||
trigger_config,
|
trigger_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self { transfer }
|
||||||
transfer,
|
|
||||||
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
|
||||||
next_buffer: unsafe { Some(&mut DAC_BUF[$index][2]) },
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
self.transfer.start(|spi| spi.start_dma());
|
self.transfer.start(|spi| spi.start_dma());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquire the next output buffer to populate it with DAC codes.
|
/// Wait for the transfer of the currently active buffer to complete,
|
||||||
pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] {
|
/// then call a function on the now inactive buffer and acknowledge the
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
/// transfer complete flag.
|
||||||
// there is no time-out checks here in the interest of execution speed.
|
///
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
/// NOTE(unsafe): Memory safety and access ordering is not guaranteed
|
||||||
|
/// (see the HAL DMA docs).
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
|
||||||
|
where
|
||||||
// Start the next transfer.
|
F: FnOnce(&mut SampleBuffer) -> R,
|
||||||
let (prev_buffer, _, _) =
|
{
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
unsafe {
|
||||||
|
self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
|
||||||
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
}
|
||||||
self.next_buffer.replace(prev_buffer);
|
|
||||||
|
|
||||||
self.next_buffer.as_mut().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -50,5 +50,7 @@ pub const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2;
|
|||||||
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
pub const SAMPLE_BUFFER_SIZE_LOG2: u8 = 3;
|
||||||
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2;
|
||||||
|
|
||||||
|
pub type SampleBuffer = [u16; SAMPLE_BUFFER_SIZE];
|
||||||
|
|
||||||
// The MQTT broker IPv4 address
|
// The MQTT broker IPv4 address
|
||||||
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
|
pub const MQTT_BROKER: [u8; 4] = [10, 34, 16, 10];
|
||||||
|
@ -52,9 +52,11 @@
|
|||||||
///! compile-time-known register update sequence needed for the application, the serialization
|
///! compile-time-known register update sequence needed for the application, the serialization
|
||||||
///! process can be done once and then register values can be written into a pre-computed serialized
|
///! process can be done once and then register values can be written into a pre-computed serialized
|
||||||
///! buffer to avoid the software overhead of much of the serialization process.
|
///! buffer to avoid the software overhead of much of the serialization process.
|
||||||
|
use log::warn;
|
||||||
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use super::{hrtimer::HighResTimerE, QspiInterface};
|
use super::{hrtimer::HighResTimerE, QspiInterface};
|
||||||
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
||||||
use stm32h7xx_hal as hal;
|
|
||||||
|
|
||||||
/// The DDS profile update stream.
|
/// The DDS profile update stream.
|
||||||
pub struct DdsOutput {
|
pub struct DdsOutput {
|
||||||
|
@ -13,50 +13,24 @@
|
|||||||
///! Once the timer is configured, an input capture is configured to record the timer count
|
///! Once the timer is configured, an input capture is configured to record the timer count
|
||||||
///! register. The input capture is configured to utilize an internal trigger for the input capture.
|
///! register. The input capture is configured to utilize an internal trigger for the input capture.
|
||||||
///! The internal trigger is selected such that when a sample is generated on ADC0, the input
|
///! The internal trigger is selected such that when a sample is generated on ADC0, the input
|
||||||
///! capture is simultaneously triggered. This results in the input capture triggering identically
|
///! capture is simultaneously triggered. That trigger is prescaled (its rate is divided) by the
|
||||||
///! to when the ADC samples the input.
|
///! batch size. This results in the input capture triggering identically to when the ADC samples
|
||||||
///!
|
///! the last sample of the batch. That sample is then available for processing by the user.
|
||||||
///! Once the input capture is properly configured, a DMA transfer is configured to collect all of
|
use crate::hardware::{design_parameters, timers};
|
||||||
///! timestamps. The DMA transfer collects 1 timestamp for each ADC sample collected. In order to
|
use core::convert::TryFrom;
|
||||||
///! avoid potentially losing a timestamp for a sample, the DMA transfer operates in double-buffer
|
|
||||||
///! mode. As soon as the DMA transfer completes, the hardware automatically swaps over to a second
|
|
||||||
///! buffer to continue capturing. This alleviates timing sensitivities of the DMA transfer
|
|
||||||
///! schedule.
|
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer};
|
|
||||||
|
|
||||||
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
|
|
||||||
// region. Note that AXISRAM is not initialized on boot, so their initial contents are undefined.
|
|
||||||
#[link_section = ".axisram.buffers"]
|
|
||||||
static mut BUF: [[u16; SAMPLE_BUFFER_SIZE]; 3] = [[0; SAMPLE_BUFFER_SIZE]; 3];
|
|
||||||
|
|
||||||
/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
|
/// Software unit to timestamp stabilizer ADC samples using an external pounder reference clock.
|
||||||
pub struct Timestamper {
|
pub struct Timestamper {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
timer: timers::PounderTimestampTimer,
|
timer: timers::PounderTimestampTimer,
|
||||||
transfer: Transfer<
|
capture_channel: timers::tim8::Channel1InputCapture,
|
||||||
hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
|
||||||
timers::tim8::Channel1InputCapture,
|
|
||||||
PeripheralToMemory,
|
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
|
||||||
hal::dma::DBTransfer,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timestamper {
|
impl Timestamper {
|
||||||
/// Construct the pounder sample timestamper.
|
/// Construct the pounder sample timestamper.
|
||||||
///
|
///
|
||||||
/// # Note
|
|
||||||
/// The DMA is immediately configured after instantiation. It will not collect any samples
|
|
||||||
/// until the sample timer begins to cause input capture triggers.
|
|
||||||
///
|
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `timestamp_timer` - The timer peripheral used for capturing timestamps from.
|
/// * `timestamp_timer` - The timer peripheral used for capturing timestamps from.
|
||||||
/// * `stream` - The DMA stream to use for collecting timestamps.
|
|
||||||
/// * `capture_channel` - The input capture channel for collecting timestamps.
|
/// * `capture_channel` - The input capture channel for collecting timestamps.
|
||||||
/// * `sampling_timer` - The stabilizer ADC sampling timer.
|
/// * `sampling_timer` - The stabilizer ADC sampling timer.
|
||||||
/// * `_clock_input` - The input pin for the external clock from Pounder.
|
/// * `_clock_input` - The input pin for the external clock from Pounder.
|
||||||
@ -65,18 +39,12 @@ impl Timestamper {
|
|||||||
/// The new pounder timestamper in an operational state.
|
/// The new pounder timestamper in an operational state.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut timestamp_timer: timers::PounderTimestampTimer,
|
mut timestamp_timer: timers::PounderTimestampTimer,
|
||||||
stream: hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
|
||||||
capture_channel: timers::tim8::Channel1,
|
capture_channel: timers::tim8::Channel1,
|
||||||
sampling_timer: &mut timers::SamplingTimer,
|
sampling_timer: &mut timers::SamplingTimer,
|
||||||
_clock_input: hal::gpio::gpioa::PA0<
|
_clock_input: hal::gpio::gpioa::PA0<
|
||||||
hal::gpio::Alternate<hal::gpio::AF3>,
|
hal::gpio::Alternate<hal::gpio::AF3>,
|
||||||
>,
|
>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let config = DmaConfig::default()
|
|
||||||
.memory_increment(true)
|
|
||||||
.circular_buffer(true)
|
|
||||||
.double_buffer(true);
|
|
||||||
|
|
||||||
// The sampling timer should generate a trigger output when CH1 comparison occurs.
|
// The sampling timer should generate a trigger output when CH1 comparison occurs.
|
||||||
sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
|
sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse);
|
||||||
|
|
||||||
@ -85,64 +53,39 @@ impl Timestamper {
|
|||||||
timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1);
|
timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1);
|
||||||
|
|
||||||
// The capture channel should capture whenever the trigger input occurs.
|
// The capture channel should capture whenever the trigger input occurs.
|
||||||
let input_capture = capture_channel
|
let mut input_capture = capture_channel
|
||||||
.into_input_capture(timers::tim8::CaptureSource1::TRC);
|
.into_input_capture(timers::tim8::CaptureSource1::TRC);
|
||||||
input_capture.listen_dma();
|
|
||||||
|
|
||||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
// Capture at the batch period.
|
||||||
let data_transfer: Transfer<_, _, PeripheralToMemory, _, _> =
|
input_capture.configure_prescaler(
|
||||||
Transfer::init(
|
timers::Prescaler::try_from(
|
||||||
stream,
|
design_parameters::SAMPLE_BUFFER_SIZE_LOG2,
|
||||||
input_capture,
|
)
|
||||||
// Note(unsafe): BUF[0] and BUF[1] are "owned" by this peripheral.
|
.unwrap(),
|
||||||
// They shall not be used anywhere else in the module.
|
);
|
||||||
unsafe { &mut BUF[0] },
|
|
||||||
unsafe { Some(&mut BUF[1]) },
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
timer: timestamp_timer,
|
timer: timestamp_timer,
|
||||||
transfer: data_transfer,
|
capture_channel: input_capture,
|
||||||
|
|
||||||
// Note(unsafe): BUF[2] is "owned" by this peripheral. It shall not be used anywhere
|
|
||||||
// else in the module.
|
|
||||||
next_buffer: unsafe { Some(&mut BUF[2]) },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the DMA transfer for collecting timestamps.
|
/// Start collecting timestamps.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
self.transfer
|
self.capture_channel.enable();
|
||||||
.start(|capture_channel| capture_channel.enable());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the period of the underlying timestamp timer.
|
/// Update the period of the underlying timestamp timer.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn update_period(&mut self, period: u16) {
|
pub fn update_period(&mut self, period: u16) {
|
||||||
self.timer.set_period_ticks(period);
|
self.timer.set_period_ticks(period);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with timestamps.
|
/// Obtain a timestamp.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A reference to the underlying buffer that has been filled with timestamps.
|
/// A `Result` potentially indicating capture overflow and containing a `Option` of a captured
|
||||||
#[allow(dead_code)]
|
/// timestamp.
|
||||||
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
pub fn latest_timestamp(&mut self) -> Result<Option<u16>, Option<u16>> {
|
||||||
// Wait for the transfer to fully complete before continuing.
|
self.capture_channel.latest_capture()
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as there is
|
|
||||||
// no time-out checks here in the interest of execution speed.
|
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
|
||||||
|
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
|
||||||
|
|
||||||
// Start the next transfer.
|
|
||||||
let (prev_buffer, _, _) =
|
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
|
||||||
|
|
||||||
self.next_buffer.replace(prev_buffer); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
||||||
use super::hal;
|
use super::hal;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use hal::stm32::{
|
use hal::stm32::{
|
||||||
// TIM1 and TIM8 have identical registers.
|
// TIM1 and TIM8 have identical registers.
|
||||||
@ -34,6 +35,8 @@ pub enum TriggerSource {
|
|||||||
|
|
||||||
/// Prescalers for externally-supplied reference clocks.
|
/// Prescalers for externally-supplied reference clocks.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(TryFromPrimitive)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum Prescaler {
|
pub enum Prescaler {
|
||||||
Div1 = 0b00,
|
Div1 = 0b00,
|
||||||
Div2 = 0b01,
|
Div2 = 0b01,
|
||||||
@ -353,6 +356,21 @@ macro_rules! timer_channels {
|
|||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
|
regs.[< $ccmrx _input >]().modify(|_, w| w.[< ic $index f >]().bits(filter as u8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the input capture prescaler.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `psc` - Prescaler exponent.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn configure_prescaler(&mut self, prescaler: super::Prescaler) {
|
||||||
|
// 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() };
|
||||||
|
// Note(unsafe): Enum values are all valid.
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
|
regs.[< $ccmrx _input >]().modify(|_, w| unsafe {
|
||||||
|
w.[< ic $index psc >]().bits(prescaler 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
|
||||||
|
16
src/lib.rs
16
src/lib.rs
@ -1,8 +1,18 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
|
||||||
|
/// Macro to reduce rightward drift when calling the same closure-based API
|
||||||
|
/// on multiple structs simultaneously, e.g. when accessing DMA buffers.
|
||||||
|
/// This could be improved a bit using the tuple-based style from `mutex-trait`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! flatten_closures {
|
||||||
|
($fn:ident, $e:ident, $fun:block) => {
|
||||||
|
$e.$fn(|$e| $fun ).unwrap()
|
||||||
|
};
|
||||||
|
($fn:ident, $e:ident, $($es:ident),+, $fun:block) => {
|
||||||
|
$e.$fn(|$e| flatten_closures!($fn, $($es),*, $fun)).unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
///! Respones to settings updates are sent without quality-of-service guarantees, so there's no
|
///! Respones to settings updates are sent without quality-of-service guarantees, so there's no
|
||||||
///! guarantee that the requestee will be informed that settings have been applied.
|
///! guarantee that the requestee will be informed that settings have been applied.
|
||||||
use heapless::String;
|
use heapless::String;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
||||||
use crate::hardware::design_parameters::MQTT_BROKER;
|
use crate::hardware::design_parameters::MQTT_BROKER;
|
||||||
@ -102,7 +103,7 @@ where
|
|||||||
let path = match topic.strip_prefix(prefix) {
|
let path = match topic.strip_prefix(prefix) {
|
||||||
// For paths, we do not want to include the leading slash.
|
// For paths, we do not want to include the leading slash.
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
if path.len() > 0 {
|
if !path.is_empty() {
|
||||||
&path[1..]
|
&path[1..]
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
@ -116,9 +117,8 @@ where
|
|||||||
|
|
||||||
let message: SettingsResponse = settings
|
let message: SettingsResponse = settings
|
||||||
.string_set(path.split('/').peekable(), message)
|
.string_set(path.split('/').peekable(), message)
|
||||||
.and_then(|_| {
|
.map(|_| {
|
||||||
update = true;
|
update = true;
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user