Merge pull request #195 from vertigo-designs/feature/digital-input-stamp

Feature/digital input stamp
master
Ryan Summers 2021-01-06 05:45:04 -08:00 committed by GitHub
commit a2fb4630b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 444 additions and 163 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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<hal::stm32::DMA1>,
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

View File

@ -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<hal::stm32::$spi, hal::spi::Disabled, u16>,
_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<hal::stm32::$spi, hal::spi::Disabled, u16>,
) -> Self {
Self { _channel, spi }
@ -73,7 +73,7 @@ macro_rules! dac_output {
pub fn new(
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
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

View File

@ -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;

View File

@ -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<hal::gpio::Alternate<hal::gpio::AF2>>,
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<hal::gpio::Alternate<hal::gpio::AF2>>,
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<u32> {
self.capture_channel
.latest_capture()
.expect("DI0 timestamp overrun")
}
}

View File

@ -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<hal::stm32::I2C2>,
@ -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);

View File

@ -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<hal::stm32::TIM2>,
channels: Option<tim2::Channels>,
}
impl SamplingTimer {
/// Construct the sampling timer.
pub fn new(mut timer: hal::timer::Timer<hal::stm32::TIM2>) -> 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)
);
}

221
src/timers.rs Normal file
View File

@ -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<hal::stm32::[< $TY >]>,
channels: Option<[< $TY:lower >]::Channels>,
}
impl $name {
/// Construct the sampling timer.
pub fn new(mut timer: hal::timer::Timer<hal::stm32::[< $TY>]>) -> 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<Option<u32>, ()> {
// 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<PeripheralToMemory> for [< Channel $index InputCapture >] {
type MemSize = u32;
const REQUEST_LINE: Option<u8> = Some(DMAReq::[< $TY _CH $index >]as u8);
fn address(&self) -> u32 {
let regs = unsafe { &*<$TY>::ptr() };
&regs.[<ccr $index >] as *const _ as u32
}
}
}
};
}
timer_channels!(SamplingTimer, TIM2, u32);
timer_channels!(TimestampTimer, TIM5, u32);