Removing DMA support from DI0 timestamping
This commit is contained in:
parent
2e0681ebcc
commit
fc81f3d55d
@ -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,18 @@
|
|||||||
///! 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};
|
||||||
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
|
|
||||||
// is off-limits to the normal DMA peripheral. Instead, we use AXISRAM.
|
|
||||||
#[link_section = ".axisram.buffers"]
|
|
||||||
static mut BUF: [[u32; SAMPLE_BUFFER_SIZE]; 2] = [[0; SAMPLE_BUFFER_SIZE]; 2];
|
|
||||||
|
|
||||||
/// 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 +37,40 @@ 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::tim5::CC4S_A::TI4);
|
timer_channel.to_input_capture(timers::tim5::CC4S_A::TI4);
|
||||||
|
|
||||||
// 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> {
|
||||||
}
|
if self.capture_channel.check_overcapture() {
|
||||||
});
|
panic!("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 => &[],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.capture_channel.latest_capture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -844,9 +844,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,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -882,7 +880,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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user