Teensy 4.1 and AD9833

KarenColumbo

Active member
I'm trying to get the AD9833 to accept SPI control instead of using a library. The library ("MD_AD9833.h") works great, but to connect more AD9833 I'd have to use precious PWM pins that I need elsewhere. With direct SPI access I could use a MCP23S17 to connect those little 9833s.

I'd be extremely glad if I could "translate this into SPI":
Code:
#include <SPI.h>
#include <MD_AD9833.h>

// Pins for SPI comm with the AD9833 IC
#define DATA  11	///< SPI Data pin number
#define CLK   13	///< SPI Clock pin number
#define FSYNC 9	///< SPI Load pin number (FSYNC in AD9833 usage)

float noteFrequency[73] = {
  32.7032, 34.6478, 36.7081, 38.8909, 41.2034, 43.6535, 46.2493, 48.9994, 51.9131, 55, 58.2705, 61.7354, 
  65.4064, 69.2957, 73.4162, 77.7817, 82.4069, 87.3071, 92.4986, 97.9989, 103.826, 110, 116.541, 123.471, 
  130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 
  261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 
  523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 
  1046.5, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760, 1864.66, 1975.53, 
  2093
};

MD_AD9833	AD(FSYNC);

void setup(void)
{
  
	AD.begin();
  AD.setMode(MD_AD9833::MODE_TRIANGLE);
}

void loop (void)
{
  for (int i = 0; i <= 72; i++) 
  {
    AD.setFrequency(MD_AD9833::CHAN_0, noteFrequency[i]);
    delay(500);
  }
}

I tried and tried but I can't seem to get this to work:

Code:
#include <SPI.h>

// Pins for SPI comm with the AD9833 IC
#define DATA  11	///< SPI Data pin number
#define CLK   13	///< SPI Clock pin number
#define FSYNC 9	///< SPI Load pin number (FSYNC in AD9833 usage)

// AD9833 control word and frequency register addresses
const uint16_t AD_CTRL = 0x2000U;
const uint16_t AD_FREQ0 = 0x4000U;
const uint16_t AD_FREQ1 = 0x8000U;
const uint32_t FREQ_FACTOR = 268435456;
const uint32_t F_MCLK = 25000000;

float noteFrequency[73] = {
  32.7032, 34.6478, 36.7081, 38.8909, 41.2034, 43.6535, 46.2493, 48.9994, 51.9131, 55, 58.2705, 61.7354, 
  65.4064, 69.2957, 73.4162, 77.7817, 82.4069, 87.3071, 92.4986, 97.9989, 103.826, 110, 116.541, 123.471, 
  130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 
  261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 
  523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 
  1046.5, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760, 1864.66, 1975.53, 
  2093
};

void adWrite(float frequency) {

  digitalWrite(CLK, LOW);
  digitalWrite(DATA, LOW);

  SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE2));

  digitalWrite(FSYNC, LOW);
  
  SPI.transfer16(AD_CTRL);

  uint32_t freqWord = frequency * FREQ_FACTOR / F_MCLK;
  Serial.println(freqWord);
  uint16_t freqLow = freqWord & 0x3FFF;
  uint16_t freqHigh = (freqWord >> 14) & 0x3FFF;

  SPI.transfer16(AD_FREQ0 | freqLow);
  SPI.transfer16(AD_FREQ1 | freqHigh);
  
  digitalWrite(FSYNC, HIGH);
  
  SPI.endTransaction();
}

void setup() {
  Serial.begin(9600);
  SPI.begin();
  pinMode(DATA, OUTPUT);
  pinMode(CLK, OUTPUT);
  pinMode(FSYNC, OUTPUT);
}

void loop () {
  for (int i = 0; i <= 72; i++) {
    adWrite(noteFrequency[i]);
    Serial.println(noteFrequency[i]);
    delay(500);
  }
}

Maybe someone has experience with this particular chip and can help me along?
 
Do you mean you want to use MCP23017 to provide the chip select to multiple AD9833? I haven't looked at the MD_AD9833 library, but since that works, I assume you copied the working SPI configuration and code from there? You should not be configuring the clock and data pins as digital outputs or writing to them directly. That is probably disabling/overriding their use for SPI.
 
Do you mean you want to use MCP23017 to provide the chip select to multiple AD9833? I haven't looked at the MD_AD9833 library, but since that works, I assume you copied the working SPI configuration and code from there? You should not be configuring the clock and data pins as digital outputs or writing to them directly. That is probably disabling/overriding their use for SPI.

I must confess I abandoned the idea of getting rid of the MD_AD9833 library after looking at the library code. First example works flawlessly.

So I suppose the MCP23S17 (the SPI version, the MCP23017 is I2C) should be able to transport SDATA and SCLK from the Teensy to the AD9833 and FSYNC it with a GPAX or GPBX pin.

