Merge branch 'feature/digital-input-stamp' into feature/pounder-timestamping
This commit is contained in:
commit
37595405c3
|
@ -18,6 +18,9 @@ load
|
||||||
# tbreak cortex_m_rt::reset_handler
|
# tbreak cortex_m_rt::reset_handler
|
||||||
monitor reset halt
|
monitor reset halt
|
||||||
|
|
||||||
|
source ../../PyCortexMDebug/cmdebug/svd_gdb.py
|
||||||
|
svd_load ~/Downloads/STM32H743x.svd
|
||||||
|
|
||||||
# cycle counter delta tool, place two bkpts around the section
|
# cycle counter delta tool, place two bkpts around the section
|
||||||
set var $cc=0xe0001004
|
set var $cc=0xe0001004
|
||||||
define qq
|
define qq
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
|
use super::hal::time::MegaHertz;
|
||||||
|
|
||||||
/// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock
|
/// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock
|
||||||
/// may begin. This is used for performing the internal ADC conversion.
|
/// may begin. This is used for performing the internal ADC conversion.
|
||||||
pub const ADC_SETUP_TIME: f32 = 220e-9;
|
pub const ADC_SETUP_TIME: f32 = 220e-9;
|
||||||
|
|
||||||
/// The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
|
/// The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
|
||||||
pub const ADC_DAC_SCK_MHZ_MAX: u32 = 50;
|
pub const ADC_DAC_SCK_MAX: MegaHertz = MegaHertz(50);
|
||||||
|
|
||||||
/// The optimal counting frequency of the hardware timers used for timestamping and sampling.
|
/// The optimal counting frequency of the hardware timers used for timestamping and sampling.
|
||||||
pub const TIMER_FREQUENCY_MHZ: u32 = 100;
|
pub const TIMER_FREQUENCY: MegaHertz = MegaHertz(100);
|
||||||
|
|
||||||
|
/// The QSPI frequency for communicating with the pounder DDS.
|
||||||
|
pub const POUNDER_QSPI_FREQUENCY: MegaHertz = MegaHertz(40);
|
||||||
|
|
||||||
|
/// The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS.
|
||||||
|
// Pounder Profile writes are always 16 bytes, with 2 cycles required per byte, coming out to a
|
||||||
|
// total of 32 QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an offset
|
||||||
|
// of 800nS. We use 900ns to be safe.
|
||||||
|
pub const POUNDER_IO_UPDATE_DELAY: f32 = 900_e-9;
|
||||||
|
|
||||||
|
/// The duration to assert IO_Update for the pounder DDS.
|
||||||
|
// IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile write. With pounder
|
||||||
|
// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 400MHz), this corresponds to
|
||||||
|
// 40ns. To accomodate rounding errors, we use 50ns instead.
|
||||||
|
pub const POUNDER_IO_UPDATE_DURATION: f32 = 50_e-9;
|
||||||
|
|
||||||
/// The DDS reference clock frequency in MHz.
|
/// The DDS reference clock frequency in MHz.
|
||||||
pub const DDS_REF_CLK_MHZ: u32 = 100;
|
pub const DDS_REF_CLK_MHZ: u32 = 100;
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
///! This module provides a means of timestamping the rising edges of an external reference clock on
|
///! This module provides a means of timestamping the rising edges of an external reference clock on
|
||||||
///! the DI0 with a timer value from TIM5.
|
///! the DI0 with a timer value from TIM5.
|
||||||
///!
|
///!
|
||||||
///! This module only supports input clocks on DI0 and may or may not utilize DMA to collect
|
|
||||||
///! timestamps.
|
|
||||||
///!
|
|
||||||
///! # Design
|
///! # Design
|
||||||
///! An input capture channel is configured on DI0 and fed into TIM5's capture channel 4. TIM5 is
|
///! An input capture channel is configured on DI0 and fed into TIM5's capture channel 4. TIM5 is
|
||||||
///! then run in a free-running mode with a configured tick rate (PSC) and maximum count value
|
///! then run in a free-running mode with a configured tick rate (PSC) and maximum count value
|
||||||
|
@ -13,12 +10,6 @@
|
||||||
///! recorded as a timestamp. This timestamp can be either directly read from the timer channel or
|
///! recorded as a timestamp. This timestamp can be either directly read from the timer channel or
|
||||||
///! can be collected asynchronously via DMA collection.
|
///! can be collected asynchronously via DMA collection.
|
||||||
///!
|
///!
|
||||||
///! When DMA is used for timestamp collection, a DMA transfer is configured to collect as many
|
|
||||||
///! timestamps as there are samples, but it is intended that this DMA transfer should never
|
|
||||||
///! complete. Instead, when all samples are collected, the module pauses the DMA transfer and
|
|
||||||
///! checks to see how many timestamps were collected. These collected timestamps are then returned
|
|
||||||
///! for further processing.
|
|
||||||
///!
|
|
||||||
///! To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is
|
///! To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is
|
||||||
///! continually checked. Any over-capture event (which indicates an overwritten timestamp) then
|
///! continually checked. Any over-capture event (which indicates an overwritten timestamp) then
|
||||||
///! triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted.
|
///! triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted.
|
||||||
|
@ -27,35 +18,53 @@
|
||||||
///! It appears that DMA transfers can take a significant amount of time to disable (400ns) if they
|
///! It appears that DMA transfers can take a significant amount of time to disable (400ns) if they
|
||||||
///! are being prematurely stopped (such is the case here). As such, for a sample batch size of 1,
|
///! are being prematurely stopped (such is the case here). As such, for a sample batch size of 1,
|
||||||
///! this can take up a significant amount of the total available processing time for the samples.
|
///! this can take up a significant amount of the total available processing time for the samples.
|
||||||
///! To avoid this, the module does not use DMA when the sample batch size is one. Instead, the
|
///! This module checks for any captured timestamps from the timer capture channel manually. In
|
||||||
///! module manually checks for any captured timestamps from the timer capture channel manually. In
|
///! this mode, the maximum input clock frequency supported is dependant on the sampling rate and
|
||||||
///! this mode, the maximum input clock frequency supported is equal to the configured sample rate.
|
///! batch size.
|
||||||
///!
|
///!
|
||||||
///! There is a small window while the DMA buffers are swapped where a timestamp could potentially
|
///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If
|
||||||
///! be lost. To prevent this, the `acuire_buffer()` method should not be pre-empted. Any lost
|
///! timestamping is desired in DI1, a separate timer + capture channel will be necessary.
|
||||||
///! timestamp will trigger an over-capture interrupt.
|
use super::{hal, timers, ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE};
|
||||||
use super::{
|
|
||||||
hal, timers, DmaConfig, PeripheralToMemory, Transfer, SAMPLE_BUFFER_SIZE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The DMA buffers must exist in a location where DMA can access. By default, RAM uses DTCM, which
|
/// Calculate the period of the digital input timestampe timer.
|
||||||
// is off-limits to the normal DMA peripheral. Instead, we use AXISRAM.
|
///
|
||||||
#[link_section = ".axisram.buffers"]
|
/// # Note
|
||||||
static mut BUF: [[u32; SAMPLE_BUFFER_SIZE]; 2] = [[0; SAMPLE_BUFFER_SIZE]; 2];
|
/// The period returned will be 1 less than the required period in timer ticks. The value returned
|
||||||
|
/// can be immediately programmed into a hardware timer period register.
|
||||||
|
///
|
||||||
|
/// The period is calcualted to be some power-of-two multiple of the batch size, such that N batches
|
||||||
|
/// will occur between each timestamp timer overflow.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A 32-bit value that can be programmed into a hardware timer period register.
|
||||||
|
pub fn calculate_timestamp_timer_period() -> u32 {
|
||||||
|
// Calculate how long a single batch requires in timer ticks.
|
||||||
|
let batch_duration_ticks: u64 =
|
||||||
|
SAMPLE_BUFFER_SIZE as u64 * ADC_SAMPLE_TICKS as u64;
|
||||||
|
|
||||||
|
// Calculate the largest power-of-two that is less than or equal to
|
||||||
|
// `batches_per_overflow`. This is completed by eliminating the least significant
|
||||||
|
// bits of the value until only the msb remains, which is always a power of two.
|
||||||
|
let batches_per_overflow: u64 =
|
||||||
|
(1u64 + u32::MAX as u64) / batch_duration_ticks;
|
||||||
|
let mut j = batches_per_overflow;
|
||||||
|
while (j & (j - 1)) != 0 {
|
||||||
|
j = j & (j - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the number of batches per timestamp overflow is calculated, we can figure out the final
|
||||||
|
// period of the timestamp timer. The period is always 1 larger than the value configured in the
|
||||||
|
// register.
|
||||||
|
let period: u64 = batch_duration_ticks * j - 1u64;
|
||||||
|
assert!(period <= u32::MAX as u64);
|
||||||
|
|
||||||
|
period as u32
|
||||||
|
}
|
||||||
|
|
||||||
/// The timestamper for DI0 reference clock inputs.
|
/// The timestamper for DI0 reference clock inputs.
|
||||||
pub struct InputStamper {
|
pub struct InputStamper {
|
||||||
_di0_trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<hal::gpio::AF2>>,
|
_di0_trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<hal::gpio::AF2>>,
|
||||||
next_buffer: Option<&'static mut [u32; SAMPLE_BUFFER_SIZE]>,
|
capture_channel: timers::tim5::Channel4InputCapture,
|
||||||
transfer: Option<
|
|
||||||
Transfer<
|
|
||||||
hal::dma::dma::Stream6<hal::stm32::DMA1>,
|
|
||||||
timers::tim5::Channel4InputCapture,
|
|
||||||
PeripheralToMemory,
|
|
||||||
&'static mut [u32; SAMPLE_BUFFER_SIZE],
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
capture_channel: Option<timers::tim5::Channel4InputCapture>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputStamper {
|
impl InputStamper {
|
||||||
|
@ -63,100 +72,38 @@ impl InputStamper {
|
||||||
///
|
///
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `trigger` - The capture trigger input pin.
|
/// * `trigger` - The capture trigger input pin.
|
||||||
/// * `stream` - The DMA stream to use for collecting timestamps.
|
|
||||||
/// * `timer_channel - The timer channel used for capturing timestamps.
|
/// * `timer_channel - The timer channel used for capturing timestamps.
|
||||||
/// * `batch_size` - The number of samples collected per processing batch.
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<hal::gpio::AF2>>,
|
trigger: hal::gpio::gpioa::PA3<hal::gpio::Alternate<hal::gpio::AF2>>,
|
||||||
stream: hal::dma::dma::Stream6<hal::stm32::DMA1>,
|
|
||||||
timer_channel: timers::tim5::Channel4,
|
timer_channel: timers::tim5::Channel4,
|
||||||
batch_size: usize,
|
|
||||||
) -> 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 input_capture =
|
||||||
timer_channel.to_input_capture(timers::CaptureTrigger::Input24);
|
timer_channel.into_input_capture(timers::CaptureTrigger::Input24);
|
||||||
|
|
||||||
// For small batch sizes, the overhead of DMA can become burdensome to the point where
|
|
||||||
// timing is not met. The DMA requires 500ns overhead, whereas a direct register read only
|
|
||||||
// requires ~80ns. When batches of 2-or-greater are used, use a DMA-based approach.
|
|
||||||
let (transfer, input_capture) = if batch_size >= 2 {
|
|
||||||
input_capture.listen_dma();
|
|
||||||
|
|
||||||
// Set up the DMA transfer.
|
|
||||||
let dma_config = DmaConfig::default().memory_increment(true);
|
|
||||||
|
|
||||||
let timestamp_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
|
||||||
Transfer::init(
|
|
||||||
stream,
|
|
||||||
input_capture,
|
|
||||||
unsafe { &mut BUF[0] },
|
|
||||||
None,
|
|
||||||
dma_config,
|
|
||||||
);
|
|
||||||
(Some(timestamp_transfer), None)
|
|
||||||
} else {
|
|
||||||
(None, Some(input_capture))
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
next_buffer: unsafe { Some(&mut BUF[1]) },
|
|
||||||
transfer,
|
|
||||||
capture_channel: input_capture,
|
capture_channel: input_capture,
|
||||||
_di0_trigger: trigger,
|
_di0_trigger: trigger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start capture timestamps on DI0.
|
/// Start to capture timestamps on DI0.
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
if let Some(transfer) = &mut self.transfer {
|
self.capture_channel.enable();
|
||||||
transfer.start(|capture_channel| {
|
|
||||||
capture_channel.enable();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.capture_channel.as_mut().unwrap().enable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all of the timestamps that have occurred during the last processing cycle.
|
/// Get the latest timestamp that has occurred.
|
||||||
pub fn acquire_buffer(&mut self) -> &[u32] {
|
///
|
||||||
// If we are using DMA, finish the transfer and swap over buffers.
|
/// # Note
|
||||||
if self.transfer.is_some() {
|
/// This function must be called sufficiently often. If an over-capture event occurs, this
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
/// function will panic, as this indicates a timestamp was inadvertently dropped.
|
||||||
|
///
|
||||||
self.transfer.as_mut().unwrap().pause(|channel| {
|
/// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at
|
||||||
if channel.check_overcapture() {
|
/// most one timestamp will occur in each data processing cycle.
|
||||||
panic!("DI0 timestamp overrun");
|
pub fn latest_timestamp(&mut self) -> Option<u32> {
|
||||||
}
|
self.capture_channel
|
||||||
});
|
.latest_capture()
|
||||||
|
.expect("DI0 timestamp overrun")
|
||||||
let (prev_buffer, _, remaining_transfers) = self
|
|
||||||
.transfer
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.next_transfer(next_buffer)
|
|
||||||
.unwrap();
|
|
||||||
let valid_count = prev_buffer.len() - remaining_transfers;
|
|
||||||
|
|
||||||
self.next_buffer.replace(prev_buffer);
|
|
||||||
|
|
||||||
// Note that we likely didn't finish the transfer, so only return the number of
|
|
||||||
// timestamps actually collected.
|
|
||||||
&self.next_buffer.as_ref().unwrap()[..valid_count]
|
|
||||||
} else {
|
|
||||||
if self.capture_channel.as_ref().unwrap().check_overcapture() {
|
|
||||||
panic!("DI0 timestamp overrun");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we aren't using DMA, just manually check the input capture channel for a
|
|
||||||
// timestamp.
|
|
||||||
match self.capture_channel.as_mut().unwrap().latest_capture() {
|
|
||||||
Some(stamp) => {
|
|
||||||
self.next_buffer.as_mut().unwrap()[0] = stamp;
|
|
||||||
&self.next_buffer.as_ref().unwrap()[..1]
|
|
||||||
}
|
|
||||||
None => &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
82
src/main.rs
82
src/main.rs
|
@ -30,8 +30,6 @@ extern crate panic_halt;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use core::convert::TryInto;
|
|
||||||
|
|
||||||
// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
|
// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
|
||||||
use cortex_m_rt::exception;
|
use cortex_m_rt::exception;
|
||||||
use rtic::cyccnt::{Instant, U32Ext};
|
use rtic::cyccnt::{Instant, U32Ext};
|
||||||
|
@ -58,7 +56,8 @@ use heapless::{consts::*, String};
|
||||||
|
|
||||||
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
||||||
// equal to 10ns per tick.
|
// equal to 10ns per tick.
|
||||||
const ADC_SAMPLE_TICKS: u32 = 128;
|
// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz
|
||||||
|
const ADC_SAMPLE_TICKS: u32 = 256;
|
||||||
|
|
||||||
// The desired ADC sample processing buffer size.
|
// The desired ADC sample processing buffer size.
|
||||||
const SAMPLE_BUFFER_SIZE: usize = 8;
|
const SAMPLE_BUFFER_SIZE: usize = 8;
|
||||||
|
@ -296,10 +295,10 @@ const APP: () = {
|
||||||
// Configure the timer to count at the designed tick rate. We will manually set the
|
// Configure the timer to count at the designed tick rate. We will manually set the
|
||||||
// period below.
|
// period below.
|
||||||
timer2.pause();
|
timer2.pause();
|
||||||
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY_MHZ.mhz());
|
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(ADC_SAMPLE_TICKS - 1);
|
sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1);
|
||||||
|
|
||||||
sampling_timer
|
sampling_timer
|
||||||
};
|
};
|
||||||
|
@ -315,32 +314,16 @@ const APP: () = {
|
||||||
// Configure the timer to count at the designed tick rate. We will manually set the
|
// Configure the timer to count at the designed tick rate. We will manually set the
|
||||||
// period below.
|
// period below.
|
||||||
timer5.pause();
|
timer5.pause();
|
||||||
timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY_MHZ.mhz());
|
timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
// The time stamp timer must run at exactly a multiple of the sample timer based on the
|
// The time stamp timer must run at exactly a multiple of the sample timer based on the
|
||||||
// batch size. To accomodate this, we manually set the period identical to the sample
|
// batch size. To accomodate this, we manually set the prescaler identical to the sample
|
||||||
// timer, but use a prescaler that is `BATCH_SIZE` longer.
|
// timer, but use a period that is longer.
|
||||||
let mut timer = timers::TimestampTimer::new(timer5);
|
let mut timer = timers::TimestampTimer::new(timer5);
|
||||||
|
|
||||||
let period: u32 = {
|
let period =
|
||||||
let batch_duration: u64 =
|
digital_input_stamper::calculate_timestamp_timer_period();
|
||||||
SAMPLE_BUFFER_SIZE as u64 * ADC_SAMPLE_TICKS as u64;
|
timer.set_period_ticks(period);
|
||||||
let batches_per_overflow: u64 =
|
|
||||||
(1u64 + u32::MAX as u64) / batch_duration;
|
|
||||||
|
|
||||||
// Calculate the largest power-of-two that is less than `batches_per_overflow`.
|
|
||||||
// This is completed by eliminating the least significant bits of the value until
|
|
||||||
// only the msb remains, which is always a power of two.
|
|
||||||
let mut j = batches_per_overflow;
|
|
||||||
while (j & (j - 1)) != 0 {
|
|
||||||
j = j & (j - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let period: u64 = batch_duration * j - 1u64;
|
|
||||||
period.try_into().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
timer.set_period(period);
|
|
||||||
|
|
||||||
timer
|
timer
|
||||||
};
|
};
|
||||||
|
@ -374,7 +357,7 @@ const APP: () = {
|
||||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
||||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||||
config,
|
config,
|
||||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
design_parameters::ADC_DAC_SCK_MAX,
|
||||||
ccdr.peripheral.SPI2,
|
ccdr.peripheral.SPI2,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
);
|
);
|
||||||
|
@ -412,7 +395,7 @@ const APP: () = {
|
||||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
||||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||||
config,
|
config,
|
||||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
design_parameters::ADC_DAC_SCK_MAX,
|
||||||
ccdr.peripheral.SPI3,
|
ccdr.peripheral.SPI3,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
);
|
);
|
||||||
|
@ -462,7 +445,7 @@ const APP: () = {
|
||||||
dp.SPI4.spi(
|
dp.SPI4.spi(
|
||||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||||
config,
|
config,
|
||||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
design_parameters::ADC_DAC_SCK_MAX,
|
||||||
ccdr.peripheral.SPI4,
|
ccdr.peripheral.SPI4,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
)
|
)
|
||||||
|
@ -494,7 +477,7 @@ const APP: () = {
|
||||||
dp.SPI5.spi(
|
dp.SPI5.spi(
|
||||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||||
config,
|
config,
|
||||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
design_parameters::ADC_DAC_SCK_MAX,
|
||||||
ccdr.peripheral.SPI5,
|
ccdr.peripheral.SPI5,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
)
|
)
|
||||||
|
@ -564,7 +547,7 @@ const APP: () = {
|
||||||
let qspi = hal::qspi::Qspi::bank2(
|
let qspi = hal::qspi::Qspi::bank2(
|
||||||
dp.QUADSPI,
|
dp.QUADSPI,
|
||||||
qspi_pins,
|
qspi_pins,
|
||||||
40.mhz(),
|
design_parameters::POUNDER_QSPI_FREQUENCY,
|
||||||
&ccdr.clocks,
|
&ccdr.clocks,
|
||||||
ccdr.peripheral.QSPI,
|
ccdr.peripheral.QSPI,
|
||||||
);
|
);
|
||||||
|
@ -689,30 +672,27 @@ const APP: () = {
|
||||||
ccdr.peripheral.HRTIM,
|
ccdr.peripheral.HRTIM,
|
||||||
);
|
);
|
||||||
|
|
||||||
// IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile
|
// IO_Update occurs after a fixed delay from the QSPI write. Note that the timer
|
||||||
// write. With pounder SYNC_CLK running at 125MHz (1/4 of the pounder reference
|
// is triggered after the QSPI write, which can take approximately 120nS, so
|
||||||
// clock of 500MHz), this corresponds to 32ns. To accomodate rounding errors, we
|
// there is additional margin.
|
||||||
// use 50ns instead.
|
|
||||||
//
|
|
||||||
// Profile writes are always 16 bytes, with 2 cycles required per byte, coming
|
|
||||||
// out to a total of 32 QSPI clock cycles. The QSPI is configured for 40MHz, so
|
|
||||||
// this comes out to an offset of 800nS. We use 900ns to be safe - note that the
|
|
||||||
// timer is triggered after the QSPI write, which can take approximately 120nS,
|
|
||||||
// so there is additional margin.
|
|
||||||
hrtimer.configure_single_shot(
|
hrtimer.configure_single_shot(
|
||||||
hrtimer::Channel::Two,
|
hrtimer::Channel::Two,
|
||||||
50_e-9,
|
design_parameters::POUNDER_IO_UPDATE_DURATION,
|
||||||
900_e-9,
|
design_parameters::POUNDER_IO_UPDATE_DELAY,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that we have enough time for an IO-update every sample.
|
// Ensure that we have enough time for an IO-update every sample.
|
||||||
let sample_frequency =
|
let sample_frequency = {
|
||||||
(design_parameters::TIMER_FREQUENCY_MHZ as f32
|
let timer_frequency: hal::time::Hertz =
|
||||||
* 1_000_000.0)
|
design_parameters::TIMER_FREQUENCY.into();
|
||||||
/ ADC_SAMPLE_TICKS as f32;
|
timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32
|
||||||
|
};
|
||||||
|
|
||||||
let sample_period = 1.0 / sample_frequency;
|
let sample_period = 1.0 / sample_frequency;
|
||||||
assert!(sample_period > 900_e-9);
|
assert!(
|
||||||
|
sample_period
|
||||||
|
> design_parameters::POUNDER_IO_UPDATE_DELAY
|
||||||
|
);
|
||||||
|
|
||||||
hrtimer
|
hrtimer
|
||||||
};
|
};
|
||||||
|
@ -849,9 +829,7 @@ const APP: () = {
|
||||||
let trigger = gpioa.pa3.into_alternate_af2();
|
let trigger = gpioa.pa3.into_alternate_af2();
|
||||||
digital_input_stamper::InputStamper::new(
|
digital_input_stamper::InputStamper::new(
|
||||||
trigger,
|
trigger,
|
||||||
dma_streams.6,
|
|
||||||
timestamp_timer_channels.ch4,
|
timestamp_timer_channels.ch4,
|
||||||
SAMPLE_BUFFER_SIZE,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -933,7 +911,7 @@ const APP: () = {
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
c.resources.dacs.1.acquire_buffer(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let _timestamps = c.resources.input_stamper.acquire_buffer();
|
let _timestamp = c.resources.input_stamper.latest_timestamp();
|
||||||
|
|
||||||
for channel in 0..adc_samples.len() {
|
for channel in 0..adc_samples.len() {
|
||||||
for sample in 0..adc_samples[0].len() {
|
for sample in 0..adc_samples[0].len() {
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl Timestamper {
|
||||||
|
|
||||||
// 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 input_capture = capture_channel
|
||||||
.to_input_capture(timers::CaptureTrigger::TriggerInput);
|
.into_input_capture(timers::CaptureTrigger::TriggerInput);
|
||||||
input_capture.listen_dma();
|
input_capture.listen_dma();
|
||||||
|
|
||||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
||||||
|
@ -68,7 +68,7 @@ impl Timestamper {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_period(&mut self, period: u16) {
|
pub fn update_period(&mut self, period: u16) {
|
||||||
self.timer.set_period(period);
|
self.timer.set_period_ticks(period);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with timestamps.
|
/// Obtain a buffer filled with timestamps.
|
||||||
|
|
|
@ -85,7 +85,7 @@ macro_rules! timer_channels {
|
||||||
|
|
||||||
/// Manually set the period of the timer.
|
/// Manually set the period of the timer.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_period(&mut self, period: $size) {
|
pub fn set_period_ticks(&mut self, period: $size) {
|
||||||
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
||||||
regs.arr.write(|w| w.arr().bits(period));
|
regs.arr.write(|w| w.arr().bits(period));
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ macro_rules! timer_channels {
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `input` - The input source for the input capture event.
|
/// * `input` - The input source for the input capture event.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn to_input_capture(self, input: super::CaptureTrigger) -> [< Channel $index InputCapture >]{
|
pub fn into_input_capture(self, input: super::CaptureTrigger) -> [< Channel $index InputCapture >]{
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
|
|
||||||
// Note(unsafe): The bit configuration is guaranteed to be valid by the
|
// Note(unsafe): The bit configuration is guaranteed to be valid by the
|
||||||
|
@ -255,17 +255,28 @@ macro_rules! timer_channels {
|
||||||
impl [< Channel $index InputCapture >] {
|
impl [< Channel $index InputCapture >] {
|
||||||
/// Get the latest capture from the channel.
|
/// Get the latest capture from the channel.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn latest_capture(&mut self) -> Option<$size> {
|
pub fn latest_capture(&mut self) -> Result<Option<$size>, ()> {
|
||||||
// Note(unsafe): This channel owns all access to the specific timer channel.
|
// Note(unsafe): This channel owns all access to the specific timer channel.
|
||||||
// Only atomic operations on completed on the timer registers.
|
// Only atomic operations on completed on the timer registers.
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
let sr = regs.sr.read();
|
let sr = regs.sr.read();
|
||||||
let ccx = regs.[< ccr $index >].read();
|
|
||||||
if sr.[< cc $index if >]().bit_is_set() {
|
let result = if sr.[< cc $index if >]().bit_is_set() {
|
||||||
regs.sr.modify(|_, w| w.[< cc $index if >]().clear_bit());
|
// Read the capture value. Reading the captured value clears the flag in the
|
||||||
|
// status register automatically.
|
||||||
|
let ccx = regs.[< ccr $index >].read();
|
||||||
Some(ccx.ccr().bits())
|
Some(ccx.ccr().bits())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read SR again to check for a potential over-capture. If there is an
|
||||||
|
// overcapture, return an error.
|
||||||
|
if regs.sr.read().[< cc $index of >]().bit_is_clear() {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
regs.sr.modify(|_, w| w.[< cc $index of >]().clear_bit());
|
||||||
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue