Introduction

About three years ago, I spent some time generating Lissajous figures1 with a couple of AD98502 DDS synthesizers.

Today (October 2018) the AD9850 boards on eBay go for about £10, but you can buy a similar product based around the AD98333 for about £2.50 or £5 with buffers and fancy connectors.

The AD9850 handles higher-frequencies (up to 60MHz) whilst the AD8933 is limited to 12.5MHz. That’s fine for me though: I’m interested in frequencies below 100kHz. To be pedantic the AD9833 can handle frequencies up to half the frequency of the reference clock, but all the eBay boards use a 25MHz oscillator.

I was interested in using these to build a swept-frequency generator i.e. to create a signal which is basically sinusoidal with slowly increasing frequency.

Incidentally you can also get similar boards based around the Silicon Labs Si53514 chip. These generate multiple clocks and cost about £10.

A conceptual view

At the heart of the AD9833 is a phase accumulator which increments at a rate determined by both the external master clock and the value loaded into one of the chip’s registers. The official block diagram shows this:

You can see there is more than I’ve described. In particular:

However, we can ignore these if we just want to generate a slowly changing sine wave.

Arduino interfacing

Google will furnish many articles on ad9833 arduino7, but it’s not quite clear of their relative merits.

Most of the libraries implement a C++ class which hides the functionality of the chip behind a nice API. Sadly the one I tried seemed to reset the phase accumulator when the frequency changed, leading to discontinuities in the output.

Given the datasheet, It is simple to program the chip to generate a single frequency, so I thought it better to just drive it directly.

There is one wrinkle though: although the interface to the AD9833 is essentially SPI, blog posts commonly talk about it finding it hard to use the system SPI and instead use bespoke bit-banging code. I followed that advice here, and stole the SPI code from Marco Colli’s library8.

// for chip info see https://www.analog.com/en/products/ad9833.html
// SPI code taken from https://github.com/MajicDesigns/MD_AD9833/

const uint8_t _dataPin  = 11;
const uint8_t _clkPin   = 13;
const uint8_t _fsyncPin = 10;

// send raw 16-bit word
void spiSend(const uint16_t data)
{
  digitalWrite(_fsyncPin, LOW);

  uint16_t m = 1UL << 15;
  for (uint8_t i = 0; i < 16; i++)
    {
      digitalWrite(_dataPin, data & m ? HIGH : LOW);
      digitalWrite(_clkPin, LOW); //data is valid on falling edge
      digitalWrite(_clkPin, HIGH);
      m >>= 1;
    }
  digitalWrite(_dataPin, LOW); //idle low
  digitalWrite(_fsyncPin, HIGH);
}

void setFreq(double f)
{
  const uint16_t b28  = (1UL << 13);
  const uint16_t freq = (1UL << 14);

  const double   f_clk = 25e6;
  const double   scale = 1UL << 28;
  const uint32_t n_reg = f * scale / f_clk;

  const uint16_t f_low  = n_reg         & 0x3fffUL;
  const uint16_t f_high = (n_reg >> 14) & 0x3fffUL;

  spiSend(b28);
  spiSend(f_low  | freq);
  spiSend(f_high | freq);
}

void setup() {
  pinMode(_clkPin,   OUTPUT);
  pinMode(_fsyncPin, OUTPUT);
  pinMode(_dataPin,  OUTPUT);

  digitalWrite(_fsyncPin, HIGH);
  digitalWrite(_clkPin,   LOW);
}

void loop() {
  setFreq(800.0);
  delay(50);
  setFreq(1600.0);
  delay(50);
}

Raspberry Pi

The code above is entirely self-contained, so it is easy to port it to the Raspberry Pi. I switched the code from C to Python, targetting the gpiozero9 API.

import gpiozero

class AD9833:

    def __init__(self, data, clk, fsync):
        self.dataPin  = gpiozero.OutputDevice(pin = data)
        self.clkPin   = gpiozero.OutputDevice(pin = clk)
        self.fsyncPin = gpiozero.OutputDevice(pin = fsync)

        self.fsyncPin.on()
        self.clkPin.on()
        self.dataPin.off()

        self.clk_freq = 25.0e6

    def set_freq(self, f):
        flag_b28  = 1 << 13
        flag_freq = 1 << 14

        scale = 1 << 28
        n_reg = int(f * scale / self.clk_freq)

        n_low = n_reg         & 0x3fff
        n_hi  = (n_reg >> 14) & 0x3fff

        self.send16(flag_b28)
        self.send16(flag_freq | n_low)
        self.send16(flag_freq | n_hi)

    def send16(self, n):
        self.fsyncPin.off()

        mask = 1 << 15
        for i in range(0, 16):

            self.dataPin.value = bool(n & mask)
            self.clkPin.off()
            self.clkPin.on()

            mask = mask >> 1

        self.dataPin.off()
        self.fsyncPin.on()

ad = AD9833(10, 11, 8)
while 1:
    for f in range(10,10000):
        ad.set_freq(f)

Code

You can get the code from GitHub10.