Updating DAC output format, adding DDS stream docs

master
Ryan Summers 2020-12-17 14:09:18 +01:00
parent 8e4a7c8fa9
commit 438b291974
3 changed files with 74 additions and 31 deletions

View File

@ -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()
}
}
};

View File

@ -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])]

View File

@ -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};