diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index fd3c25b..b591c37 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use bit_field::BitField; -use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; +use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin}; /// A device driver for the AD9959 direct digital synthesis (DDS) chip. /// @@ -14,7 +14,7 @@ use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; /// The chip supports a number of serial interfaces to improve data throughput, including normal, /// dual, and quad SPI configurations. pub struct Ad9959 { - pub interface: INTERFACE, + interface: INTERFACE, reference_clock_frequency: f32, system_clock_multiplier: u8, communication_mode: Mode, @@ -72,6 +72,7 @@ pub enum Register { } /// Specifies an output channel of the AD9959 DDS chip. +#[derive(Copy, Clone, PartialEq)] pub enum Channel { One = 0, Two = 1, @@ -103,9 +104,9 @@ impl Ad9959 { /// `clock_frequency` to generate the system clock. pub fn new( interface: I, - reset_pin: &mut impl OutputPin, + mut reset_pin: impl OutputPin, io_update: &mut impl OutputPin, - delay: &mut impl DelayMs, + delay: &mut impl DelayUs, desired_mode: Mode, clock_frequency: f32, multiplier: u8, @@ -124,8 +125,9 @@ impl Ad9959 { io_update.set_low().or(Err(Error::Pin))?; - // Delay for a clock cycle to allow the device to reset. - delay.delay_ms((1000.0 / clock_frequency as f32) as u8); + // 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); reset_pin.set_low().or(Err(Error::Pin))?; @@ -141,8 +143,11 @@ impl Ad9959 { // Latch the new interface configuration. io_update.set_high().or(Err(Error::Pin))?; - // Delay for a clock cycle to allow the device to reset. - delay.delay_ms(2 * (1000.0 / clock_frequency as f32) as u8); + + // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + delay.delay_us(5); + io_update.set_low().or(Err(Error::Pin))?; ad9959 @@ -150,6 +155,13 @@ impl Ad9959 { .configure_mode(desired_mode) .or(Err(Error::Interface))?; + // Empirical evidence indicates a delay is necessary here for the IO update to become + // active. This is likely due to needing to wait at least 1 clock cycle of the DDS for the + // interface update to occur. + // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + delay.delay_us(5); + // Read back the CSR to ensure it specifies the mode correctly. let mut updated_csr: [u8; 1] = [0]; ad9959.read(Register::CSR, &mut updated_csr)?; @@ -480,24 +492,96 @@ impl Ad9959 { / (1u64 << 32) as f32) } - pub fn free(self) -> I { - self.interface + pub fn freeze(self) -> (I, DdsConfig) { + let config = DdsConfig { + mode: self.communication_mode, + }; + (self.interface, config) } } -struct ProfileSerializer { +pub struct DdsConfig { + mode: Mode, +} + +impl DdsConfig { + pub fn builder(&self) -> ProfileSerializer { + ProfileSerializer::new(self.mode) + } +} + +pub struct ProfileSerializer { data: [u8; 16], index: usize, + mode: Mode, } impl ProfileSerializer { - fn new() -> Self { + fn new(mode: Mode) -> Self { Self { + mode, data: [0; 16], index: 0, } } + pub fn update_channels( + &mut self, + channels: &[Channel], + ftw: Option, + 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"); + } + 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); + } + + self.add_write(Register::CSR, &[csr]); + + if let Some(ftw) = ftw { + self.add_write(Register::CFTW0, &ftw.to_be_bytes()); + } + + if let Some(pow) = pow { + self.add_write(Register::CPOW0, &pow.to_be_bytes()); + } + + if let Some(acr) = acr { + self.add_write(Register::ACR, &acr.to_be_bytes()); + } + } + + 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 { + 0 => {} + 1 => { + // For a pad size of 1, we have to pad with 5 bytes to align things. + self.add_write(Register::CSR, &[(self.mode as u8) << 1]); + self.add_write(Register::FR2, &[0, 0, 0]); + } + 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), + 3 => self.add_write(Register::FR2, &[0, 0, 0]), + + _ => panic!("Invalid"), + } + unsafe { + core::slice::from_raw_parts::<'a, u32>( + &self.data as *const _ as *const u32, + self.index / 4, + ) + } + } + fn add_write(&mut self, register: Register, value: &[u8]) { let data = &mut self.data[self.index..]; assert!(value.len() + 1 <= data.len()); @@ -506,47 +590,4 @@ impl ProfileSerializer { data[1..][..value.len()].copy_from_slice(value); self.index += value.len() + 1; } - - fn finalize(self) -> [u32; 4] { - assert!(self.index == self.data.len()); - unsafe { core::mem::transmute(self.data) } - } -} - -pub fn serialize_profile( - channel: Channel, - ftw: u32, - pow: u16, - acr: Option, -) -> [u32; 4] { - let mut serializer = ProfileSerializer::new(); - - let csr: u8 = *0x00_u8 - .set_bits(1..=2, Mode::FourBitSerial as u8) - .set_bit(4 + channel as usize, true); - - let acr: [u8; 3] = { - let mut data = [0u8; 3]; - if acr.is_some() { - data[2].set_bit(0, acr.is_some()); - data[0..2].copy_from_slice(&acr.unwrap().to_be_bytes()); - } - - data - }; - - // 4 bytes - serializer.add_write(Register::CSR, &[csr]); - serializer.add_write(Register::CSR, &[csr]); - - // 5 bytes - serializer.add_write(Register::CFTW0, &ftw.to_be_bytes()); - - // 3 bytes - serializer.add_write(Register::CPOW0, &pow.to_be_bytes()); - - // 4 bytes - serializer.add_write(Register::ACR, &acr); - - serializer.finalize() } diff --git a/src/main.rs b/src/main.rs index eb70cd7..f6a6d91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,8 @@ mod server; use adc::{Adc0Input, Adc1Input, AdcInputs}; use dac::{Dac0Output, Dac1Output, DacOutputs}; -use pounder::DdsOutput; use dsp::iir; +use pounder::DdsOutput; #[cfg(not(feature = "semihosting"))] fn init_log() {} @@ -509,12 +509,12 @@ const APP: () = { pounder::QspiInterface::new(qspi).unwrap() }; - let mut reset_pin = gpioa.pa0.into_push_pull_output(); + let reset_pin = gpioa.pa0.into_push_pull_output(); let mut io_update = gpiog.pg7.into_push_pull_output(); let ad9959 = ad9959::Ad9959::new( qspi_interface, - &mut reset_pin, + reset_pin, &mut io_update, &mut delay, ad9959::Mode::FourBitSerial, @@ -643,8 +643,9 @@ const APP: () = { hrtimer }; - let qspi = ad9959.free(); - DdsOutput::new(qspi, io_update_trigger) + let (mut qspi, config) = ad9959.freeze(); + qspi.start_stream().unwrap(); + DdsOutput::new(qspi, io_update_trigger, config) }; (Some(pounder_devices), Some(dds_output)) @@ -817,14 +818,13 @@ const APP: () = { } if let Some(dds_output) = c.resources.dds_output { - let profile = ad9959::serialize_profile( - pounder::Channel::Out0.into(), - u32::MAX / 4, - 0, + let builder = dds_output.builder().update_channels( + &[pounder::Channel::Out0.into()], + Some(u32::MAX / 4), + None, None, ); - - dds_output.write_profile(profile); + builder.write_profile(); } c.resources.dacs.commit_data(); diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index f242033..3a3ce6c 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,42 +1,77 @@ use super::QspiInterface; use crate::hrtimer::HighResTimerE; +use ad9959::{Channel, DdsConfig, ProfileSerializer}; use stm32h7xx_hal as hal; pub struct DdsOutput { _qspi: QspiInterface, io_update_trigger: HighResTimerE, + config: DdsConfig, } impl DdsOutput { - pub fn new(_qspi: QspiInterface, io_update_trigger: HighResTimerE) -> Self { + pub fn new( + _qspi: QspiInterface, + io_update_trigger: HighResTimerE, + dds_config: DdsConfig, + ) -> Self { Self { + config: dds_config, _qspi, io_update_trigger, } } - pub fn write_profile(&mut self, profile: [u32; 4]) { + pub fn builder(&mut self) -> ProfileBuilder { + let builder = self.config.builder(); + ProfileBuilder { + dds_stream: self, + serializer: builder, + } + } + + 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() }; - unsafe { - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[0], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[1], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[2], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[3], - ); + + 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, + ); + } } // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse. self.io_update_trigger.trigger(); } } + +pub struct ProfileBuilder<'a> { + dds_stream: &'a mut DdsOutput, + serializer: ProfileSerializer, +} + +impl<'a> ProfileBuilder<'a> { + pub fn update_channels( + mut self, + channels: &[Channel], + ftw: Option, + pow: Option, + acr: Option, + ) -> Self { + self.serializer.update_channels(channels, ftw, pow, acr); + self + } + + pub fn write_profile(mut self) { + let profile = self.serializer.finalize(); + self.dds_stream.write_profile(profile); + } +} diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index a1e9dbc..c20d251 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -115,7 +115,7 @@ impl QspiInterface { }) } - fn start_stream(&mut self) -> Result<(), Error> { + pub fn start_stream(&mut self) -> Result<(), Error> { if self.qspi.is_busy() { return Err(Error::Qspi); } @@ -323,9 +323,6 @@ impl PounderDevices { .configure_system_clock(100_000_000f32, 4) .map_err(|_| Error::Dds)?; - // Run the DDS in stream-only mode (no read support). - ad9959.interface.start_stream().unwrap(); - Ok(devices) } }