From fc81f3d55db88f954dd64225cb6bc96a84cf563c Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 15 Dec 2020 14:34:14 +0100 Subject: [PATCH] Removing DMA support from DI0 timestamping --- src/digital_input_stamper.rs | 130 ++++++----------------------------- src/main.rs | 4 +- 2 files changed, 23 insertions(+), 111 deletions(-) diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs index d4204f3..5843528 100644 --- a/src/digital_input_stamper.rs +++ b/src/digital_input_stamper.rs @@ -3,9 +3,6 @@ ///! This module provides a means of timestamping the rising edges of an external reference clock on ///! 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 ///! 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 @@ -13,12 +10,6 @@ ///! recorded as a timestamp. This timestamp can be either directly read from the timer channel or ///! 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 ///! 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. @@ -27,35 +18,18 @@ ///! 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, ///! 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 -///! module manually checks for any captured timestamps from the timer capture channel manually. In -///! this mode, the maximum input clock frequency supported is equal to the configured sample rate. +///! This module 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 +///! batch size. ///! -///! There is a small window while the DMA buffers are swapped where a timestamp could potentially -///! be lost. To prevent this, the `acuire_buffer()` method should not be pre-empted. Any lost -///! timestamp will trigger an over-capture interrupt. -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]; +///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If +///! timestamping is desired in DI1, a separate timer + capture channel will be necessary. +use super::{hal, timers}; /// The timestamper for DI0 reference clock inputs. pub struct InputStamper { _di0_trigger: hal::gpio::gpioa::PA3>, - next_buffer: Option<&'static mut [u32; SAMPLE_BUFFER_SIZE]>, - transfer: Option< - Transfer< - hal::dma::dma::Stream6, - timers::tim5::Channel4InputCapture, - PeripheralToMemory, - &'static mut [u32; SAMPLE_BUFFER_SIZE], - >, - >, - capture_channel: Option, + capture_channel: timers::tim5::Channel4InputCapture, } impl InputStamper { @@ -63,100 +37,40 @@ impl InputStamper { /// /// # Args /// * `trigger` - The capture trigger input pin. - /// * `stream` - The DMA stream to use for collecting timestamps. /// * `timer_channel - The timer channel used for capturing timestamps. - /// * `batch_size` - The number of samples collected per processing batch. pub fn new( trigger: hal::gpio::gpioa::PA3>, - stream: hal::dma::dma::Stream6, timer_channel: timers::tim5::Channel4, - batch_size: usize, ) -> Self { // Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the // capture source. let input_capture = 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 { - next_buffer: unsafe { Some(&mut BUF[1]) }, - transfer, capture_channel: input_capture, _di0_trigger: trigger, } } - /// Start capture timestamps on DI0. + /// Start to capture timestamps on DI0. pub fn start(&mut self) { - if let Some(transfer) = &mut self.transfer { - transfer.start(|capture_channel| { - capture_channel.enable(); - }); - } else { - self.capture_channel.as_mut().unwrap().enable(); - } + self.capture_channel.enable(); } - /// Get all of the timestamps that have occurred during the last processing cycle. - pub fn acquire_buffer(&mut self) -> &[u32] { - // If we are using DMA, finish the transfer and swap over buffers. - if self.transfer.is_some() { - let next_buffer = self.next_buffer.take().unwrap(); - - self.transfer.as_mut().unwrap().pause(|channel| { - if 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 => &[], - } + /// Get the latest timestamp that has occurred. + /// + /// # Note + /// This function must be called sufficiently often. If an over-capture event occurs, this + /// function will panic, as this indicates a timestamp was inadvertently dropped. + /// + /// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at + /// most one timestamp will occur in each data processing cycle. + pub fn latest_timestamp(&mut self) -> Option { + if self.capture_channel.check_overcapture() { + panic!("DI0 timestamp overrun"); } + + self.capture_channel.latest_capture() } } diff --git a/src/main.rs b/src/main.rs index 308a4f0..5ae2b0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -844,9 +844,7 @@ const APP: () = { let trigger = gpioa.pa3.into_alternate_af2(); digital_input_stamper::InputStamper::new( trigger, - dma_streams.6, timestamp_timer_channels.ch4, - SAMPLE_BUFFER_SIZE, ) }; @@ -882,7 +880,7 @@ const APP: () = { 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 sample in 0..adc_samples[0].len() {