diff --git a/Cargo.lock b/Cargo.lock index e35ab18..030ff99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#0bfeeca4ce120c1b7c6d140a7da73a4372b874d8" +source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#25ee0f3a9ae27d1fd6bb390d6045aa312f29f096" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/src/adc.rs b/src/adc.rs index e9120aa..8d2b61a 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, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, - PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, + hal, timers, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, + Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, }; // The following data is written by the timer ADC sample trigger into each of the SPI TXFIFOs. Note @@ -38,12 +38,10 @@ macro_rules! adc_input { /// $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: sampling_timer::tim2::$trigger_channel, + _channel: timers::tim2::$trigger_channel, } impl $spi { - pub fn new( - _channel: sampling_timer::tim2::$trigger_channel, - ) -> Self { + pub fn new(_channel: timers::tim2::$trigger_channel) -> Self { Self { _channel } } } @@ -100,7 +98,7 @@ macro_rules! adc_input { hal::stm32::DMA1, >, data_stream: hal::dma::dma::$data_stream, - trigger_channel: sampling_timer::tim2::$trigger_channel, + trigger_channel: timers::tim2::$trigger_channel, ) -> Self { // Generate DMA events when an output compare of the timer hitting zero (timer roll over) // occurs. @@ -195,7 +193,7 @@ macro_rules! adc_input { // 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); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633 diff --git a/src/dac.rs b/src/dac.rs index d96109c..06a6362 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -4,7 +4,7 @@ ///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and ///! results in DAC update for both channels. use super::{ - hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, + hal, timers, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, }; @@ -22,12 +22,12 @@ macro_rules! dac_output { /// $spi is used as a type for indicating a DMA transfer into the SPI TX FIFO struct $spi { spi: hal::spi::Spi, - _channel: sampling_timer::tim2::$trigger_channel, + _channel: timers::tim2::$trigger_channel, } impl $spi { pub fn new( - _channel: sampling_timer::tim2::$trigger_channel, + _channel: timers::tim2::$trigger_channel, spi: hal::spi::Spi, ) -> Self { Self { _channel, spi } @@ -73,7 +73,7 @@ macro_rules! dac_output { pub fn new( spi: hal::spi::Spi, stream: hal::dma::dma::$data_stream, - trigger_channel: sampling_timer::tim2::$trigger_channel, + trigger_channel: timers::tim2::$trigger_channel, ) -> Self { // Generate DMA events when an output compare of the timer hitting zero (timer roll over) // occurs. @@ -143,7 +143,7 @@ macro_rules! dac_output { // Start the next transfer. self.transfer.clear_interrupts(); - let (prev_buffer, _) = + let (prev_buffer, _, _) = self.transfer.next_transfer(next_buffer).unwrap(); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633 diff --git a/src/design_parameters.rs b/src/design_parameters.rs index 9835568..125e133 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -1,6 +1,26 @@ +use super::hal::time::MegaHertz; + /// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock /// may begin. This is used for performing the internal ADC conversion. pub const ADC_SETUP_TIME: f32 = 220e-9; /// The maximum DAC/ADC serial clock line frequency. This is a hardware limit. -pub const ADC_DAC_SCK_MHZ_MAX: u32 = 50; +pub const ADC_DAC_SCK_MAX: MegaHertz = MegaHertz(50); + +/// The optimal counting frequency of the hardware timers used for timestamping and sampling. +pub const TIMER_FREQUENCY: MegaHertz = MegaHertz(100); + +/// The QSPI frequency for communicating with the pounder DDS. +pub const POUNDER_QSPI_FREQUENCY: MegaHertz = MegaHertz(40); + +/// The delay after initiating a QSPI transfer before asserting the IO_Update for the pounder DDS. +// Pounder Profile writes are always 16 bytes, with 2 cycles required per byte, coming out to a +// total of 32 QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an offset +// of 800nS. We use 900ns to be safe. +pub const POUNDER_IO_UPDATE_DELAY: f32 = 900_e-9; + +/// The duration to assert IO_Update for the pounder DDS. +// IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile write. With pounder +// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 400MHz), this corresponds to +// 40ns. To accomodate rounding errors, we use 50ns instead. +pub const POUNDER_IO_UPDATE_DURATION: f32 = 50_e-9; diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs new file mode 100644 index 0000000..910ae98 --- /dev/null +++ b/src/digital_input_stamper.rs @@ -0,0 +1,109 @@ +///! Digital Input 0 (DI0) reference clock timestamper +///! +///! This module provides a means of timestamping the rising edges of an external reference clock on +///! the DI0 with a timer value from TIM5. +///! +///! # Design +///! An input capture channel is configured on DI0 and fed into TIM5's capture channel 4. TIM5 is +///! then run in a free-running mode with a configured tick rate (PSC) and maximum count value +///! (ARR). Whenever an edge on DI0 triggers, the current TIM5 counter value is captured and +///! recorded as a timestamp. This timestamp can be either directly read from the timer channel or +///! can be collected asynchronously via DMA collection. +///! +///! To prevent silently discarding timestamps, the TIM5 input capture over-capture flag is +///! continually checked. Any over-capture event (which indicates an overwritten timestamp) then +///! triggers a panic to indicate the dropped timestamp so that design parameters can be adjusted. +///! +///! # Tradeoffs +///! It appears that DMA transfers can take a significant amount of time to disable (400ns) if they +///! are being prematurely stopped (such is the case here). As such, for a sample batch size of 1, +///! this can take up a significant amount of the total available processing time for the samples. +///! This module checks for any captured timestamps from the timer capture channel manually. In +///! this mode, the maximum input clock frequency supported is dependant on the sampling rate and +///! batch size. +///! +///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If +///! timestamping is desired in DI1, a separate timer + capture channel will be necessary. +use super::{hal, timers, ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE}; + +/// Calculate the period of the digital input timestampe timer. +/// +/// # Note +/// The period returned will be 1 less than the required period in timer ticks. The value returned +/// can be immediately programmed into a hardware timer period register. +/// +/// The period is calcualted to be some power-of-two multiple of the batch size, such that N batches +/// will occur between each timestamp timer overflow. +/// +/// # Returns +/// A 32-bit value that can be programmed into a hardware timer period register. +pub fn calculate_timestamp_timer_period() -> u32 { + // Calculate how long a single batch requires in timer ticks. + let batch_duration_ticks: u64 = + SAMPLE_BUFFER_SIZE as u64 * ADC_SAMPLE_TICKS as u64; + + // Calculate the largest power-of-two that is less than or equal to + // `batches_per_overflow`. This is completed by eliminating the least significant + // bits of the value until only the msb remains, which is always a power of two. + let batches_per_overflow: u64 = + (1u64 + u32::MAX as u64) / batch_duration_ticks; + let mut j = batches_per_overflow; + while (j & (j - 1)) != 0 { + j = j & (j - 1); + } + + // Once the number of batches per timestamp overflow is calculated, we can figure out the final + // period of the timestamp timer. The period is always 1 larger than the value configured in the + // register. + let period: u64 = batch_duration_ticks * j - 1u64; + assert!(period <= u32::MAX as u64); + + period as u32 +} + +/// The timestamper for DI0 reference clock inputs. +pub struct InputStamper { + _di0_trigger: hal::gpio::gpioa::PA3>, + capture_channel: timers::tim5::Channel4InputCapture, +} + +impl InputStamper { + /// Construct the DI0 input timestamper. + /// + /// # Args + /// * `trigger` - The capture trigger input pin. + /// * `timer_channel - The timer channel used for capturing timestamps. + pub fn new( + trigger: hal::gpio::gpioa::PA3>, + timer_channel: timers::tim5::Channel4, + ) -> Self { + // Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the + // capture source. + let input_capture = + timer_channel.into_input_capture(timers::tim5::CC4S_A::TI4); + + Self { + capture_channel: input_capture, + _di0_trigger: trigger, + } + } + + /// Start to capture timestamps on DI0. + pub fn start(&mut self) { + self.capture_channel.enable(); + } + + /// Get the latest timestamp that has occurred. + /// + /// # Note + /// This function must be called sufficiently often. If an over-capture event occurs, this + /// function will panic, as this indicates a timestamp was inadvertently dropped. + /// + /// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at + /// most one timestamp will occur in each data processing cycle. + pub fn latest_timestamp(&mut self) -> Option { + self.capture_channel + .latest_capture() + .expect("DI0 timestamp overrun") + } +} diff --git a/src/main.rs b/src/main.rs index b37f740..a0430d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,8 +54,10 @@ use smoltcp::wire::Ipv4Address; use heapless::{consts::*, String}; -// The desired sampling frequency of the ADCs. -const SAMPLE_FREQUENCY_KHZ: u32 = 500; +// 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; // The desired ADC sample processing buffer size. const SAMPLE_BUFFER_SIZE: usize = 1; @@ -70,11 +72,12 @@ mod adc; mod afe; mod dac; mod design_parameters; +mod digital_input_stamper; mod eeprom; mod hrtimer; mod pounder; -mod sampling_timer; mod server; +mod timers; use adc::{Adc0Input, Adc1Input}; use dac::{Dac0Output, Dac1Output}; @@ -196,9 +199,9 @@ macro_rules! route_request { const APP: () = { struct Resources { afes: (AFE0, AFE1), - adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), + input_stamper: digital_input_stamper::InputStamper, eeprom_i2c: hal::i2c::I2c, @@ -281,15 +284,50 @@ const APP: () = { 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 = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer2 = + dp.TIM2.timer(1.khz(), ccdr.peripheral.TIM2, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer2.pause(); + timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + let mut sampling_timer = timers::SamplingTimer::new(timer2); + sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); + + sampling_timer + }; - let mut sampling_timer = sampling_timer::SamplingTimer::new(timer2); let sampling_timer_channels = sampling_timer.channels(); + let mut timestamp_timer = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer5 = + dp.TIM5.timer(1.khz(), ccdr.peripheral.TIM5, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer5.pause(); + timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + // The time stamp timer must run at exactly a multiple of the sample timer based on the + // batch size. To accomodate this, we manually set the prescaler identical to the sample + // timer, but use a period that is longer. + let mut timer = timers::TimestampTimer::new(timer5); + + let period = + digital_input_stamper::calculate_timestamp_timer_period(); + timer.set_period_ticks(period); + + timer + }; + + let timestamp_timer_channels = timestamp_timer.channels(); + // Configure the SPI interfaces to the ADCs and DACs. let adcs = { let adc0 = { @@ -317,7 +355,7 @@ const APP: () = { let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI2, &ccdr.clocks, ); @@ -355,7 +393,7 @@ const APP: () = { let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI3, &ccdr.clocks, ); @@ -405,7 +443,7 @@ const APP: () = { dp.SPI4.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI4, &ccdr.clocks, ) @@ -437,7 +475,7 @@ const APP: () = { dp.SPI5.spi( (spi_sck, spi_miso, hal::spi::NoMosi), config, - design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(), + design_parameters::ADC_DAC_SCK_MAX, ccdr.peripheral.SPI5, &ccdr.clocks, ) @@ -507,7 +545,7 @@ const APP: () = { let qspi = hal::qspi::Qspi::bank2( dp.QUADSPI, qspi_pins, - 40.mhz(), + design_parameters::POUNDER_QSPI_FREQUENCY, &ccdr.clocks, ccdr.peripheral.QSPI, ); @@ -629,25 +667,26 @@ const APP: () = { ccdr.peripheral.HRTIM, ); - // IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile - // write. With pounder SYNC_CLK running at 100MHz (1/4 of the pounder reference - // clock of 400MHz), this corresponds to 40ns. To accomodate rounding errors, we - // use 50ns instead. - // - // Profile writes are always 16 bytes, with 2 cycles required per byte, coming - // out to a total of 32 QSPI clock cycles. The QSPI is configured for 40MHz, so - // this comes out to an offset of 800nS. We use 900ns to be safe - note that the - // timer is triggered after the QSPI write, which can take approximately 120nS, - // so there is additional margin. + // IO_Update occurs after a fixed delay from the QSPI write. Note that the timer + // is triggered after the QSPI write, which can take approximately 120nS, so + // there is additional margin. hrtimer.configure_single_shot( hrtimer::Channel::Two, - 50_e-9, - 900_e-9, + design_parameters::POUNDER_IO_UPDATE_DURATION, + design_parameters::POUNDER_IO_UPDATE_DELAY, ); // Ensure that we have enough time for an IO-update every sample. + let sample_frequency = { + let timer_frequency: hal::time::Hertz = + design_parameters::TIMER_FREQUENCY.into(); + timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32 + }; + + let sample_period = 1.0 / sample_frequency; assert!( - 1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9 + sample_period + > design_parameters::POUNDER_IO_UPDATE_DELAY ); hrtimer @@ -781,14 +820,25 @@ const APP: () = { // Utilize the cycle counter for RTIC scheduling. cp.DWT.enable_cycle_counter(); + let mut input_stamper = { + let trigger = gpioa.pa3.into_alternate_af2(); + digital_input_stamper::InputStamper::new( + trigger, + timestamp_timer_channels.ch4, + ) + }; + // Start sampling ADCs. sampling_timer.start(); + timestamp_timer.start(); + input_stamper.start(); init::LateResources { afes: (afe0, afe1), adcs, dacs, + input_stamper, dds_output, pounder: pounder_devices, @@ -799,7 +849,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch, dds_output], priority=2)] + #[task(binds=DMA1_STR3, resources=[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(), @@ -810,6 +860,8 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; + let _timestamp = c.resources.input_stamper.latest_timestamp(); + for channel in 0..adc_samples.len() { for sample in 0..adc_samples[0].len() { let x = f32::from(adc_samples[channel][sample] as i16); diff --git a/src/sampling_timer.rs b/src/sampling_timer.rs deleted file mode 100644 index 4755886..0000000 --- a/src/sampling_timer.rs +++ /dev/null @@ -1,119 +0,0 @@ -///! The sampling timer is used for managing ADC sampling and external reference timestamping. -use super::hal; - -/// The timer used for managing ADC sampling. -pub struct SamplingTimer { - timer: hal::timer::Timer, - channels: Option, -} - -impl SamplingTimer { - /// Construct the sampling timer. - pub fn new(mut timer: hal::timer::Timer) -> Self { - timer.pause(); - - 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. - channels: unsafe { Some(tim2::Channels::new()) }, - } - } - - /// Get the timer capture/compare channels. - pub fn channels(&mut self) -> tim2::Channels { - self.channels.take().unwrap() - } - - /// Start the sampling timer. - pub fn start(&mut self) { - self.timer.reset_counter(); - self.timer.resume(); - } -} - -macro_rules! timer_channel { - ($name:ident, $TY:ty, ($ccxde:expr, $ccrx:expr, $ccmrx_output:expr, $ccxs:expr)) => { - pub struct $name {} - - paste::paste! { - impl $name { - /// Construct a new timer channel. - /// - /// Note(unsafe): This function must only be called once. Once constructed, the - /// constructee guarantees to never modify the timer channel. - unsafe fn new() -> Self { - Self {} - } - - /// Allow CH4 to generate DMA requests. - pub fn listen_dma(&self) { - let regs = unsafe { &*<$TY>::ptr() }; - regs.dier.modify(|_, w| w.[< $ccxde >]().set_bit()); - } - - /// Operate CH2 as an output-compare. - /// - /// # Args - /// * `value` - The value to compare the sampling timer's counter against. - pub fn to_output_compare(&self, value: u32) { - let regs = unsafe { &*<$TY>::ptr() }; - assert!(value <= regs.arr.read().bits()); - regs.[< $ccrx >].write(|w| w.ccr().bits(value)); - regs.[< $ccmrx_output >]() - .modify(|_, w| unsafe { w.[< $ccxs >]().bits(0) }); - } - } - } - }; -} - -pub mod tim2 { - use stm32h7xx_hal as hal; - - /// The channels representing the timer. - pub struct Channels { - pub ch1: Channel1, - pub ch2: Channel2, - pub ch3: Channel3, - pub ch4: Channel4, - } - - impl Channels { - /// Construct a new set of channels. - /// - /// Note(unsafe): This is only safe to call once. - pub unsafe fn new() -> Self { - Self { - ch1: Channel1::new(), - ch2: Channel2::new(), - ch3: Channel3::new(), - ch4: Channel4::new(), - } - } - } - - timer_channel!( - Channel1, - hal::stm32::TIM2, - (cc1de, ccr1, ccmr1_output, cc1s) - ); - timer_channel!( - Channel2, - hal::stm32::TIM2, - (cc2de, ccr2, ccmr1_output, cc1s) - ); - timer_channel!( - Channel3, - hal::stm32::TIM2, - (cc3de, ccr3, ccmr2_output, cc3s) - ); - timer_channel!( - Channel4, - hal::stm32::TIM2, - (cc4de, ccr4, ccmr2_output, cc4s) - ); -} diff --git a/src/timers.rs b/src/timers.rs new file mode 100644 index 0000000..8d7d010 --- /dev/null +++ b/src/timers.rs @@ -0,0 +1,221 @@ +///! The sampling timer is used for managing ADC sampling and external reference timestamping. +use super::hal; + +macro_rules! timer_channels { + ($name:ident, $TY:ident, u32) => { + paste::paste! { + + /// The timer used for managing ADC sampling. + pub struct $name { + timer: hal::timer::Timer]>, + channels: Option<[< $TY:lower >]::Channels>, + } + + impl $name { + /// Construct the sampling timer. + pub fn new(mut timer: hal::timer::Timer]>) -> Self { + timer.pause(); + + 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. + channels: unsafe { Some([< $TY:lower >]::Channels::new()) }, + } + } + + /// Get the timer capture/compare channels. + pub fn channels(&mut self) -> [< $TY:lower >]::Channels { + self.channels.take().unwrap() + } + + /// Get the period of the timer. + #[allow(dead_code)] + pub fn get_period(&self) -> u32 { + 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_ticks(&mut self, period: u32) { + let regs = unsafe { &*hal::stm32::$TY::ptr() }; + regs.arr.write(|w| w.arr().bits(period)); + } + + /// Start the timer. + pub fn start(mut self) { + // Force a refresh of the frequency settings. + self.timer.apply_freq(); + + self.timer.reset_counter(); + self.timer.resume(); + } + } + + 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; + + /// The channels representing the timer. + pub struct Channels { + pub ch1: Channel1, + pub ch2: Channel2, + pub ch3: Channel3, + pub ch4: Channel4, + } + + impl Channels { + /// Construct a new set of channels. + /// + /// Note(unsafe): This is only safe to call once. + pub unsafe fn new() -> Self { + Self { + ch1: Channel1::new(), + ch2: Channel2::new(), + ch3: Channel3::new(), + ch4: Channel4::new(), + } + } + } + + timer_channels!(1, $TY, ccmr1); + timer_channels!(2, $TY, ccmr1); + timer_channels!(3, $TY, ccmr2); + timer_channels!(4, $TY, ccmr2); + } + } + }; + + ($index:expr, $TY:ty, $ccmrx:expr) => { + paste::paste! { + /// A capture/compare channel of the timer. + pub struct [< Channel $index >] {} + + /// A capture channel of the timer. + pub struct [< Channel $index InputCapture>] {} + + impl [< Channel $index >] { + /// Construct a new timer channel. + /// + /// Note(unsafe): This function must only be called once. Once constructed, the + /// constructee guarantees to never modify the timer channel. + unsafe fn new() -> Self { + Self {} + } + + /// Allow the channel to generate DMA requests. + #[allow(dead_code)] + pub fn listen_dma(&self) { + let regs = unsafe { &*<$TY>::ptr() }; + regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit()); + } + + /// Operate the channel as an output-compare. + /// + /// # Args + /// * `value` - The value to compare the sampling timer's counter against. + #[allow(dead_code)] + pub fn to_output_compare(&self, value: u32) { + let regs = unsafe { &*<$TY>::ptr() }; + assert!(value <= regs.arr.read().bits()); + regs.[< ccr $index >].write(|w| w.ccr().bits(value)); + regs.[< $ccmrx _output >]() + .modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) }); + } + + /// Operate the channel in input-capture mode. + /// + /// # Args + /// * `input` - The input source for the input capture event. + #[allow(dead_code)] + pub fn into_input_capture(self, input: hal::stm32::tim2::[< $ccmrx _input >]::[< CC $index S_A >]) -> [< Channel $index InputCapture >]{ + let regs = unsafe { &*<$TY>::ptr() }; + regs.[< $ccmrx _input >]().modify(|_, w| w.[< cc $index s>]().variant(input)); + + [< Channel $index InputCapture >] {} + } + } + + impl [< Channel $index InputCapture >] { + /// Get the latest capture from the channel. + #[allow(dead_code)] + pub fn latest_capture(&mut self) -> Result, ()> { + // Note(unsafe): This channel owns all access to the specific timer channel. + // Only atomic operations on completed on the timer registers. + let regs = unsafe { &*<$TY>::ptr() }; + let sr = regs.sr.read(); + + let result = if sr.[< cc $index if >]().bit_is_set() { + // Read the capture value. Reading the captured value clears the flag in the + // status register automatically. + let ccx = regs.[< ccr $index >].read(); + Some(ccx.ccr().bits()) + } else { + None + }; + + // Read SR again to check for a potential over-capture. If there is an + // overcapture, return an error. + if regs.sr.read().[< cc $index of >]().bit_is_clear() { + Ok(result) + } else { + regs.sr.modify(|_, w| w.[< cc $index of >]().clear_bit()); + Err(()) + } + } + + /// Allow the channel to generate DMA requests. + #[allow(dead_code)] + pub fn listen_dma(&self) { + // 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() }; + regs.dier.modify(|_, w| w.[< cc $index de >]().set_bit()); + } + + /// Enable the input capture to begin capturing timer values. + #[allow(dead_code)] + pub fn enable(&mut self) { + // 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() }; + regs.ccer.modify(|_, w| w.[< cc $index e >]().set_bit()); + } + + /// Check if an over-capture event has occurred. + #[allow(dead_code)] + pub fn check_overcapture(&self) -> bool { + // 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() }; + regs.sr.read().[< cc $index of >]().bit_is_set() + } + } + + // Note(unsafe): This manually implements DMA support for input-capture channels. This + // 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; + + const REQUEST_LINE: Option = Some(DMAReq::[< $TY _CH $index >]as u8); + + fn address(&self) -> u32 { + let regs = unsafe { &*<$TY>::ptr() }; + ®s.[] as *const _ as u32 + } + } + } + }; +} + +timer_channels!(SamplingTimer, TIM2, u32); +timer_channels!(TimestampTimer, TIM5, u32);