2020-12-03 00:40:24 +08:00
|
|
|
///! The DdsOutput is used as an output stream to the pounder DDS.
|
2020-12-17 21:09:18 +08:00
|
|
|
///!
|
|
|
|
///! # 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.
|
2021-01-18 23:47:47 +08:00
|
|
|
use super::{hrtimer::HighResTimerE, QspiInterface};
|
2020-12-03 00:01:40 +08:00
|
|
|
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
2020-11-17 17:51:31 +08:00
|
|
|
use stm32h7xx_hal as hal;
|
|
|
|
|
2020-12-03 00:40:24 +08:00
|
|
|
/// The DDS profile update stream.
|
2020-11-17 17:51:31 +08:00
|
|
|
pub struct DdsOutput {
|
2020-11-17 21:23:56 +08:00
|
|
|
_qspi: QspiInterface,
|
2020-11-17 17:51:31 +08:00
|
|
|
io_update_trigger: HighResTimerE,
|
2020-12-03 00:01:40 +08:00
|
|
|
config: DdsConfig,
|
2020-11-17 17:51:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DdsOutput {
|
2020-12-03 00:40:24 +08:00
|
|
|
/// Construct a new DDS output stream.
|
|
|
|
///
|
|
|
|
/// # Note
|
|
|
|
/// It is assumed that the QSPI stream and the IO_Update trigger timer have been configured in a
|
|
|
|
/// way such that the profile has sufficient time to be written before the IO_Update signal is
|
|
|
|
/// generated.
|
|
|
|
///
|
|
|
|
/// # Args
|
|
|
|
/// * `qspi` - The QSPI interface to the run the stream on.
|
|
|
|
/// * `io_update_trigger` - The HighResTimerE used to generate IO_Update pulses.
|
|
|
|
/// * `dds_config` - The frozen DDS configuration.
|
2020-12-03 00:01:40 +08:00
|
|
|
pub fn new(
|
2020-12-03 00:40:24 +08:00
|
|
|
mut qspi: QspiInterface,
|
2020-12-03 00:01:40 +08:00
|
|
|
io_update_trigger: HighResTimerE,
|
|
|
|
dds_config: DdsConfig,
|
|
|
|
) -> Self {
|
2020-12-03 01:08:49 +08:00
|
|
|
qspi.start_stream().unwrap();
|
2020-11-17 17:51:31 +08:00
|
|
|
Self {
|
2020-12-03 00:01:40 +08:00
|
|
|
config: dds_config,
|
2020-12-03 01:08:49 +08:00
|
|
|
_qspi: qspi,
|
2020-11-17 17:51:31 +08:00
|
|
|
io_update_trigger,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-03 00:40:24 +08:00
|
|
|
/// Get a builder for serializing a Pounder DDS profile.
|
2021-01-18 23:47:47 +08:00
|
|
|
#[allow(dead_code)]
|
2020-12-03 00:01:40 +08:00
|
|
|
pub fn builder(&mut self) -> ProfileBuilder {
|
|
|
|
let builder = self.config.builder();
|
|
|
|
ProfileBuilder {
|
|
|
|
dds_stream: self,
|
|
|
|
serializer: builder,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-03 00:40:24 +08:00
|
|
|
/// Write a profile to the stream.
|
|
|
|
///
|
|
|
|
/// # Note:
|
|
|
|
/// If a profile of more than 4 words is provided, it is possible that the QSPI interface will
|
|
|
|
/// stall execution.
|
|
|
|
///
|
|
|
|
/// # Args
|
|
|
|
/// * `profile` - The serialized DDS profile to write.
|
2020-12-03 00:01:40 +08:00
|
|
|
fn write_profile(&mut self, profile: &[u32]) {
|
|
|
|
// Note(unsafe): We own the QSPI interface, so it is safe to access the registers in a raw
|
|
|
|
// fashion.
|
2020-11-17 17:51:31 +08:00
|
|
|
let regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
|
2020-12-03 00:01:40 +08:00
|
|
|
|
2020-12-07 17:55:09 +08:00
|
|
|
if regs.sr.read().flevel() != 0 {
|
|
|
|
warn!("QSPI stalling")
|
|
|
|
}
|
|
|
|
|
2020-12-03 00:01:40 +08:00
|
|
|
for word in profile.iter() {
|
|
|
|
// Note(unsafe): We are writing to the SPI TX FIFO in a raw manner for performance. This
|
|
|
|
// is safe because we know the data register is a valid address to write to.
|
|
|
|
unsafe {
|
|
|
|
core::ptr::write_volatile(
|
|
|
|
®s.dr as *const _ as *mut u32,
|
|
|
|
*word,
|
|
|
|
);
|
|
|
|
}
|
2020-11-17 17:51:31 +08:00
|
|
|
}
|
2020-12-03 00:40:24 +08:00
|
|
|
|
2020-11-17 17:51:31 +08:00
|
|
|
// Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse.
|
|
|
|
self.io_update_trigger.trigger();
|
|
|
|
}
|
|
|
|
}
|
2020-12-03 00:01:40 +08:00
|
|
|
|
2020-12-03 00:40:24 +08:00
|
|
|
/// A temporary builder for serializing and writing profiles.
|
2020-12-03 00:01:40 +08:00
|
|
|
pub struct ProfileBuilder<'a> {
|
|
|
|
dds_stream: &'a mut DdsOutput,
|
|
|
|
serializer: ProfileSerializer,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> ProfileBuilder<'a> {
|
2020-12-03 00:40:24 +08:00
|
|
|
/// Update a number of channels with the provided configuration
|
|
|
|
///
|
|
|
|
/// # Args
|
|
|
|
/// * `channels` - A list of channels to apply the configuration to.
|
|
|
|
/// * `ftw` - If provided, indicates a frequency tuning word for the channels.
|
|
|
|
/// * `pow` - If provided, indicates a phase offset word for the channels.
|
|
|
|
/// * `acr` - If provided, indicates the amplitude control register for the channels.
|
2021-01-18 23:47:47 +08:00
|
|
|
#[allow(dead_code)]
|
2020-12-03 00:01:40 +08:00
|
|
|
pub fn update_channels(
|
|
|
|
mut self,
|
|
|
|
channels: &[Channel],
|
|
|
|
ftw: Option<u32>,
|
|
|
|
pow: Option<u16>,
|
|
|
|
acr: Option<u16>,
|
|
|
|
) -> Self {
|
|
|
|
self.serializer.update_channels(channels, ftw, pow, acr);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-12-03 00:40:24 +08:00
|
|
|
/// Write the profile to the DDS asynchronously.
|
2021-01-18 23:47:47 +08:00
|
|
|
#[allow(dead_code)]
|
2020-12-03 00:01:40 +08:00
|
|
|
pub fn write_profile(mut self) {
|
|
|
|
let profile = self.serializer.finalize();
|
|
|
|
self.dds_stream.write_profile(profile);
|
|
|
|
}
|
|
|
|
}
|