From 8e4a7c8fa9993f41e9386116af9daad5f7a9d222 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 15 Dec 2020 16:46:12 +0100 Subject: [PATCH 01/12] Adding documentation for ADCs and DACs --- src/adc.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++-------- src/dac.rs | 75 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 126 insertions(+), 30 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index e9120aa..9b0aa33 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,18 +1,75 @@ ///! Stabilizer ADC management interface ///! -///! The Stabilizer ADCs utilize a DMA channel to trigger sampling. The SPI streams are configured -///! 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. +///! # Design ///! -///! 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, -///! these DMA transfers stall when no data is available in the FIFO. Thus, the DMA transfer only -///! completes after all samples have been read. When this occurs, a CPU interrupt is generated so -///! that software can process the acquired samples from both ADCs. Only one of the ADC DMA streams -///! 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 -///! busy-waiting because the transfers should complete at approximately the same time. +///! Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs +///! require a setup conversion time after asserting the CSn (convert) signal to generate the ADC +///! code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of +///! MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter +///! during SPI configuration, which allows offloading the management of the setup time to hardware. +///! +///! Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate +///! 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::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, diff --git a/src/dac.rs b/src/dac.rs index d96109c..ef0561a 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -1,8 +1,45 @@ ///! Stabilizer DAC management interface ///! -///! The Stabilizer DAC utilize a DMA channel to generate output updates. A timer channel is -///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and -///! results in DAC update for both channels. +///! # Design +///! +///! 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::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, @@ -90,12 +127,11 @@ macro_rules! dac_output { let mut spi = spi.disable(); spi.listen(hal::spi::Event::Error); - // 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()); + // AXISRAM is uninitialized. As such, we manually zero-initialize it here before + // starting the transfer. + for byte in DAC_BUF[$index].iter_mut() { + *byte = 0; + } // Construct the trigger stream to write from memory to the peripheral. let transfer: Transfer<_, _, MemoryToPeripheral, _> = @@ -108,6 +144,15 @@ macro_rules! dac_output { 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 { 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, next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE], ) { - // If the last transfer was not complete, we didn't write all our previous DAC codes. - // Wait for all the DAC codes to get written as well. - if self.first_transfer { - 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() {} - } + // 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. self.transfer.clear_interrupts(); From 438b2919741e7c2bd7b455212dd4b7db002d9286 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:09:18 +0100 Subject: [PATCH 02/12] Updating DAC output format, adding DDS stream docs --- src/dac.rs | 47 +++++++++++++++------------------- src/main.rs | 5 +--- src/pounder/dds_output.rs | 53 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/dac.rs b/src/dac.rs index ef0561a..ef4601a 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -69,6 +69,15 @@ macro_rules! dac_output { ) -> Self { Self { _channel, spi } } + + pub fn start_dma(&mut self) { + // Allow the SPI FIFOs to operate using only DMA data channels. + self.spi.enable_dma_tx(); + + // Enable SPI and start it in infinite transaction mode. + self.spi.inner().cr1.modify(|_, w| w.spe().set_bit()); + self.spi.inner().cr1.modify(|_, w| w.cstart().started()); + } } // Note(unsafe): This is safe because the DMA request line is logically owned by this module. @@ -97,7 +106,6 @@ macro_rules! dac_output { MemoryToPeripheral, &'static mut [u16; SAMPLE_BUFFER_SIZE], >, - first_transfer: bool, } impl $name { @@ -129,12 +137,14 @@ macro_rules! dac_output { // AXISRAM is uninitialized. As such, we manually zero-initialize it here before // starting the transfer. - for byte in DAC_BUF[$index].iter_mut() { - *byte = 0; + for buf in unsafe { DAC_BUF[$index].iter_mut() } { + for byte in buf.iter_mut() { + *byte = 0; + } } // Construct the trigger stream to write from memory to the peripheral. - let transfer: Transfer<_, _, MemoryToPeripheral, _> = + let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init( stream, $spi::new(trigger_channel, spi), @@ -144,42 +154,23 @@ macro_rules! dac_output { 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()); - }); + transfer.start(|spi| spi.start_dma()); Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) }, - first_transfer: true, } } /// Acquire the next output buffer to populate it with DAC codes. - pub fn acquire_buffer( - &mut self, - ) -> &'static mut [u16; SAMPLE_BUFFER_SIZE] { - self.next_buffer.take().unwrap() - } - - /// Enqueue the next buffer for transmission to the DAC. - /// - /// # Args - /// * `data` - The next data to write to the DAC. - pub fn release_buffer( - &mut self, - next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE], - ) { + pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] { // 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, _) = @@ -187,6 +178,8 @@ macro_rules! dac_output { // .unwrap_none() https://github.com/rust-lang/rust/issues/62633 self.next_buffer.replace(prev_buffer); + + self.next_buffer.as_mut().unwrap() } } }; diff --git a/src/main.rs b/src/main.rs index b37f740..0ac8857 100644 --- a/src/main.rs +++ b/src/main.rs @@ -805,6 +805,7 @@ const APP: () = { c.resources.adcs.0.acquire_buffer(), c.resources.adcs.1.acquire_buffer(), ]; + let dac_samples = [ c.resources.dacs.0.acquire_buffer(), c.resources.dacs.1.acquire_buffer(), @@ -836,10 +837,6 @@ const APP: () = { builder.write_profile(); } - - let [dac0, dac1] = dac_samples; - c.resources.dacs.0.release_buffer(dac0); - c.resources.dacs.1.release_buffer(dac1); } #[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afes])] diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index 418eb65..36399f2 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,4 +1,57 @@ ///! The DdsOutput is used as an output stream to the pounder DDS. +///! +///! # Design +///! +///! The DDS stream interface is a means of quickly updating pounder DDS (direct digital synthesis) +///! outputs of the AD9959 DDS chip. The DDS communicates via a quad-SPI interface and a single +///! IO-update output pin. +///! +///! In order to update the DDS interface, the frequency tuning word, amplitude control word, and +///! the phase offset word for a channel can be modified to change the frequency, amplitude, or +///! phase on any of the 4 available output channels. Changes do not propagate to DDS outputs until +///! the IO-update pin is toggled high to activate the new configurations. This allows multiple +///! channels or parameters to be updated and then effects can take place simultaneously. +///! +///! In this implementation, the phase, frequency, or amplitude can be updated for any single +///! collection of outputs simultaneously. This is done by serializing the register writes to the +///! DDS into a single buffer of data and then writing the data over QSPI to the DDS. +///! +///! In order to minimize software overhead, data is written directly into the QSPI output FIFO. In +///! order to accomplish this most efficiently, serialized data is written as 32-bit words to +///! minimize the number of bus cycles necessary to write to the peripheral FIFO. A consequence of +///! this is that additional unneeded register writes may be appended to align a transfer to 32-bit +///! word sizes. +///! +///! In order to pulse the IO-update signal, the high-resolution timer output is used. The timer is +///! configured to assert the IO-update signal after a predefined delay and then de-assert the +///! signal after a predefined assertion duration. This allows for the actual QSPI transfer and +///! IO-update toggle to be completed asynchronously to the rest of software processing - that is, +///! software can schedule the DDS updates and then continue data processing. DDS updates then take +///! place in the future when the IO-update is toggled by hardware. +///! +///! +///! # Limitations +///! +///! The QSPI output FIFO is used as an intermediate buffer for holding pending QSPI writes. Because +///! of this, the implementation only supports up to 16 serialized bytes (the QSPI FIFO is 4 32-bit +///! words wide) in a single update. +///! +///! There is currently no synchronization between completion of the QSPI data write and the +///! IO-update signal. It is currently assumed that the QSPI transfer will always complete within a +///! predefined delay (the pre-programmed IO-update timer delay). +///! +///! +///! # Future Improvement +///! +///! In the future, it would be possible to utilize a DMA transfer to complete the QSPI transfer. +///! Once the QSPI transfer completed, this could trigger the IO-update timer to start to +///! asynchronously complete IO-update automatically. This would allow for arbitrary profile sizes +///! and ensure that IO-update was in-sync with the QSPI transfer. +///! +///! Currently, serialization is performed on each processing cycle. If there is a +///! compile-time-known register update sequence needed for the application, the serialization +///! process can be done once and then register values can be written into a pre-computed serialized +///! buffer to avoid the software overhead of much of the serialization process. use super::QspiInterface; use crate::hrtimer::HighResTimerE; use ad9959::{Channel, DdsConfig, ProfileSerializer}; From 8fb37c2db9e7386abbb5036752b5e562ce74886e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:10:36 +0100 Subject: [PATCH 03/12] Adding docs --- src/dac.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dac.rs b/src/dac.rs index ef4601a..ef65d19 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -70,6 +70,7 @@ macro_rules! dac_output { Self { _channel, spi } } + /// Start the SPI and begin operating in a DMA-driven transfer mode. pub fn start_dma(&mut self) { // Allow the SPI FIFOs to operate using only DMA data channels. self.spi.enable_dma_tx(); From ec2aaecb480cd288a15ba7efb122f74c3ebd46fb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:11:28 +0100 Subject: [PATCH 04/12] Adding safety documentation --- src/dac.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index ef65d19..f21ccfd 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -138,6 +138,8 @@ macro_rules! dac_output { // AXISRAM is uninitialized. As such, we manually zero-initialize it here before // starting the transfer. + // Note(unsafe): We currently own all DAC_BUF[index] buffers and are not using them + // elsewhere, so it is safe to access them here. for buf in unsafe { DAC_BUF[$index].iter_mut() } { for byte in buf.iter_mut() { *byte = 0; From fb1ea765ce9d8a9737b95a0c6e73cf6bf0a05b43 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:27:47 +0100 Subject: [PATCH 05/12] Updating DACs to utilize DBM --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/adc.rs | 4 ++-- src/dac.rs | 23 ++++++++++++----------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e35ab18..866c958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#0bfeeca4ce120c1b7c6d140a7da73a4372b874d8" +source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/dma-buffer-swap-logic#5f97920b639f8cb29c9f30c89a33960d5b2082f8" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 9dfa28a..73b2f69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/stm32-rs/stm32h7xx-hal" -branch = "dma" +git = "https://github.com/quartiq/stm32h7xx-hal" +branch = "feature/dma-buffer-swap-logic" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/adc.rs b/src/adc.rs index 9b0aa33..a652d3d 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -117,11 +117,11 @@ macro_rules! adc_input { /// Whenever the DMA request occurs, it should write into SPI's TX FIFO to start a DMA /// transfer. - fn address(&self) -> u32 { + fn address(&self) -> usize { // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is // only used for the transmit-half of DMA. let regs = unsafe { &*hal::stm32::$spi::ptr() }; - ®s.txdr as *const _ as u32 + ®s.txdr as *const _ as usize } } diff --git a/src/dac.rs b/src/dac.rs index f21ccfd..3e44379 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -34,12 +34,11 @@ ///! DMA channels to arbitrate which transfer occurs first. ///! ///! -///! # Future Improvements +///! # Limitations ///! -///! 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. +///! While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check +///! for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is +///! served promptly after the transfer completes. use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, @@ -50,8 +49,8 @@ use super::{ // processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on // startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`. #[link_section = ".axisram.buffers"] -static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] = - [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2]; +static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] = + [[[0; SAMPLE_BUFFER_SIZE]; 3]; 2]; macro_rules! dac_output { ($name:ident, $index:literal, $data_stream:ident, @@ -92,8 +91,8 @@ macro_rules! dac_output { const REQUEST_LINE: Option = Some(DMAReq::$dma_req as u8); /// Whenever the DMA request occurs, it should write into SPI's TX FIFO. - fn address(&self) -> u32 { - &self.spi.inner().txdr as *const _ as u32 + fn address(&self) -> usize { + &self.spi.inner().txdr as *const _ as usize } } @@ -129,6 +128,7 @@ macro_rules! dac_output { // The stream constantly writes to the TX FIFO to write new update codes. let trigger_config = DmaConfig::default() .memory_increment(true) + .double_buffer(true) .peripheral_increment(false); // Listen for any potential SPI error signals, which may indicate that we are not generating @@ -153,7 +153,8 @@ macro_rules! dac_output { $spi::new(trigger_channel, spi), // Note(unsafe): This buffer is only used once and provided for the DMA transfer. unsafe { &mut DAC_BUF[$index][0] }, - None, + // Note(unsafe): This buffer is only used once and provided for the DMA transfer. + unsafe { Some(&mut DAC_BUF[$index][1]) }, trigger_config, ); @@ -162,7 +163,7 @@ macro_rules! dac_output { Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. - next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) }, + next_buffer: unsafe { Some(&mut DAC_BUF[$index][2]) }, } } From ee8f4d849f51274de5f45a7c2b7f10e540032186 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:32:53 +0100 Subject: [PATCH 06/12] Adding documentation about double-buffered mode to DACs --- src/dac.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index 3e44379..53076b0 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -13,6 +13,16 @@ ///! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update ///! the DAC without requiring the CPU. ///! +///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC +///! output batch is always exactly 1 batch after the ADC batch that generated it. +///! +///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any +///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA +///! transfer completion is handled). In this mode, there should always be a valid buffer in the +///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, +///! software then has exactly one batch duration to properly configure the next buffer before it +///! begins transfer. +///! ///! ## Multiple Samples to Single DAC Codes ///! ///! For some applications, it may be desirable to generate a single DAC code from multiple ADC From f825f527850712aa65185303bbb8e548feffe6d5 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 06:08:07 -0800 Subject: [PATCH 07/12] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- src/adc.rs | 4 ++-- src/dac.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index a652d3d..09f1b05 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -49,7 +49,7 @@ ///! ///! 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 +///! batch sizes generally provide for lower overhead and more processing time per sample, but come at the expense of ///! increased input -> output latency. ///! ///! @@ -69,7 +69,7 @@ ///! 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). +///! double-buffered mode offers less overhead due to the DMA disable/enable procedure). use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, diff --git a/src/dac.rs b/src/dac.rs index 53076b0..22f33fa 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -14,14 +14,14 @@ ///! the DAC without requiring the CPU. ///! ///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC -///! output batch is always exactly 1 batch after the ADC batch that generated it. +///! output batch is always exactly 2 batches after the ADC batch that generated it. ///! ///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any ///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA -///! transfer completion is handled). In this mode, there should always be a valid buffer in the +///! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the ///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, -///! software then has exactly one batch duration to properly configure the next buffer before it -///! begins transfer. +///! software then has exactly one batch duration to fill the next buffer before its +///! transfer begins. ///! ///! ## Multiple Samples to Single DAC Codes ///! From eefb2acfda7bbf516f1a71b135e4d29f2d8470db Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:12:03 +0100 Subject: [PATCH 08/12] Updating dependencies --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/timers.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 030ff99..76fb832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#25ee0f3a9ae27d1fd6bb390d6045aa312f29f096" +source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#3da22d4935c8f6e412b99e6662ec11da5265fb88" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 73b2f69..9dfa28a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/quartiq/stm32h7xx-hal" -branch = "feature/dma-buffer-swap-logic" +git = "https://github.com/stm32-rs/stm32h7xx-hal" +branch = "dma" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/timers.rs b/src/timers.rs index 8d7d010..0f4cdb8 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -208,9 +208,9 @@ macro_rules! timer_channels { const REQUEST_LINE: Option = Some(DMAReq::[< $TY _CH $index >]as u8); - fn address(&self) -> u32 { + fn address(&self) -> usize { let regs = unsafe { &*<$TY>::ptr() }; - ®s.[] as *const _ as u32 + ®s.[] as *const _ as usize } } } From 56366a013fe647735e957ffcb80f9ad109679483 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:34:12 +0100 Subject: [PATCH 09/12] Specifying consequences of failing to meet timing --- src/dac.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dac.rs b/src/dac.rs index efa9588..109dc78 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -21,7 +21,8 @@ ///! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the ///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, ///! software then has exactly one batch duration to fill the next buffer before its -///! transfer begins. +///! transfer begins. If software does not meet this deadline, old data will be repeatedly generated +///! on the output and output will be shifted by one batch. ///! ///! ## Multiple Samples to Single DAC Codes ///! From 5eab732d9336ca95f0801a3ea02beaee9c1426f8 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:38:04 +0100 Subject: [PATCH 10/12] Adding information about DSP timing specifications --- src/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main.rs b/src/main.rs index c38baeb..320679a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -849,6 +849,22 @@ const APP: () = { } } + /// Main DSP processing routine for Stabilizer. + /// + /// # Note + /// Processing time for the DSP application code is bounded by the following constraints: + /// + /// DSP application code starts after the ADC has generated a batch of samples and must be + /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer + /// time). If this constraint is not met, firmware will panic due to an ADC input overrun. + /// + /// The DSP application code must also fill out the next DAC output buffer in time such that the + /// DAC can switch to it when it has completed the current buffer. If this constraint is not met + /// it's possible that old DAC codes will be generated on the output and the output samples will + /// be delayed by 1 batch. + /// + /// 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. #[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { let adc_samples = [ From bcf7a5999374b646037c6b70d747b49387575dbe Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 12 Jan 2021 14:15:45 +0100 Subject: [PATCH 11/12] Removing dac isr clear --- src/dac.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dac.rs b/src/dac.rs index 4a2f789..971a7de 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -187,7 +187,6 @@ macro_rules! dac_output { 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(); From 7a2f950667ef9de53a114958fb0749990dac91e1 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 13:41:23 +0100 Subject: [PATCH 12/12] Updating timer compare offsets --- src/adc.rs | 2 +- src/dac.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index 76a42a4..48792d9 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -246,7 +246,7 @@ macro_rules! adc_input { // Generate DMA events when an output compare of the timer hits the specified // value. trigger_channel.listen_dma(); - trigger_channel.to_output_compare(2); + trigger_channel.to_output_compare(2 + $index); // 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 diff --git a/src/dac.rs b/src/dac.rs index 971a7de..a22e5da 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -14,7 +14,7 @@ ///! the DAC without requiring the CPU. ///! ///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC -///! output batch is always exactly 2 batches after the ADC batch that generated it. +///! output batch is always exactly 3 batches after the ADC batch that generated it. ///! ///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any ///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA @@ -134,7 +134,7 @@ macro_rules! dac_output { // 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); + trigger_channel.to_output_compare(4 + $index); // The stream constantly writes to the TX FIFO to write new update codes. let trigger_config = DmaConfig::default()