Merge pull request #223 from quartiq/rs/issue-219/adc-setup
Conforming to external ADC conversion timing
This commit is contained in:
commit
ad3681f30b
443
src/adc.rs
443
src/adc.rs
|
@ -1,9 +1,13 @@
|
||||||
///! Stabilizer ADC management interface
|
///! Stabilizer ADC management interface
|
||||||
///!
|
///!
|
||||||
///! The Stabilizer ADCs utilize a DMA channel to trigger sampling. The SPI streams are configured
|
///! The Stabilizer ADCs utilize three DMA channels each: one to trigger sampling, one to collect
|
||||||
///! for full-duplex operation, but only RX is connected to physical pins. A timer channel is
|
///! samples, and one to clear the EOT flag betwen samples. The SPI interfaces are configured
|
||||||
///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and
|
///! for receiver-only operation. A timer channel is
|
||||||
///! results in an ADC sample read for both channels.
|
///! configured to generate a DMA write into the SPI CR1 register, which initiates a SPI transfer and
|
||||||
|
///! results in a single ADC sample read for both channels. A separate timer channel is configured to
|
||||||
|
///! occur immediately before the trigger channel, which initiates a write to the IFCR (flag-clear)
|
||||||
|
///! register to clear the EOT flag, which allows for a new transmission to be generated by the
|
||||||
|
///! trigger channel.
|
||||||
///!
|
///!
|
||||||
///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is
|
///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is
|
||||||
///! configured to read from each of the ADC SPI RX FIFOs. Due to the design of the SPI peripheral,
|
///! configured to read from each of the ADC SPI RX FIFOs. Due to the design of the SPI peripheral,
|
||||||
|
@ -18,11 +22,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
|
||||||
|
@ -33,176 +43,273 @@ 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.
|
||||||
_channel: timers::tim2::$trigger_channel,
|
struct [< $spi CR >] {
|
||||||
}
|
_channel: timers::tim2::$trigger_channel,
|
||||||
impl $spi {
|
|
||||||
pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
|
|
||||||
Self { _channel }
|
|
||||||
}
|
}
|
||||||
}
|
impl [< $spi CR >] {
|
||||||
|
pub fn new(_channel: timers::tim2::$trigger_channel) -> Self {
|
||||||
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is hard-coded and
|
Self { _channel }
|
||||||
// may only be used if ownership of the timer2 $trigger_channel compare channel is assured, which is
|
|
||||||
// ensured by maintaining ownership of the channel.
|
|
||||||
unsafe impl TargetAddress<MemoryToPeripheral> for $spi {
|
|
||||||
/// SPI is configured to operate using 16-bit transfer words.
|
|
||||||
type MemSize = u16;
|
|
||||||
|
|
||||||
/// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
|
|
||||||
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
|
|
||||||
/// transfer.
|
|
||||||
fn address(&self) -> usize {
|
|
||||||
// Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is
|
|
||||||
// only used for the transmit-half of DMA.
|
|
||||||
let regs = unsafe { &*hal::stm32::$spi::ptr() };
|
|
||||||
®s.txdr as *const _ as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents data associated with ADC.
|
|
||||||
pub struct $name {
|
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
|
||||||
transfer: Transfer<
|
|
||||||
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
|
||||||
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
|
||||||
PeripheralToMemory,
|
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
|
||||||
>,
|
|
||||||
_trigger_transfer: Transfer<
|
|
||||||
hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
|
|
||||||
$spi,
|
|
||||||
MemoryToPeripheral,
|
|
||||||
&'static mut [u16; 1],
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
/// Construct the ADC input channel.
|
|
||||||
///
|
|
||||||
/// # Args
|
|
||||||
/// * `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
|
|
||||||
/// the SPI TX FIFO.
|
|
||||||
/// * `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.
|
|
||||||
pub fn new(
|
|
||||||
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
|
|
||||||
trigger_stream: hal::dma::dma::$trigger_stream<
|
|
||||||
hal::stm32::DMA1,
|
|
||||||
>,
|
|
||||||
data_stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
|
||||||
trigger_channel: timers::tim2::$trigger_channel,
|
|
||||||
) -> Self {
|
|
||||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
|
||||||
// occurs.
|
|
||||||
trigger_channel.listen_dma();
|
|
||||||
trigger_channel.to_output_compare(0);
|
|
||||||
|
|
||||||
// The trigger stream constantly writes to the TX FIFO using a static word (dont-care
|
|
||||||
// contents). 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()
|
|
||||||
.priority(Priority::High)
|
|
||||||
.circular_buffer(true);
|
|
||||||
|
|
||||||
// Construct the trigger stream to write from memory to the peripheral.
|
|
||||||
let mut trigger_transfer: Transfer<
|
|
||||||
_,
|
|
||||||
_,
|
|
||||||
MemoryToPeripheral,
|
|
||||||
_,
|
|
||||||
> = Transfer::init(
|
|
||||||
trigger_stream,
|
|
||||||
$spi::new(trigger_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_START },
|
|
||||||
None,
|
|
||||||
trigger_config,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
|
|
||||||
// stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
|
|
||||||
// after the requested number of samples have been collected. Note that only ADC1's (sic!)
|
|
||||||
// data stream is used to trigger a transfer completion interrupt.
|
|
||||||
let data_config = DmaConfig::default()
|
|
||||||
.memory_increment(true)
|
|
||||||
.transfer_complete_interrupt($index == 1)
|
|
||||||
.priority(Priority::VeryHigh);
|
|
||||||
|
|
||||||
// A SPI peripheral error interrupt is used to determine if the RX FIFO overflows. This
|
|
||||||
// indicates that samples were dropped due to excessive processing time in the main
|
|
||||||
// application (e.g. a second DMA transfer completes before the first was done with
|
|
||||||
// processing). This is used as a flow control indicator to guarantee that no ADC samples
|
|
||||||
// are lost.
|
|
||||||
let mut spi = spi.disable();
|
|
||||||
spi.listen(hal::spi::Event::Error);
|
|
||||||
|
|
||||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
|
||||||
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
|
||||||
Transfer::init(
|
|
||||||
data_stream,
|
|
||||||
spi,
|
|
||||||
// Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral.
|
|
||||||
// It shall not be used anywhere else in the module.
|
|
||||||
unsafe { &mut ADC_BUF[$index][0] },
|
|
||||||
None,
|
|
||||||
data_config,
|
|
||||||
);
|
|
||||||
|
|
||||||
data_transfer.start(|spi| {
|
|
||||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
|
||||||
spi.enable_dma_rx();
|
|
||||||
spi.enable_dma_tx();
|
|
||||||
|
|
||||||
// Enable SPI and start it in infinite transaction mode.
|
|
||||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
|
||||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
|
||||||
});
|
|
||||||
|
|
||||||
trigger_transfer.start(|_| {});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It shall not be used
|
|
||||||
// anywhere else in the module.
|
|
||||||
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
|
||||||
transfer: data_transfer,
|
|
||||||
_trigger_transfer: trigger_transfer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a buffer filled with ADC samples.
|
// 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 timer2 $trigger_channel compare
|
||||||
/// # Returns
|
// channel is assured, which is ensured by maintaining ownership of the channel.
|
||||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
unsafe impl TargetAddress<MemoryToPeripheral> for [< $spi CR >] {
|
||||||
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();
|
type MemSize = u32;
|
||||||
|
|
||||||
// Start the next transfer.
|
/// SPI DMA requests are generated whenever TIM2 CHx ($dma_req) comparison occurs.
|
||||||
self.transfer.clear_interrupts();
|
const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
|
||||||
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
|
/// Whenever the DMA request occurs, it should write into SPI's CR1 to start the
|
||||||
|
/// transfer.
|
||||||
|
fn address(&self) -> usize {
|
||||||
|
// Note(unsafe): It is assumed that SPI is owned by another DMA transfer. This
|
||||||
|
// is only safe because we are writing to a configuration register.
|
||||||
|
let regs = unsafe { &*hal::stm32::$spi::ptr() };
|
||||||
|
®s.cr1 as *const _ as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.next_buffer.as_ref().unwrap()
|
/// $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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents data associated with ADC.
|
||||||
|
pub struct $name {
|
||||||
|
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||||
|
transfer: Transfer<
|
||||||
|
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
|
||||||
|
hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
|
||||||
|
PeripheralToMemory,
|
||||||
|
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||||
|
>,
|
||||||
|
_trigger_transfer: Transfer<
|
||||||
|
hal::dma::dma::$trigger_stream<hal::stm32::DMA1>,
|
||||||
|
[< $spi CR >],
|
||||||
|
MemoryToPeripheral,
|
||||||
|
&'static mut [u32; 1],
|
||||||
|
>,
|
||||||
|
_flag_clear_transfer: Transfer<
|
||||||
|
hal::dma::dma::$clear_stream<hal::stm32::DMA1>,
|
||||||
|
[< $spi IFCR >],
|
||||||
|
MemoryToPeripheral,
|
||||||
|
&'static mut [u32; 1],
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
/// Construct the ADC input channel.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `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 the SPI TX FIFO.
|
||||||
|
/// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
|
||||||
|
/// * `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(
|
||||||
|
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
|
||||||
|
trigger_stream: hal::dma::dma::$trigger_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,
|
||||||
|
clear_channel: timers::tim3::$clear_channel,
|
||||||
|
) -> Self {
|
||||||
|
// The flag clear DMA transfer always clears the EOT flag in the SPI
|
||||||
|
// peripheral. It has the highest priority to ensure it is completed before the
|
||||||
|
// transfer trigger.
|
||||||
|
let clear_config = DmaConfig::default()
|
||||||
|
.priority(Priority::VeryHigh)
|
||||||
|
.circular_buffer(true);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
SPI_EOT_CLEAR[0] = 1 << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
.priority(Priority::High)
|
||||||
|
.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.
|
||||||
|
let mut trigger_transfer: Transfer<
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
MemoryToPeripheral,
|
||||||
|
_,
|
||||||
|
> = Transfer::init(
|
||||||
|
trigger_stream,
|
||||||
|
[< $spi CR >]::new(trigger_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_START },
|
||||||
|
None,
|
||||||
|
trigger_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
|
||||||
|
// stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
|
||||||
|
// after the requested number of samples have been collected. Note that only ADC1's (sic!)
|
||||||
|
// data stream is used to trigger a transfer completion interrupt.
|
||||||
|
let data_config = DmaConfig::default()
|
||||||
|
.memory_increment(true)
|
||||||
|
.transfer_complete_interrupt($index == 1)
|
||||||
|
.priority(Priority::VeryHigh);
|
||||||
|
|
||||||
|
// A SPI peripheral error interrupt is used to determine if the RX FIFO
|
||||||
|
// overflows. This indicates that samples were dropped due to excessive
|
||||||
|
// processing time in the main application (e.g. a second DMA transfer completes
|
||||||
|
// before the first was done with processing). This is used as a flow control
|
||||||
|
// indicator to guarantee that no ADC samples are lost.
|
||||||
|
let mut spi = spi.disable();
|
||||||
|
spi.listen(hal::spi::Event::Error);
|
||||||
|
|
||||||
|
// The data transfer is always a transfer of data from the peripheral to a RAM
|
||||||
|
// buffer.
|
||||||
|
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
||||||
|
Transfer::init(
|
||||||
|
data_stream,
|
||||||
|
spi,
|
||||||
|
// Note(unsafe): The ADC_BUF[$index][0] is "owned" by this peripheral.
|
||||||
|
// It shall not be used anywhere else in the module.
|
||||||
|
unsafe { &mut ADC_BUF[$index][0] },
|
||||||
|
None,
|
||||||
|
data_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
data_transfer.start(|spi| {
|
||||||
|
// Allow the SPI RX FIFO to generate DMA transfer requests when data is
|
||||||
|
// available.
|
||||||
|
spi.enable_dma_rx();
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
});
|
||||||
|
|
||||||
|
clear_transfer.start(|_| {});
|
||||||
|
trigger_transfer.start(|_| {});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It
|
||||||
|
// shall not be used anywhere else in the module.
|
||||||
|
next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) },
|
||||||
|
transfer: data_transfer,
|
||||||
|
_trigger_transfer: trigger_transfer,
|
||||||
|
_flag_clear_transfer: clear_transfer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain a buffer filled with ADC samples.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to the underlying buffer that has been filled with ADC samples.
|
||||||
|
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.
|
||||||
|
self.transfer.clear_interrupts();
|
||||||
|
let (prev_buffer, _, _) =
|
||||||
|
self.transfer.next_transfer(next_buffer).unwrap();
|
||||||
|
|
||||||
|
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
||||||
|
self.next_buffer.replace(prev_buffer);
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
|
@ -153,5 +153,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);
|
||||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -60,7 +60,7 @@ 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 = 8;
|
const SAMPLE_BUFFER_SIZE: usize = 8;
|
||||||
|
@ -298,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
|
||||||
|
@ -355,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(
|
||||||
|
@ -369,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,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -393,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(
|
||||||
|
@ -405,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,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -488,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)
|
||||||
|
@ -841,6 +881,9 @@ const APP: () = {
|
||||||
|
|
||||||
#[cfg(feature = "pounder_v1_1")]
|
#[cfg(feature = "pounder_v1_1")]
|
||||||
let pounder_stamper = {
|
let pounder_stamper = {
|
||||||
|
let dma2_streams =
|
||||||
|
hal::dma::dma::StreamsTuple::new(dp.DMA2, ccdr.peripheral.DMA2);
|
||||||
|
|
||||||
let etr_pin = gpioa.pa0.into_alternate_af3();
|
let etr_pin = gpioa.pa0.into_alternate_af3();
|
||||||
|
|
||||||
// The frequency in the constructor is dont-care, as we will modify the period + clock
|
// The frequency in the constructor is dont-care, as we will modify the period + clock
|
||||||
|
@ -872,7 +915,7 @@ const APP: () = {
|
||||||
|
|
||||||
let stamper = pounder::timestamp::Timestamper::new(
|
let stamper = pounder::timestamp::Timestamper::new(
|
||||||
timestamp_timer,
|
timestamp_timer,
|
||||||
dma_streams.7,
|
dma2_streams.0,
|
||||||
tim8_channels.ch1,
|
tim8_channels.ch1,
|
||||||
&mut sampling_timer,
|
&mut sampling_timer,
|
||||||
etr_pin,
|
etr_pin,
|
||||||
|
@ -906,7 +949,7 @@ const APP: () = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds=DMA1_STR3, resources=[pounder_stamper, 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 {
|
if let Some(stamper) = c.resources.pounder_stamper {
|
||||||
let pounder_timestamps = stamper.acquire_buffer();
|
let pounder_timestamps = stamper.acquire_buffer();
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub struct Timestamper {
|
||||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||||
timer: timers::PounderTimestampTimer,
|
timer: timers::PounderTimestampTimer,
|
||||||
transfer: Transfer<
|
transfer: Transfer<
|
||||||
hal::dma::dma::Stream7<hal::stm32::DMA1>,
|
hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
||||||
timers::tim8::Channel1InputCapture,
|
timers::tim8::Channel1InputCapture,
|
||||||
PeripheralToMemory,
|
PeripheralToMemory,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||||
|
@ -64,7 +64,7 @@ impl Timestamper {
|
||||||
/// The new pounder timestamper in an operational state.
|
/// The new pounder timestamper in an operational state.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut timestamp_timer: timers::PounderTimestampTimer,
|
mut timestamp_timer: timers::PounderTimestampTimer,
|
||||||
stream: hal::dma::dma::Stream7<hal::stm32::DMA1>,
|
stream: hal::dma::dma::Stream0<hal::stm32::DMA2>,
|
||||||
capture_channel: timers::tim8::Channel1,
|
capture_channel: timers::tim8::Channel1,
|
||||||
sampling_timer: &mut timers::SamplingTimer,
|
sampling_timer: &mut timers::SamplingTimer,
|
||||||
_clock_input: hal::gpio::gpioa::PA0<
|
_clock_input: hal::gpio::gpioa::PA0<
|
||||||
|
|
|
@ -39,6 +39,12 @@ pub enum Prescaler {
|
||||||
Div8 = 0b11,
|
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, $size:ty) => {
|
($name:ident, $TY:ident, $size:ty) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
|
@ -92,6 +98,9 @@ macro_rules! timer_channels {
|
||||||
pub fn set_period_ticks(&mut self, period: $size) {
|
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.
|
/// Clock the timer from an external source.
|
||||||
|
@ -139,6 +148,14 @@ macro_rules! timer_channels {
|
||||||
// always in range.
|
// always in range.
|
||||||
regs.smcr.modify(|_, w| unsafe { w.ts().bits(source as u8) } );
|
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 >] {
|
||||||
|
@ -333,5 +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);
|
timer_channels!(PounderTimestampTimer, TIM8, u16);
|
||||||
|
|
Loading…
Reference in New Issue