Merge pull request #196 from vertigo-designs/feature/pounder-timestamping

Feature/pounder timestamping
This commit is contained in:
Ryan Summers 2021-01-11 01:50:09 -08:00 committed by GitHub
commit 1307ddb0ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 393 additions and 47 deletions

View File

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

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#25ee0f3a9ae27d1fd6bb390d6045aa312f29f096"
source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#3da22d4935c8f6e412b99e6662ec11da5265fb88"
dependencies = [
"bare-metal 1.0.0",
"cast",

View File

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

View File

@ -172,6 +172,17 @@ impl<I: Interface> Ad9959<I> {
// 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<I: Interface> Ad9959<I> {
///
/// 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,

View File

@ -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() };
&regs.txdr as *const _ as u32
&regs.txdr as *const _ as usize
}
}

View File

@ -45,8 +45,8 @@ macro_rules! dac_output {
const REQUEST_LINE: Option<u8> = 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
}
}

View File

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

View File

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

View File

@ -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::PounderDevices>,
pounder_stamper: Option<pounder::timestamp::Timestamper>,
// 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(),

View File

@ -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<hal::i2c::I2c<hal::stm32::I2C1>>,
ad9959: &mut ad9959::Ad9959<QspiInterface>,
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
adc2: hal::adc::Adc<hal::stm32::ADC2, hal::adc::Enabled>,
@ -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)
}

140
src/pounder/timestamp.rs Normal file
View File

@ -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<hal::stm32::DMA1>,
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<hal::stm32::DMA1>,
capture_channel: timers::tim8::Channel1,
sampling_timer: &mut timers::SamplingTimer,
_clock_input: hal::gpio::gpioa::PA0<
hal::gpio::Alternate<hal::gpio::AF3>,
>,
) -> 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()
}
}

View File

@ -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<hal::stm32::[< $TY >]>,
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<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.
// 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<Option<u32>, ()> {
pub fn latest_capture(&mut self) -> Result<Option<$size>, ()> {
// 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<PeripheralToMemory> for [< Channel $index InputCapture >] {
type MemSize = u32;
type MemSize = $size;
const REQUEST_LINE: Option<u8> = Some(DMAReq::[< $TY _CH $index >]as u8);
fn address(&self) -> u32 {
fn address(&self) -> usize {
let regs = unsafe { &*<$TY>::ptr() };
&regs.[<ccr $index >] as *const _ as u32
&regs.[<ccr $index >] 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);