#![no_std] use bit_field::BitField; use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin}; /// A device driver for the AD9959 direct digital synthesis (DDS) chip. /// /// This chip provides four independently controllable digital-to-analog output sinusoids with /// configurable phase, amplitude, and frequency. All channels are inherently synchronized as they /// are derived off a common system clock. /// /// The chip contains a configurable PLL and supports system clock frequencies up to 500 MHz. /// /// The chip supports a number of serial interfaces to improve data throughput, including normal, /// dual, and quad SPI configurations. pub struct Ad9959 { interface: INTERFACE, reference_clock_frequency: f32, system_clock_multiplier: u8, communication_mode: Mode, } /// A trait that allows a HAL to provide a means of communicating with the AD9959. pub trait Interface { type Error; fn configure_mode(&mut self, mode: Mode) -> Result<(), Self::Error>; fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>; fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>; } /// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to /// the configuration bits of the DDS CSR register. #[derive(Copy, Clone, PartialEq)] #[repr(u8)] pub enum Mode { SingleBitTwoWire = 0b00, SingleBitThreeWire = 0b01, TwoBitSerial = 0b10, FourBitSerial = 0b11, } /// The configuration registers within the AD9959 DDS device. The values of each register are /// equivalent to the address. #[allow(clippy::upper_case_acronyms)] pub enum Register { CSR = 0x00, FR1 = 0x01, FR2 = 0x02, CFR = 0x03, CFTW0 = 0x04, CPOW0 = 0x05, ACR = 0x06, LSRR = 0x07, RDW = 0x08, FDW = 0x09, CW1 = 0x0a, CW2 = 0x0b, CW3 = 0x0c, CW4 = 0x0d, CW5 = 0x0e, CW6 = 0x0f, CW7 = 0x10, CW8 = 0x11, CW9 = 0x12, CW10 = 0x13, CW11 = 0x14, CW12 = 0x15, CW13 = 0x16, CW14 = 0x17, CW15 = 0x18, } /// Specifies an output channel of the AD9959 DDS chip. #[derive(Copy, Clone, PartialEq)] pub enum Channel { One = 0, Two = 1, Three = 2, Four = 3, } /// Possible errors generated by the AD9959 driver. #[derive(Debug)] pub enum Error { Interface, Check, Bounds, Pin, Frequency, } impl Ad9959 { /// Construct and initialize the DDS. /// /// Args: /// * `interface` - An interface to the DDS. /// * `reset_pin` - A pin connected to the DDS reset input. /// * `io_update` - A pin connected to the DDS io_update input. /// * `delay` - A delay implementation for blocking operation for specific amounts of time. /// * `desired_mode` - The desired communication mode of the interface to the DDS. /// * `clock_frequency` - The clock frequency of the reference clock input. /// * `multiplier` - The desired clock multiplier for the system clock. This multiplies /// `clock_frequency` to generate the system clock. pub fn new( interface: I, mut reset_pin: impl OutputPin, io_update: &mut impl OutputPin, delay: &mut impl DelayUs, desired_mode: Mode, clock_frequency: f32, multiplier: u8, ) -> Result { let mut ad9959 = Ad9959 { interface, reference_clock_frequency: clock_frequency, system_clock_multiplier: 1, communication_mode: desired_mode, }; io_update.set_low().or(Err(Error::Pin))?; // Reset the AD9959 reset_pin.set_high().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). We use 5uS instead of 4uS to // guarantee conformance with datasheet requirements. delay.delay_us(5); reset_pin.set_low().or(Err(Error::Pin))?; ad9959 .interface .configure_mode(Mode::SingleBitTwoWire) .or(Err(Error::Interface))?; // Program the interface configuration in the AD9959. Default to all channels enabled. let mut csr: [u8; 1] = [0xF0]; csr[0].set_bits(1..3, desired_mode as u8); ad9959.write(Register::CSR, &csr)?; // Latch the new interface configuration. io_update.set_high().or(Err(Error::Pin))?; // 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). We use 5uS instead of 4uS to // guarantee conformance with datasheet requirements. delay.delay_us(5); io_update.set_low().or(Err(Error::Pin))?; ad9959 .interface .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). We use 5uS instead of 4uS to // guarantee conformance with datasheet requirements. 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)?; if updated_csr[0] != csr[0] { return Err(Error::Check); } // Set the clock frequency to configure the device as necessary. ad9959.configure_system_clock(clock_frequency, multiplier)?; // Latch the new clock configuration. io_update.set_high().or(Err(Error::Pin))?; // 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). We use 5uS instead of 4uS to // guarantee conformance with datasheet requirements. delay.delay_us(5); io_update.set_low().or(Err(Error::Pin))?; Ok(ad9959) } fn read(&mut self, reg: Register, data: &mut [u8]) -> Result<(), Error> { self.interface .read(reg as u8, data) .or(Err(Error::Interface)) } fn write(&mut self, reg: Register, data: &[u8]) -> Result<(), Error> { self.interface .write(reg as u8, data) .or(Err(Error::Interface)) } /// Configure the internal system clock of the chip. /// /// Arguments: /// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core. /// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20. /// /// Returns: /// The actual frequency configured for the internal system clock. fn configure_system_clock( &mut self, reference_clock_frequency: f32, multiplier: u8, ) -> Result { self.reference_clock_frequency = reference_clock_frequency; if multiplier != 1 && !(4..=20).contains(&multiplier) { return Err(Error::Bounds); } let frequency = multiplier as f32 * self.reference_clock_frequency as f32; if frequency > 500_000_000.0f32 { return Err(Error::Frequency); } // TODO: Update / disable any enabled channels? let mut fr1: [u8; 3] = [0, 0, 0]; self.read(Register::FR1, &mut fr1)?; fr1[0].set_bits(2..=6, multiplier); let vco_range = frequency > 255e6; fr1[0].set_bit(7, vco_range); self.write(Register::FR1, &fr1)?; self.system_clock_multiplier = multiplier; Ok(self.system_clock_frequency()) } /// Get the current reference clock frequency in Hz. pub fn get_reference_clock_frequency(&self) -> f32 { self.reference_clock_frequency } /// Get the current reference clock multiplier. pub fn get_reference_clock_multiplier(&mut self) -> Result { let mut fr1: [u8; 3] = [0, 0, 0]; self.read(Register::FR1, &mut fr1)?; Ok(fr1[0].get_bits(2..=6) as u8) } /// Perform a self-test of the communication interface. /// /// Note: /// This modifies the existing channel enables. They are restored upon exit. /// /// Returns: /// True if the self test succeeded. False otherwise. pub fn self_test(&mut self) -> Result { let mut csr: [u8; 1] = [0]; self.read(Register::CSR, &mut csr)?; let old_csr = csr[0]; // Enable all channels. csr[0].set_bits(4..8, 0xF); self.write(Register::CSR, &csr)?; // Read back the enable. csr[0] = 0; self.read(Register::CSR, &mut csr)?; if csr[0].get_bits(4..8) != 0xF { return Ok(false); } // Clear all channel enables. csr[0].set_bits(4..8, 0x0); self.write(Register::CSR, &csr)?; // Read back the enable. csr[0] = 0xFF; self.read(Register::CSR, &mut csr)?; if csr[0].get_bits(4..8) != 0 { return Ok(false); } // Restore the CSR. csr[0] = old_csr; self.write(Register::CSR, &csr)?; Ok(true) } /// Get the current system clock frequency in Hz. fn system_clock_frequency(&self) -> f32 { self.system_clock_multiplier as f32 * self.reference_clock_frequency as f32 } /// Update an output channel configuration register. /// /// Args: /// * `channel` - The channel to configure. /// * `register` - The register to update. /// * `data` - The contents to write to the provided register. fn modify_channel( &mut self, channel: Channel, register: Register, data: &[u8], ) -> Result<(), Error> { // Disable all other outputs so that we can update the configuration register of only the // specified channel. let csr: u8 = *0x00_u8 .set_bits(1..=2, self.communication_mode as u8) .set_bit(4 + channel as usize, true); self.interface .write(Register::CSR as u8, &[csr]) .map_err(|_| Error::Interface)?; self.write(register, data)?; Ok(()) } /// Read a configuration register of a specific channel. /// /// Args: /// * `channel` - The channel to read. /// * `register` - The register to read. /// * `data` - A location to store the read register contents. fn read_channel( &mut self, channel: Channel, register: Register, mut data: &mut [u8], ) -> Result<(), Error> { // Disable all other channels in the CSR so that we can read the configuration register of // only the desired channel. let mut csr: [u8; 1] = [0]; self.read(Register::CSR, &mut csr)?; let mut new_csr = csr; new_csr[0].set_bits(4..8, 0); new_csr[0].set_bit(4 + channel as usize, true); self.write(Register::CSR, &new_csr)?; self.read(register, &mut data)?; // Restore the previous CSR. Note that the re-enable of the channel happens immediately, so // the CSR update does not need to be latched. self.write(Register::CSR, &csr)?; Ok(()) } /// Configure the phase of a specified channel. /// /// Arguments: /// * `channel` - The channel to configure the frequency of. /// * `phase_turns` - The desired phase offset in turns. /// /// Returns: /// The actual programmed phase offset of the channel in turns. pub fn set_phase( &mut self, channel: Channel, phase_turns: f32, ) -> Result { let phase_offset: u16 = (phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16; self.modify_channel( channel, Register::CPOW0, &phase_offset.to_be_bytes(), )?; Ok((phase_offset as f32) / ((1 << 14) as f32)) } /// Get the current phase of a specified channel. /// /// Args: /// * `channel` - The channel to get the phase of. /// /// Returns: /// The phase of the channel in turns. pub fn get_phase(&mut self, channel: Channel) -> Result { let mut phase_offset: [u8; 2] = [0; 2]; self.read_channel(channel, Register::CPOW0, &mut phase_offset)?; let phase_offset = u16::from_be_bytes(phase_offset) & 0x3FFFu16; Ok((phase_offset as f32) / ((1 << 14) as f32)) } /// Configure the amplitude of a specified channel. /// /// Arguments: /// * `channel` - The channel to configure the frequency of. /// * `amplitude` - A normalized amplitude setting [0, 1]. /// /// Returns: /// The actual normalized amplitude of the channel relative to full-scale range. pub fn set_amplitude( &mut self, channel: Channel, amplitude: f32, ) -> Result { if !(0.0..=1.0).contains(&litude) { return Err(Error::Bounds); } let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; let mut acr: [u8; 3] = [0; 3]; // Enable the amplitude multiplier for the channel if required. The amplitude control has // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever // full-scale is used. if amplitude_control < (1 << 10) { let masked_control = amplitude_control & 0x3FF; acr[1] = masked_control.to_be_bytes()[0]; acr[2] = masked_control.to_be_bytes()[1]; // Enable the amplitude multiplier acr[1].set_bit(4, true); } self.modify_channel(channel, Register::ACR, &acr)?; Ok(amplitude_control as f32 / (1 << 10) as f32) } /// Get the configured amplitude of a channel. /// /// Args: /// * `channel` - The channel to get the amplitude of. /// /// Returns: /// The normalized amplitude of the channel. pub fn get_amplitude(&mut self, channel: Channel) -> Result { let mut acr: [u8; 3] = [0; 3]; self.read_channel(channel, Register::ACR, &mut acr)?; if acr[1].get_bit(4) { let amplitude_control: u16 = (((acr[1] as u16) << 8) | (acr[2] as u16)) & 0x3FF; Ok(amplitude_control as f32 / (1 << 10) as f32) } else { Ok(1.0) } } /// Configure the frequency of a specified channel. /// /// Arguments: /// * `channel` - The channel to configure the frequency of. /// * `frequency` - The desired output frequency in Hz. /// /// Returns: /// The actual programmed frequency of the channel. pub fn set_frequency( &mut self, channel: Channel, frequency: f32, ) -> Result { if frequency < 0.0 || frequency > self.system_clock_frequency() { return Err(Error::Bounds); } // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the // frequency tuning word and f_s is the system clock rate. let tuning_word: u32 = ((frequency as f32 / self.system_clock_frequency()) * 1u64.wrapping_shl(32) as f32) as u32; self.modify_channel( channel, Register::CFTW0, &tuning_word.to_be_bytes(), )?; Ok((tuning_word as f32 / 1u64.wrapping_shl(32) as f32) * self.system_clock_frequency()) } /// Get the frequency of a channel. /// /// Arguments: /// * `channel` - The channel to get the frequency of. /// /// Returns: /// The frequency of the channel in Hz. pub fn get_frequency(&mut self, channel: Channel) -> Result { // Read the frequency tuning word for the channel. let mut tuning_word: [u8; 4] = [0; 4]; self.read_channel(channel, Register::CFTW0, &mut tuning_word)?; let tuning_word = u32::from_be_bytes(tuning_word); // Convert the tuning word into a frequency. Ok((tuning_word as f32 * self.system_clock_frequency()) / (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, }; (self.interface, config) } } /// 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 serializer(&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, mode: Mode, } impl ProfileSerializer { /// Construct a new serializer. /// /// # Args /// * `mode` - The communication mode of the DDS. fn new(mode: Mode) -> Self { Self { mode, data: [0; 16], index: 0, } } /// 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. The ACR /// should be stored in the 3 LSB of the word. Note that if amplitude scaling is to be used, /// the "Amplitude multiplier enable" bit must be set. #[inline] pub fn update_channels( &mut self, channels: &[Channel], ftw: Option, pow: Option, acr: Option, ) { 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()[1..]); } } /// Add a register write to the serialization data. fn add_write(&mut self, register: Register, value: &[u8]) { let data = &mut self.data[self.index..]; data[0] = register as u8; data[1..][..value.len()].copy_from_slice(value); self.index += value.len() + 1; } #[inline] fn pad(&mut self) { // Pad the buffer to 32-bit (4 byte) alignment by adding dummy writes to CSR and LSRR. // In the case of 1 byte padding, this instead pads with 5 bytes as there is no // valid single-byte write that could be used. if self.index & 1 != 0 { // Pad with 3 bytes self.add_write(Register::LSRR, &[0, 0]); } if self.index & 2 != 0 { // Pad with 2 bytes self.add_write(Register::CSR, &[(self.mode as u8) << 1]); } debug_assert_eq!(self.index & 3, 0); } /// 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 LSRR registers. /// /// # Returns /// A slice of `u32` words representing the serialized profile. #[inline] pub fn finalize<'a>(&'a mut self) -> &'a [u32] { self.pad(); unsafe { core::slice::from_raw_parts::<'a, u32>( &self.data as *const _ as *const u32, self.index / 4, ) } } }