Updating DAC output format, adding DDS stream docs
This commit is contained in:
parent
8e4a7c8fa9
commit
438b291974
47
src/dac.rs
47
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()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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])]
|
||||
|
|
|
@ -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};
|
||||
|
|
Loading…
Reference in New Issue