The AD9833 is a Direct Digital Synthesizer that can generate sine,
square or triangle waves and is controlled using the SPI protocol.
A few years ago you would have to pay a lot of money for a DDS now
you can get one for $10! Amazing - this thing can generate signals at
0.1Hz resolution and works up to 12.5MHz.
Note: If you want to get complex you can make it perform coding schemes such as FSK, GMSK and QPSK. Here we''ll just keep it simple!
Since the device is in a MSOP package the easiest way to use it is by
getting yourself a breakout board. You can buy two types, one comes with an opamp
buffer chip and digital attenuator for adjusting the output level.
AD9833 Breakout Board
The right hand side output is direct from the DDS (labelled Vout)
whereas the left side has the output going through the pot and the
amplifier (labelled PGA).
This is the Direct Digital Synthesis chip Datasheet (click to open in new window).
AD9833 Breakout Board
The reason that the opamp is needed is that the output of the
AD9833 is about 600mV. The opamp amplifies the signal by 5 to give a 3V
output. Using the digital pot allows you to reduce this output to a
level you need.
Block Diagram
One interesting point in the diagram above is that there are two
frequency registers FREQ0 and FREQ1 and you can select between them
using a mux. This means frequency shift keying is easy i.e. change
frequency between two stored frequencies without re-loading a frequency
register, so it is fast. The same is true of phase.
Breakout Board Components
The breakout board does not seem to have any schematics available but
it is easy to follow the board traces. The components on the board are:
Component
Description
AD9833
DDS chip
MCP41010
Digital Potentiometer
H2A
AD8051 (see below)
The component that is difficult to identify, is the one marked H2A
on the SOT23-5 package. This is an output buffer configured as an
inverting op amp and is probably the AD8051 - a very high speed (300MHz
3dB bandwidth) rail-to-rail opamp.
#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 10 ///< SPI Load pin number (FSYNC in AD9833 usage)
#define CS_DIGIPOT 9 // MCP41010 chip select - digital potentiometer.
MD_AD9833AD(FSYNC);// Hardware SPI
//MD_AD9833 AD(DATA, CLK, FSYNC); // Arbitrary SPI pins
// Character constants for commands
constcharCMD_HELP='?';
constcharBLANK=' ';
constcharPACKET_START=':';
constcharPACKET_END=';';
constcharCMD_FREQ='F';
constcharCMD_PHASE='P';
constcharCMD_OUTPUT='O';
constcharOPT_FREQ='F';
constcharOPT_PHASE='P';
constcharOPT_SIGNAL='S';
constcharOPT_1='1';
constcharOPT_2='2';
constcharOPT_MODULATE='M';
constuint8_tPACKET_SIZE=20;
voidsetup()
{
pinMode(CS_DIGIPOT,OUTPUT);
Serial.begin(57600);
AD.begin();
usage();
// take the CS pin low to select the chip:
digitalWrite(CS_DIGIPOT,LOW);
// send in the address and value via SPI:
SPI.transfer(B00010001);
// write out the value
SPI.transfer(127);
// take the CS pin high to de-select the chip:
digitalWrite(CS_DIGIPOT,HIGH);
}
voidusage(void)
{
Serial.print(F("\n\n[MD_AD9833_Tester]"));
Serial.print(F("\n?\thelp - this message"));
Serial.print(F("\n\n:<cmd><opt> <param>;"));
Serial.print(F("\n:f1n;\tset frequency 1 to n Hz"));
Serial.print(F("\n:f2n;\tset frequency 2 to n Hz"));
Serial.print(F("\n:fmn;\tset frequency modulation to n Hz"));
Serial.print(F("\n:p1n;\tset phase 1 to n in tenths of a degree (1201 is 120.1 deg)"));
Serial.print(F("\n:p2n;\tset phase 2 to n in tenths of a degree (1201 is 120.1 deg)"));
Serial.print(F("\n:ofn;\toutput frequency n or modulation [n=1/2/m]"));
Serial.print(F("\n:opn;\toutput phase n or modulation [n=1/2/m]"));
Serial.print(F("\n:osn;\toutput signal type n [n=(o)ff/(s)ine/(t)riangle/s(q)uare]"));
}
uint8_thtoi(charc)
{
c=toupper(c);
if(c>='0'&&c<='9')
return(c-'0');
elseif(c>='A'&&c<='F')
return(c-'A'+10);
else
return(0);
}
charnextChar(void)
// Read the next character from the serial input stream
// Blocking wait
{
while(!Serial.available())
;/* do nothing */
return(toupper(Serial.read()));
}
char*readPacket(void)
// read a packet and return the
{
staticenum{S_IDLE,S_READ_CMD,S_READ_MOD,S_READ_PKT}state=S_IDLE;
staticcharcBuf[PACKET_SIZE+1];
staticchar*cp;
charc;
switch(state)
{
caseS_IDLE:// waiting for packet start
c=nextChar();
if(c==CMD_HELP)
{
usage();
break;
}
if(c==PACKET_START)
{
cp=cBuf;
state=S_READ_CMD;
}
break;
caseS_READ_CMD:// waiting for command char
c=nextChar();
if(c==CMD_FREQ||c==CMD_PHASE||c==CMD_OUTPUT)
{
*cp++=c;
state=S_READ_MOD;
}
else
state=S_IDLE;
break;
caseS_READ_MOD:// Waiting for command modifier
c=nextChar();
if(c==OPT_FREQ||c==OPT_PHASE||c==OPT_SIGNAL||
c==OPT_1||c==OPT_2||c==OPT_MODULATE)
{
*cp++=c;
state=S_READ_PKT;
}
else
state=S_IDLE;
break;
caseS_READ_PKT:// Reading parameter until packet end
c=nextChar();
if(c==PACKET_END)
{
*cp='\0';
state=S_IDLE;
return(cBuf);
}
*cp++=c;
break;
default:
state=S_IDLE;
break;
}
return(NULL);
}
voidprocessPacket(char*cp)
// Assume we have a correctly formed packet from the parsing in readPacket()
{
uint32_tul;
MD_AD9833::channel_tchan;
MD_AD9833::mode_tmode;
switch(*cp++)
{
caseCMD_FREQ:
switch(*cp++)
{
caseOPT_1:chan=MD_AD9833::CHAN_0;break;
caseOPT_2:chan=MD_AD9833::CHAN_1;break;
caseOPT_MODULATE:/* do something in future */break;
}
ul=strtoul(cp,NULL,10);
AD.setFrequency(chan,ul);
break;
caseCMD_PHASE:
switch(*cp++)
{
caseOPT_1:chan=MD_AD9833::CHAN_0;break;
caseOPT_2:chan=MD_AD9833::CHAN_1;break;
}
ul=strtoul(cp,NULL,10);
AD.setPhase(chan,(uint16_t)ul);
break;
caseCMD_OUTPUT:
switch(*cp++)
{
caseOPT_FREQ:
switch(*cp)
{
caseOPT_1:chan=MD_AD9833::CHAN_0;break;
caseOPT_2:chan=MD_AD9833::CHAN_1;break;
caseOPT_MODULATE:/* do something in future */break;
}
AD.setActiveFrequency(chan);
break;
caseOPT_PHASE:
switch(*cp)
{
caseOPT_1:chan=MD_AD9833::CHAN_0;break;
caseOPT_2:chan=MD_AD9833::CHAN_1;break;
caseOPT_MODULATE:/* do something in future */break;
}
AD.setActivePhase(chan);
break;
caseOPT_SIGNAL:
switch(*cp)
{
case'O':mode=MD_AD9833::MODE_OFF;break;
case'S':mode=MD_AD9833::MODE_SINE;break;
case'T':mode=MD_AD9833::MODE_TRIANGLE;break;
case'Q':mode=MD_AD9833::MODE_SQUARE1;break;
}
AD.setMode(mode);
break;
}
break;
}
return;
}
voidloop()
{
char*cp;
if((cp=readPacket())!=NULL)
processPacket(cp);
}
Note: The square wave output has far greater amplitude than the other signals.
Example of Sweep generation
This code sweeps from 1MHz to 3MHz in 2 seconds.
// Pins for SPI comm with the AD9833 IC
#define DATA 11 ///< SPI Data pin number
#define CLK 13 ///< SPI Clock pin number
#define FSYNC 10 ///< SPI Load pin number (FSYNC in AD9833 usage)
#define CS_DIGIPOT 9 // MCP41010 chip select - digital potentiometer.
MD_AD9833AD(FSYNC);// Hardware SPI
voidsetup(){
AD.begin();
}
voidloop(){
unsignedlongf;
for(f=1000000UL;f<3000000UL;f+=10000UL){
delay(10);
AD.setFrequency(MD_AD9833::CHAN_0,f);
}
}
Breakout board IC Pinouts
AD9833 pinout
This is available in 10 lead MSOP i.e. surface mount only.
MCP41010 pinout
On the board a surface mount SOIC is used but you can also get it in PDIP.
Note: The part is specified as MCP41xxx, where when xxx=010 it is a 10k pot and when xxx=100 it is a 100k pot.
AD8051 pinout
This comes in many different packages but this is the SOT23-5 one i.e.
very small. The one on the breakout board is marked H2A on the top.
IC Datasheets
Either right click the following links and use save-as to save the
pdf to your hard disk, or click to open in a new browser window.
AD9833 Datasheet
This is the Direct Digital Synthesis chip Datasheet (click to open in new window).
AD8051 Datasheet
This is the 300MHz rail to-rail opamp Datasheet (click to open in new window)
MCP41010 Datasheet
This is the 256 tap digital pot. Datasheet (click to open in new window).
Arduino Nano ISP: How to program an ATmega328P using an Arduino Nano as the ISP programmmer. One common problem: Programming a sketch into the chip without a reset control - solved here.
New! Comments
Have your say about what you just read! Leave me a comment in the box below.