From 438b2919741e7c2bd7b455212dd4b7db002d9286 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:09:18 +0100 Subject: [PATCH] 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};