Saturday, May 10, 2008

Controlling an AD9851 DDS with an Arduino

ad9851.jpgBeen playing with the AD9851 DDS for a while now using other people's software mostly written in assembly language that I find rather hard to get my head around. Finally tonight, with the help of several others who have published their work on the internet I have got some simple code to drive this chip the way I want.

This can tell it the frequency the generate in Hz and it just works.

This minimal example does a small sweep from 10Mhz so it's easy to listen to on a radio.

While I prefer to use the atmel chips "naked" I do find the Arduino board a very convenient way to muck around and get things going in an interactive environment. This code doesn't rely on any magic Arduino libraries so it should be easy to port, I'll post a straight ATMEGA version soon.

The code presented here shows some things I've struggled to figure out: how to calculate the tuning word from the frequency and how to send the serial commands to the chip. I hope this helps someone else.


// Control a AD9851 DDS based on the good work of others including:
// Mike Bowthorpe, http://www.ladyada.net/rant/2007/02/cotw-ltc6903/ and
// http://www.geocities.com/leon_heller/dds.html
// This code by Peter Marks http://marxy.org

#define DDS_CLOCK 180000000

byte LOAD = 8;
byte CLOCK = 9;
byte DATA = 10;
byte LED = 13;

void setup()
{
pinMode (DATA, OUTPUT); // sets pin 10 as OUPUT
pinMode (CLOCK, OUTPUT); // sets pin 9 as OUTPUT
pinMode (LOAD, OUTPUT); // sets pin 8 as OUTPUT
pinMode (LED, OUTPUT);
}

void loop()
{
// Do a frequency sweep in Hz
for(unsigned long freq = 10000000; freq < 10001000; freq++)
{
sendFrequency(freq);
delay(2);
}
}

void sendFrequency(unsigned long frequency)
{
unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;
digitalWrite (LOAD, LOW); // take load pin low

for(int i = 0; i < 32; i++)
{
if ((tuning_word & 1) == 1)
outOne();
else
outZero();
tuning_word = tuning_word >> 1;
}
byte_out(0x09);

digitalWrite (LOAD, HIGH); // Take load pin high again
}

void byte_out(unsigned char byte)
{
int i;

for (i = 0; i < 8; i++)
{
if ((byte & 1) == 1)
outOne();
else
outZero();
byte = byte >> 1;
}
}

void outOne()
{
digitalWrite(CLOCK, LOW);
digitalWrite(DATA, HIGH);
digitalWrite(CLOCK, HIGH);
digitalWrite(DATA, LOW);
}

void outZero()
{
digitalWrite(CLOCK, LOW);
digitalWrite(DATA, LOW);
digitalWrite(CLOCK, HIGH);
}


The board I'm using is the excellent carrier from AmQRP.

14 comments:

Nick said...

This is exactly the code I've been looking for. Now all I need to do is build my DDS board and I'm ready to start building my new radio. Thanks a bunch!

Nick KB3RRM

g7nbp said...

Yes this is indeed good stuff. Its saved me lots of time with my DDS tests with arduino.

Im currently playing about with a jog/shuttle style control to go with the DDS via an arduino - basically to make the super-wide tuning range easier to wrangle.

http://g7nbp.blogspot.com/2008/12/jogging-and-shuttling-with-arduino.html



Thanks again - great work!

languer said...

I am looking for a similar implementation on a PIC-based system. I am struggling to understand the following implementation:
unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;I believe the long int is 32-bit wide. Wouldn't this equation cause an overflow of the 32-bit word in the first multiplication?

EA7ARX said...

Hello!

Good Work!! Thank You very much for share it with the comunity.

The idea is good, the implementation not. Yes, it make a overflow, but I think howhever it works.

You can make: unsigned long tuning_word = (frequency * pow(2, 24)) / (DDS_CLOCK/256)

It is easy, Then it not overflow.
Thanks!!

Ross said...

Hi Peter

I know this is kind of old now but I'm working on something similar. Here's a couple of refinements I've come up with.

