Merge branch 'rs/issue-219/adc-setup' into feature/io-docs
This commit is contained in:
commit
09ecd3291a
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: stable
|
||||||
target: thumbv7em-none-eabihf
|
target: thumbv7em-none-eabihf
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
@ -29,7 +29,6 @@ jobs:
|
||||||
- uses: actions-rs/clippy-check@v1
|
- uses: actions-rs/clippy-check@v1
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
compile:
|
compile:
|
||||||
|
@ -85,6 +84,12 @@ jobs:
|
||||||
target/*/release/stabilizer
|
target/*/release/stabilizer
|
||||||
stabilizer-release.bin
|
stabilizer-release.bin
|
||||||
|
|
||||||
|
- name: Build (Pounder v1.1)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --features pounder_v1_1
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: HITL
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ hitl ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hitl:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: hitl
|
||||||
|
steps:
|
||||||
|
- uses: peter-evans/repository-dispatch@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DISPATCH_PAT }}
|
||||||
|
event-type: stabilizer
|
||||||
|
repository: quartiq/hitl
|
||||||
|
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
|
|
@ -63,6 +63,7 @@ branch = "dma"
|
||||||
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
||||||
bkpt = [ ]
|
bkpt = [ ]
|
||||||
nightly = ["cortex-m/inline-asm", "dsp/nightly"]
|
nightly = ["cortex-m/inline-asm", "dsp/nightly"]
|
||||||
|
pounder_v1_1 = [ ]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org)
|
[![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org)
|
||||||
|
[![HITL (private)](https://github.com/quartiq/hitl/workflows/Stabilizer/badge.svg)](https://github.com/quartiq/hitl/actions?query=workflow%3AStabilizer)
|
||||||
|
|
||||||
# Stabilizer Firmware
|
# Stabilizer Firmware
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,17 @@ impl<I: Interface> Ad9959<I> {
|
||||||
|
|
||||||
// Set the clock frequency to configure the device as necessary.
|
// Set the clock frequency to configure the device as necessary.
|
||||||
ad9959.configure_system_clock(clock_frequency, multiplier)?;
|
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)
|
Ok(ad9959)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +206,7 @@ impl<I: Interface> Ad9959<I> {
|
||||||
///
|
///
|
||||||
/// Returns:
|
/// Returns:
|
||||||
/// The actual frequency configured for the internal system clock.
|
/// The actual frequency configured for the internal system clock.
|
||||||
pub fn configure_system_clock(
|
fn configure_system_clock(
|
||||||
&mut self,
|
&mut self,
|
||||||
reference_clock_frequency: f32,
|
reference_clock_frequency: f32,
|
||||||
multiplier: u8,
|
multiplier: u8,
|
||||||
|
|
|
@ -14,6 +14,9 @@ break DefaultHandler
|
||||||
break HardFault
|
break HardFault
|
||||||
break rust_begin_unwind
|
break rust_begin_unwind
|
||||||
|
|
||||||
|
source ../../PyCortexMDebug/cmdebug/svd_gdb.py
|
||||||
|
svd_load ~/Downloads/STM32H743x.svd
|
||||||
|
|
||||||
load
|
load
|
||||||
# tbreak cortex_m_rt::reset_handler
|
# tbreak cortex_m_rt::reset_handler
|
||||||
monitor reset halt
|
monitor reset halt
|
||||||
|
|
227
src/adc.rs
227
src/adc.rs
|
@ -12,16 +12,18 @@
|
||||||
///! the collection of multiple ADC samples without requiring processing by the CPU, which reduces
|
///! the collection of multiple ADC samples without requiring processing by the CPU, which reduces
|
||||||
///! overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
|
///! overhead and provides the CPU with more time for processing-intensive tasks, like DSP.
|
||||||
///!
|
///!
|
||||||
///! The automation of sample collection utilizes two DMA streams, the SPI peripheral, and a timer
|
///! The automation of sample collection utilizes three DMA streams, the SPI peripheral, and two
|
||||||
///! compare channel for each ADC. The timer comparison channel is configured to generate a
|
///! timer compare channel for each ADC. One timer comparison channel is configured to generate a
|
||||||
///! comparison event every time the timer is equal to a specific value. Each comparison then
|
///! comparison event every time the timer is equal to a specific value. Each comparison then
|
||||||
///! generates a DMA transfer event to write into the SPI TX buffer. Although the SPI is a simplex,
|
///! generates a DMA transfer event to write into the SPI CR1 register to initiate the transfer.
|
||||||
///! RX-only interface, it is configured in full-duplex mode and the TX pin is left disconnected.
|
///! This allows the SPI interface to periodically read a single sample. The other timer comparison
|
||||||
///! This allows the SPI interface to periodically read a single word whenever a word is written to
|
///! channel is configured to generate a comparison event slightly before the first (~10 timer
|
||||||
///! the TX side. Thus, by running a continuous DMA transfer to periodically write a value into the
|
///! cycles). This channel triggers a separate DMA stream to clear the EOT flag within the SPI
|
||||||
///! TX FIFO, we can schedule the regular collection of ADC samples in the SPI RX buffer.
|
///! peripheral. The EOT flag must be cleared after each transfer or the SPI peripheral will not
|
||||||
|
///! properly complete the single conversion. Thus, by using two DMA streams and timer comparison
|
||||||
|
///! channels, the SPI can regularly acquire ADC samples.
|
||||||
///!
|
///!
|
||||||
///! In order to collect the acquired ADC samples into a RAM buffer, a second DMA transfer is
|
///! In order to collect the acquired ADC samples into a RAM buffer, a final DMA transfer is
|
||||||
///! configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to
|
///! configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to
|
||||||
///! the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is
|
///! the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is
|
||||||
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
||||||
|
@ -49,8 +51,8 @@
|
||||||
///!
|
///!
|
||||||
///! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch
|
///! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch
|
||||||
///! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger
|
///! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger
|
||||||
///! batch sizes generally provide for lower overhead and more processing time per sample, but come at the expense of
|
///! batch sizes generally provide for lower overhead and more processing time per sample, but come
|
||||||
///! increased input -> output latency.
|
///! at the expense of increased input -> output latency.
|
||||||
///!
|
///!
|
||||||
///!
|
///!
|
||||||
///! # Note
|
///! # Note
|
||||||
|
@ -75,11 +77,17 @@ use super::{
|
||||||
Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE,
|
Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following data is written by the timer ADC sample trigger into each of the SPI TXFIFOs. Note
|
// The following data is written by the timer ADC sample trigger into the SPI CR1 to start the
|
||||||
// that because the SPI MOSI line is not connected, this data is dont-care. Data in AXI SRAM is not
|
// transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is
|
||||||
// initialized on boot, so the contents are random.
|
// initialized during setup.
|
||||||
#[link_section = ".axisram.buffers"]
|
#[link_section = ".axisram.buffers"]
|
||||||
static mut SPI_START: [u16; 1] = [0x00];
|
static mut SPI_START: [u32; 1] = [0x00; 1];
|
||||||
|
|
||||||
|
// The following data is written by the timer flag clear trigger into the SPI IFCR register to clear
|
||||||
|
// the EOT flag. Data in AXI SRAM is not initialized on boot, so the contents are random. This
|
||||||
|
// value is initialized during setup.
|
||||||
|
#[link_section = ".axisram.buffers"]
|
||||||
|
static mut SPI_EOT_CLEAR: [u32; 1] = [0x00];
|
||||||
|
|
||||||
// The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for
|
// The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for
|
||||||
// each transfer in a ping-pong buffer configuration (one is being acquired while the other is being
|
// each transfer in a ping-pong buffer configuration (one is being acquired while the other is being
|
||||||
|
@ -90,36 +98,70 @@ static mut ADC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] =
|
||||||
[[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
[[[0; SAMPLE_BUFFER_SIZE]; 2]; 2];
|
||||||
|
|
||||||
macro_rules! adc_input {
|
macro_rules! adc_input {
|
||||||
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident,
|
($name:ident, $index:literal, $trigger_stream:ident, $data_stream:ident, $clear_stream:ident,
|
||||||
$spi:ident, $trigger_channel:ident, $dma_req:ident) => {
|
$spi:ident, $trigger_channel:ident, $dma_req:ident, $clear_channel:ident, $dma_clear_req:ident) => {
|
||||||
/// $spi is used as a type for indicating a DMA transfer into the SPI TX FIFO
|
paste::paste! {
|
||||||
/// whenever the tim2 update dma request occurs.
|
/// $spi-CR is used as a type for indicating a DMA transfer into the SPI control
|
||||||
struct $spi {
|
/// register whenever the tim2 update dma request occurs.
|
||||||
|
struct [< $spi CR >] {
|
||||||
_channel: timers::tim2::$trigger_channel,
|
_channel: timers::tim2::$trigger_channel,
|
||||||
}
|
}
|
||||||
impl $spi {
|
impl [< $spi CR >] {
|
||||||
pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
|
pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
|
||||||
Self { _channel }
|
Self { _channel }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is hard-coded and
|
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is
|
||||||
// may only be used if ownership of the timer2 $trigger_channel compare channel is assured, which is
|
// hard-coded and may only be used if ownership of the timer2 $trigger_channel compare
|
||||||
// ensured by maintaining ownership of the channel.
|
// channel is assured, which is ensured by maintaining ownership of the channel.
|
||||||
unsafe impl TargetAddress<MemoryToPeripheral> for $spi {
|
unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi CR >] {
|
||||||
/// SPI is configured to operate using 16-bit transfer words.
|
|
||||||
type MemSize = u16;
|
type MemSize = u32;
|
||||||
|
|
||||||
/// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
|
/// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
|
||||||
const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
|
const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
|
||||||
|
|
||||||
/// Whenever the DMA request occurs, it should write into SPI's TX FIFO to start a DMA
|
/// Whenever the DMA request occurs, it should write into SPI's CR1 to start the
|
||||||
/// transfer.
|
/// transfer.
|
||||||
fn address(&self) -> usize {
|
fn address(&self) -> usize {
|
||||||
// Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is
|
// Note(unsafe): It is assumed that SPI is owned by another DMA transfer. This
|
||||||
// only used for the transmit-half of DMA.
|
// is only safe because we are writing to a configuration register.
|
||||||
let regs = unsafe { &*hal::stm32::$spi::ptr() };
|
let regs = unsafe { &*hal::stm32::$spi::ptr() };
|
||||||
®s.txdr as *const _ as usize
|
®s.cr1 as *const _ as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// $spi-IFCR is used as a type for indicating a DMA transfer into the SPI flag clear
|
||||||
|
/// register whenever the tim3 compare dma request occurs. The flag must be cleared
|
||||||
|
/// before the transfer starts.
|
||||||
|
struct [< $spi IFCR >] {
|
||||||
|
_channel: timers::tim3::$clear_channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl [< $spi IFCR >] {
|
||||||
|
pub fn new(_channel: timers::tim3::$clear_channel) -> Self {
|
||||||
|
Self { _channel }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is
|
||||||
|
// hard-coded and may only be used if ownership of the timer3 $clear_channel compare
|
||||||
|
// channel is assured, which is ensured by maintaining ownership of the channel.
|
||||||
|
unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi IFCR >] {
|
||||||
|
type MemSize = u32;
|
||||||
|
|
||||||
|
/// SPI DMA requests are generated whenever TIM3 CHx ($dma_clear_req) comparison
|
||||||
|
/// occurs.
|
||||||
|
const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_clear_req as u8);
|
||||||
|
|
||||||
|
/// Whenever the DMA request occurs, it should write into SPI's IFCR to clear the
|
||||||
|
/// EOT flag to allow the next transmission.
|
||||||
|
fn address(&self) -> usize {
|
||||||
|
// Note(unsafe): It is assumed that SPI is owned by another DMA transfer and
|
||||||
|
// this DMA is only used for writing to the configuration registers.
|
||||||
|
let regs = unsafe { &*hal::stm32::$spi::ptr() };
|
||||||
|
®s.ifcr as *const _ as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,9 +176,15 @@ macro_rules! adc_input {
|
||||||
>,
|
>,
|
||||||
_trigger_transfer: Transfer<
|
_trigger_transfer: Transfer<
|
||||||
hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
|
hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
|
||||||
$spi,
|
[< $spi CR >],
|
||||||
MemoryToPeripheral,
|
MemoryToPeripheral,
|
||||||
&'static mut [u16; 1],
|
&'static mut [u32; 1],
|
||||||
|
>,
|
||||||
|
_flag_clear_transfer: Transfer<
|
||||||
|
hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
|
||||||
|
[< $spi IFCR >],
|
||||||
|
MemoryToPeripheral,
|
||||||
|
&'static mut [u32; 1],
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,30 +193,76 @@ macro_rules! adc_input {
|
||||||
///
|
///
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `spi` - The SPI interface used to communicate with the ADC.
|
/// * `spi` - The SPI interface used to communicate with the ADC.
|
||||||
/// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by writing a word into
|
/// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by
|
||||||
/// the SPI TX FIFO.
|
/// writing a word into the SPI TX FIFO.
|
||||||
/// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
|
/// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
|
||||||
/// * `_trigger_channel` - The ADC sampling timer output compare channel for read triggers.
|
/// * `clear_stream` - The DMA stream used to clear the EOT flag in the SPI peripheral.
|
||||||
|
/// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers.
|
||||||
|
/// * `clear_channel` - The shadow sampling timer output compare channel used for
|
||||||
|
/// clearing the SPI EOT flag.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
|
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
|
||||||
trigger_stream: hal::dma::dma::$trigger_stream<
|
trigger_stream: hal::dma::dma::$trigger_stream<
|
||||||
hal::stm32::DMA1,
|
hal::stm32::DMA1,
|
||||||
>,
|
>,
|
||||||
data_stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
data_stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
|
clear_stream: hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
|
||||||
trigger_channel: timers::tim2::$trigger_channel,
|
trigger_channel: timers::tim2::$trigger_channel,
|
||||||
|
clear_channel: timers::tim3::$clear_channel,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
// The flag clear DMA transfer always clears the EOT flag in the SPI
|
||||||
// occurs.
|
// peripheral. It has the highest priority to ensure it is completed before the
|
||||||
trigger_channel.listen_dma();
|
// transfer trigger.
|
||||||
trigger_channel.to_output_compare(0);
|
let clear_config = DmaConfig::default()
|
||||||
|
.priority(Priority::VeryHigh)
|
||||||
|
.circular_buffer(true);
|
||||||
|
|
||||||
// The trigger stream constantly writes to the TX FIFO using a static word (dont-care
|
unsafe {
|
||||||
// contents). Thus, neither the memory or peripheral address ever change. This is run in
|
SPI_EOT_CLEAR[0] = 1 << 3;
|
||||||
// circular mode to be completed at every DMA request.
|
}
|
||||||
|
|
||||||
|
// Generate DMA events when the timer hits zero (roll-over). This must be before
|
||||||
|
// the trigger channel DMA occurs, as if the trigger occurs first, the
|
||||||
|
// transmission will not occur.
|
||||||
|
clear_channel.listen_dma();
|
||||||
|
clear_channel.to_output_compare(0);
|
||||||
|
|
||||||
|
let mut clear_transfer: Transfer<
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
MemoryToPeripheral,
|
||||||
|
_,
|
||||||
|
> = Transfer::init(
|
||||||
|
clear_stream,
|
||||||
|
[< $spi IFCR >]::new(clear_channel),
|
||||||
|
// Note(unsafe): Because this is a Memory->Peripheral transfer, this data is
|
||||||
|
// never actually modified. It technically only needs to be immutably
|
||||||
|
// borrowed, but the current HAL API only supports mutable borrows.
|
||||||
|
unsafe { &mut SPI_EOT_CLEAR },
|
||||||
|
None,
|
||||||
|
clear_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate DMA events when an output compare of the timer hits the specified
|
||||||
|
// value.
|
||||||
|
trigger_channel.listen_dma();
|
||||||
|
trigger_channel.to_output_compare(2);
|
||||||
|
|
||||||
|
// The trigger stream constantly writes to the SPI CR1 using a static word
|
||||||
|
// (which is a static value to enable the SPI transfer). Thus, neither the
|
||||||
|
// memory or peripheral address ever change. This is run in circular mode to be
|
||||||
|
// completed at every DMA request.
|
||||||
let trigger_config = DmaConfig::default()
|
let trigger_config = DmaConfig::default()
|
||||||
.priority(Priority::High)
|
.priority(Priority::High)
|
||||||
.circular_buffer(true);
|
.circular_buffer(true);
|
||||||
|
|
||||||
|
// Note(unsafe): This word is initialized once per ADC initialization to verify
|
||||||
|
// it is initialized properly.
|
||||||
|
unsafe {
|
||||||
|
// Write a binary code into the SPI control register to initiate a transfer.
|
||||||
|
SPI_START[0] = 0x201;
|
||||||
|
};
|
||||||
|
|
||||||
// Construct the trigger stream to write from memory to the peripheral.
|
// Construct the trigger stream to write from memory to the peripheral.
|
||||||
let mut trigger_transfer: Transfer<
|
let mut trigger_transfer: Transfer<
|
||||||
_,
|
_,
|
||||||
|
@ -177,7 +271,7 @@ macro_rules! adc_input {
|
||||||
_,
|
_,
|
||||||
> = Transfer::init(
|
> = Transfer::init(
|
||||||
trigger_stream,
|
trigger_stream,
|
||||||
$spi::new(trigger_channel),
|
[< $spi CR >]::new(trigger_channel),
|
||||||
// Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never
|
// Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never
|
||||||
// actually modified. It technically only needs to be immutably borrowed, but the
|
// actually modified. It technically only needs to be immutably borrowed, but the
|
||||||
// current HAL API only supports mutable borrows.
|
// current HAL API only supports mutable borrows.
|
||||||
|
@ -195,15 +289,16 @@ macro_rules! adc_input {
|
||||||
.transfer_complete_interrupt($index == 1)
|
.transfer_complete_interrupt($index == 1)
|
||||||
.priority(Priority::VeryHigh);
|
.priority(Priority::VeryHigh);
|
||||||
|
|
||||||
// A SPI peripheral error interrupt is used to determine if the RX FIFO overflows. This
|
// A SPI peripheral error interrupt is used to determine if the RX FIFO
|
||||||
// indicates that samples were dropped due to excessive processing time in the main
|
// overflows. This indicates that samples were dropped due to excessive
|
||||||
// application (e.g. a second DMA transfer completes before the first was done with
|
// processing time in the main application (e.g. a second DMA transfer completes
|
||||||
// processing). This is used as a flow control indicator to guarantee that no ADC samples
|
// before the first was done with processing). This is used as a flow control
|
||||||
// are lost.
|
// indicator to guarantee that no ADC samples are lost.
|
||||||
let mut spi = spi.disable();
|
let mut spi = spi.disable();
|
||||||
spi.listen(hal::spi::Event::Error);
|
spi.listen(hal::spi::Event::Error);
|
||||||
|
|
||||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
// The data transfer is always a transfer of data from the peripheral to a RAM
|
||||||
|
// buffer.
|
||||||
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
||||||
Transfer::init(
|
Transfer::init(
|
||||||
data_stream,
|
data_stream,
|
||||||
|
@ -216,23 +311,25 @@ macro_rules! adc_input {
|
||||||
);
|
);
|
||||||
|
|
||||||
data_transfer.start(|spi| {
|
data_transfer.start(|spi| {
|
||||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
// Allow the SPI RX FIFO to generate DMA transfer requests when data is
|
||||||
|
// available.
|
||||||
spi.enable_dma_rx();
|
spi.enable_dma_rx();
|
||||||
spi.enable_dma_tx();
|
|
||||||
|
|
||||||
// Enable SPI and start it in infinite transaction mode.
|
// Each transaction is 1 word (16 bytes).
|
||||||
|
spi.inner().cr2.modify(|_, w| w.tsize().bits(1));
|
||||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
||||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clear_transfer.start(|_| {});
|
||||||
trigger_transfer.start(|_| {});
|
trigger_transfer.start(|_| {});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It shall not be used
|
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It
|
||||||
// anywhere else in the module.
|
// shall not be used anywhere else in the module.
|
||||||
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
||||||
transfer: data_transfer,
|
transfer: data_transfer,
|
||||||
_trigger_transfer: trigger_transfer,
|
_trigger_transfer: trigger_transfer,
|
||||||
|
_flag_clear_transfer: clear_transfer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,9 +338,9 @@ macro_rules! adc_input {
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
/// A reference to the underlying buffer that has been filled with ADC samples.
|
||||||
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
||||||
// Wait for the transfer to fully complete before continuing.
|
// Wait for the transfer to fully complete before continuing. Note: If a device
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as there is
|
// hangs up, check that this conditional is passing correctly, as there is no
|
||||||
// no time-out checks here in the interest of execution speed.
|
// time-out checks here in the interest of execution speed.
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
while !self.transfer.get_transfer_complete_flag() {}
|
||||||
|
|
||||||
let next_buffer = self.next_buffer.take().unwrap();
|
let next_buffer = self.next_buffer.take().unwrap();
|
||||||
|
@ -253,13 +350,21 @@ macro_rules! adc_input {
|
||||||
let (prev_buffer, _, _) =
|
let (prev_buffer, _, _) =
|
||||||
self.transfer.next_transfer(next_buffer).unwrap();
|
self.transfer.next_transfer(next_buffer).unwrap();
|
||||||
|
|
||||||
self.next_buffer.replace(prev_buffer); // .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
||||||
|
self.next_buffer.replace(prev_buffer);
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
self.next_buffer.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
adc_input!(Adc0Input, 0, Stream0, Stream1, SPI2, Channel1, TIM2_CH1);
|
adc_input!(
|
||||||
adc_input!(Adc1Input, 1, Stream2, Stream3, SPI3, Channel2, TIM2_CH2);
|
Adc0Input, 0, Stream0, Stream1, Stream2, SPI2, Channel1, TIM2_CH1,
|
||||||
|
Channel1, TIM3_CH1
|
||||||
|
);
|
||||||
|
adc_input!(
|
||||||
|
Adc1Input, 1, Stream3, Stream4, Stream5, SPI3, Channel2, TIM2_CH2,
|
||||||
|
Channel2, TIM3_CH2
|
||||||
|
);
|
||||||
|
|
|
@ -200,5 +200,5 @@ macro_rules! dac_output {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
dac_output!(Dac0Output, 0, Stream4, SPI4, Channel3, TIM2_CH3);
|
dac_output!(Dac0Output, 0, Stream6, SPI4, Channel3, TIM2_CH3);
|
||||||
dac_output!(Dac1Output, 1, Stream5, SPI5, Channel4, TIM2_CH4);
|
dac_output!(Dac1Output, 1, Stream7, SPI5, Channel4, TIM2_CH4);
|
||||||
|
|
|
@ -21,6 +21,21 @@ pub const POUNDER_IO_UPDATE_DELAY: f32 = 900_e-9;
|
||||||
|
|
||||||
/// The duration to assert IO_Update for the pounder DDS.
|
/// 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
|
// 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
|
// SYNC_CLK running at 100MHz (1/4 of the pounder reference clock of 500MHz), this corresponds to
|
||||||
// 40ns. To accomodate rounding errors, we use 50ns instead.
|
// 32ns. To accomodate rounding errors, we use 50ns instead.
|
||||||
pub const POUNDER_IO_UPDATE_DURATION: f32 = 50_e-9;
|
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;
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl InputStamper {
|
||||||
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
|
// Utilize the TIM5 CH4 as an input capture channel - use TI4 (the DI0 input trigger) as the
|
||||||
// capture source.
|
// capture source.
|
||||||
let input_capture =
|
let input_capture =
|
||||||
timer_channel.into_input_capture(timers::tim5::CC4S_A::TI4);
|
timer_channel.into_input_capture(timers::CaptureTrigger::Input24);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
capture_channel: input_capture,
|
capture_channel: input_capture,
|
||||||
|
|
127
src/main.rs
127
src/main.rs
|
@ -30,6 +30,9 @@ extern crate panic_halt;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use core::convert::TryInto;
|
||||||
|
|
||||||
// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
|
// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
|
||||||
use cortex_m_rt::exception;
|
use cortex_m_rt::exception;
|
||||||
use rtic::cyccnt::{Instant, U32Ext};
|
use rtic::cyccnt::{Instant, U32Ext};
|
||||||
|
@ -57,10 +60,10 @@ use heapless::{consts::*, String};
|
||||||
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is
|
||||||
// equal to 10ns per tick.
|
// equal to 10ns per tick.
|
||||||
// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz
|
// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz
|
||||||
const ADC_SAMPLE_TICKS: u32 = 256;
|
const ADC_SAMPLE_TICKS: u16 = 256;
|
||||||
|
|
||||||
// The desired ADC sample processing buffer size.
|
// 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!
|
// The number of cascaded IIR biquads per channel. Select 1 or 2!
|
||||||
const IIR_CASCADE_LENGTH: usize = 1;
|
const IIR_CASCADE_LENGTH: usize = 1;
|
||||||
|
@ -220,6 +223,8 @@ const APP: () = {
|
||||||
|
|
||||||
pounder: Option<pounder::PounderDevices>,
|
pounder: Option<pounder::PounderDevices>,
|
||||||
|
|
||||||
|
pounder_stamper: Option<pounder::timestamp::Timestamper>,
|
||||||
|
|
||||||
// Format: iir_state[ch][cascade-no][coeff]
|
// Format: iir_state[ch][cascade-no][coeff]
|
||||||
#[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])]
|
#[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])]
|
||||||
iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2],
|
iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2],
|
||||||
|
@ -293,15 +298,49 @@ const APP: () = {
|
||||||
// Configure the timer to count at the designed tick rate. We will manually set the
|
// Configure the timer to count at the designed tick rate. We will manually set the
|
||||||
// period below.
|
// period below.
|
||||||
timer2.pause();
|
timer2.pause();
|
||||||
|
timer2.reset_counter();
|
||||||
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
let mut sampling_timer = timers::SamplingTimer::new(timer2);
|
||||||
sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1);
|
sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32);
|
||||||
|
|
||||||
|
// The sampling timer is used as the master timer for the shadow-sampling timer. Thus,
|
||||||
|
// it generates a trigger whenever it is enabled.
|
||||||
|
|
||||||
sampling_timer
|
sampling_timer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut shadow_sampling_timer = {
|
||||||
|
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
||||||
|
// dont-care.
|
||||||
|
let mut timer3 =
|
||||||
|
dp.TIM3.timer(1.khz(), ccdr.peripheral.TIM3, &ccdr.clocks);
|
||||||
|
|
||||||
|
// Configure the timer to count at the designed tick rate. We will manually set the
|
||||||
|
// period below.
|
||||||
|
timer3.pause();
|
||||||
|
timer3.reset_counter();
|
||||||
|
timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY);
|
||||||
|
|
||||||
|
let mut shadow_sampling_timer =
|
||||||
|
timers::ShadowSamplingTimer::new(timer3);
|
||||||
|
shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1);
|
||||||
|
|
||||||
|
// The shadow sampling timer is a slave-mode timer to the sampling timer. It should
|
||||||
|
// always be in-sync - thus, we configure it to operate in slave mode using "Trigger
|
||||||
|
// mode".
|
||||||
|
// For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the
|
||||||
|
// SamplingTimer start now gates the start of the ShadowSamplingTimer.
|
||||||
|
shadow_sampling_timer.set_slave_mode(
|
||||||
|
timers::TriggerSource::Trigger1,
|
||||||
|
timers::SlaveMode::Trigger,
|
||||||
|
);
|
||||||
|
|
||||||
|
shadow_sampling_timer
|
||||||
|
};
|
||||||
|
|
||||||
let sampling_timer_channels = sampling_timer.channels();
|
let sampling_timer_channels = sampling_timer.channels();
|
||||||
|
let shadow_sampling_timer_channels = shadow_sampling_timer.channels();
|
||||||
|
|
||||||
let mut timestamp_timer = {
|
let mut timestamp_timer = {
|
||||||
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
// The timer frequency is manually adjusted below, so the 1KHz setting here is a
|
||||||
|
@ -350,6 +389,7 @@ const APP: () = {
|
||||||
})
|
})
|
||||||
.manage_cs()
|
.manage_cs()
|
||||||
.suspend_when_inactive()
|
.suspend_when_inactive()
|
||||||
|
.communication_mode(hal::spi::CommunicationMode::Receiver)
|
||||||
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
||||||
|
|
||||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
||||||
|
@ -364,7 +404,9 @@ const APP: () = {
|
||||||
spi,
|
spi,
|
||||||
dma_streams.0,
|
dma_streams.0,
|
||||||
dma_streams.1,
|
dma_streams.1,
|
||||||
|
dma_streams.2,
|
||||||
sampling_timer_channels.ch1,
|
sampling_timer_channels.ch1,
|
||||||
|
shadow_sampling_timer_channels.ch1,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -388,6 +430,7 @@ const APP: () = {
|
||||||
})
|
})
|
||||||
.manage_cs()
|
.manage_cs()
|
||||||
.suspend_when_inactive()
|
.suspend_when_inactive()
|
||||||
|
.communication_mode(hal::spi::CommunicationMode::Receiver)
|
||||||
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
||||||
|
|
||||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
||||||
|
@ -400,9 +443,11 @@ const APP: () = {
|
||||||
|
|
||||||
Adc1Input::new(
|
Adc1Input::new(
|
||||||
spi,
|
spi,
|
||||||
dma_streams.2,
|
|
||||||
dma_streams.3,
|
dma_streams.3,
|
||||||
|
dma_streams.4,
|
||||||
|
dma_streams.5,
|
||||||
sampling_timer_channels.ch2,
|
sampling_timer_channels.ch2,
|
||||||
|
shadow_sampling_timer_channels.ch2,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,12 +528,12 @@ const APP: () = {
|
||||||
|
|
||||||
let dac0 = Dac0Output::new(
|
let dac0 = Dac0Output::new(
|
||||||
dac0_spi,
|
dac0_spi,
|
||||||
dma_streams.4,
|
dma_streams.6,
|
||||||
sampling_timer_channels.ch3,
|
sampling_timer_channels.ch3,
|
||||||
);
|
);
|
||||||
let dac1 = Dac1Output::new(
|
let dac1 = Dac1Output::new(
|
||||||
dac1_spi,
|
dac1_spi,
|
||||||
dma_streams.5,
|
dma_streams.7,
|
||||||
sampling_timer_channels.ch4,
|
sampling_timer_channels.ch4,
|
||||||
);
|
);
|
||||||
(dac0, dac1)
|
(dac0, dac1)
|
||||||
|
@ -509,7 +554,7 @@ const APP: () = {
|
||||||
delay.delay_ms(2u8);
|
delay.delay_ms(2u8);
|
||||||
let (pounder_devices, dds_output) = if pounder_pgood.is_high().unwrap()
|
let (pounder_devices, dds_output) = if pounder_pgood.is_high().unwrap()
|
||||||
{
|
{
|
||||||
let mut ad9959 = {
|
let ad9959 = {
|
||||||
let qspi_interface = {
|
let qspi_interface = {
|
||||||
// Instantiate the QUADSPI pins and peripheral interface.
|
// Instantiate the QUADSPI pins and peripheral interface.
|
||||||
let qspi_pins = {
|
let qspi_pins = {
|
||||||
|
@ -553,17 +598,24 @@ const APP: () = {
|
||||||
pounder::QspiInterface::new(qspi).unwrap()
|
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 reset_pin = gpioa.pa0.into_push_pull_output();
|
||||||
|
|
||||||
let mut io_update = gpiog.pg7.into_push_pull_output();
|
let mut io_update = gpiog.pg7.into_push_pull_output();
|
||||||
|
|
||||||
|
let ref_clk: hal::time::Hertz =
|
||||||
|
design_parameters::DDS_REF_CLK.into();
|
||||||
|
|
||||||
let ad9959 = ad9959::Ad9959::new(
|
let ad9959 = ad9959::Ad9959::new(
|
||||||
qspi_interface,
|
qspi_interface,
|
||||||
reset_pin,
|
reset_pin,
|
||||||
&mut io_update,
|
&mut io_update,
|
||||||
&mut delay,
|
&mut delay,
|
||||||
ad9959::Mode::FourBitSerial,
|
ad9959::Mode::FourBitSerial,
|
||||||
100_000_000_f32,
|
ref_clk.0 as f32,
|
||||||
5,
|
design_parameters::DDS_MULTIPLIER,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -642,7 +694,6 @@ const APP: () = {
|
||||||
|
|
||||||
let pounder_devices = pounder::PounderDevices::new(
|
let pounder_devices = pounder::PounderDevices::new(
|
||||||
io_expander,
|
io_expander,
|
||||||
&mut ad9959,
|
|
||||||
spi,
|
spi,
|
||||||
adc1,
|
adc1,
|
||||||
adc2,
|
adc2,
|
||||||
|
@ -828,6 +879,54 @@ const APP: () = {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "pounder_v1_1")]
|
||||||
|
let pounder_stamper = {
|
||||||
|
let dma2_streams =
|
||||||
|
hal::dma::dma::StreamsTuple::new(dp.DMA2, ccdr.peripheral.DMA2);
|
||||||
|
|
||||||
|
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,
|
||||||
|
dma2_streams.0,
|
||||||
|
tim8_channels.ch1,
|
||||||
|
&mut sampling_timer,
|
||||||
|
etr_pin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(stamper)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "pounder_v1_1"))]
|
||||||
|
let pounder_stamper = None;
|
||||||
|
|
||||||
// Start sampling ADCs.
|
// Start sampling ADCs.
|
||||||
sampling_timer.start();
|
sampling_timer.start();
|
||||||
timestamp_timer.start();
|
timestamp_timer.start();
|
||||||
|
@ -841,6 +940,7 @@ const APP: () = {
|
||||||
input_stamper,
|
input_stamper,
|
||||||
dds_output,
|
dds_output,
|
||||||
pounder: pounder_devices,
|
pounder: pounder_devices,
|
||||||
|
pounder_stamper,
|
||||||
|
|
||||||
eeprom_i2c,
|
eeprom_i2c,
|
||||||
net_interface: network_interface,
|
net_interface: network_interface,
|
||||||
|
@ -865,8 +965,13 @@ const APP: () = {
|
||||||
///
|
///
|
||||||
/// Because the ADC and DAC operate at the same rate, these two constraints actually implement
|
/// Because the ADC and DAC operate at the same rate, these two constraints actually implement
|
||||||
/// the same time bounds, meeting one also means the other is also met.
|
/// the same time bounds, meeting one also means the other is also met.
|
||||||
#[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)]
|
#[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)]
|
||||||
fn process(c: process::Context) {
|
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 = [
|
let adc_samples = [
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
c.resources.adcs.0.acquire_buffer(),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
c.resources.adcs.1.acquire_buffer(),
|
||||||
|
|
|
@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||||
mod attenuators;
|
mod attenuators;
|
||||||
mod dds_output;
|
mod dds_output;
|
||||||
mod rf_power;
|
mod rf_power;
|
||||||
|
pub mod timestamp;
|
||||||
|
|
||||||
pub use dds_output::DdsOutput;
|
pub use dds_output::DdsOutput;
|
||||||
|
|
||||||
|
@ -274,7 +275,6 @@ impl PounderDevices {
|
||||||
/// Construct and initialize pounder-specific hardware.
|
/// Construct and initialize pounder-specific hardware.
|
||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
/// * `ad9959` - The DDS driver for the pounder hardware.
|
|
||||||
/// * `attenuator_spi` - A SPI interface to control digital attenuators.
|
/// * `attenuator_spi` - A SPI interface to control digital attenuators.
|
||||||
/// * `adc1` - The ADC1 peripheral for measuring power.
|
/// * `adc1` - The ADC1 peripheral for measuring power.
|
||||||
/// * `adc2` - The ADC2 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.
|
/// * `adc2_in_p` - The input channel for the RF power measurement on IN1.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>,
|
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>,
|
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
|
||||||
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
|
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
|
||||||
adc2: hal::adc::Adc<hal::stm32::ADC2, 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)
|
.write_gpio(mcp23017::Port::GPIOB, 1 << 5)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
|
|
||||||
// Select the on-board clock with a 4x prescaler (400MHz).
|
|
||||||
devices
|
devices
|
||||||
.mcp23017
|
.mcp23017
|
||||||
.digital_write(EXT_CLK_SEL_PIN, false)
|
.digital_write(EXT_CLK_SEL_PIN, false)
|
||||||
.map_err(|_| Error::I2c)?;
|
.map_err(|_| Error::I2c)?;
|
||||||
ad9959
|
|
||||||
.configure_system_clock(100_000_000f32, 4)
|
|
||||||
.map_err(|_| Error::Dds)?;
|
|
||||||
|
|
||||||
Ok(devices)
|
Ok(devices)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::Stream0<hal::stm32::DMA2>,
|
||||||
|
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::Stream0<hal::stm32::DMA2>,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
183
src/timers.rs
183
src/timers.rs
|
@ -1,69 +1,195 @@
|
||||||
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
||||||
use super::hal;
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional slave operation modes of a timer.
|
||||||
|
pub enum SlaveMode {
|
||||||
|
Disabled = 0,
|
||||||
|
Trigger = 0b0110,
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! timer_channels {
|
macro_rules! timer_channels {
|
||||||
($name:ident, $TY:ident, u32) => {
|
($name:ident, $TY:ident, $size:ty) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
|
|
||||||
/// The timer used for managing ADC sampling.
|
/// The timer used for managing ADC sampling.
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
timer: hal::timer::Timer<hal::stm32::[< $TY >]>,
|
timer: hal::timer::Timer<hal::stm32::[< $TY >]>,
|
||||||
channels: Option<[< $TY:lower >]::Channels>,
|
channels: Option<[< $TY:lower >]::Channels>,
|
||||||
|
update_event: Option<[< $TY:lower >]::UpdateEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
/// Construct the sampling timer.
|
/// Construct the sampling timer.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn new(mut timer: hal::timer::Timer<hal::stm32::[< $TY>]>) -> Self {
|
pub fn new(mut timer: hal::timer::Timer<hal::stm32::[< $TY>]>) -> Self {
|
||||||
timer.pause();
|
timer.pause();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
timer,
|
timer,
|
||||||
// Note(unsafe): Once these channels are taken, we guarantee that we do not modify any
|
// Note(unsafe): Once these channels are taken, we guarantee that we do not
|
||||||
// of the underlying timer channel registers, as ownership of the channels is now
|
// modify any of the underlying timer channel registers, as ownership of the
|
||||||
// provided through the associated channel structures. We additionally guarantee this
|
// channels is now provided through the associated channel structures. We
|
||||||
// can only be called once because there is only one Timer2 and this resource takes
|
// additionally guarantee this can only be called once because there is only
|
||||||
// ownership of it once instantiated.
|
// one Timer2 and this resource takes ownership of it once instantiated.
|
||||||
channels: unsafe { Some([< $TY:lower >]::Channels::new()) },
|
channels: unsafe { Some([< $TY:lower >]::Channels::new()) },
|
||||||
|
update_event: unsafe { Some([< $TY:lower >]::UpdateEvent::new()) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the timer capture/compare channels.
|
/// Get the timer capture/compare channels.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn channels(&mut self) -> [< $TY:lower >]::Channels {
|
pub fn channels(&mut self) -> [< $TY:lower >]::Channels {
|
||||||
self.channels.take().unwrap()
|
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.
|
/// Get the period of the timer.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_period(&self) -> u32 {
|
pub fn get_period(&self) -> $size {
|
||||||
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
||||||
regs.arr.read().arr().bits()
|
regs.arr.read().arr().bits()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually set the period of the timer.
|
/// Manually set the period of the timer.
|
||||||
#[allow(dead_code)]
|
#[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() };
|
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
||||||
regs.arr.write(|w| w.arr().bits(period));
|
regs.arr.write(|w| w.arr().bits(period));
|
||||||
|
|
||||||
|
// Force the new period to take effect immediately.
|
||||||
|
self.timer.apply_freq();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Start the timer.
|
||||||
pub fn start(mut self) {
|
#[allow(dead_code)]
|
||||||
|
pub fn start(&mut self) {
|
||||||
// Force a refresh of the frequency settings.
|
// Force a refresh of the frequency settings.
|
||||||
self.timer.apply_freq();
|
self.timer.apply_freq();
|
||||||
|
|
||||||
self.timer.reset_counter();
|
self.timer.reset_counter();
|
||||||
|
|
||||||
self.timer.resume();
|
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) } );
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_slave_mode(&mut self, source: TriggerSource, mode: SlaveMode) {
|
||||||
|
let regs = unsafe { &*hal::stm32::$TY::ptr() };
|
||||||
|
// Note(unsafe) The TriggerSource and SlaveMode enumerations are specified such
|
||||||
|
// that they are always in range.
|
||||||
|
regs.smcr.modify(|_, w| unsafe { w.sms().bits(mode as u8).ts().bits(source as u8) } );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod [< $TY:lower >] {
|
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 stm32h7xx_hal as hal;
|
||||||
use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq};
|
use hal::dma::{traits::TargetAddress, PeripheralToMemory, dma::DMAReq};
|
||||||
use hal::stm32::$TY;
|
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.
|
/// The channels representing the timer.
|
||||||
pub struct Channels {
|
pub struct Channels {
|
||||||
pub ch1: Channel1,
|
pub ch1: Channel1,
|
||||||
|
@ -76,6 +202,7 @@ macro_rules! timer_channels {
|
||||||
/// Construct a new set of channels.
|
/// Construct a new set of channels.
|
||||||
///
|
///
|
||||||
/// Note(unsafe): This is only safe to call once.
|
/// Note(unsafe): This is only safe to call once.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub unsafe fn new() -> Self {
|
pub unsafe fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ch1: Channel1::new(),
|
ch1: Channel1::new(),
|
||||||
|
@ -86,15 +213,15 @@ macro_rules! timer_channels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_channels!(1, $TY, ccmr1);
|
timer_channels!(1, $TY, ccmr1, $size);
|
||||||
timer_channels!(2, $TY, ccmr1);
|
timer_channels!(2, $TY, ccmr1, $size);
|
||||||
timer_channels!(3, $TY, ccmr2);
|
timer_channels!(3, $TY, ccmr2, $size);
|
||||||
timer_channels!(4, $TY, ccmr2);
|
timer_channels!(4, $TY, ccmr2, $size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($index:expr, $TY:ty, $ccmrx:expr) => {
|
($index:expr, $TY:ty, $ccmrx:expr, $size:ty) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
/// A capture/compare channel of the timer.
|
/// A capture/compare channel of the timer.
|
||||||
pub struct [< Channel $index >] {}
|
pub struct [< Channel $index >] {}
|
||||||
|
@ -107,6 +234,7 @@ macro_rules! timer_channels {
|
||||||
///
|
///
|
||||||
/// Note(unsafe): This function must only be called once. Once constructed, the
|
/// Note(unsafe): This function must only be called once. Once constructed, the
|
||||||
/// constructee guarantees to never modify the timer channel.
|
/// constructee guarantees to never modify the timer channel.
|
||||||
|
#[allow(dead_code)]
|
||||||
unsafe fn new() -> Self {
|
unsafe fn new() -> Self {
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +251,10 @@ macro_rules! timer_channels {
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `value` - The value to compare the sampling timer's counter against.
|
/// * `value` - The value to compare the sampling timer's counter against.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn to_output_compare(&self, value: u32) {
|
pub fn to_output_compare(&self, value: $size) {
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
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.[< ccr $index >].write(|w| w.ccr().bits(value));
|
||||||
regs.[< $ccmrx _output >]()
|
regs.[< $ccmrx _output >]()
|
||||||
.modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) });
|
.modify(|_, w| unsafe { w.[< cc $index s >]().bits(0) });
|
||||||
|
@ -136,9 +265,12 @@ macro_rules! timer_channels {
|
||||||
/// # Args
|
/// # Args
|
||||||
/// * `input` - The input source for the input capture event.
|
/// * `input` - The input source for the input capture event.
|
||||||
#[allow(dead_code)]
|
#[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() };
|
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 >] {}
|
[< Channel $index InputCapture >] {}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +279,7 @@ macro_rules! timer_channels {
|
||||||
impl [< Channel $index InputCapture >] {
|
impl [< Channel $index InputCapture >] {
|
||||||
/// Get the latest capture from the channel.
|
/// Get the latest capture from the channel.
|
||||||
#[allow(dead_code)]
|
#[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.
|
// Note(unsafe): This channel owns all access to the specific timer channel.
|
||||||
// Only atomic operations on completed on the timer registers.
|
// Only atomic operations on completed on the timer registers.
|
||||||
let regs = unsafe { &*<$TY>::ptr() };
|
let regs = unsafe { &*<$TY>::ptr() };
|
||||||
|
@ -204,7 +336,7 @@ macro_rules! timer_channels {
|
||||||
// is safe as it is only completed once per channel and each DMA request is allocated to
|
// is safe as it is only completed once per channel and each DMA request is allocated to
|
||||||
// each channel as the owner.
|
// each channel as the owner.
|
||||||
unsafe impl TargetAddress<PeripheralToMemory> for [< Channel $index InputCapture >] {
|
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);
|
const REQUEST_LINE: Option<u8> = Some(DMAReq::[< $TY _CH $index >]as u8);
|
||||||
|
|
||||||
|
@ -218,4 +350,7 @@ macro_rules! timer_channels {
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_channels!(SamplingTimer, TIM2, u32);
|
timer_channels!(SamplingTimer, TIM2, u32);
|
||||||
|
timer_channels!(ShadowSamplingTimer, TIM3, u16);
|
||||||
|
|
||||||
timer_channels!(TimestampTimer, TIM5, u32);
|
timer_channels!(TimestampTimer, TIM5, u32);
|
||||||
|
timer_channels!(PounderTimestampTimer, TIM8, u16);
|
||||||
|
|
Loading…
Reference in New Issue