Adding documentation for ADCs and DACs
This commit is contained in:
parent
a71f790574
commit
8e4a7c8fa9
81
src/adc.rs
81
src/adc.rs
@ -1,18 +1,75 @@
|
|||||||
///! Stabilizer ADC management interface
|
///! Stabilizer ADC management interface
|
||||||
///!
|
///!
|
||||||
///! The Stabilizer ADCs utilize a DMA channel to trigger sampling. The SPI streams are configured
|
///! # Design
|
||||||
///! for full-duplex operation, but only RX is connected to physical pins. A timer channel is
|
|
||||||
///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and
|
|
||||||
///! results in an ADC sample read for both channels.
|
|
||||||
///!
|
///!
|
||||||
///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is
|
///! Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs
|
||||||
///! configured to read from each of the ADC SPI RX FIFOs. Due to the design of the SPI peripheral,
|
///! require a setup conversion time after asserting the CSn (convert) signal to generate the ADC
|
||||||
///! these DMA transfers stall when no data is available in the FIFO. Thus, the DMA transfer only
|
///! code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of
|
||||||
///! completes after all samples have been read. When this occurs, a CPU interrupt is generated so
|
///! MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter
|
||||||
///! that software can process the acquired samples from both ADCs. Only one of the ADC DMA streams
|
///! during SPI configuration, which allows offloading the management of the setup time to hardware.
|
||||||
///! is configured to generate an interrupt to handle both transfers, so it is necessary to ensure
|
///!
|
||||||
///! both transfers are completed before reading the data. This is usually not significant for
|
///! Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate
|
||||||
///! busy-waiting because the transfers should complete at approximately the same time.
|
///! 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.
|
||||||
|
///!
|
||||||
|
///! The automation of sample collection utilizes two DMA streams, the SPI peripheral, and a timer
|
||||||
|
///! compare channel for each ADC. The timer comparison channel is configured to generate a
|
||||||
|
///! 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,
|
||||||
|
///! 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 word whenever a word is written to
|
||||||
|
///! the TX side. Thus, by running a continuous DMA transfer to periodically write a value into the
|
||||||
|
///! TX FIFO, we can schedule the regular collection of ADC samples in the SPI RX buffer.
|
||||||
|
///!
|
||||||
|
///! In order to collect the acquired ADC samples into a RAM buffer, a second DMA transfer is
|
||||||
|
///! 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
|
||||||
|
///! available. When enough samples have been collected, a transfer-complete interrupt is generated
|
||||||
|
///! and the ADC samples are available for processing.
|
||||||
|
///!
|
||||||
|
///! The SPI peripheral internally has an 8- or 16-byte TX and RX FIFO, which corresponds to a 4- or
|
||||||
|
///! 8-sample buffer for incoming ADC samples. During the handling of the DMA transfer completion,
|
||||||
|
///! there is a small window where buffers are swapped over where it's possible that a sample could
|
||||||
|
///! be lost. In order to avoid this, the SPI RX FIFO is effectively used as a "sample overflow"
|
||||||
|
///! region and can buffer a number of samples until the next DMA transfer is configured. If a DMA
|
||||||
|
///! transfer is still not set in time, the SPI peripheral will generate an input-overrun interrupt.
|
||||||
|
///! This interrupt then serves as a means of detecting if samples have been lost, which will occur
|
||||||
|
///! whenever data processing takes longer than the collection period.
|
||||||
|
///!
|
||||||
|
///!
|
||||||
|
///! ## Starting Data Collection
|
||||||
|
///!
|
||||||
|
///! Because the DMA data collection is automated via timer count comparisons and DMA transfers, the
|
||||||
|
///! ADCs can be initialized and configured, but will not begin sampling the external ADCs until the
|
||||||
|
///! sampling timer is enabled. As such, the sampling timer should be enabled after all
|
||||||
|
///! initialization has completed and immediately before the embedded processing loop begins.
|
||||||
|
///!
|
||||||
|
///!
|
||||||
|
///! ## Batch Sizing
|
||||||
|
///!
|
||||||
|
///! 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
|
||||||
|
///! batch sizes generally provide for more processing time per sample, but come at the expense of
|
||||||
|
///! increased input -> output latency.
|
||||||
|
///!
|
||||||
|
///!
|
||||||
|
///! # Note
|
||||||
|
///!
|
||||||
|
///! While there are two ADCs, only a single ADC is configured to generate transfer-complete
|
||||||
|
///! interrupts. This is done because it is assumed that the ADCs will always be sampled
|
||||||
|
///! simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate
|
||||||
|
///! transfer-complete interrupts.
|
||||||
|
///!
|
||||||
|
///! There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As
|
||||||
|
///! such, one of the ADCs will be sampled marginally earlier before the other because the DMA
|
||||||
|
///! requests are generated simultaneously. This can be avoided by providing a known offset to the
|
||||||
|
///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter
|
||||||
|
///! value of 0 and ADC1's comparison to a counter value of 1.
|
||||||
|
///!
|
||||||
|
///! In this implementation, single buffer mode DMA transfers are used because the SPI RX FIFO can
|
||||||
|
///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because
|
||||||
|
///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless
|
||||||
|
///! double-buffered mode offers less overhead when accessing data).
|
||||||
use super::{
|
use super::{
|
||||||
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral,
|
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral,
|
||||||
PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE,
|
PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE,
|
||||||
|
75
src/dac.rs
75
src/dac.rs
@ -1,8 +1,45 @@
|
|||||||
///! Stabilizer DAC management interface
|
///! Stabilizer DAC management interface
|
||||||
///!
|
///!
|
||||||
///! The Stabilizer DAC utilize a DMA channel to generate output updates. A timer channel is
|
///! # Design
|
||||||
///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and
|
///!
|
||||||
///! results in DAC update for both channels.
|
///! Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC
|
||||||
|
///! accepts a 16-bit output code.
|
||||||
|
///!
|
||||||
|
///! In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using
|
||||||
|
///! a timer compare channel, DMA stream, and the DAC SPI interface.
|
||||||
|
///!
|
||||||
|
///! The timer comparison channel is configured to generate a DMA request whenever the comparison
|
||||||
|
///! occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By
|
||||||
|
///! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update
|
||||||
|
///! the DAC without requiring the CPU.
|
||||||
|
///!
|
||||||
|
///! ## Multiple Samples to Single DAC Codes
|
||||||
|
///!
|
||||||
|
///! For some applications, it may be desirable to generate a single DAC code from multiple ADC
|
||||||
|
///! samples. In order to maintain timing characteristics between ADC samples and DAC code outputs,
|
||||||
|
///! applications are required to generate one DAC code for each ADC sample. To accomodate mapping
|
||||||
|
///! multiple inputs to a single output, the output code can be repeated a number of times in the
|
||||||
|
///! output buffer corresponding with the number of input samples that were used to generate it.
|
||||||
|
///!
|
||||||
|
///!
|
||||||
|
///! # Note
|
||||||
|
///!
|
||||||
|
///! There is a very small amount of latency between updating the two DACs due to bus matrix
|
||||||
|
///! priority. As such, one of the DACs will be updated marginally earlier before the other because
|
||||||
|
///! the DMA requests are generated simultaneously. This can be avoided by providing a known offset
|
||||||
|
///! to other DMA requests, which can be completed by setting e.g. DAC0's comparison to a
|
||||||
|
///! counter value of 2 and DAC1's comparison to a counter value of 3. This will have the effect of
|
||||||
|
///! generating the DAC updates with a known latency of 1 timer tick to each other and prevent the
|
||||||
|
///! DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the
|
||||||
|
///! DMA channels to arbitrate which transfer occurs first.
|
||||||
|
///!
|
||||||
|
///!
|
||||||
|
///! # Future Improvements
|
||||||
|
///!
|
||||||
|
///! In this implementation, single buffer mode DMA transfers are used. As a result of this, it's
|
||||||
|
///! possible that a timer comparison could be missed during the swap-over, which will result in a
|
||||||
|
///! delay of a single output code. In the future, this can be remedied by utilize double-buffer
|
||||||
|
///! mode for the DMA transfers.
|
||||||
use super::{
|
use super::{
|
||||||
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress,
|
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress,
|
||||||
Transfer, SAMPLE_BUFFER_SIZE,
|
Transfer, SAMPLE_BUFFER_SIZE,
|
||||||
@ -90,12 +127,11 @@ macro_rules! dac_output {
|
|||||||
let mut spi = spi.disable();
|
let mut spi = spi.disable();
|
||||||
spi.listen(hal::spi::Event::Error);
|
spi.listen(hal::spi::Event::Error);
|
||||||
|
|
||||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
// AXISRAM is uninitialized. As such, we manually zero-initialize it here before
|
||||||
spi.enable_dma_tx();
|
// starting the transfer.
|
||||||
|
for byte in DAC_BUF[$index].iter_mut() {
|
||||||
// Enable SPI and start it in infinite transaction mode.
|
*byte = 0;
|
||||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
}
|
||||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
|
||||||
|
|
||||||
// Construct the trigger stream to write from memory to the peripheral.
|
// Construct the trigger stream to write from memory to the peripheral.
|
||||||
let transfer: Transfer<_, _, MemoryToPeripheral, _> =
|
let transfer: Transfer<_, _, MemoryToPeripheral, _> =
|
||||||
@ -108,6 +144,15 @@ macro_rules! dac_output {
|
|||||||
trigger_config,
|
trigger_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
transfer.start(|spi| {
|
||||||
|
// Allow the SPI FIFOs to operate using only DMA data channels.
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
transfer,
|
transfer,
|
||||||
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
||||||
@ -131,15 +176,9 @@ macro_rules! dac_output {
|
|||||||
&mut self,
|
&mut self,
|
||||||
next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE],
|
next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||||
) {
|
) {
|
||||||
// If the last transfer was not complete, we didn't write all our previous DAC codes.
|
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
||||||
// Wait for all the DAC codes to get written as well.
|
// there is no time-out checks here in the interest of execution speed.
|
||||||
if self.first_transfer {
|
while !self.transfer.get_transfer_complete_flag() {}
|
||||||
self.first_transfer = false
|
|
||||||
} else {
|
|
||||||
// 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() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the next transfer.
|
// Start the next transfer.
|
||||||
self.transfer.clear_interrupts();
|
self.transfer.clear_interrupts();
|
||||||
|
Loading…
Reference in New Issue
Block a user