From f8cab17ffc8af756f1d2bba617398fb46ed07b96 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 9 Dec 2020 13:44:26 +0100 Subject: [PATCH 01/17] Adding WIP for pounder timestamps --- src/main.rs | 15 ++++ src/pounder/mod.rs | 1 + src/pounder/timestamp.rs | 176 +++++++++++++++++++++++++++++++++++++++ src/timers.rs | 38 +++++++-- 4 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 src/pounder/timestamp.rs diff --git a/src/main.rs b/src/main.rs index 308a4f0..7cff452 100644 --- a/src/main.rs +++ b/src/main.rs @@ -220,6 +220,7 @@ const APP: () = { mac_addr: net::wire::EthernetAddress, pounder: Option, + pounder_stamper: pounder::timestamp::Timestamper, // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -850,6 +851,19 @@ const APP: () = { ) }; + let pounder_stamper = { + let etr_pin = gpioa.pa0.into_alternate_af3(); + let timestamp_timer = pounder::timestamp::Timer::new( + dp.TIM8, + ccdr.peripheral.TIM8, + etr_pin, + pounder::timestamp::Prescaler::Div4, + sampling_timer.update_event(), + 128, + ); + pounder::timestamp::Timestamper::new(timestamp_timer, dma_streams.7) + }; + // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); @@ -863,6 +877,7 @@ const APP: () = { input_stamper, dds_output, pounder: pounder_devices, + pounder_stamper, eeprom_i2c, net_interface: network_interface, diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index c20d251..36a73fd 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; mod attenuators; mod dds_output; mod rf_power; +pub mod timestamp; pub use dds_output::DdsOutput; diff --git a/src/pounder/timestamp.rs b/src/pounder/timestamp.rs new file mode 100644 index 0000000..44cd668 --- /dev/null +++ b/src/pounder/timestamp.rs @@ -0,0 +1,176 @@ +///! ADC sample timestamper using external Pounder reference clock. +use stm32h7xx_hal as hal; + +use hal::{ + dma::{ + dma::{DMAReq, DmaConfig}, + traits::TargetAddress, + PeripheralToMemory, Transfer, + }, + rcc::ResetEnable, +}; + +use crate::{timers, SAMPLE_BUFFER_SIZE}; + +struct TimestampDma { + _dma_request: timers::tim2::UpdateEvent, +} + +pub struct Timer { + timer: hal::stm32::TIM8, + dma: Option, +} + +// Note(unsafe): This is safe to implement because we take ownership of the DMA request. +// Additionally, we only read the registers in PeripheralToMemory mode, so it is always safe to +// access them. +unsafe impl TargetAddress for TimestampDma { + // TIM8 is a 16-bit timer. + type MemSize = u16; + + // Note: It is safe for us to us the TIM2_UPDATE DMA request because the Timer + // maintains ownership of the UpdateEvent object for this timer. + const REQUEST_LINE: Option = Some(DMAReq::TIM2_UP as u8); + + fn address(&self) -> u32 { + let regs = unsafe { &*hal::stm32::TIM8::ptr() }; + ®s.cnt as *const _ as u32 + } +} + +pub enum Prescaler { + Div4, + Div8, +} + +impl Timer { + pub fn new( + timer: hal::stm32::TIM8, + prec: hal::rcc::rec::Tim8, + _external_source: hal::gpio::gpioa::PA0< + hal::gpio::Alternate, + >, + prescaler: Prescaler, + update_event: timers::tim2::UpdateEvent, + period: u16, + ) -> Self { + prec.reset().enable(); + + let divisor = match prescaler { + Prescaler::Div4 => hal::stm32::tim1::smcr::ETPS_A::DIV4, + Prescaler::Div8 => hal::stm32::tim1::smcr::ETPS_A::DIV8, + }; + + // Configure the timer to utilize an external clock source with the provided divider on the + // ETR. + timer + .smcr + .modify(|_, w| w.etps().variant(divisor).ece().set_bit()); + + // Set the timer period and generate an update of the timer registers so that ARR takes + // effect. + timer.arr.write(|w| w.arr().bits(period)); + + timer.egr.write(|w| w.ug().set_bit()); + + // Allow TIM2 updates to generate DMA requests. + update_event.listen_dma(); + + // Enable the timer. + timer.cr1.modify(|_, w| w.cen().set_bit()); + + let dma = TimestampDma { + _dma_request: update_event, + }; + + Self { + timer, + dma: Some(dma), + } + } + + /// Update the timer period. + /// + /// # Note + /// Timer period updates will take effect after the current period elapses. + fn set_period(&mut self, period: u16) { + // Modify the period register. + self.timer.arr.write(|w| w.arr().bits(period)); + } + + fn dma_transfer(&mut self) -> TimestampDma { + self.dma.take().unwrap() + } +} + +#[link_section = ".axisram.buffers"] +static mut BUF: [[u16; SAMPLE_BUFFER_SIZE]; 3] = [[0; SAMPLE_BUFFER_SIZE]; 3]; + +pub struct Timestamper { + next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, + timer: Timer, + transfer: Transfer< + hal::dma::dma::Stream7, + TimestampDma, + PeripheralToMemory, + &'static mut [u16; SAMPLE_BUFFER_SIZE], + >, +} + +impl Timestamper { + pub fn new( + mut timer: Timer, + stream: hal::dma::dma::Stream7, + ) -> Self { + let config = DmaConfig::default() + .memory_increment(true) + .circular_buffer(true) + .double_buffer(true); + + // The data transfer is always a transfer of data from the peripheral to a RAM buffer. + let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = + Transfer::init( + stream, + timer.dma_transfer(), + // Note(unsafe): The BUF[0] and BUF[1] is "owned" by this peripheral. + // It shall not be used anywhere else in the module. + unsafe { &mut BUF[0] }, + unsafe { Some(&mut BUF[1]) }, + config, + ); + + data_transfer.start(|_| {}); + + Self { + timer, + transfer: data_transfer, + next_buffer: unsafe { Some(&mut BUF[2]) }, + } + } + + pub fn update_period(&mut self, period: u16) { + self.timer.set_period(period); + } + + /// Obtain a buffer filled with timestamps. + /// + /// # Returns + /// A reference to the underlying buffer that has been filled with timestamps. + pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] { + // Wait for the transfer to fully complete before continuing. + // 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. + self.transfer.clear_interrupts(); + 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() + } +} diff --git a/src/timers.rs b/src/timers.rs index 03bc0aa..3025edf 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -9,6 +9,7 @@ macro_rules! timer_channels { pub struct $name { timer: hal::timer::Timer]>, channels: Option<[< $TY:lower >]::Channels>, + update_event: Option<[< $TY:lower >]::UpdateEvent>, } impl $name { @@ -18,12 +19,13 @@ macro_rules! timer_channels { Self { timer, - // Note(unsafe): Once these channels are taken, we guarantee that we do not modify any - // of the underlying timer channel registers, as ownership of the channels is now - // provided through the associated channel structures. We additionally guarantee this - // can only be called once because there is only one Timer2 and this resource takes - // ownership of it once instantiated. + // Note(unsafe): Once these channels are taken, we guarantee that we do not + // modify any of the underlying timer channel registers, as ownership of the + // channels is now provided through the associated channel structures. We + // additionally guarantee this can only be called once because there is only + // one Timer2 and this resource takes ownership of it once instantiated. channels: unsafe { Some([< $TY:lower >]::Channels::new()) }, + update_event: unsafe { Some([< $TY:lower >]::UpdateEvent::new()) }, } } @@ -32,6 +34,12 @@ macro_rules! timer_channels { self.channels.take().unwrap() } + /// Get the timer update event. + #[allow(dead_code)] + pub fn update_event(&mut self) -> [< $TY:lower >]::UpdateEvent { + self.update_event.take().unwrap() + } + /// Get the period of the timer. #[allow(dead_code)] pub fn get_period(&self) -> u32 { @@ -64,6 +72,26 @@ macro_rules! timer_channels { use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq}; use hal::stm32::$TY; + pub struct UpdateEvent {} + + impl UpdateEvent { + /// Create a new update event + /// + /// Note(unsafe): This is only safe to call once. + #[allow(dead_code)] + pub unsafe fn new() -> Self { + Self {} + } + + /// Enable DMA requests upon timer updates. + #[allow(dead_code)] + pub fn listen_dma(&self) { + // Note(unsafe): We perofmr only atomic operations on the timer registers. + let regs = unsafe { &*<$TY>::ptr() }; + regs.dier.modify(|_, w| w.ude().set_bit()); + } + } + /// The channels representing the timer. pub struct Channels { pub ch1: Channel1, From e9d74ae6da5b86b78ebef2056bc8eb5a47224155 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 9 Dec 2020 15:13:04 +0100 Subject: [PATCH 02/17] Adding support for pounder DDS timestamping --- src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7cff452..9201de9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -571,7 +571,7 @@ const APP: () = { pounder::QspiInterface::new(qspi).unwrap() }; - let reset_pin = gpioa.pa0.into_push_pull_output(); + let reset_pin = gpiog.pg6.into_push_pull_output(); let mut io_update = gpiog.pg7.into_push_pull_output(); let ad9959 = ad9959::Ad9959::new( @@ -886,7 +886,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] + #[task(binds=DMA1_STR3, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { let adc_samples = [ c.resources.adcs.0.acquire_buffer(), @@ -897,6 +897,7 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; + let _pounder_timestamps = c.resources.pounder_stamper.acquire_buffer(); let _timestamps = c.resources.input_stamper.acquire_buffer(); for channel in 0..adc_samples.len() { From 72d14adfbf8de8f4d12fdd0c9c010d0d666a81db Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 9 Dec 2020 18:19:33 +0100 Subject: [PATCH 03/17] Adding support for pounder ETR timestamping --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/adc.rs | 4 +- src/dac.rs | 4 +- src/digital_input_stamper.rs | 2 +- src/main.rs | 31 ++++++--- src/pounder/timestamp.rs | 130 +++++++---------------------------- src/timers.rs | 119 ++++++++++++++++++++++++++------ 8 files changed, 150 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edc2864..6a2a6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -517,7 +517,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/number-of-transfers#e70a78788e74be5281321213b53e8cd1d213550e" +source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/dma-buffer-swap/num-transfers#b87614f432a635e904dea2383ff481f3cc002e80" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index f1acbe0..9bfc570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] git = "https://github.com/quartiq/stm32h7xx-hal" -branch = "feature/number-of-transfers" +branch = "feature/dma-buffer-swap/num-transfers" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/adc.rs b/src/adc.rs index 8d2b61a..f6e937e 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -58,11 +58,11 @@ macro_rules! adc_input { /// Whenever the DMA request occurs, it should write into SPI's TX FIFO to start a DMA /// transfer. - fn address(&self) -> u32 { + fn address(&self) -> usize { // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is // only used for the transmit-half of DMA. let regs = unsafe { &*hal::stm32::$spi::ptr() }; - ®s.txdr as *const _ as u32 + ®s.txdr as *const _ as usize } } diff --git a/src/dac.rs b/src/dac.rs index 06a6362..abea097 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -45,8 +45,8 @@ macro_rules! dac_output { const REQUEST_LINE: Option = Some(DMAReq::$dma_req as u8); /// Whenever the DMA request occurs, it should write into SPI's TX FIFO. - fn address(&self) -> u32 { - &self.spi.inner().txdr as *const _ as u32 + fn address(&self) -> usize { + &self.spi.inner().txdr as *const _ as usize } } diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs index d4204f3..8469195 100644 --- a/src/digital_input_stamper.rs +++ b/src/digital_input_stamper.rs @@ -75,7 +75,7 @@ impl InputStamper { // 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); + 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 diff --git a/src/main.rs b/src/main.rs index 9201de9..30ed33c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,10 +58,10 @@ 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 = 256; +const ADC_SAMPLE_TICKS: u32 = 128; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 1; +const SAMPLE_BUFFER_SIZE: usize = 8; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; @@ -853,15 +853,23 @@ const APP: () = { let pounder_stamper = { let etr_pin = gpioa.pa0.into_alternate_af3(); - let timestamp_timer = pounder::timestamp::Timer::new( - dp.TIM8, - ccdr.peripheral.TIM8, + + // The frequency in the constructor is dont-care, as we will modify the period + clock + // source manually below. + let tim8 = + dp.TIM8.timer(1.khz(), ccdr.peripheral.TIM8, &ccdr.clocks); + let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8); + timestamp_timer.set_external_clock(timers::Prescaler::Div4); + timestamp_timer.set_period(128); + let tim8_channels = timestamp_timer.channels(); + + pounder::timestamp::Timestamper::new( + timestamp_timer, + dma_streams.7, + tim8_channels.ch1, + &mut sampling_timer, etr_pin, - pounder::timestamp::Prescaler::Div4, - sampling_timer.update_event(), - 128, - ); - pounder::timestamp::Timestamper::new(timestamp_timer, dma_streams.7) + ) }; // Start sampling ADCs. @@ -888,6 +896,8 @@ const APP: () = { #[task(binds=DMA1_STR3, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { + let _pounder_timestamps = c.resources.pounder_stamper.acquire_buffer(); + let adc_samples = [ c.resources.adcs.0.acquire_buffer(), c.resources.adcs.1.acquire_buffer(), @@ -897,7 +907,6 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let _pounder_timestamps = c.resources.pounder_stamper.acquire_buffer(); let _timestamps = c.resources.input_stamper.acquire_buffer(); for channel in 0..adc_samples.len() { diff --git a/src/pounder/timestamp.rs b/src/pounder/timestamp.rs index 44cd668..295cb12 100644 --- a/src/pounder/timestamp.rs +++ b/src/pounder/timestamp.rs @@ -1,117 +1,19 @@ ///! ADC sample timestamper using external Pounder reference clock. use stm32h7xx_hal as hal; -use hal::{ - dma::{ - dma::{DMAReq, DmaConfig}, - traits::TargetAddress, - PeripheralToMemory, Transfer, - }, - rcc::ResetEnable, -}; +use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer}; use crate::{timers, SAMPLE_BUFFER_SIZE}; -struct TimestampDma { - _dma_request: timers::tim2::UpdateEvent, -} - -pub struct Timer { - timer: hal::stm32::TIM8, - dma: Option, -} - -// Note(unsafe): This is safe to implement because we take ownership of the DMA request. -// Additionally, we only read the registers in PeripheralToMemory mode, so it is always safe to -// access them. -unsafe impl TargetAddress for TimestampDma { - // TIM8 is a 16-bit timer. - type MemSize = u16; - - // Note: It is safe for us to us the TIM2_UPDATE DMA request because the Timer - // maintains ownership of the UpdateEvent object for this timer. - const REQUEST_LINE: Option = Some(DMAReq::TIM2_UP as u8); - - fn address(&self) -> u32 { - let regs = unsafe { &*hal::stm32::TIM8::ptr() }; - ®s.cnt as *const _ as u32 - } -} - -pub enum Prescaler { - Div4, - Div8, -} - -impl Timer { - pub fn new( - timer: hal::stm32::TIM8, - prec: hal::rcc::rec::Tim8, - _external_source: hal::gpio::gpioa::PA0< - hal::gpio::Alternate, - >, - prescaler: Prescaler, - update_event: timers::tim2::UpdateEvent, - period: u16, - ) -> Self { - prec.reset().enable(); - - let divisor = match prescaler { - Prescaler::Div4 => hal::stm32::tim1::smcr::ETPS_A::DIV4, - Prescaler::Div8 => hal::stm32::tim1::smcr::ETPS_A::DIV8, - }; - - // Configure the timer to utilize an external clock source with the provided divider on the - // ETR. - timer - .smcr - .modify(|_, w| w.etps().variant(divisor).ece().set_bit()); - - // Set the timer period and generate an update of the timer registers so that ARR takes - // effect. - timer.arr.write(|w| w.arr().bits(period)); - - timer.egr.write(|w| w.ug().set_bit()); - - // Allow TIM2 updates to generate DMA requests. - update_event.listen_dma(); - - // Enable the timer. - timer.cr1.modify(|_, w| w.cen().set_bit()); - - let dma = TimestampDma { - _dma_request: update_event, - }; - - Self { - timer, - dma: Some(dma), - } - } - - /// Update the timer period. - /// - /// # Note - /// Timer period updates will take effect after the current period elapses. - fn set_period(&mut self, period: u16) { - // Modify the period register. - self.timer.arr.write(|w| w.arr().bits(period)); - } - - fn dma_transfer(&mut self) -> TimestampDma { - self.dma.take().unwrap() - } -} - #[link_section = ".axisram.buffers"] static mut BUF: [[u16; SAMPLE_BUFFER_SIZE]; 3] = [[0; SAMPLE_BUFFER_SIZE]; 3]; pub struct Timestamper { next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, - timer: Timer, + timer: timers::PounderTimestampTimer, transfer: Transfer< hal::dma::dma::Stream7, - TimestampDma, + timers::tim8::Channel1InputCapture, PeripheralToMemory, &'static mut [u16; SAMPLE_BUFFER_SIZE], >, @@ -119,19 +21,36 @@ pub struct Timestamper { impl Timestamper { pub fn new( - mut timer: Timer, + mut timestamp_timer: timers::PounderTimestampTimer, stream: hal::dma::dma::Stream7, + capture_channel: timers::tim8::Channel1, + sampling_timer: &mut timers::SamplingTimer, + _clock_input: hal::gpio::gpioa::PA0< + hal::gpio::Alternate, + >, ) -> 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. + sampling_timer.generate_trigger(timers::TriggerGenerator::ComparePulse); + + // The timestamp timer trigger input should use TIM2 (SamplingTimer)'s trigger, which is + // mapped to ITR1. + timestamp_timer.set_trigger_source(timers::TriggerSource::Trigger1); + + // The capture channel should capture whenever the trigger input occurs. + let input_capture = capture_channel + .to_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. let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( stream, - timer.dma_transfer(), + input_capture, // Note(unsafe): The BUF[0] and BUF[1] is "owned" by this peripheral. // It shall not be used anywhere else in the module. unsafe { &mut BUF[0] }, @@ -139,10 +58,10 @@ impl Timestamper { config, ); - data_transfer.start(|_| {}); + data_transfer.start(|capture_channel| capture_channel.enable()); Self { - timer, + timer: timestamp_timer, transfer: data_transfer, next_buffer: unsafe { Some(&mut BUF[2]) }, } @@ -165,7 +84,6 @@ impl Timestamper { 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(); diff --git a/src/timers.rs b/src/timers.rs index 3025edf..1292dab 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -1,8 +1,40 @@ ///! The sampling timer is used for managing ADC sampling and external reference timestamping. use super::hal; +#[allow(dead_code)] +pub enum CaptureTrigger { + Input13 = 0b01, + Input24 = 0b10, + TriggerInput = 0b11, +} + +#[allow(dead_code)] +pub enum TriggerGenerator { + Reset = 0b000, + Enable = 0b001, + Update = 0b010, + ComparePulse = 0b011, + Ch1Compare = 0b100, + Ch2Compare = 0b101, + Ch3Compare = 0b110, + Ch4Compare = 0b111, +} + +#[allow(dead_code)] +pub enum TriggerSource { + Trigger0 = 0, + Trigger1 = 0b01, + Trigger2 = 0b10, + Trigger3 = 0b11, +} + +pub enum Prescaler { + Div4 = 0b10, + Div8 = 0b11, +} + macro_rules! timer_channels { - ($name:ident, $TY:ident, u32) => { + ($name:ident, $TY:ident, $size:ty) => { paste::paste! { /// The timer used for managing ADC sampling. @@ -14,6 +46,7 @@ macro_rules! timer_channels { impl $name { /// Construct the sampling timer. + #[allow(dead_code)] pub fn new(mut timer: hal::timer::Timer]>) -> Self { timer.pause(); @@ -30,6 +63,7 @@ macro_rules! timer_channels { } /// Get the timer capture/compare channels. + #[allow(dead_code)] pub fn channels(&mut self) -> [< $TY:lower >]::Channels { self.channels.take().unwrap() } @@ -42,19 +76,36 @@ macro_rules! timer_channels { /// Get the period of the timer. #[allow(dead_code)] - pub fn get_period(&self) -> u32 { + pub fn get_period(&self) -> $size { let regs = unsafe { &*hal::stm32::$TY::ptr() }; regs.arr.read().arr().bits() } /// Manually set the period of the timer. #[allow(dead_code)] - pub fn set_period(&mut self, period: u32) { + pub fn set_period(&mut self, period: $size) { let regs = unsafe { &*hal::stm32::$TY::ptr() }; regs.arr.write(|w| w.arr().bits(period)); } + /// Clock the timer from an external source. + /// + /// # Note: + /// * Currently, only an external source applied to ETR is supported. + /// + /// # Args + /// * `prescaler` - The prescaler to use for the external source. + #[allow(dead_code)] + pub fn set_external_clock(&mut self, prescaler: Prescaler) { + let regs = unsafe { &*hal::stm32::$TY::ptr() }; + regs.smcr.modify(|_, w| w.etps().bits(prescaler as u8).ece().set_bit()); + + // Use a DIV4 prescaler. + + } + /// Start the timer. + #[allow(dead_code)] pub fn start(mut self) { // Force a refresh of the frequency settings. self.timer.apply_freq(); @@ -62,12 +113,26 @@ macro_rules! timer_channels { self.timer.reset_counter(); self.timer.resume(); } + + #[allow(dead_code)] + pub fn generate_trigger(&mut self, source: TriggerGenerator) { + let regs = unsafe { &*hal::stm32::$TY::ptr() }; + // Note(unsafe) The TriggerGenerator enumeration is specified such that this is + // always in range. + regs.cr2.modify(|_, w| w.mms().bits(source as u8)); + + } + + #[allow(dead_code)] + pub fn set_trigger_source(&mut self, source: TriggerSource) { + let regs = unsafe { &*hal::stm32::$TY::ptr() }; + // Note(unsafe) The TriggerSource enumeration is specified such that this is + // always in range. + regs.smcr.modify(|_, w| unsafe { w.ts().bits(source as u8) } ); + } } pub mod [< $TY:lower >] { - pub use hal::stm32::tim2::ccmr1_input::{CC1S_A, CC2S_A}; - pub use hal::stm32::tim2::ccmr2_input::{CC3S_A, CC4S_A}; - use stm32h7xx_hal as hal; use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq}; use hal::stm32::$TY; @@ -86,10 +151,17 @@ macro_rules! timer_channels { /// Enable DMA requests upon timer updates. #[allow(dead_code)] pub fn listen_dma(&self) { - // Note(unsafe): We perofmr only atomic operations on the timer registers. + // Note(unsafe): We perform only atomic operations on the timer registers. let regs = unsafe { &*<$TY>::ptr() }; regs.dier.modify(|_, w| w.ude().set_bit()); } + + /// Trigger a DMA request manually + #[allow(dead_code)] + pub fn trigger(&self) { + let regs = unsafe { &*<$TY>::ptr() }; + regs.egr.write(|w| w.ug().set_bit()); + } } /// The channels representing the timer. @@ -104,6 +176,7 @@ macro_rules! timer_channels { /// Construct a new set of channels. /// /// Note(unsafe): This is only safe to call once. + #[allow(dead_code)] pub unsafe fn new() -> Self { Self { ch1: Channel1::new(), @@ -114,15 +187,15 @@ macro_rules! timer_channels { } } - timer_channels!(1, $TY, ccmr1); - timer_channels!(2, $TY, ccmr1); - timer_channels!(3, $TY, ccmr2); - timer_channels!(4, $TY, ccmr2); + timer_channels!(1, $TY, ccmr1, $size); + timer_channels!(2, $TY, ccmr1, $size); + timer_channels!(3, $TY, ccmr2, $size); + timer_channels!(4, $TY, ccmr2, $size); } } }; - ($index:expr, $TY:ty, $ccmrx:expr) => { + ($index:expr, $TY:ty, $ccmrx:expr, $size:ty) => { paste::paste! { /// A capture/compare channel of the timer. pub struct [< Channel $index >] {} @@ -135,6 +208,7 @@ macro_rules! timer_channels { /// /// Note(unsafe): This function must only be called once. Once constructed, the /// constructee guarantees to never modify the timer channel. + #[allow(dead_code)] unsafe fn new() -> Self { Self {} } @@ -151,9 +225,10 @@ macro_rules! timer_channels { /// # Args /// * `value` - The value to compare the sampling timer's counter against. #[allow(dead_code)] - pub fn to_output_compare(&self, value: u32) { + pub fn to_output_compare(&self, value: $size) { let regs = unsafe { &*<$TY>::ptr() }; - assert!(value <= regs.arr.read().bits()); + let arr = regs.arr.read().bits() as $size; + assert!(value <= arr); regs.[< ccr $index >].write(|w| w.ccr().bits(value)); regs.[< $ccmrx _output >]() .modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) }); @@ -164,9 +239,12 @@ macro_rules! timer_channels { /// # Args /// * `input` - The input source for the input capture event. #[allow(dead_code)] - pub fn to_input_capture(self, input: hal::stm32::tim2::[< $ccmrx _input >]::[< CC $index S_A >]) -> [< Channel $index InputCapture >]{ + pub fn to_input_capture(self, input: super::CaptureTrigger) -> [< Channel $index InputCapture >]{ let regs = unsafe { &*<$TY>::ptr() }; - regs.[< $ccmrx _input >]().modify(|_, w| w.[< cc $index s>]().variant(input)); + + // Note(unsafe): The bit configuration is guaranteed to be valid by the + // CaptureTrigger enum definition. + regs.[< $ccmrx _input >]().modify(|_, w| unsafe { w.[< cc $index s>]().bits(input as u8) }); [< Channel $index InputCapture >] {} } @@ -175,7 +253,7 @@ 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 { + pub fn latest_capture(&mut self) -> Option<$size> { // 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() }; @@ -221,13 +299,13 @@ macro_rules! timer_channels { // is safe as it is only completed once per channel and each DMA request is allocated to // each channel as the owner. unsafe impl TargetAddress for [< Channel $index InputCapture >] { - type MemSize = u32; + type MemSize = $size; const REQUEST_LINE: Option = Some(DMAReq::[< $TY _CH $index >]as u8); - fn address(&self) -> u32 { + fn address(&self) -> usize { let regs = unsafe { &*<$TY>::ptr() }; - ®s.[] as *const _ as u32 + ®s.[] as *const _ as usize } } } @@ -236,3 +314,4 @@ macro_rules! timer_channels { timer_channels!(SamplingTimer, TIM2, u32); timer_channels!(TimestampTimer, TIM5, u32); +timer_channels!(PounderTimestampTimer, TIM8, u16); From 352884ea0616c87e321e66f590d8b0a746bd93b4 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 15 Dec 2020 13:13:05 +0100 Subject: [PATCH 04/17] Fixing pounder timestamps after manual testing --- ad9959/src/lib.rs | 13 ++++++++++++- src/design_parameters.rs | 9 +++++++++ src/main.rs | 26 +++++++++++++++++++------- src/pounder/mod.rs | 6 ------ src/timers.rs | 10 ++++++---- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index d5de1ec..206d90a 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -172,6 +172,17 @@ impl Ad9959 { // Set the clock frequency to configure the device as necessary. ad9959.configure_system_clock(clock_frequency, multiplier)?; + + // Latch the new clock configuration. + io_update.set_high().or(Err(Error::Pin))?; + + // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to + // guarantee conformance with datasheet requirements. + delay.delay_us(5); + + io_update.set_low().or(Err(Error::Pin))?; + Ok(ad9959) } @@ -195,7 +206,7 @@ impl Ad9959 { /// /// Returns: /// The actual frequency configured for the internal system clock. - pub fn configure_system_clock( + fn configure_system_clock( &mut self, reference_clock_frequency: f32, multiplier: u8, diff --git a/src/design_parameters.rs b/src/design_parameters.rs index 414a9e2..98490df 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -7,3 +7,12 @@ pub const ADC_DAC_SCK_MHZ_MAX: u32 = 50; /// The optimal counting frequency of the hardware timers used for timestamping and sampling. pub const TIMER_FREQUENCY_MHZ: u32 = 100; + +/// The DDS reference clock frequency in MHz. +pub const DDS_REF_CLK_MHZ: u32 = 100; + +/// The multiplier used for the DDS reference clock PLL. +pub const DDS_MULTIPLIER: u8 = 5; + +/// The rate of the DDS SYNC_CLK in MHz is always 1/4 that of the internal PLL clock. +pub const DDS_SYNC_CLK_MHZ: u32 = DDS_REF_CLK_MHZ * DDS_MULTIPLIER as u32 / 4; diff --git a/src/main.rs b/src/main.rs index 30ed33c..18931c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -527,7 +527,7 @@ const APP: () = { delay.delay_ms(2u8); let (pounder_devices, dds_output) = if pounder_pgood.is_high().unwrap() { - let mut ad9959 = { + let ad9959 = { let qspi_interface = { // Instantiate the QUADSPI pins and peripheral interface. let qspi_pins = { @@ -580,8 +580,8 @@ const APP: () = { &mut io_update, &mut delay, ad9959::Mode::FourBitSerial, - 100_000_000_f32, - 5, + design_parameters::DDS_REF_CLK_MHZ as f32 * 1_000_000_f32, + design_parameters::DDS_MULTIPLIER, ) .unwrap(); @@ -660,7 +660,6 @@ const APP: () = { let pounder_devices = pounder::PounderDevices::new( io_expander, - &mut ad9959, spi, adc1, adc2, @@ -686,8 +685,8 @@ const APP: () = { ); // 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 + // 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 @@ -859,8 +858,21 @@ const APP: () = { let tim8 = dp.TIM8.timer(1.khz(), ccdr.peripheral.TIM8, &ccdr.clocks); let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8); + + // Pounder is configured to generate a 400MHz reference clock, so a 125MHz sync-clock is + // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for + // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate. timestamp_timer.set_external_clock(timers::Prescaler::Div4); - timestamp_timer.set_period(128); + timestamp_timer.start(); + + // We want the pounder timestamp timer to overflow once per batch. + let tick_ratio = design_parameters::DDS_SYNC_CLK_MHZ as f32 + / design_parameters::TIMER_FREQUENCY_MHZ as f32; + let period = (tick_ratio + * ADC_SAMPLE_TICKS as f32 + * SAMPLE_BUFFER_SIZE as f32) as u32 + / 4; + timestamp_timer.set_period((period - 1).try_into().unwrap()); let tim8_channels = timestamp_timer.channels(); pounder::timestamp::Timestamper::new( diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 36a73fd..2811e92 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -275,7 +275,6 @@ impl PounderDevices { /// Construct and initialize pounder-specific hardware. /// /// Args: - /// * `ad9959` - The DDS driver for the pounder hardware. /// * `attenuator_spi` - A SPI interface to control digital attenuators. /// * `adc1` - The ADC1 peripheral for measuring power. /// * `adc2` - The ADC2 peripheral for measuring power. @@ -283,7 +282,6 @@ impl PounderDevices { /// * `adc2_in_p` - The input channel for the RF power measurement on IN1. pub fn new( mcp23017: mcp23017::MCP23017>, - ad9959: &mut ad9959::Ad9959, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, adc2: hal::adc::Adc, @@ -315,14 +313,10 @@ impl PounderDevices { .write_gpio(mcp23017::Port::GPIOB, 1 << 5) .map_err(|_| Error::I2c)?; - // Select the on-board clock with a 4x prescaler (400MHz). devices .mcp23017 .digital_write(EXT_CLK_SEL_PIN, false) .map_err(|_| Error::I2c)?; - ad9959 - .configure_system_clock(100_000_000f32, 4) - .map_err(|_| Error::Dds)?; Ok(devices) } diff --git a/src/timers.rs b/src/timers.rs index 1292dab..10f300f 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -29,6 +29,8 @@ pub enum TriggerSource { } pub enum Prescaler { + Div1 = 0b00, + Div2 = 0b01, Div4 = 0b10, Div8 = 0b11, } @@ -100,17 +102,17 @@ macro_rules! timer_channels { let regs = unsafe { &*hal::stm32::$TY::ptr() }; regs.smcr.modify(|_, w| w.etps().bits(prescaler as u8).ece().set_bit()); - // Use a DIV4 prescaler. - + // Clear any other prescaler configuration. + regs.psc.write(|w| w.psc().bits(0)); } /// Start the timer. #[allow(dead_code)] - pub fn start(mut self) { + pub fn start(&mut self) { // Force a refresh of the frequency settings. self.timer.apply_freq(); - self.timer.reset_counter(); + self.timer.resume(); } From 3332a8e927ec9e4ae82926e219096f65c05aa267 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 12:59:24 +0100 Subject: [PATCH 05/17] Updating branch dependencies --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/main.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a2a6f8..42bce0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -517,7 +517,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/dma-buffer-swap/num-transfers#b87614f432a635e904dea2383ff481f3cc002e80" +source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#3da22d4935c8f6e412b99e6662ec11da5265fb88" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 9bfc570..7217589 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,8 +53,8 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/quartiq/stm32h7xx-hal" -branch = "feature/dma-buffer-swap/num-transfers" +git = "https://github.com/stm32-rs/stm32h7xx-hal" +branch = "dma" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/main.rs b/src/main.rs index 18931c1..f9e2db5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -859,7 +859,7 @@ const APP: () = { dp.TIM8.timer(1.khz(), ccdr.peripheral.TIM8, &ccdr.clocks); let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8); - // Pounder is configured to generate a 400MHz reference clock, so a 125MHz sync-clock is + // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate. timestamp_timer.set_external_clock(timers::Prescaler::Div4); From da34756df75f165a98fbfd2b2f90baded314e561 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 13:29:19 +0100 Subject: [PATCH 06/17] Adding support for pounder v1.1 --- Cargo.toml | 1 + src/design_parameters.rs | 6 +++++- src/main.rs | 22 ++++++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7217589..ab9b1b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ branch = "dma" semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] bkpt = [ ] nightly = ["cortex-m/inline-asm", "dsp/nightly"] +pounder_v1_1 = [ ] [profile.dev] codegen-units = 1 diff --git a/src/design_parameters.rs b/src/design_parameters.rs index 98490df..4e2af9d 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -14,5 +14,9 @@ pub const DDS_REF_CLK_MHZ: u32 = 100; /// The multiplier used for the DDS reference clock PLL. pub const DDS_MULTIPLIER: u8 = 5; +/// The DDS system clock frequency after the internal PLL multiplication. +pub const DDS_SYSTEM_CLK_MHZ: u32 = DDS_REF_CLK_MHZ * DDS_MULTIPLIER as u32; + /// The rate of the DDS SYNC_CLK in MHz is always 1/4 that of the internal PLL clock. -pub const DDS_SYNC_CLK_MHZ: u32 = DDS_REF_CLK_MHZ * DDS_MULTIPLIER as u32 / 4; +#[allow(dead_code)] +pub const DDS_SYNC_CLK_MHZ: u32 = DDS_SYSTEM_CLK_MHZ / 4; diff --git a/src/main.rs b/src/main.rs index f9e2db5..a2dd7ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -220,7 +220,8 @@ const APP: () = { mac_addr: net::wire::EthernetAddress, pounder: Option, - pounder_stamper: pounder::timestamp::Timestamper, + + pounder_stamper: Option, // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -571,7 +572,11 @@ const APP: () = { pounder::QspiInterface::new(qspi).unwrap() }; + #[cfg(feature = "pounder_v1_1")] let reset_pin = gpiog.pg6.into_push_pull_output(); + #[cfg(not(feature = "pounder_v1_1"))] + let reset_pin = gpioa.pa0.into_push_pull_output(); + let mut io_update = gpiog.pg7.into_push_pull_output(); let ad9959 = ad9959::Ad9959::new( @@ -850,6 +855,7 @@ const APP: () = { ) }; + #[cfg(feature = "pounder_v1_1")] let pounder_stamper = { let etr_pin = gpioa.pa0.into_alternate_af3(); @@ -875,15 +881,20 @@ const APP: () = { timestamp_timer.set_period((period - 1).try_into().unwrap()); let tim8_channels = timestamp_timer.channels(); - pounder::timestamp::Timestamper::new( + let stamper = pounder::timestamp::Timestamper::new( timestamp_timer, dma_streams.7, tim8_channels.ch1, &mut sampling_timer, etr_pin, - ) + ); + + Some(stamper) }; + #[cfg(not(feature = "pounder_v1_1"))] + let pounder_stamper = None; + // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); @@ -908,7 +919,10 @@ const APP: () = { #[task(binds=DMA1_STR3, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { - let _pounder_timestamps = c.resources.pounder_stamper.acquire_buffer(); + if let Some(stamper) = c.resources.pounder_stamper { + let pounder_timestamps = stamper.acquire_buffer(); + info!("{:?}", pounder_timestamps); + } let adc_samples = [ c.resources.adcs.0.acquire_buffer(), From 8a98428ed417af9dbbbe98f475e38140117de79a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 14:42:36 +0100 Subject: [PATCH 07/17] Adding documentation --- src/pounder/timestamp.rs | 50 ++++++++++++++++++++++++++++++++++++++-- src/timers.rs | 7 ++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/pounder/timestamp.rs b/src/pounder/timestamp.rs index 2431634..0ce0bfd 100644 --- a/src/pounder/timestamp.rs +++ b/src/pounder/timestamp.rs @@ -1,13 +1,40 @@ ///! ADC sample timestamper using external Pounder reference clock. +///! +///! # Design +///! +///! The pounder timestamper utilizes the pounder SYNC_CLK output as a fast external reference clock +///! for recording a timestamp for each of the ADC samples. +///! +///! To accomplish this, a timer peripheral is configured to be driven by an external clock input. +///! Due to the limitations of clock frequencies allowed by the timer peripheral, the SYNC_CLK input +///! is divided by 4. This clock then clocks the timer peripheral in a free-running mode with an ARR +///! (max count register value) configured to overflow once per ADC sample batch. +///! +///! 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. +///! 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 +///! to when the ADC samples the input. +///! +///! Once the input capture is properly configured, a DMA transfer is configured to collect all of +///! timestamps. The DMA transfer collects 1 timestamp for each ADC sample collected. In order to +///! 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 hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer}; use crate::{timers, SAMPLE_BUFFER_SIZE}; +// 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. pub struct Timestamper { next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, timer: timers::PounderTimestampTimer, @@ -20,6 +47,21 @@ pub struct Timestamper { } impl 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 + /// * `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. + /// * `sampling_timer` - The stabilizer ADC sampling timer. + /// * `_clock_input` - The input pin for the external clock from Pounder. + /// + /// # Returns + /// The new pounder timestamper in an operational state. pub fn new( mut timestamp_timer: timers::PounderTimestampTimer, stream: hal::dma::dma::Stream7, @@ -51,8 +93,8 @@ impl Timestamper { Transfer::init( stream, input_capture, - // Note(unsafe): The BUF[0] and BUF[1] is "owned" by this peripheral. - // It shall not be used anywhere else in the module. + // Note(unsafe): BUF[0] and BUF[1] are "owned" by this peripheral. + // They shall not be used anywhere else in the module. unsafe { &mut BUF[0] }, unsafe { Some(&mut BUF[1]) }, config, @@ -63,10 +105,14 @@ impl Timestamper { Self { timer: timestamp_timer, transfer: data_transfer, + + // 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]) }, } } + /// Update the period of the underlying timestamp timer. pub fn update_period(&mut self, period: u16) { self.timer.set_period_ticks(period); } diff --git a/src/timers.rs b/src/timers.rs index 851e6b8..977eed6 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -1,6 +1,7 @@ ///! The sampling timer is used for managing ADC sampling and external reference timestamping. use super::hal; +/// The source of an input capture trigger. #[allow(dead_code)] pub enum CaptureTrigger { Input13 = 0b01, @@ -8,6 +9,7 @@ pub enum CaptureTrigger { TriggerInput = 0b11, } +/// The event that should generate an external trigger from the peripheral. #[allow(dead_code)] pub enum TriggerGenerator { Reset = 0b000, @@ -20,6 +22,7 @@ pub enum TriggerGenerator { Ch4Compare = 0b111, } +/// Selects the trigger source for the timer peripheral. #[allow(dead_code)] pub enum TriggerSource { Trigger0 = 0, @@ -28,6 +31,7 @@ pub enum TriggerSource { Trigger3 = 0b11, } +/// Prescalers for externally-supplied reference clocks. pub enum Prescaler { Div1 = 0b00, Div2 = 0b01, @@ -116,6 +120,8 @@ macro_rules! timer_channels { self.timer.resume(); } + /// Configure the timer peripheral to generate a trigger based on the provided + /// source. #[allow(dead_code)] pub fn generate_trigger(&mut self, source: TriggerGenerator) { let regs = unsafe { &*hal::stm32::$TY::ptr() }; @@ -125,6 +131,7 @@ macro_rules! timer_channels { } + /// Select a trigger source for the timer peripheral. #[allow(dead_code)] pub fn set_trigger_source(&mut self, source: TriggerSource) { let regs = unsafe { &*hal::stm32::$TY::ptr() }; From e9cef7bbaca382a6ea7d6ccf559c440406d7c237 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 14:59:01 +0100 Subject: [PATCH 08/17] Fixing after review --- .github/workflows/ci.yml | 6 ++++++ src/design_parameters.rs | 14 +++++++------- src/main.rs | 18 ++++++++++++++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8e0e4e..9d0c2fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,12 @@ jobs: target/*/release/stabilizer stabilizer-release.bin + - name: Build (Pounder v1.1) + uses: actions-rs/cargo@v1 + with: + command: build + args: --features ${{ matrix.features }} + test: runs-on: ubuntu-latest strategy: diff --git a/src/design_parameters.rs b/src/design_parameters.rs index f6222d2..3788764 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -21,19 +21,19 @@ 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. +// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 500MHz), this corresponds to +// 32ns. 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; +pub const DDS_REF_CLK: MegaHertz = MegaHertz(100); /// The multiplier used for the DDS reference clock PLL. pub const DDS_MULTIPLIER: u8 = 5; /// The DDS system clock frequency after the internal PLL multiplication. -pub const DDS_SYSTEM_CLK_MHZ: u32 = DDS_REF_CLK_MHZ * DDS_MULTIPLIER as u32; +pub const DDS_SYSTEM_CLK: MegaHertz = + MegaHertz(DDS_REF_CLK.0 * DDS_MULTIPLIER as u32); -/// The rate of the DDS SYNC_CLK in MHz is always 1/4 that of the internal PLL clock. -#[allow(dead_code)] -pub const DDS_SYNC_CLK_MHZ: u32 = DDS_SYSTEM_CLK_MHZ / 4; +/// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk). +pub const DDS_SYNC_CLK_DIV: u8 = 4; diff --git a/src/main.rs b/src/main.rs index c01c27d..2684eed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ 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}; @@ -562,13 +564,16 @@ const APP: () = { let mut io_update = gpiog.pg7.into_push_pull_output(); + let ref_clk: hal::time::Hertz = + design_parameters::DDS_REF_CLK.into(); + let ad9959 = ad9959::Ad9959::new( qspi_interface, reset_pin, &mut io_update, &mut delay, ad9959::Mode::FourBitSerial, - design_parameters::DDS_REF_CLK_MHZ as f32 * 1_000_000_f32, + ref_clk.0 as f32, design_parameters::DDS_MULTIPLIER, ) .unwrap(); @@ -850,13 +855,18 @@ const APP: () = { timestamp_timer.start(); // We want the pounder timestamp timer to overflow once per batch. - let tick_ratio = design_parameters::DDS_SYNC_CLK_MHZ as f32 - / design_parameters::TIMER_FREQUENCY_MHZ as f32; + let tick_ratio = { + let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0 + as f32 + / design_parameters::DDS_SYNC_CLK_DIV as f32; + sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32 + }; + let period = (tick_ratio * ADC_SAMPLE_TICKS as f32 * SAMPLE_BUFFER_SIZE as f32) as u32 / 4; - timestamp_timer.set_period((period - 1).try_into().unwrap()); + timestamp_timer.set_period_ticks((period - 1).try_into().unwrap()); let tim8_channels = timestamp_timer.channels(); let stamper = pounder::timestamp::Timestamper::new( From 18068082aca603703aa71a52882961339dc421ae Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:04:06 +0100 Subject: [PATCH 09/17] Fixing CI --- src/design_parameters.rs | 2 ++ src/main.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/design_parameters.rs b/src/design_parameters.rs index 3788764..3edf04b 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -32,8 +32,10 @@ pub const DDS_REF_CLK: MegaHertz = MegaHertz(100); pub const DDS_MULTIPLIER: u8 = 5; /// The DDS system clock frequency after the internal PLL multiplication. +#[allow(dead_code)] pub const DDS_SYSTEM_CLK: MegaHertz = MegaHertz(DDS_REF_CLK.0 * DDS_MULTIPLIER as u32); /// The divider from the DDS system clock to the SYNC_CLK output (sync-clk is always 1/4 of sysclk). +#[allow(dead_code)] pub const DDS_SYNC_CLK_DIV: u8 = 4; diff --git a/src/main.rs b/src/main.rs index 2684eed..7083576 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ extern crate panic_halt; #[macro_use] extern crate log; +#[allow(unused_imports)] use core::convert::TryInto; // use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; From f6062c666e39699a6b65a92d135125650144690a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:13:28 +0100 Subject: [PATCH 10/17] Fixing pounder v1.1 build --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d0c2fe..493d011 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --features ${{ matrix.features }} + args: --features pounder_v1_1 test: runs-on: ubuntu-latest From 72d69960caf2c716598b1bb14bc2c5ff026b7e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 8 Jan 2021 17:28:07 +0100 Subject: [PATCH 11/17] Create hitl.yml --- .github/workflows/hitl.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/hitl.yml diff --git a/.github/workflows/hitl.yml b/.github/workflows/hitl.yml new file mode 100644 index 0000000..8e7caed --- /dev/null +++ b/.github/workflows/hitl.yml @@ -0,0 +1,18 @@ +name: HITL + +on: + push: + branches: [ hitl ] + workflow_dispatch: + +jobs: + cd: + runs-on: ubuntu-latest + environment: hitl + steps: + - uses: peter-evans/repository-dispatch@v1 + with: + token: ${{ secrets.DISPATCH_PAT }} + event-type: hitl + repository: quartiq/hitl + client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' From 96dc13da35fd8686b3b75b7c71f006227c1d50a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 8 Jan 2021 19:04:59 +0100 Subject: [PATCH 12/17] hitl: rename, add badge --- .github/workflows/hitl.yml | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hitl.yml b/.github/workflows/hitl.yml index 8e7caed..4eb16a6 100644 --- a/.github/workflows/hitl.yml +++ b/.github/workflows/hitl.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: jobs: - cd: + hitl: runs-on: ubuntu-latest environment: hitl steps: diff --git a/README.md b/README.md index cf231c0..8ee69f2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org) +[![HITL (private)](https://github.com/quartiq/hitl/workflows/Stabilizer/badge.svg)](https://github.com/quartiq/hitl/actions?query=workflow%3AStabilizer) # Stabilizer Firmware From 09a7ab27738814d772584570590be8e186fd1191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 8 Jan 2021 19:09:42 +0100 Subject: [PATCH 13/17] ci: correctly use stable toolchain --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8e0e4e..155446a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly + toolchain: stable target: thumbv7em-none-eabihf override: true components: rustfmt, clippy @@ -29,7 +29,6 @@ jobs: - uses: actions-rs/clippy-check@v1 continue-on-error: true with: - toolchain: stable token: ${{ secrets.GITHUB_TOKEN }} compile: From f785ec2f51b661d069f45491ecd02951b2c3da16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 8 Jan 2021 19:13:23 +0100 Subject: [PATCH 14/17] hitl: dispatch stabilizer event --- .github/workflows/hitl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hitl.yml b/.github/workflows/hitl.yml index 4eb16a6..3073f66 100644 --- a/.github/workflows/hitl.yml +++ b/.github/workflows/hitl.yml @@ -13,6 +13,6 @@ jobs: - uses: peter-evans/repository-dispatch@v1 with: token: ${{ secrets.DISPATCH_PAT }} - event-type: hitl + event-type: stabilizer repository: quartiq/hitl client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' From d5c21efc9d2e09c3d2acf6978ff70c6fd8d638ae Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 11 Jan 2021 12:31:15 +0100 Subject: [PATCH 15/17] Adding extra DMA transfer to clear TXTF in ADC SPI transfers --- src/adc.rs | 434 ++++++++++++++++++++++++--------------- src/dac.rs | 4 +- src/main.rs | 57 ++++- src/pounder/timestamp.rs | 4 +- src/timers.rs | 16 ++ 5 files changed, 341 insertions(+), 174 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index f6e937e..f6b4506 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -18,11 +18,17 @@ use super::{ Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, }; -// The following data is written by the timer ADC sample trigger into each of the SPI TXFIFOs. Note -// that because the SPI MOSI line is not connected, this data is dont-care. Data in AXI SRAM is not -// initialized on boot, so the contents are random. +// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the +// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is +// initialized during setup. #[link_section = ".axisram.buffers"] -static mut SPI_START: [u16; 1] = [0x00]; +static mut SPI_START: [u32; 1] = [0x00]; + +// The following data is written by the timer flag clear trigger into the SPI IFCR register to clear +// the TXTF flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This +// value is initialized during setup. +#[link_section = ".axisram.buffers"] +static mut SPI_TXTF_CLEAR: [u32; 1] = [0x00]; // The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for // each transfer in a ping-pong buffer configuration (one is being acquired while the other is being @@ -33,176 +39,276 @@ static mut ADC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] = [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2]; macro_rules! adc_input { - ($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, - $spi:ident, $trigger_channel:ident, $dma_req:ident) => { - /// $spi is used as a type for indicating a DMA transfer into the SPI TX FIFO - /// whenever the tim2 update dma request occurs. - struct $spi { - _channel: timers::tim2::$trigger_channel, - } - impl $spi { - pub fn new(_channel: timers::tim2::$trigger_channel) -> Self { - Self { _channel } + ($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident, + $spi:ident, $trigger_channel:ident, $dma_req:ident, $clear_channel:ident, $dma_clear_req:ident) => { + paste::paste! { + /// $spi-CR is used as a type for indicating a DMA transfer into the SPI control + /// register whenever the tim2 update dma request occurs. + struct [< $spi CR >] { + _channel: timers::tim2::$trigger_channel, } - } - - // Note(unsafe): This structure is only safe to instantiate once. The DMA request is hard-coded and - // may only be used if ownership of the timer2 $trigger_channel compare channel is assured, which is - // ensured by maintaining ownership of the channel. - unsafe impl TargetAddress for $spi { - /// SPI is configured to operate using 16-bit transfer words. - type MemSize = u16; - - /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs. - const REQUEST_LINE: Option = Some(DMAReq::$dma_req as u8); - - /// Whenever the DMA request occurs, it should write into SPI's TX FIFO to start a DMA - /// transfer. - fn address(&self) -> usize { - // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is - // only used for the transmit-half of DMA. - let regs = unsafe { &*hal::stm32::$spi::ptr() }; - ®s.txdr as *const _ as usize - } - } - - /// Represents data associated with ADC. - pub struct $name { - next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, - transfer: Transfer< - hal::dma::dma::$data_stream, - hal::spi::Spi, - PeripheralToMemory, - &'static mut [u16; SAMPLE_BUFFER_SIZE], - >, - _trigger_transfer: Transfer< - hal::dma::dma::$trigger_stream, - $spi, - MemoryToPeripheral, - &'static mut [u16; 1], - >, - } - - impl $name { - /// Construct the ADC input channel. - /// - /// # Args - /// * `spi` - The SPI interface used to communicate with the ADC. - /// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by writing a word into - /// the SPI TX FIFO. - /// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer. - /// * `_trigger_channel` - The ADC sampling timer output compare channel for read triggers. - pub fn new( - spi: hal::spi::Spi, - trigger_stream: hal::dma::dma::$trigger_stream< - hal::stm32::DMA1, - >, - data_stream: hal::dma::dma::$data_stream, - trigger_channel: timers::tim2::$trigger_channel, - ) -> Self { - // Generate DMA events when an output compare of the timer hitting zero (timer roll over) - // occurs. - trigger_channel.listen_dma(); - trigger_channel.to_output_compare(0); - - // The trigger stream constantly writes to the TX FIFO using a static word (dont-care - // contents). Thus, neither the memory or peripheral address ever change. This is run in - // circular mode to be completed at every DMA request. - let trigger_config = DmaConfig::default() - .priority(Priority::High) - .circular_buffer(true); - - // Construct the trigger stream to write from memory to the peripheral. - let mut trigger_transfer: Transfer< - _, - _, - MemoryToPeripheral, - _, - > = Transfer::init( - trigger_stream, - $spi::new(trigger_channel), - // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never - // actually modified. It technically only needs to be immutably borrowed, but the - // current HAL API only supports mutable borrows. - unsafe { &mut SPI_START }, - None, - trigger_config, - ); - - // The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral - // stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes - // after the requested number of samples have been collected. Note that only ADC1's (sic!) - // data stream is used to trigger a transfer completion interrupt. - let data_config = DmaConfig::default() - .memory_increment(true) - .transfer_complete_interrupt($index == 1) - .priority(Priority::VeryHigh); - - // A SPI peripheral error interrupt is used to determine if the RX FIFO overflows. This - // indicates that samples were dropped due to excessive processing time in the main - // application (e.g. a second DMA transfer completes before the first was done with - // processing). This is used as a flow control indicator to guarantee that no ADC samples - // are lost. - let mut spi = spi.disable(); - spi.listen(hal::spi::Event::Error); - - // The data transfer is always a transfer of data from the peripheral to a RAM buffer. - let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = - Transfer::init( - data_stream, - spi, - // Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral. - // It shall not be used anywhere else in the module. - unsafe { &mut ADC_BUF[$index][0] }, - None, - data_config, - ); - - data_transfer.start(|spi| { - // Allow the SPI FIFOs to operate using only DMA data channels. - spi.enable_dma_rx(); - spi.enable_dma_tx(); - - // Enable SPI and start it in infinite transaction mode. - spi.inner().cr1.modify(|_, w| w.spe().set_bit()); - spi.inner().cr1.modify(|_, w| w.cstart().started()); - }); - - trigger_transfer.start(|_| {}); - - 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, - _trigger_transfer: trigger_transfer, + impl [< $spi CR >] { + pub fn new(_channel: timers::tim2::$trigger_channel) -> Self { + Self { _channel } } } - /// Obtain a buffer filled with ADC samples. - /// - /// # Returns - /// A reference to the underlying buffer that has been filled with ADC samples. - pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] { - // Wait for the transfer to fully complete before continuing. - // 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() {} + // Note(unsafe): This structure is only safe to instantiate once. The DMA request is + // hard-coded and may only be used if ownership of the timer2 $trigger_channel compare + // channel is assured, which is ensured by maintaining ownership of the channel. + unsafe impl TargetAddress for [< $spi CR >] { - let next_buffer = self.next_buffer.take().unwrap(); + type MemSize = u32; - // Start the next transfer. - self.transfer.clear_interrupts(); - let (prev_buffer, _, _) = - self.transfer.next_transfer(next_buffer).unwrap(); + /// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs. + const REQUEST_LINE: Option = Some(DMAReq::$dma_req as u8); - self.next_buffer.replace(prev_buffer); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633 + /// Whenever the DMA request occurs, it should write into SPI's CR1 to start the + /// transfer. + fn address(&self) -> usize { + // Note(unsafe): It is assumed that SPI is owned by another DMA transfer. This + // is only safe because we are writing to a configuration register. + let regs = unsafe { &*hal::stm32::$spi::ptr() }; + ®s.cr1 as *const _ as usize + } + } - self.next_buffer.as_ref().unwrap() + /// $spi-IFCR is used as a type for indicating a DMA transfer into the SPI flag clear + /// register whenever the tim3 compare dma request occurs. The flag must be cleared + /// before the transfer starts. + struct [< $spi IFCR >] { + _channel: timers::tim3::$clear_channel, + } + + impl [< $spi IFCR >] { + pub fn new(_channel: timers::tim3::$clear_channel) -> Self { + Self { _channel } + } + } + + // Note(unsafe): This structure is only safe to instantiate once. The DMA request is + // hard-coded and may only be used if ownership of the timer3 $clear_channel compare + // channel is assured, which is ensured by maintaining ownership of the channel. + unsafe impl TargetAddress for [< $spi IFCR >] { + type MemSize = u32; + + /// SPI DMA requests are generated whenever TIM3 CHx ($dma_clear_req) comparison + /// occurs. + const REQUEST_LINE: Option = Some(DMAReq::$dma_clear_req as u8); + + /// Whenever the DMA request occurs, it should write into SPI's IFCR to clear the + /// TXTF flag to allow the next transmission. + fn address(&self) -> usize { + // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and + // this DMA is only used for writing to the configuration registers. + let regs = unsafe { &*hal::stm32::$spi::ptr() }; + ®s.ifcr as *const _ as usize + } + } + + /// Represents data associated with ADC. + pub struct $name { + next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, + transfer: Transfer< + hal::dma::dma::$data_stream, + hal::spi::Spi, + PeripheralToMemory, + &'static mut [u16; SAMPLE_BUFFER_SIZE], + >, + _trigger_transfer: Transfer< + hal::dma::dma::$trigger_stream, + [< $spi CR >], + MemoryToPeripheral, + &'static mut [u32; 1], + >, + _flag_clear_transfer: Transfer< + hal::dma::dma::$clear_stream, + [< $spi IFCR >], + MemoryToPeripheral, + &'static mut [u32; 1], + >, + } + + impl $name { + /// Construct the ADC input channel. + /// + /// # Args + /// * `spi` - The SPI interface used to communicate with the ADC. + /// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by + /// writing a word into the SPI TX FIFO. + /// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer. + /// * `clear_stream` - The DMA stream used to clear the TXTF flag in the SPI peripheral. + /// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers. + /// * `clear_channel` - The shadow sampling timer output compare channel used for + /// clearing the SPI TXTF flag. + pub fn new( + spi: hal::spi::Spi, + trigger_stream: hal::dma::dma::$trigger_stream< + hal::stm32::DMA1, + >, + data_stream: hal::dma::dma::$data_stream, + clear_stream: hal::dma::dma::$clear_stream, + trigger_channel: timers::tim2::$trigger_channel, + clear_channel: timers::tim3::$clear_channel, + ) -> Self { + // The flag clear DMA transfer always clears the TXTF flag in the SPI + // peripheral. It has the highest priority to ensure it is completed before the + // transfer trigger. + let clear_config = DmaConfig::default() + .priority(Priority::VeryHigh) + .circular_buffer(true); + + unsafe { + SPI_TXTF_CLEAR[0] = 1 << 4; + } + + // Generate DMA events when the timer hits zero (roll-over). This must be before + // the trigger channel DMA occurs, as if the trigger occurs first, the + // transmission will not occur. + clear_channel.listen_dma(); + clear_channel.to_output_compare(0); + + let mut clear_transfer: Transfer< + _, + _, + MemoryToPeripheral, + _, + > = Transfer::init( + clear_stream, + [< $spi IFCR >]::new(clear_channel), + // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is + // never actually modified. It technically only needs to be immutably + // borrowed, but the current HAL API only supports mutable borrows. + unsafe { &mut SPI_TXTF_CLEAR }, + None, + clear_config, + ); + + // Generate DMA events when an output compare of the timer hits the specified + // value. + trigger_channel.listen_dma(); + trigger_channel.to_output_compare(1); + + // The trigger stream constantly writes to the SPI CR1 using a static word + // (which is a static value to enable the SPI transfer). Thus, neither the + // memory or peripheral address ever change. This is run in circular mode to be + // completed at every DMA request. + let trigger_config = DmaConfig::default() + .priority(Priority::High) + .circular_buffer(true); + + // Note(unsafe): This word is initialized once per ADC initialization to verify + // it is initialized properly. + unsafe { + // Write a binary code into the SPI control register to initiate a transfer. + SPI_START[0] = 0x201; + }; + + // Construct the trigger stream to write from memory to the peripheral. + let mut trigger_transfer: Transfer< + _, + _, + MemoryToPeripheral, + _, + > = Transfer::init( + trigger_stream, + [< $spi CR >]::new(trigger_channel), + // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never + // actually modified. It technically only needs to be immutably borrowed, but the + // current HAL API only supports mutable borrows. + unsafe { &mut SPI_START }, + None, + trigger_config, + ); + + // The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral + // stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes + // after the requested number of samples have been collected. Note that only ADC1's (sic!) + // data stream is used to trigger a transfer completion interrupt. + let data_config = DmaConfig::default() + .memory_increment(true) + .transfer_complete_interrupt($index == 1) + .priority(Priority::VeryHigh); + + // A SPI peripheral error interrupt is used to determine if the RX FIFO + // overflows. This indicates that samples were dropped due to excessive + // processing time in the main application (e.g. a second DMA transfer completes + // before the first was done with processing). This is used as a flow control + // indicator to guarantee that no ADC samples are lost. + let mut spi = spi.disable(); + spi.listen(hal::spi::Event::Error); + + // The data transfer is always a transfer of data from the peripheral to a RAM + // buffer. + let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = + Transfer::init( + data_stream, + spi, + // Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral. + // It shall not be used anywhere else in the module. + unsafe { &mut ADC_BUF[$index][0] }, + None, + data_config, + ); + + data_transfer.start(|spi| { + // Allow the SPI RX FIFO to generate DMA transfer requests when data is + // available. + spi.enable_dma_rx(); + + // Each transaction is 1 word (16 bytes). + spi.inner().cr2.modify(|_, w| w.tsize().bits(1)); + + // Enable SPI and start it in infinite transaction mode. + spi.inner().cr1.modify(|_, w| w.spe().set_bit()); + spi.inner().cr1.modify(|_, w| w.cstart().started()); + }); + + clear_transfer.start(|_| {}); + trigger_transfer.start(|_| {}); + + 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, + _trigger_transfer: trigger_transfer, + _flag_clear_transfer: clear_transfer, + } + } + + /// Obtain a buffer filled with ADC samples. + /// + /// # Returns + /// A reference to the underlying buffer that has been filled with ADC samples. + pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] { + // Wait for the transfer to fully complete before continuing. 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. + 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() + } } } }; } -adc_input!(Adc0Input, 0, Stream0, Stream1, SPI2, Channel1, TIM2_CH1); -adc_input!(Adc1Input, 1, Stream2, Stream3, SPI3, Channel2, TIM2_CH2); +adc_input!( + Adc0Input, 0, Stream0, Stream1, Stream2, SPI2, Channel1, TIM2_CH1, + Channel1, TIM3_CH1 +); +adc_input!( + Adc1Input, 1, Stream3, Stream4, Stream5, SPI3, Channel2, TIM2_CH2, + Channel2, TIM3_CH2 +); diff --git a/src/dac.rs b/src/dac.rs index abea097..8c93f74 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -153,5 +153,5 @@ macro_rules! dac_output { }; } -dac_output!(Dac0Output, 0, Stream4, SPI4, Channel3, TIM2_CH3); -dac_output!(Dac1Output, 1, Stream5, SPI5, Channel4, TIM2_CH4); +dac_output!(Dac0Output, 0, Stream6, SPI4, Channel3, TIM2_CH3); +dac_output!(Dac1Output, 1, Stream7, SPI5, Channel4, TIM2_CH4); diff --git a/src/main.rs b/src/main.rs index 7083576..f2b7c7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ 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. // Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -const ADC_SAMPLE_TICKS: u32 = 256; +const ADC_SAMPLE_TICKS: u16 = 256; // The desired ADC sample processing buffer size. const SAMPLE_BUFFER_SIZE: usize = 8; @@ -301,12 +301,44 @@ const APP: () = { timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); let mut sampling_timer = timers::SamplingTimer::new(timer2); - sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); + sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32); + + // The sampling timer is used as the master timer for the shadow-sampling timer. Thus, + // it generates a trigger whenever it is enabled. sampling_timer }; + let mut shadow_sampling_timer = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer3 = + dp.TIM3.timer(1.khz(), ccdr.peripheral.TIM3, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer3.pause(); + timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + let mut shadow_sampling_timer = + timers::ShadowSamplingTimer::new(timer3); + shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); + + // The shadow sampling timer is a slave-mode timer to the sampling timer. It should + // always be in-sync - thus, we configure it to operate in slave mode using "Trigger + // mode". + // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the + // SamplingTimer start now gates the start of the ShadowSamplingTimer. + shadow_sampling_timer.set_slave_mode( + timers::TriggerSource::Trigger1, + timers::SlaveMode::Trigger, + ); + + shadow_sampling_timer + }; + let sampling_timer_channels = sampling_timer.channels(); + let shadow_sampling_timer_channels = shadow_sampling_timer.channels(); let mut timestamp_timer = { // The timer frequency is manually adjusted below, so the 1KHz setting here is a @@ -355,6 +387,7 @@ const APP: () = { }) .manage_cs() .suspend_when_inactive() + .communication_mode(hal::spi::CommunicationMode::Receiver) .cs_delay(design_parameters::ADC_SETUP_TIME); let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi( @@ -369,7 +402,9 @@ const APP: () = { spi, dma_streams.0, dma_streams.1, + dma_streams.2, sampling_timer_channels.ch1, + shadow_sampling_timer_channels.ch1, ) }; @@ -393,6 +428,7 @@ const APP: () = { }) .manage_cs() .suspend_when_inactive() + .communication_mode(hal::spi::CommunicationMode::Receiver) .cs_delay(design_parameters::ADC_SETUP_TIME); let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi( @@ -405,9 +441,11 @@ const APP: () = { Adc1Input::new( spi, - dma_streams.2, dma_streams.3, + dma_streams.4, + dma_streams.5, sampling_timer_channels.ch2, + shadow_sampling_timer_channels.ch2, ) }; @@ -488,12 +526,12 @@ const APP: () = { let dac0 = Dac0Output::new( dac0_spi, - dma_streams.4, + dma_streams.6, sampling_timer_channels.ch3, ); let dac1 = Dac1Output::new( dac1_spi, - dma_streams.5, + dma_streams.7, sampling_timer_channels.ch4, ); (dac0, dac1) @@ -841,6 +879,9 @@ const APP: () = { #[cfg(feature = "pounder_v1_1")] let pounder_stamper = { + let dma2_streams = + hal::dma::dma::StreamsTuple::new(dp.DMA2, ccdr.peripheral.DMA2); + let etr_pin = gpioa.pa0.into_alternate_af3(); // The frequency in the constructor is dont-care, as we will modify the period + clock @@ -872,7 +913,7 @@ const APP: () = { let stamper = pounder::timestamp::Timestamper::new( timestamp_timer, - dma_streams.7, + dma2_streams.0, tim8_channels.ch1, &mut sampling_timer, etr_pin, @@ -884,6 +925,10 @@ const APP: () = { #[cfg(not(feature = "pounder_v1_1"))] let pounder_stamper = None; + // Force an update of the shadow sampling timer configuration and enable it. It will not + // start counting until the sampling timer starts due to the slave mode configuration. + shadow_sampling_timer.start(); + // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); diff --git a/src/pounder/timestamp.rs b/src/pounder/timestamp.rs index 0ce0bfd..f6c1a14 100644 --- a/src/pounder/timestamp.rs +++ b/src/pounder/timestamp.rs @@ -39,7 +39,7 @@ pub struct Timestamper { next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>, timer: timers::PounderTimestampTimer, transfer: Transfer< - hal::dma::dma::Stream7, + hal::dma::dma::Stream0, timers::tim8::Channel1InputCapture, PeripheralToMemory, &'static mut [u16; SAMPLE_BUFFER_SIZE], @@ -64,7 +64,7 @@ impl Timestamper { /// The new pounder timestamper in an operational state. pub fn new( mut timestamp_timer: timers::PounderTimestampTimer, - stream: hal::dma::dma::Stream7, + stream: hal::dma::dma::Stream0, capture_channel: timers::tim8::Channel1, sampling_timer: &mut timers::SamplingTimer, _clock_input: hal::gpio::gpioa::PA0< diff --git a/src/timers.rs b/src/timers.rs index 977eed6..fa6ae15 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -39,6 +39,12 @@ pub enum Prescaler { Div8 = 0b11, } +/// Optional slave operation modes of a timer. +pub enum SlaveMode { + Disabled = 0, + Trigger = 0b0110, +} + macro_rules! timer_channels { ($name:ident, $TY:ident, $size:ty) => { paste::paste! { @@ -139,6 +145,14 @@ macro_rules! timer_channels { // always in range. regs.smcr.modify(|_, w| unsafe { w.ts().bits(source as u8) } ); } + + #[allow(dead_code)] + pub fn set_slave_mode(&mut self, source: TriggerSource, mode: SlaveMode) { + let regs = unsafe { &*hal::stm32::$TY::ptr() }; + // Note(unsafe) The TriggerSource and SlaveMode enumerations are specified such + // that they are always in range. + regs.smcr.modify(|_, w| unsafe { w.sms().bits(mode as u8).ts().bits(source as u8) } ); + } } pub mod [< $TY:lower >] { @@ -333,5 +347,7 @@ macro_rules! timer_channels { } timer_channels!(SamplingTimer, TIM2, u32); +timer_channels!(ShadowSamplingTimer, TIM3, u16); + timer_channels!(TimestampTimer, TIM5, u32); timer_channels!(PounderTimestampTimer, TIM8, u16); From 91975993cf28ec819d850e65db8cfa3a96a13361 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 11 Jan 2021 12:38:20 +0100 Subject: [PATCH 16/17] Fixing docs --- src/adc.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index f6b4506..3e60a0d 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,9 +1,13 @@ ///! Stabilizer ADC management interface ///! -///! The Stabilizer ADCs utilize a DMA channel to trigger sampling. The SPI streams are configured -///! for full-duplex operation, but only RX is connected to physical pins. A timer channel is -///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and -///! results in an ADC sample read for both channels. +///! The Stabilizer ADCs utilize three DMA channels: one to trigger sampling, one to collect +///! samples, and one to clear the TXTF flag betwen samples. The SPI interfaces are configured +///! for receiver-only operation. A timer channel is +///! configured to generate a DMA write into the SPI CR1 register, which initiates a SPI transfer and +///! results in a single ADC sample read for both channels. A separate timer channel is configured to +///! occur immediately before the trigger channel, which initiates a write to the IFCR (flag-clear) +///! register to clear the TXTF flag, which allows for a new transmission to be generated by the +///! trigger channel. ///! ///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is ///! configured to read from each of the ADC SPI RX FIFOs. Due to the design of the SPI peripheral, From 6b170c25edc27da458c7f4639e8f69d28edd4792 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 12 Jan 2021 13:29:15 +0100 Subject: [PATCH 17/17] Fixing timing synchronization --- src/adc.rs | 27 ++++++++++++--------------- src/main.rs | 8 +++----- src/timers.rs | 3 +++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index 3e60a0d..b4d4359 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,12 +1,12 @@ ///! Stabilizer ADC management interface ///! ///! The Stabilizer ADCs utilize three DMA channels: one to trigger sampling, one to collect -///! samples, and one to clear the TXTF flag betwen samples. The SPI interfaces are configured +///! samples, and one to clear the EOT flag betwen samples. The SPI interfaces are configured ///! for receiver-only operation. A timer channel is ///! configured to generate a DMA write into the SPI CR1 register, which initiates a SPI transfer and ///! results in a single ADC sample read for both channels. A separate timer channel is configured to ///! occur immediately before the trigger channel, which initiates a write to the IFCR (flag-clear) -///! register to clear the TXTF flag, which allows for a new transmission to be generated by the +///! register to clear the EOT flag, which allows for a new transmission to be generated by the ///! trigger channel. ///! ///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is @@ -26,13 +26,13 @@ use super::{ // transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is // initialized during setup. #[link_section = ".axisram.buffers"] -static mut SPI_START: [u32; 1] = [0x00]; +static mut SPI_START: [u32; 1] = [0x00; 1]; // The following data is written by the timer flag clear trigger into the SPI IFCR register to clear -// the TXTF flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This +// the EOT flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This // value is initialized during setup. #[link_section = ".axisram.buffers"] -static mut SPI_TXTF_CLEAR: [u32; 1] = [0x00]; +static mut SPI_EOT_CLEAR: [u32; 1] = [0x00]; // The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for // each transfer in a ping-pong buffer configuration (one is being acquired while the other is being @@ -101,7 +101,7 @@ macro_rules! adc_input { const REQUEST_LINE: Option = Some(DMAReq::$dma_clear_req as u8); /// Whenever the DMA request occurs, it should write into SPI's IFCR to clear the - /// TXTF flag to allow the next transmission. + /// EOT flag to allow the next transmission. fn address(&self) -> usize { // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and // this DMA is only used for writing to the configuration registers. @@ -141,10 +141,10 @@ macro_rules! adc_input { /// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by /// writing a word into the SPI TX FIFO. /// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer. - /// * `clear_stream` - The DMA stream used to clear the TXTF flag in the SPI peripheral. + /// * `clear_stream` - The DMA stream used to clear the EOT flag in the SPI peripheral. /// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers. /// * `clear_channel` - The shadow sampling timer output compare channel used for - /// clearing the SPI TXTF flag. + /// clearing the SPI EOT flag. pub fn new( spi: hal::spi::Spi, trigger_stream: hal::dma::dma::$trigger_stream< @@ -155,7 +155,7 @@ macro_rules! adc_input { trigger_channel: timers::tim2::$trigger_channel, clear_channel: timers::tim3::$clear_channel, ) -> Self { - // The flag clear DMA transfer always clears the TXTF flag in the SPI + // The flag clear DMA transfer always clears the EOT flag in the SPI // peripheral. It has the highest priority to ensure it is completed before the // transfer trigger. let clear_config = DmaConfig::default() @@ -163,7 +163,7 @@ macro_rules! adc_input { .circular_buffer(true); unsafe { - SPI_TXTF_CLEAR[0] = 1 << 4; + SPI_EOT_CLEAR[0] = 1 << 3; } // Generate DMA events when the timer hits zero (roll-over). This must be before @@ -183,7 +183,7 @@ macro_rules! adc_input { // Note(unsafe): Because this is a Memory->Peripheral transfer, this data is // never actually modified. It technically only needs to be immutably // borrowed, but the current HAL API only supports mutable borrows. - unsafe { &mut SPI_TXTF_CLEAR }, + unsafe { &mut SPI_EOT_CLEAR }, None, clear_config, ); @@ -191,7 +191,7 @@ macro_rules! adc_input { // Generate DMA events when an output compare of the timer hits the specified // value. trigger_channel.listen_dma(); - trigger_channel.to_output_compare(1); + trigger_channel.to_output_compare(2); // The trigger stream constantly writes to the SPI CR1 using a static word // (which is a static value to enable the SPI transfer). Thus, neither the @@ -262,10 +262,7 @@ macro_rules! adc_input { // Each transaction is 1 word (16 bytes). spi.inner().cr2.modify(|_, w| w.tsize().bits(1)); - - // Enable SPI and start it in infinite transaction mode. spi.inner().cr1.modify(|_, w| w.spe().set_bit()); - spi.inner().cr1.modify(|_, w| w.cstart().started()); }); clear_transfer.start(|_| {}); diff --git a/src/main.rs b/src/main.rs index f2b7c7a..e2de7d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -298,6 +298,7 @@ const APP: () = { // Configure the timer to count at the designed tick rate. We will manually set the // period below. timer2.pause(); + timer2.reset_counter(); timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); let mut sampling_timer = timers::SamplingTimer::new(timer2); @@ -318,6 +319,7 @@ const APP: () = { // Configure the timer to count at the designed tick rate. We will manually set the // period below. timer3.pause(); + timer3.reset_counter(); timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY); let mut shadow_sampling_timer = @@ -925,10 +927,6 @@ const APP: () = { #[cfg(not(feature = "pounder_v1_1"))] let pounder_stamper = None; - // Force an update of the shadow sampling timer configuration and enable it. It will not - // start counting until the sampling timer starts due to the slave mode configuration. - shadow_sampling_timer.start(); - // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); @@ -951,7 +949,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR3, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] + #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { if let Some(stamper) = c.resources.pounder_stamper { let pounder_timestamps = stamper.acquire_buffer(); diff --git a/src/timers.rs b/src/timers.rs index fa6ae15..062eb4b 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -98,6 +98,9 @@ macro_rules! timer_channels { pub fn set_period_ticks(&mut self, period: $size) { let regs = unsafe { &*hal::stm32::$TY::ptr() }; regs.arr.write(|w| w.arr().bits(period)); + + // Force the new period to take effect immediately. + self.timer.apply_freq(); } /// Clock the timer from an external source.