From 3a59f3e989c125d886f4a7d9b29ed1eb60aa71c8 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 11 Nov 2020 18:28:48 +0100 Subject: [PATCH] Adding WIP updates for digital input stamping --- Cargo.lock | 1 - Cargo.toml | 5 +- src/adc.rs | 22 +++++-- src/digital_input_stamper.rs | 84 ++++++++++++++++++++++++++ src/hrtimer.rs | 4 +- src/main.rs | 77 ++++++++++++++++-------- src/pounder/mod.rs | 6 +- src/sampling_timer.rs | 112 +++++++++++++++++++++++++++++++++++ 8 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 src/digital_input_stamper.rs create mode 100644 src/sampling_timer.rs diff --git a/Cargo.lock b/Cargo.lock index f247a6c..c6c3f58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,7 +501,6 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/dma-rtic-example#d8cb6fa5099282665f5e5068a9dcdc9ebaa63240" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 049e61c..5b41667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,9 @@ path = "ad9959" [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/quartiq/stm32h7xx-hal" -branch = "feature/dma-rtic-example" +# git = "https://github.com/quartiq/stm32h7xx-hal" +# branch = "feature/dma-rtic-example" +path = "../stm32h7xx-hal" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/adc.rs b/src/adc.rs index e3310f4..9971fbb 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -14,8 +14,8 @@ ///! both transfers are completed before reading the data. This is usually not significant for ///! busy-waiting because the transfers should complete at approximately the same time. use super::{ - hal, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, Priority, - TargetAddress, Transfer, + hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, + PeripheralToMemory, Priority, TargetAddress, Transfer, }; // The desired ADC input buffer size. This is use configurable. @@ -142,11 +142,18 @@ impl Adc0Input { /// * `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::Stream0, data_stream: hal::dma::dma::Stream1, + trigger_channel: sampling_timer::Timer2Channel1, ) -> 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. @@ -224,7 +231,7 @@ impl Adc0Input { // Start the next transfer. self.transfer.clear_interrupts(); - let (prev_buffer, _) = + let (prev_buffer, _, _) = self.transfer.next_transfer(next_buffer).unwrap(); self.next_buffer.replace(prev_buffer); @@ -256,11 +263,18 @@ impl Adc1Input { /// * `spi` - The SPI interface connected to ADC1. /// * `trigger_stream` - The DMA stream used to trigger ADC conversions on the SPI interface. /// * `data_stream` - The DMA stream used to read ADC samples from the SPI RX FIFO. + /// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers. pub fn new( spi: hal::spi::Spi, trigger_stream: hal::dma::dma::Stream2, data_stream: hal::dma::dma::Stream3, + trigger_channel: sampling_timer::Timer2Channel2, ) -> 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. @@ -339,7 +353,7 @@ impl Adc1Input { // Start the next transfer. self.transfer.clear_interrupts(); - let (prev_buffer, _) = + let (prev_buffer, _, _) = self.transfer.next_transfer(next_buffer).unwrap(); self.next_buffer.replace(prev_buffer); diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs new file mode 100644 index 0000000..88b1c58 --- /dev/null +++ b/src/digital_input_stamper.rs @@ -0,0 +1,84 @@ +use super::{hal, sampling_timer, DmaConfig, PeripheralToMemory, Transfer}; + +const INPUT_BUFFER_SIZE: usize = 1; + +#[link_section = ".axisram.buffers"] +static mut BUF0: [u16; INPUT_BUFFER_SIZE] = [0; INPUT_BUFFER_SIZE]; + +#[link_section = ".axisram.buffers"] +static mut BUF1: [u16; INPUT_BUFFER_SIZE] = [0; INPUT_BUFFER_SIZE]; + +pub struct InputStamper { + _di0_trigger: hal::gpio::gpioa::PA3>, + timestamp_buffer: heapless::Vec, + next_buffer: Option<&'static mut [u16; INPUT_BUFFER_SIZE]>, + transfer: Transfer< + hal::dma::dma::Stream4, + sampling_timer::Timer2Channel4, + PeripheralToMemory, + &'static mut [u16; INPUT_BUFFER_SIZE], + >, +} + +impl InputStamper { + pub fn new( + trigger: hal::gpio::gpioa::PA3>, + stream: hal::dma::dma::Stream4, + timer_channel: sampling_timer::Timer2Channel4, + ) -> Self { + // Utilize the TIM2 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the + // capture source. + timer_channel.listen_dma(); + timer_channel.to_input_capture(sampling_timer::CC4S_A::TI4); + + // Set up the DMA transfer. + let dma_config = DmaConfig::default() + .memory_increment(true) + .peripheral_increment(false); + + let mut timestamp_transfer: Transfer<_, _, PeripheralToMemory, _> = + Transfer::init( + stream, + timer_channel, + unsafe { &mut BUF0 }, + None, + dma_config, + ); + + timestamp_transfer.start(|_| {}); + + Self { + timestamp_buffer: heapless::Vec::new(), + next_buffer: unsafe { Some(&mut BUF1) }, + transfer: timestamp_transfer, + _di0_trigger: trigger, + } + } + + pub fn transfer_complete_handler(&mut self) { + let next_buffer = self.next_buffer.take().unwrap(); + self.transfer.clear_interrupts(); + let (prev_buffer, _, remaining_transfers) = + self.transfer.next_transfer(next_buffer).unwrap(); + + let valid_count = prev_buffer.len() - remaining_transfers; + self.timestamp_buffer + .extend_from_slice(&prev_buffer[..valid_count]) + .unwrap(); + + self.next_buffer.replace(prev_buffer); + } + + pub fn with_timestamps(&mut self, f: F) + where + F: FnOnce(&[u16]), + { + // First, run the transfer complete handler to retrieve any timestamps that are pending in + // the DMA transfer. + self.transfer_complete_handler(); + + f(self.timestamp_buffer.as_ref()); + + self.timestamp_buffer.clear(); + } +} diff --git a/src/hrtimer.rs b/src/hrtimer.rs index d344396..47ea5c2 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -47,7 +47,8 @@ impl HighResTimerE { let minimum_duration = set_duration + set_offset; let source_frequency: u32 = self.clocks.timy_ker_ck().0; - let source_cycles = (minimum_duration * source_frequency as f32) as u32 + 1; + let source_cycles = + (minimum_duration * source_frequency as f32) as u32 + 1; // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that // allows us the highest resolution per tick, so lower dividers are favored. @@ -92,7 +93,6 @@ impl HighResTimerE { } } - // Enable the timer now that it is configured. self.master.mcr.modify(|_, w| w.tecen().set_bit()); } diff --git a/src/main.rs b/src/main.rs index f1d6541..d8a5d7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,12 @@ static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); mod adc; mod afe; mod dac; +mod digital_input_stamper; mod eeprom; mod hrtimer; mod iir; mod pounder; +mod sampling_timer; mod server; use adc::{Adc0Input, Adc1Input, AdcInputs}; @@ -185,11 +187,10 @@ const APP: () = { adcs: AdcInputs, dacs: DacOutputs, + input_stamper: digital_input_stamper::InputStamper, eeprom_i2c: hal::i2c::I2c, - timer: hal::timer::Timer, - profiles: heapless::spsc::Queue<[u32; 4], heapless::consts::U32>, // Note: It appears that rustfmt generates a format that GDB cannot recognize, which @@ -267,6 +268,16 @@ const APP: () = { let dma_streams = hal::dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); + // Configure timer 2 to trigger conversions for the ADC + let timer2 = dp.TIM2.timer( + SAMPLE_FREQUENCY_KHZ.khz(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + + let mut sampling_timer = sampling_timer::SamplingTimer::new(timer2); + let sampling_timer_channels = sampling_timer.channels(); + // Configure the SPI interfaces to the ADCs and DACs. let adcs = { let adc0 = { @@ -299,7 +310,12 @@ const APP: () = { &ccdr.clocks, ); - Adc0Input::new(spi, dma_streams.0, dma_streams.1) + Adc0Input::new( + spi, + dma_streams.0, + dma_streams.1, + sampling_timer_channels.ch1, + ) }; let adc1 = { @@ -332,7 +348,12 @@ const APP: () = { &ccdr.clocks, ); - Adc1Input::new(spi, dma_streams.2, dma_streams.3) + Adc1Input::new( + spi, + dma_streams.2, + dma_streams.3, + sampling_timer_channels.ch2, + ) }; AdcInputs::new(adc0, adc1) @@ -478,9 +499,7 @@ const APP: () = { }; let mut reset_pin = gpioa.pa0.into_push_pull_output(); - let mut io_update = gpiog - .pg7 - .into_push_pull_output(); + let mut io_update = gpiog.pg7.into_push_pull_output(); let ad9959 = ad9959::Ad9959::new( qspi_interface, @@ -736,22 +755,17 @@ const APP: () = { // Utilize the cycle counter for RTIC scheduling. cp.DWT.enable_cycle_counter(); - // Configure timer 2 to trigger conversions for the ADC - let timer2 = dp.TIM2.timer( - SAMPLE_FREQUENCY_KHZ.khz(), - ccdr.peripheral.TIM2, - &ccdr.clocks, - ); - { - // Listen to the CH1 and CH2 comparison events. These channels should have a value of - // zero loaded into them, so the event should occur whenever the timer overflows. Note - // that we use channels instead of timer updates because each SPI DMA transfer needs a - // unique request line. - let t2_regs = unsafe { &*hal::stm32::TIM2::ptr() }; - t2_regs - .dier - .modify(|_, w| w.cc1de().set_bit().cc2de().set_bit()); - } + let input_stamper = { + let trigger = gpioa.pa3.into_alternate_af1(); + digital_input_stamper::InputStamper::new( + trigger, + dma_streams.4, + sampling_timer_channels.ch4, + ) + }; + + // Start sampling ADCs. + sampling_timer.start(); init::LateResources { afe0: afe0, @@ -760,7 +774,8 @@ const APP: () = { adcs, dacs, - timer: timer2, + input_stamper, + pounder: pounder_devices, eeprom_i2c, @@ -772,6 +787,11 @@ const APP: () = { } } + #[task(binds=DMA1_STR4, resources=[input_stamper], priority = 2)] + fn digital_stamper(c: digital_stamper::Context) { + let _timestamps = c.resources.input_stamper.transfer_complete_handler(); + } + #[task(binds = TIM3, resources=[dacs, profiles, pounder], priority = 3)] fn dac_update(c: dac_update::Context) { c.resources.dacs.update(); @@ -812,10 +832,15 @@ const APP: () = { c.resources.pounder.lock(|pounder| { if let Some(pounder) = pounder { profiles.lock(|profiles| { - let profile = pounder.ad9959.serialize_profile(pounder::Channel::Out0.into(), + let profile = pounder + .ad9959 + .serialize_profile( + pounder::Channel::Out0.into(), 100_000_000_f32, 0.0_f32, - *adc0 as f32 / 0xFFFF as f32).unwrap(); + *adc0 as f32 / 0xFFFF as f32, + ) + .unwrap(); profiles.enqueue(profile).unwrap(); }); diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 0e32a22..c19096d 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -124,9 +124,9 @@ impl QspiInterface { unsafe { qspi_regs.dlr.write(|w| w.dl().bits(0xFFFF_FFFF)); - qspi_regs - .ccr - .modify(|_, w| w.imode().bits(0).fmode().bits(0).admode().bits(0)); + qspi_regs.ccr.modify(|_, w| { + w.imode().bits(0).fmode().bits(0).admode().bits(0) + }); } self.streaming = true; diff --git a/src/sampling_timer.rs b/src/sampling_timer.rs new file mode 100644 index 0000000..f299ad6 --- /dev/null +++ b/src/sampling_timer.rs @@ -0,0 +1,112 @@ +use super::hal; + +use hal::dma::{dma::DMAReq, traits::TargetAddress, PeripheralToMemory}; +pub use hal::stm32::tim2::ccmr2_input::CC4S_A; + +pub struct SamplingTimer { + timer: hal::timer::Timer, + channels: Option, +} + +impl SamplingTimer { + pub fn new(mut timer: hal::timer::Timer) -> Self { + timer.pause(); + + Self { + timer, + channels: Some(TimerChannels::new()), + } + } + + pub fn channels(&mut self) -> TimerChannels { + self.channels.take().unwrap() + } + + pub fn start(&mut self) { + self.timer.reset_counter(); + self.timer.resume(); + } +} + +pub struct TimerChannels { + pub ch1: Timer2Channel1, + pub ch2: Timer2Channel2, + pub ch3: Timer2Channel3, + pub ch4: Timer2Channel4, +} + +impl TimerChannels { + fn new() -> Self { + Self { + ch1: Timer2Channel1 {}, + ch2: Timer2Channel2 {}, + ch3: Timer2Channel3 {}, + ch4: Timer2Channel4 {}, + } + } +} + +pub struct Timer2Channel1 {} + +impl Timer2Channel1 { + pub fn listen_dma(&self) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + regs.dier.modify(|_, w| w.cc1de().set_bit()); + } + + pub fn to_output_compare(&self, value: u32) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + assert!(value <= regs.arr.read().bits()); + regs.ccr1.write(|w| w.ccr().bits(value)); + regs.ccmr1_output() + .modify(|_, w| unsafe { w.cc1s().bits(0) }); + } +} + +pub struct Timer2Channel2 {} + +impl Timer2Channel2 { + pub fn listen_dma(&self) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + regs.dier.modify(|_, w| w.cc2de().set_bit()); + } + + pub fn to_output_compare(&self, value: u32) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + assert!(value <= regs.arr.read().bits()); + regs.ccr2.write(|w| w.ccr().bits(value)); + regs.ccmr1_output() + .modify(|_, w| unsafe { w.cc2s().bits(0) }); + } +} + +pub struct Timer2Channel3 {} + +pub struct Timer2Channel4 {} + +unsafe impl TargetAddress for Timer2Channel4 { + type MemSize = u16; + + const REQUEST_LINE: Option = Some(DMAReq::TIM2_CH4 as u8); + + fn address(&self) -> u32 { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + ®s.dmar as *const _ as u32 + } +} + +impl Timer2Channel4 { + pub fn listen_dma(&self) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + regs.dier.modify(|_, w| w.cc4de().set_bit()); + } + + pub fn to_input_capture(&self, trig: CC4S_A) { + let regs = unsafe { &*hal::stm32::TIM2::ptr() }; + regs.ccmr2_input().modify(|_, w| w.cc4s().variant(trig)); + + // Update the DMA control burst regs to point to CCR4. + regs.dcr + .modify(|_, w| unsafe { w.dbl().bits(1).dba().bits(16) }); + } +}