diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 155446a..ae27f70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,12 @@ jobs: target/*/release/stabilizer stabilizer-release.bin + - name: Build (Pounder v1.1) + uses: actions-rs/cargo@v1 + with: + command: build + args: --features pounder_v1_1 + test: runs-on: ubuntu-latest strategy: diff --git a/Cargo.lock b/Cargo.lock index 030ff99..76fb832 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#25ee0f3a9ae27d1fd6bb390d6045aa312f29f096" +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 9dfa28a..f4f90fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,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/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/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/design_parameters.rs b/src/design_parameters.rs index 125e133..3edf04b 100644 --- a/src/design_parameters.rs +++ b/src/design_parameters.rs @@ -21,6 +21,21 @@ 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: 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. +#[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/digital_input_stamper.rs b/src/digital_input_stamper.rs index 910ae98..d63849c 100644 --- a/src/digital_input_stamper.rs +++ b/src/digital_input_stamper.rs @@ -80,7 +80,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.into_input_capture(timers::tim5::CC4S_A::TI4); + timer_channel.into_input_capture(timers::CaptureTrigger::Input24); Self { capture_channel: input_capture, diff --git a/src/main.rs b/src/main.rs index a0430d5..7083576 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,9 @@ extern crate panic_halt; #[macro_use] extern crate log; +#[allow(unused_imports)] +use core::convert::TryInto; + // use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; use cortex_m_rt::exception; use rtic::cyccnt::{Instant, U32Ext}; @@ -60,7 +63,7 @@ use heapless::{consts::*, String}; const ADC_SAMPLE_TICKS: u32 = 256; // 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; @@ -220,6 +223,8 @@ const APP: () = { pounder: Option, + pounder_stamper: Option, + // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2], @@ -509,7 +514,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 = { @@ -553,17 +558,24 @@ 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 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, - 100_000_000_f32, - 5, + ref_clk.0 as f32, + design_parameters::DDS_MULTIPLIER, ) .unwrap(); @@ -642,7 +654,6 @@ const APP: () = { let pounder_devices = pounder::PounderDevices::new( io_expander, - &mut ad9959, spi, adc1, adc2, @@ -828,6 +839,51 @@ const APP: () = { ) }; + #[cfg(feature = "pounder_v1_1")] + let pounder_stamper = { + let etr_pin = gpioa.pa0.into_alternate_af3(); + + // 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); + + // 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); + timestamp_timer.start(); + + // We want the pounder timestamp timer to overflow once per batch. + 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_ticks((period - 1).try_into().unwrap()); + let tim8_channels = timestamp_timer.channels(); + + 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(); @@ -841,6 +897,7 @@ const APP: () = { input_stamper, dds_output, pounder: pounder_devices, + pounder_stamper, eeprom_i2c, net_interface: network_interface, @@ -849,8 +906,13 @@ 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) { + 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(), c.resources.adcs.1.acquire_buffer(), diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index c20d251..2811e92 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; @@ -274,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. @@ -282,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, @@ -314,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/pounder/timestamp.rs b/src/pounder/timestamp.rs new file mode 100644 index 0000000..0ce0bfd --- /dev/null +++ b/src/pounder/timestamp.rs @@ -0,0 +1,140 @@ +///! 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, + transfer: Transfer< + hal::dma::dma::Stream7, + timers::tim8::Channel1InputCapture, + PeripheralToMemory, + &'static mut [u16; SAMPLE_BUFFER_SIZE], + >, +} + +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, + 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 + .into_input_capture(timers::CaptureTrigger::TriggerInput); + input_capture.listen_dma(); + + // The data transfer is always a transfer of data from the peripheral to a RAM buffer. + let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = + Transfer::init( + stream, + input_capture, + // 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, + ); + + data_transfer.start(|capture_channel| capture_channel.enable()); + + 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); + } + + /// 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. + 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 8d7d010..977eed6 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -1,69 +1,178 @@ ///! 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, + Input24 = 0b10, + TriggerInput = 0b11, +} + +/// The event that should generate an external trigger from the peripheral. +#[allow(dead_code)] +pub enum TriggerGenerator { + Reset = 0b000, + Enable = 0b001, + Update = 0b010, + ComparePulse = 0b011, + Ch1Compare = 0b100, + Ch2Compare = 0b101, + Ch3Compare = 0b110, + Ch4Compare = 0b111, +} + +/// Selects the trigger source for the timer peripheral. +#[allow(dead_code)] +pub enum TriggerSource { + Trigger0 = 0, + Trigger1 = 0b01, + Trigger2 = 0b10, + Trigger3 = 0b11, +} + +/// Prescalers for externally-supplied reference clocks. +pub enum Prescaler { + Div1 = 0b00, + Div2 = 0b01, + 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. pub struct $name { timer: hal::timer::Timer]>, channels: Option<[< $TY:lower >]::Channels>, + update_event: Option<[< $TY:lower >]::UpdateEvent>, } impl $name { /// Construct the sampling timer. + #[allow(dead_code)] 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. + // 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()) }, } } /// Get the timer capture/compare channels. + #[allow(dead_code)] pub fn channels(&mut self) -> [< $TY:lower >]::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 { + 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_ticks(&mut self, period: u32) { + pub fn set_period_ticks(&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()); + + // Clear any other prescaler configuration. + regs.psc.write(|w| w.psc().bits(0)); + } + /// Start the timer. - pub fn start(mut self) { + #[allow(dead_code)] + pub fn start(&mut self) { // Force a refresh of the frequency settings. self.timer.apply_freq(); - self.timer.reset_counter(); + 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() }; + // Note(unsafe) The TriggerGenerator enumeration is specified such that this is + // always in range. + regs.cr2.modify(|_, w| w.mms().bits(source as u8)); + + } + + /// 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() }; + // 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; + 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 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. pub struct Channels { pub ch1: Channel1, @@ -76,6 +185,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(), @@ -86,15 +196,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 >] {} @@ -107,6 +217,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 {} } @@ -123,9 +234,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) }); @@ -136,9 +248,12 @@ macro_rules! timer_channels { /// # 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 >]{ + pub fn into_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 >] {} } @@ -147,7 +262,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) -> Result, ()> { + 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() }; @@ -204,13 +319,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 } } } @@ -219,3 +334,4 @@ macro_rules! timer_channels { timer_channels!(SamplingTimer, TIM2, u32); timer_channels!(TimestampTimer, TIM5, u32); +timer_channels!(PounderTimestampTimer, TIM8, u16);