From d5c21efc9d2e09c3d2acf6978ff70c6fd8d638ae Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 11 Jan 2021 12:31:15 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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. From db3a42a7b925e8f2caf5b5e60f6fa86bf3a328ad Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 12 Jan 2021 06:54:16 -0800 Subject: [PATCH 4/4] Update src/adc.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- src/adc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adc.rs b/src/adc.rs index b4d4359..6c1bf34 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,6 +1,6 @@ ///! Stabilizer ADC management interface ///! -///! The Stabilizer ADCs utilize three DMA channels: one to trigger sampling, one to collect +///! The Stabilizer ADCs utilize three DMA channels each: one to trigger sampling, one to collect ///! 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