I bought an AD9833 DDS module from China – you know the one:

P162SV

I wanted to control it from an Atmel ATtiny and my weapon of choice was the ATtiny44. Now I could just have easily used an ATtiny2313 (both devices are 14-pin), but I didn’t – and I went on and built a PCB.

So when I came to program the ATtiny44, I realised it does not have a proper USART-SPI-mode (which the ‘2313 does). The ATtiny44 (along with the ’24 and ’84 variants and the ATtiny85) just have the USI hardware module which can be crow-barred into service as an SPI master with some heavy lifting.

Well I tried three different software libraries to try and talk to the AD9833 and failed miserably on all three counts. Getting desperate, I decided to bit-bang the AD9833 to see if it was actually working. The bit-bang worked perfectly first time and – as I do not need to write to the AD9833 at any great speed – I decided to implement the bit-bang code and forget trying to get a library to work for now – although no doubt I will need to revisit the library again at some point.

This code should work on pretty much any Atmel Tiny or Mega processor.

Basically, we need a processor which is running at a decent speed (8MHz in my case) and any three pins: chip-select, data and clock. We manipulate these pins in accordance with the timing  and level requirements of the AD9833 datasheet. Level requirements are: chip-select is active low, clock is normally high and data is clocked on the falling edge of the clock. Data is clocked out MSB first.

We are following the software protocol in application note AN-1070; so to get the AD9833 to output a sinewave we need to send:

  • 0x2100 – put the AD9833 into reset and tell it to accept 14bit words
  • 0xnnnn – Lo word of the set-frequency-instruction
  • 0xnnnn – Hi word of the set-frequency-instruction
  • 0xC000 – set the phase angle to zero
  • 0x2000 – take the AD9833 out of reset and output sinewave

There is a formula on the application note to calculate the frequency Lo word and Hi word for any given frequency. I wrote a spreadsheet to work these numbers out for you. You can download the spreadsheet or use the php calculator wot I wrote.

So, let’s say we want to output a sinewave at 1000Hz. We use one of the resources above to find the register values, which are low = 0x69F1 and high = 0x4000 (assuming your module has a 25MHz crystal/oscillator fitted!).

Here’s the code:


#define F_CPU 8000000UL

#include <avr/io.h>
#include <inttypes.h>

#define ddsSelectPin PA0
#define ddsClockPin PA2
#define ddsDataPin PA3

#define ASM_NOP() asm volatile ("nop" :: )
#define SET(x,y) (x|=(1<<y))
#define CLEAR(x,y) (x&=(~(1<<y)))
#define CHECK(x,y) (x&(1<<y))
#define TOGGLE(x,y) (x^=(1<<y))

uint16_t freqLoWord = 0x69F1;
uint16_t freqHiWord = 0x4000;

void setDDR(void) {
    //set data direction (1 = output)
    SET(DDRA, ddsSelectPin); //output
    SET(DDRA, ddsDataPin); //output
    SET(DDRA, ddsClockPin); //output
    //pullups and initial states
    SET(PORTA, ddsSelectPin); //high => unselected
    SET(PORTA, ddsClockPin); //high => idle
}

void writeSPI(uint16_t word) {
    for (uint8_t i = 0; i < 16 ; i++) {
        if(word & 0x8000) SET(PORTA,ddsDataPin); //bit =1, set high
        else CLEAR(PORTA,ddsDataPin); //bit is 0, set low
        ASM_NOP();
        CLEAR(PORTA,ddsClockPin); //data is valid on falling edge
        ASM_NOP();
        SET(PORTA,ddsClockPin);
        word = word<<1; //shift left by 1 bit
    }
    CLEAR(PORTA,ddsDataPin); //idle low
    ASM_NOP();
}

void writeDDS() {
    CLEAR(PORTA, ddsSelectPin); //low = selected
    ASM_NOP();
    writeSPI(0x2100); // enable 16bit words and set reset bit
    writeSPI(freqLoWord);
    writeSPI(freqHiWord);
    writeSPI(0xC000); // set phase register to zero
    writeSPI(0x2000); // clear reset bit => audio on
    ASM_NOP();
    SET(PORTA, ddsSelectPin); //high = deselected
}

void setup(void) {
    setDDR();
    writeDDS();
}

int main(void) {
    setup();
    while(1) ;
    return 0;
}

You can download the above code as a text file.

If you are using Arduino, you can change SET(DDRA,ddsDataPin) to pinMode(ddsDataPin,OUTPUT) and SET(PORTA,ddsDataPin) to digitalWrite(ddsDataPin,HIGH), etc.

The ASM_NOP() function gives a single processor cycle delay (no-operation).

Have fun.  svfavicon.png

 

Leave a Reply

Set your Twitter account name in your settings to use the TwitterBar Section.