From 677d017c3cd08f86f44d00c592b1bf72af142adb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 2 Dec 2020 17:40:24 +0100 Subject: [PATCH] Adding documentation --- ad9959/src/lib.rs | 64 ++++++++++++++++++++++++++++----------- src/hrtimer.rs | 17 +++++++++++ src/main.rs | 1 - src/pounder/dds_output.rs | 37 ++++++++++++++++++++-- 4 files changed, 97 insertions(+), 22 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index b591c37..8501e3a 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -123,8 +123,6 @@ impl Ad9959 { // Reset the AD9959 reset_pin.set_high().or(Err(Error::Pin))?; - io_update.set_low().or(Err(Error::Pin))?; - // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). delay.delay_us(5); @@ -492,6 +490,13 @@ impl Ad9959 { / (1u64 << 32) as f32) } + /// Finalize DDS configuration + /// + /// # Note + /// This is intended for when the DDS profiles will be written as a stream of data to the DDS. + /// + /// # Returns + /// (I, config) where `I` is the interface to the DDS and `config` is the frozen `DdsConfig`. pub fn freeze(self) -> (I, DdsConfig) { let config = DdsConfig { mode: self.communication_mode, @@ -500,16 +505,20 @@ impl Ad9959 { } } +/// The frozen DDS configuration. pub struct DdsConfig { mode: Mode, } impl DdsConfig { + /// Create a serializer that can be used for generating a serialized DDS profile for writing to + /// a QSPI stream. pub fn builder(&self) -> ProfileSerializer { ProfileSerializer::new(self.mode) } } +/// Represents a means of serializing a DDS profile for writing to a stream. pub struct ProfileSerializer { data: [u8; 16], index: usize, @@ -517,6 +526,10 @@ pub struct ProfileSerializer { } impl ProfileSerializer { + /// Construct a new serializer. + /// + /// # Args + /// * `mode` - The communication mode of the DDS. fn new(mode: Mode) -> Self { Self { mode, @@ -525,6 +538,13 @@ impl ProfileSerializer { } } + /// Update a number of channels with the requested profile. + /// + /// # 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. pub fn update_channels( &mut self, channels: &[Channel], @@ -532,12 +552,12 @@ impl ProfileSerializer { pow: Option, acr: Option, ) { - // If there are no updates requested, skip this update cycle. - if (ftw.is_none() && acr.is_none() && pow.is_none()) - || channels.len() == 0 - { - panic!("Invalid config"); - } + // The user should have provided something to update. + assert!( + (ftw.is_some() || acr.is_some() || pow.is_some()) + && channels.len() > 0 + ); + let mut csr: u8 = *0u8.set_bits(1..3, self.mode as u8); for channel in channels.iter() { csr.set_bit(4 + *channel as usize, true); @@ -558,8 +578,25 @@ impl ProfileSerializer { } } + /// Add a register write to the serialization data. + fn add_write(&mut self, register: Register, value: &[u8]) { + let data = &mut self.data[self.index..]; + assert!(value.len() + 1 <= data.len()); + + data[0] = register as u8; + data[1..][..value.len()].copy_from_slice(value); + self.index += value.len() + 1; + } + + /// Get the serialized profile as a slice of 32-bit words. + /// + /// # Note + /// The serialized profile will be padded to the next 32-bit word boundary by adding dummy + /// writes to the CSR or FR2 registers. + /// + /// # Returns + /// A slice of `u32` words representing the serialized profile. pub fn finalize<'a>(&'a mut self) -> &[u32] { - //&self.data[..self.index] // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and FR2. let padding = 4 - (self.index % 4); match padding { @@ -581,13 +618,4 @@ impl ProfileSerializer { ) } } - - fn add_write(&mut self, register: Register, value: &[u8]) { - let data = &mut self.data[self.index..]; - assert!(value.len() + 1 <= data.len()); - - data[0] = register as u8; - data[1..][..value.len()].copy_from_slice(value); - self.index += value.len() + 1; - } } diff --git a/src/hrtimer.rs b/src/hrtimer.rs index 47ea5c2..5ccb2d7 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -1,11 +1,14 @@ +///! The HRTimer (High Resolution Timer) is used to generate IO_Update pulses to the Pounder DDS. use crate::hal; use hal::rcc::{rec, CoreClocks, ResetEnable}; +/// A HRTimer output channel. pub enum Channel { One, Two, } +/// The high resolution timer. Currently, only Timer E is supported. pub struct HighResTimerE { master: hal::stm32::HRTIM_MASTER, timer: hal::stm32::HRTIM_TIME, @@ -15,6 +18,7 @@ pub struct HighResTimerE { } impl HighResTimerE { + /// Construct a new high resolution timer for generating IO_update signals. pub fn new( timer_regs: hal::stm32::HRTIM_TIME, master_regs: hal::stm32::HRTIM_MASTER, @@ -32,6 +36,18 @@ impl HighResTimerE { } } + /// Configure the timer to operate in single-shot mode. + /// + /// # Note + /// This will configure the timer to generate a single pulse on an output channel. The timer + /// will only count up once and must be `trigger()`'d after / configured. + /// + /// The output will be asserted from `set_offset` to `set_offset` + `set_duration` in the count. + /// + /// # Args + /// * `channel` - The timer output channel to configure. + /// * `set_duration` - The duration that the output should be asserted for. + /// * `set_offset` - The first time at which the output should be asserted. pub fn configure_single_shot( &mut self, channel: Channel, @@ -97,6 +113,7 @@ impl HighResTimerE { self.master.mcr.modify(|_, w| w.tecen().set_bit()); } + /// Generate a single trigger of the timer to start the output pulse generation. pub fn trigger(&mut self) { // Generate a reset event to force the timer to start counting. self.common.cr2.write(|w| w.terst().set_bit()); diff --git a/src/main.rs b/src/main.rs index 6e120c9..80cb90a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -766,7 +766,6 @@ const APP: () = { }; cp.SCB.enable_icache(); - //cp.SCB.enable_dcache(&mut cp.CPUID); // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); // info!("Built on {}", build_info::BUILT_TIME_UTC); diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index b519c83..e2179f1 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,8 +1,10 @@ +///! The DdsOutput is used as an output stream to the pounder DDS. use super::QspiInterface; use crate::hrtimer::HighResTimerE; use ad9959::{Channel, DdsConfig, ProfileSerializer}; use stm32h7xx_hal as hal; +/// The DDS profile update stream. pub struct DdsOutput { _qspi: QspiInterface, io_update_trigger: HighResTimerE, @@ -10,11 +12,23 @@ pub struct DdsOutput { } impl DdsOutput { + /// 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. pub fn new( - _qspi: QspiInterface, + mut qspi: QspiInterface, io_update_trigger: HighResTimerE, dds_config: DdsConfig, ) -> Self { + qspi.start_stream(); Self { config: dds_config, _qspi, @@ -22,6 +36,7 @@ impl DdsOutput { } } + /// Get a builder for serializing a Pounder DDS profile. pub fn builder(&mut self) -> ProfileBuilder { let builder = self.config.builder(); ProfileBuilder { @@ -30,9 +45,15 @@ impl DdsOutput { } } + /// 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. fn write_profile(&mut self, profile: &[u32]) { - assert!(profile.len() <= 16); - // Note(unsafe): We own the QSPI interface, so it is safe to access the registers in a raw // fashion. let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; @@ -47,17 +68,26 @@ impl DdsOutput { ); } } + // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse. self.io_update_trigger.trigger(); } } +/// A temporary builder for serializing and writing profiles. pub struct ProfileBuilder<'a> { dds_stream: &'a mut DdsOutput, serializer: ProfileSerializer, } impl<'a> ProfileBuilder<'a> { + /// 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. pub fn update_channels( mut self, channels: &[Channel], @@ -69,6 +99,7 @@ impl<'a> ProfileBuilder<'a> { self } + /// Write the profile to the DDS asynchronously. pub fn write_profile(mut self) { let profile = self.serializer.finalize(); self.dds_stream.write_profile(profile);