I just tried this (wired correctly), and all I get is clock ticking. (notes.h holds the frequency array)

Code:
#include <SPI.h>
#include <MD_AD9833.h>
#include <Adafruit_MCP23X17.h>
#include "notes.h"
#include <SoftwareSerial.h>

#define MCP23S17_DATA  11
#define MCP23S17_CLK   13
#define MCP23S17_CS    10
#define FSYNC 1

Adafruit_MCP23X17 mcp;
MD_AD9833 AD(FSYNC);

void setup(void)
{
  Serial.begin(115200);
  mcp.begin_SPI(MCP23S17_CS);
  mcp.pinMode(FSYNC, OUTPUT);
  AD.begin();
  AD.setMode(MD_AD9833::MODE_TRIANGLE);
}

void loop (void)
{
  for (int i = 0; i <= 72; i++) 
  {
    Serial.print("Setting frequency to ");
    Serial.println(noteFrequency[i]);
    AD.setFrequency(MD_AD9833::CHAN_0, noteFrequency[i]);
    delay(500);
  }
}

Something's terribly wrong, and I can't point my finger at it.
 
Last edited:
The MCP23x17 are digital I/O expanders. You can use them to provide the chip select (FSYNC?) signal to each AD9833, but you cannot use them to multiplex the SPI clock and data signals. That's okay, though, because you don't need to do that. SPI, like I2C, is a bus. You can run the SPI clock and data signals to multiple AD9833, and provide a unique chip select for each AD9833. You can then read/write the AD9833 one at a time by asserting (low) the chip select for the AD9833 that is "active". The other AD9833 devices will receive the clock and data signals, but as long as their chip select pins remain de-asserted (high), they are effectively disabled. Do some googling on SPI and you will see diagrams of how to connect multiple devices on a single SPI bus.
 
The MCP23x17 are digital I/O expanders. You can use them to provide the chip select (FSYNC?) signal to each AD9833, but you cannot use them to multiplex the SPI clock and data signals. That's okay, though, because you don't need to do that. SPI, like I2C, is a bus. You can run the SPI clock and data signals to multiple AD9833, and provide a unique chip select for each AD9833. You can then read/write the AD9833 one at a time by asserting (low) the chip select for the AD9833 that is "active". The other AD9833 devices will receive the clock and data signals, but as long as their chip select pins remain de-asserted (high), they are effectively disabled. Do some googling on SPI and you will see diagrams of how to connect multiple devices on a single SPI bus.

Thanks for the quick one! I think I did that. I wired them this way:

Teensy4.1 --> MCP --> AD9833 board
pin 13 (SCL) --> pin 12 (SCK) --> SCLK
pin 11 (MOSI) --> pin 13 (SI) --> SDATA
pin 9 --> pin 11 (CS) --> -
- --> pin 22 (GPA1) --> FSYNC

Did I wire too much here?

Problem is, the AD9833 doesn't have a chip select or address pins or something. So I'd need an FSYNC pin for each of them additionally to the SDATA and SCLK. That's why I thought I could use an SPI expander to "fan out" the FSYNC pins - in this case it takes control of the FSYNC entirely.

I was hoping that the MD_AD9833 library takes care of handling the connection as if the AD was directly coupled to a Teensy instead of an MCP23S17. That's why - i suppose - it gets initialized with the FSYNC pin number as argument when it's instantiated.

Sorry for the confusion - I'd love to get that to work too much, but my knowledge is very thinly spread around the whole MCU thing. I'm very grateful for any hint - and I'm already studying SPI stuff in the WWW to get my bearings.
 
Last edited:
If you are connecting Teensy pin 13 (SCLK) and/or Teensy pin 11 (MOSI) to the MCP, that will not work. Those signals must go directly from the Teensy to the AD9833(s). Only the chip select pins can go through the MCP, and you should not even do that until you have successfully got good SPI communication to 2 or more AD9833 without using the MCP.

Here is the description of FSYNC from the AD9833 datasheet. I haven't studied the data sheet, but this sounds like a chip select to me. Say again why you don't want to use the working library? If you don't use it, you'll have to at least study it to understand what it does and why it works.

8 FSYNC Active Low Control Input. FSYNC is the frame synchronization signal for the input data. When FSYNC is taken low, the internal logic is informed that a new word is being loaded into the device.
 
If you are connecting Teensy pin 13 (SCLK) and/or Teensy pin 11 (MOSI) to the MCP, that will not work. Those signals must go directly from the Teensy to the AD9833(s). Only the chip select pins can go through the MCP, and you should not even do that until you have successfully got good SPI communication to 2 or more AD9833 without using the MCP.

