pounder_test/ad9959/src/lib.rs

629 lines
20 KiB
Rust

#![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: 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)]
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.
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<I: Interface> Ad9959<I> {
/// 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<u8>,
desired_mode: Mode,
clock_frequency: f32,
multiplier: u8,
) -> Result<Self, Error> {
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<f32, Error> {
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<u8, Error> {
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<bool, Error> {
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<f32, Error> {
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<f32, Error> {
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<f32, Error> {
if !(0.0..=1.0).contains(&amplitude) {
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<f32, Error> {
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<f32, Error> {
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<f32, Error> {
// 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 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,
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.
pub fn update_channels(
&mut self,
channels: &[Channel],
ftw: Option<u32>,
pow: Option<u16>,
acr: Option<u32>,
) {
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..=3]);
}
}
/// 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;
}
/// 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.
pub fn finalize<'a>(&'a mut self) -> &[u32] {
// Pad the buffer to 32-bit alignment by adding dummy writes to CSR and LSRR.
let padding = 4 - (self.index % 4);
match padding {
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::LSRR, &[0, 0, 0]);
}
2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]),
3 => self.add_write(Register::LSRR, &[0, 0, 0]),
4 => {}
_ => unreachable!(),
}
unsafe {
core::slice::from_raw_parts::<'a, u32>(
&self.data as *const _ as *const u32,
self.index / 4,
)
}
}
}