Updating DAC output format, adding DDS stream docs
This commit is contained in:
parent
8e4a7c8fa9
commit
438b291974
45
src/dac.rs
45
src/dac.rs
|
@ -69,6 +69,15 @@ macro_rules! dac_output {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { _channel, spi }
|
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.
|
// 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,
|
MemoryToPeripheral,
|
||||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||||
>,
|
>,
|
||||||
first_transfer: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
|
@ -129,12 +137,14 @@ macro_rules! dac_output {
|
||||||
|
|
||||||
// AXISRAM is uninitialized. As such, we manually zero-initialize it here before
|
// AXISRAM is uninitialized. As such, we manually zero-initialize it here before
|
||||||
// starting the transfer.
|
// starting the transfer.
|
||||||
for byte in DAC_BUF[$index].iter_mut() {
|
for buf in unsafe { DAC_BUF[$index].iter_mut() } {
|
||||||
|
for byte in buf.iter_mut() {
|
||||||
*byte = 0;
|
*byte = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 mut transfer: Transfer<_, _, MemoryToPeripheral, _> =
|
||||||
Transfer::init(
|
Transfer::init(
|
||||||
stream,
|
stream,
|
||||||
$spi::new(trigger_channel, spi),
|
$spi::new(trigger_channel, spi),
|
||||||
|
@ -144,42 +154,23 @@ macro_rules! dac_output {
|
||||||
trigger_config,
|
trigger_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
transfer.start(|spi| {
|
transfer.start(|spi| spi.start_dma());
|
||||||
// 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.
|
||||||
next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) },
|
next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) },
|
||||||
first_transfer: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquire the next output buffer to populate it with DAC codes.
|
/// Acquire the next output buffer to populate it with DAC codes.
|
||||||
pub fn acquire_buffer(
|
pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] {
|
||||||
&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],
|
|
||||||
) {
|
|
||||||
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
// 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.
|
// there is no time-out checks here in the interest of execution speed.
|
||||||
while !self.transfer.get_transfer_complete_flag() {}
|
while !self.transfer.get_transfer_complete_flag() {}
|
||||||
|
|
||||||
|
let next_buffer = self.next_buffer.take().unwrap();
|
||||||
|
|
||||||
// Start the next transfer.
|
// Start the next transfer.
|
||||||
self.transfer.clear_interrupts();
|
self.transfer.clear_interrupts();
|
||||||
let (prev_buffer, _) =
|
let (prev_buffer, _) =
|
||||||
|
@ -187,6 +178,8 @@ macro_rules! dac_output {
|
||||||
|
|
||||||
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
// .unwrap_none() https://github.com/rust-lang/rust/issues/62633
|
||||||
self.next_buffer.replace(prev_buffer);
|
self.next_buffer.replace(prev_buffer);
|
||||||
|
|
||||||
|
self.next_buffer.as_mut().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -805,6 +805,7 @@ const APP: () = {
|
||||||
c.resources.adcs.0.acquire_buffer(),
|
c.resources.adcs.0.acquire_buffer(),
|
||||||
c.resources.adcs.1.acquire_buffer(),
|
c.resources.adcs.1.acquire_buffer(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let dac_samples = [
|
let dac_samples = [
|
||||||
c.resources.dacs.0.acquire_buffer(),
|
c.resources.dacs.0.acquire_buffer(),
|
||||||
c.resources.dacs.1.acquire_buffer(),
|
c.resources.dacs.1.acquire_buffer(),
|
||||||
|
@ -836,10 +837,6 @@ const APP: () = {
|
||||||
|
|
||||||
builder.write_profile();
|
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])]
|
#[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afes])]
|
||||||
|
|
|
@ -1,4 +1,57 @@
|
||||||
///! The DdsOutput is used as an output stream to the pounder DDS.
|
///! 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 super::QspiInterface;
|
||||||
use crate::hrtimer::HighResTimerE;
|
use crate::hrtimer::HighResTimerE;
|
||||||
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
||||||
|
|
Loading…
Reference in New Issue