Here is the description of FSYNC from the AD9833 datasheet. I haven't studied the data sheet, but this sounds like a chip select to me. Say again why you don't want to use the working library? If you don't use it, you'll have to at least study it to understand what it does and why it works.

Thanks again! :) I should have edited the first post (will do that later) - I think it'd be best to get it working WITH the library (I already stated that after some thinking through), but through something like the MCP23S17 to save those GPIO pins. Maybe gotta get the timing right, though?

Got some fresh ideas now, will sacrifice the weekend to get some leeway here.

The strangest thing is that in following example (posted earlier, just put in the digitalWrites again) the whole program freezes as soon as I set the FSYNC pin low in the loop. If I comment those digitalWrites out, I get at least clock ticking.

Code:
#include <SPI.h>
#include <MD_AD9833.h>
#include <Adafruit_MCP23X17.h>
#include "notes.h"
#include <SoftwareSerial.h>

#define MCP23S17_DATA  11
#define MCP23S17_CLK   13
#define MCP23S17_CS    10
#define FSYNC 1

Adafruit_MCP23X17 mcp;
MD_AD9833 AD(FSYNC);

void setup(void)
{
  Serial.begin(115200);
  mcp.begin_SPI(MCP23S17_CS);
  mcp.pinMode(FSYNC, OUTPUT);
  AD.begin();
  AD.setMode(MD_AD9833::MODE_TRIANGLE);
}

void loop (void)
{
  for (int i = 0; i <= 72; i++) 
  {
    Serial.print("Setting frequency to ");
    Serial.println(noteFrequency[i]);
    digitalWrite(FSYNC, LOW);
    AD.setFrequency(MD_AD9833::CHAN_0, noteFrequency[i]);
    digitalWrite(FSYNC, HIGH);
    delay(500);
  }
}

I will most def revisit my wiring, considering your input. Then go from there. There got to be a way to get this working.
 
Okay, this works ALMOST without clock ticks:
Code:
#include <SPI.h> 
#include "notes.h"

// Teensy 4.1 pin 13 (SCK)  --->  AD9833 board SCLK
// Teensy 4.1 pin 11 (MOSI) --->  AD9833 board SDATA
// Teensy 4.1 pin 9 (GPIO)  --->  Ad9833 board FSYNC

const int FSYNC = 9;                 
#define SPI_CLOCK_SPEED 7500000                     // 7.5 MHz SPI clock - this works ALMOST without clock ticks
unsigned long MCLK = 25000000;                      // AD9833 board default reference frequency

void AD9833setFrequency(long frequency) {
  long FreqReg = (frequency * pow(2, 28)) / MCLK;   // Data sheet Freq Calc formula
  int MSB = (int)((FreqReg & 0xFFFC000) >> 14);     // only lower 14 bits are used for data
  int LSB = (int)(FreqReg & 0x3FFF);
  
  SPI.beginTransaction(SPISettings(SPI_CLOCK_SPEED, MSBFIRST, SPI_MODE2));
  digitalWrite(FSYNC, LOW);                         // set FSYNC low before writing to AD9833 registers

  LSB |= 0x4000;                                    // DB 15=0, DB14=1
  MSB |= 0x4000;                                    // DB 15=0, DB14=1
  SPI.transfer16(LSB);                              // write lower 16 bits to AD9833 registers
  SPI.transfer16(MSB);                              // write upper 16 bits to AD9833 registers
  SPI.transfer16(0xC000);                           // write phase register
  SPI.transfer16(0x2002);                           // take AD9833 out of reset and output triangle wave (DB8=0)
  delayMicroseconds(2);                             // Settle time? Doesn't make much difference …

  digitalWrite(FSYNC, HIGH);                        // write done, set FSYNC high
  SPI.endTransaction();
}

void setup() {
  pinMode(FSYNC, OUTPUT);                           // Prepare FSYNC pin for output
  digitalWrite(FSYNC, HIGH);                        // Set it high for good measure
  SPI.begin();
  SPI.transfer16(0x2100);                           // put AD9833 into reset and tell it to accept 14bit words (DB13=1, DB8=1) once
}

void loop () {
  for (int i = 0; i <= 72; i++) {                   // Read MIDI note freq array ("notes.h", C2–C7)
    AD9833setFrequency(noteFrequency[i]);           // Go play!
    delay(250);
  }
}

Now to do this with an MCP23S17 inbetween ...
 
Ok, I connected an MCP23S17 IC as "passthrough" and AD9833 select.
I get massive clock ticking and SOME notes, but their pitch is all over the place.

Code:
#include <SPI.h> 
#include "notes.h"
#include <Adafruit_MCP23X17.h>

// WIRING