To output to the DDS-60, all you need is this:

digitalWrite(LOAD, LOW);
shiftOut(DATA, CLOCK, LSBFIRST, tuning_word);
shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 8);
shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 16);
shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 24);
shiftOut(DATA, CLOCK, LSBFIRST, 0x09);
digitalWrite(LOAD, HIGH);

shiftOut is a built-in function that does exactly what we need.

It's not obvious in the documentation but the Arduino can do 64 bit integer arithmetic. If f is the frequency in Hz and you want the tuning_word then I think this is a good way to do it.

long tuning_word = (f * 4294967296LL) / 180000000LL;

I prefer that to doing floating point. It gives a slightly more accurate answer at the higher frequencies. I timed it and both methods take the same amount of time, about 4 microseconds. You can tweak the 4294967296 (2^32) as a means of calibration.

When I get this done properly I hope to write it up more fully on my own site.

Cheers
Ross
KB1KGA, Ex ZL1BNV

Anonymous said...

Dear Marx.

Sorry to post this as anonym ... but I'm too lazy to get some id.

My question is ..
How to set this sketch to a fix freq (i.e 52 Mhz) ?

Sincerely
-bino-

Anonymous said...

Hi Peter

I appreciate that this blog is now rather old, but I've also been playing around recently with Arduino and the DDS-60, and more particularly writing to the chip using the Arduino SPI library.

Building on Ross's code fragments, I changed the code writing to the DDS-60 to:

digitalWrite(LOAD, LOW);
SPI.transfer(tuning_word);
SPI.transfer(tuning_word >> 8);
SPI.transfer(tuning_word >> 16);
SPI.transfer(tuning_word >> 24);
SPI.transfer(0x09);
digitalWrite(LOAD, HIGH);

This code is to be preceded by:
Including the SPI library:

#include

Setting up SPI in setup():

SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(LSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV2);
SPI.begin();

I have not made actual measurements, but this code, on the face of it, seems to be faster.

I have successfully used a similar approach with programming the AD9835 chip via Arduino.

Best regards

Andries ZR6ABS

navn said...

Hi marx's, i am designing a signal generator usign ad9851 and adruino i need to knw,for acheiving a frequency sweep of 5MHz to 30MHz wat should be my DDS clock(the first line of my program). i connect an reference oscillator of 5MHz to AD9851

Peter Marks said...

Hi Navn,

It's a long time since I've looked at this code. I'm sure you can figure it out.

Marxy

navn said...

hi

i have understood the code. But how to verify my frequency output. Can i do it using oscilloscope or is there any other method.

Can u also give details of how u measured ur frequency output.

Peter Marks said...

A radio receiver is a good way to confirm the output frequency (assuming you don't have a frequency counter).

George Smart said...

Just to say thanks for this code. It's simple and to the point. Thanks for taking the time to write this up on your website!

Best Regards
George Smart, M1GEO
http://www.george-smart.co.uk/

Anonymous said...

Hi,
I've tried out Ross's suggestion about using shiftOut and I can confirm it works well but with one exception:
The following line
shiftOut(DATA, CLOCK, LSBFIRST, 0x09);
will set W32 to 1. This is a big NO-NO!
W32 and W33 should always be 0 in serial mode. Anything else is factory reserved and can make your chip stop responding.
I now have three non-responding chips and so far I haven't found a way to bring them back to life. I had to resort to my logic analyser to find out why they were dying.
This line should be used instead:
shiftOut(DATA, CLOCK, LSBFIRST, 0x0);

Good luck,
I'll be posting my version of the code on my web site onced I've finished testing it.
Andy G8GLZ.

Anonymous said...

Oops!
Update to my last post - I'm using AD9850's not AD9851's. Bit W32 is fatal to AD9850's but is used to tell the AD9851 to use the internal 6X multiplier for the clock.
If anyone knows a way to revive an AD9850 I'd love to know!
Andy G8GLZ.