diff --git a/openocd.gdb b/openocd.gdb index e903a33..a96f8d4 100644 --- a/openocd.gdb +++ b/openocd.gdb @@ -18,6 +18,9 @@ load # tbreak cortex_m_rt::reset_handler monitor reset halt +source ../../PyCortexMDebug/cmdebug/svd_gdb.py +svd_load ~/Downloads/STM32H743x.svd + # cycle counter delta tool, place two bkpts around the section set var $cc=0xe0001004 define qq diff --git a/src/design_parameters.rs b/src/design_parameters.rs index 4e2af9d..f6222d2 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -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 /// may begin. This is used for performing the internal ADC conversion. pub const ADC_SETUP_TIME: f32 = 220e-9; /// 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. -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. pub const DDS_REF_CLK_MHZ: u32 = 100; diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs index 8469195..d63849c 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,53 @@ ///! 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, -}; +///! 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, ADC_SAMPLE_TICKS, 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]; +/// Calculate the period of the digital input timestampe timer. +/// +/// # Note +/// 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. 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 +72,38 @@ 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::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)) - }; + timer_channel.into_input_capture(timers::CaptureTrigger::Input24); 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 { + self.capture_channel + .latest_capture() + .expect("DI0 timestamp overrun") } } diff --git a/src/main.rs b/src/main.rs index a2dd7ca..c01c27d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,8 +30,6 @@ extern crate panic_halt; #[macro_use] extern crate log; -use core::convert::TryInto; - // use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; use cortex_m_rt::exception; 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 // 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. 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 // period below. 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); - sampling_timer.set_period(ADC_SAMPLE_TICKS - 1); + sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); sampling_timer }; @@ -315,32 +314,16 @@ const APP: () = { // Configure the timer to count at the designed tick rate. We will manually set the // period below. 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 - // batch size. To accomodate this, we manually set the period identical to the sample - // timer, but use a prescaler that is `BATCH_SIZE` longer. + // batch size. To accomodate this, we manually set the prescaler identical to the sample + // timer, but use a period that is longer. let mut timer = timers::TimestampTimer::new(timer5); - let period: u32 = { - let batch_duration: u64 = - SAMPLE_BUFFER_SIZE as u64 * ADC_SAMPLE_TICKS as u64; - 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); + let period = + digital_input_stamper::calculate_timestamp_timer_period(); + timer.set_period_ticks(period); timer }; @@ -374,7 +357,7 @@ const APP: () = { let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI2, &ccdr.clocks, ); @@ -412,7 +395,7 @@ const APP: () = { let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI3, &ccdr.clocks, ); @@ -462,7 +445,7 @@ const APP: () = { dp.SPI4.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI4, &ccdr.clocks, ) @@ -494,7 +477,7 @@ const APP: () = { dp.SPI5.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI5, &ccdr.clocks, ) @@ -564,7 +547,7 @@ const APP: () = { let qspi = hal::qspi::Qspi::bank2( dp.QUADSPI, qspi_pins, - 40.mhz(), + design_parameters::POUNDER_QSPI_FREQUENCY, &ccdr.clocks, ccdr.peripheral.QSPI, ); @@ -689,30 +672,27 @@ const APP: () = { ccdr.peripheral.HRTIM, ); - // IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile - // write. With pounder SYNC_CLK running at 125MHz (1/4 of the pounder reference - // clock of 500MHz), this corresponds to 32ns. To accomodate rounding errors, we - // 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. + // IO_Update occurs after a fixed delay from the QSPI write. 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::Channel::Two, - 50_e-9, - 900_e-9, + design_parameters::POUNDER_IO_UPDATE_DURATION, + design_parameters::POUNDER_IO_UPDATE_DELAY, ); // Ensure that we have enough time for an IO-update every sample. - let sample_frequency = - (design_parameters::TIMER_FREQUENCY_MHZ as f32 - * 1_000_000.0) - / ADC_SAMPLE_TICKS as f32; + let sample_frequency = { + let timer_frequency: hal::time::Hertz = + design_parameters::TIMER_FREQUENCY.into(); + timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32 + }; let sample_period = 1.0 / sample_frequency; - assert!(sample_period > 900_e-9); + assert!( + sample_period + > design_parameters::POUNDER_IO_UPDATE_DELAY + ); hrtimer }; @@ -849,9 +829,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, ) }; @@ -933,7 +911,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() { diff --git a/src/pounder/timestamp.rs b/src/pounder/timestamp.rs index 295cb12..2431634 100644 --- a/src/pounder/timestamp.rs +++ b/src/pounder/timestamp.rs @@ -43,7 +43,7 @@ impl Timestamper { // The capture channel should capture whenever the trigger input occurs. let input_capture = capture_channel - .to_input_capture(timers::CaptureTrigger::TriggerInput); + .into_input_capture(timers::CaptureTrigger::TriggerInput); input_capture.listen_dma(); // 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) { - self.timer.set_period(period); + self.timer.set_period_ticks(period); } /// Obtain a buffer filled with timestamps. diff --git a/src/timers.rs b/src/timers.rs index 10f300f..851e6b8 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -85,7 +85,7 @@ macro_rules! timer_channels { /// Manually set the period of the timer. #[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() }; regs.arr.write(|w| w.arr().bits(period)); } @@ -241,7 +241,7 @@ macro_rules! timer_channels { /// # Args /// * `input` - The input source for the input capture event. #[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() }; // Note(unsafe): The bit configuration is guaranteed to be valid by the @@ -255,17 +255,28 @@ macro_rules! timer_channels { impl [< Channel $index InputCapture >] { /// Get the latest capture from the channel. #[allow(dead_code)] - pub fn latest_capture(&mut self) -> Option<$size> { + pub fn latest_capture(&mut self) -> Result, ()> { // 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() }; let sr = regs.sr.read(); - let ccx = regs.[< ccr $index >].read(); - if sr.[< cc $index if >]().bit_is_set() { - regs.sr.modify(|_, w| w.[< cc $index if >]().clear_bit()); + + let result = if sr.[< cc $index if >]().bit_is_set() { + // 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()) } else { 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(()) } }