// Teensy 4.1 pin 13 (SCK)  --->  MCP23S17 pin 12 (SCK)   --->    AD9833 board SCLK
// Teensy 4.1 pin 11 (MOSI) --->  MCP23S17 pin 13 (SI)    --->    AD9833 board SDATA
// Teensy 4.1 pin 10 (CS)   --->  MCP23S17 pin 11 (CS)
// MCP23S17 pin 21 (GPA0)   --->  AD9833 board FSYNC

const int MCP_FSYNC_PIN = 0;                        //MCP23S17 FSYNC PIN = GPA0, pin 21
// const int FSYNC = 9;                             // Teensy 4.1 FSYNC pin
const int CS_PIN = 10;                              // Teensy 4.1 CS pin
#define SPI_CLOCK_SPEED 7500000                    // 7.5 MHz SPI clock - this works ALMOST without clock ticks
unsigned long MCLK = 25000000;                      // AD9833 board default reference frequency

Adafruit_MCP23X17 mcp;                              // Generate MCP23S17 object

void AD9833setFrequency(long frequency) {
  long FreqReg = (frequency * pow(2, 28)) / MCLK;   // Data sheet Freq Calc formula
  int MSB = (int)((FreqReg & 0xFFFC000) >> 14);     // only lower 14 bits are used for data
  int LSB = (int)(FreqReg & 0x3FFF);
  
  SPI.beginTransaction(SPISettings(SPI_CLOCK_SPEED, MSBFIRST, SPI_MODE2));
  mcp.digitalWrite(MCP_FSYNC_PIN, LOW);             // set FSYNC low before writing to AD9833 registers

  LSB |= 0x4000;                                    // DB 15=0, DB14=1
  MSB |= 0x4000;                                    // DB 15=0, DB14=1
  SPI.transfer16(LSB);                              // write lower 16 bits to AD9833 registers
  SPI.transfer16(MSB);                              // write upper 16 bits to AD9833 registers
  SPI.transfer16(0xC000);                           // write phase register
  SPI.transfer16(0x2002);                           // take AD9833 out of reset and output triangle wave (DB8=0)
  delayMicroseconds(20);                            // Settle time? Doesn't make much difference …

  mcp.digitalWrite(MCP_FSYNC_PIN, HIGH);            // write done, set FSYNC high
  SPI.endTransaction();
}

void setup() {
  SPI.begin();                                      // Initialize SPI
  mcp.begin_SPI(CS_PIN);                            // Initialize MCP23S17
  mcp.pinMode(MCP_FSYNC_PIN, OUTPUT);               // Prepare MCP23S17 FSYNC pin for output
  //mcp.digitalWrite(MCP_FSYNC_PIN, HIGH);            // Set it high for good measure
  SPI.beginTransaction(SPISettings(SPI_CLOCK_SPEED, MSBFIRST, SPI_MODE2));
  mcp.digitalWrite(MCP_FSYNC_PIN, LOW);
  SPI.transfer16(0x2100);                           // put AD9833 into reset and tell it to accept 14bit words (DB13=1, DB8=1) once
  mcp.digitalWrite(MCP_FSYNC_PIN, HIGH);
}

void loop () {
  for (int i = 0; i <= 72; i++) {                   // Read MIDI note array ("notes.h", C2–C7)
    AD9833setFrequency(noteFrequency[i]);           // Go play!
    delay(250);
  }
}

What's wrong?

I write everything except the MCP's FSYNC pin's high/low signals to both chips. So both receive all the data, but according to what I read about SPI only the AD9833 should latch to it as soon as its FSYNC pin goes low.
 
At first glance I noticed that SPI.endTransaction(); is missing in void setup().
But don't think that's the issue...

Paul
 
At first glance I noticed that SPI.endTransaction(); is missing in void setup().
But don't think that's the issue...

Paul

Ah! Thx - good catch! The methodic part of me wee brain must have taken damage :) Will weed this one out asap!

My guess is it's the wiring. Just wired 4 AD9833s directly and played them mith a polyphonic MIDI to DCO thing I wrote. When I daisy chain clock and data lines, I get the clock ticking. When i wire those star-style they work.
Will try this later, just too excited that I'm quadraphonic at the moment :D
 
Okay, this version works so far – SPI only, no MCP23S17 inbetween.

So the opening post is half fulfilled - may be complete if I can connect the rest of the periphery via I2C expanders. I can live with that.

CLK (Teensy pin 13) and SDATA (Teensy pin 11) lines coupled with 22 Ohms resistors directly at the Teensy pins and distributed in a star pattern to the 8 AD988 boards (5V from a very tsable PSU). FSYNCs are pins 2–8 on the Teensy.

https://github.com/KarenColumbo/Teensy-4.1-DCO8-WIP
 
Back
Top