Sunday, May 11, 2008

Controlling an AD9851 with an Atmel AtTiny85 (or other)

avrdds.jpgFollowing on from my last post, I got something similar working with an ATTiny85. This sample code does a slow sweep from 10Mhz up a bit. It's just a demo, the bit I've been looking for myself and couldn't find was how to just say sendFrequency().

I have lots of ideas for applications of these things, including:


  • General purpose VFO with keypad and knob

  • Sweep test generator, paired with a digital rf signal measurer for computer plotting filters etc

  • Automated antenna analyser with a digital SWR measuring thingy



This code is written in C and uses avrlibc. I'm working on a Mac but it will work the same on Linux or that other OS that was quite popular.


/*
Control an AD9851 DDS with an ATMega85
*/

#include <avr/io.h>
// Sets up the default speed for delay.h
#define F_CPU 800000UL
#include <util/delay.h>
#include <math.h> // for pow()

// Pins used to talk to the DDS chip
#define LOAD 4
#define CLOCK 3
#define DATA 0
#define LED 1

#define DDS_CLOCK 180000000UL

#define OUTPORT PORTB
#define OUTPORTDIRECTION DDRB

void sendFrequency(unsigned long frequency);
void byte_out(unsigned char byte);
void outOne();
void outZero();
void bitSetHi(volatile uint8_t *port, int bit);
void bitSetLo(volatile uint8_t *port, int bit);

int main (void)
{
OUTPORTDIRECTION = _BV(LOAD) | _BV(CLOCK) | _BV(DATA) | _BV(LED);
unsigned long freq;

// flash an led to show we're alive
int i;
for(i = 0; i < 10; i++)
{
bitSetHi(&OUTPORT, LED);
_delay_ms(100);
bitSetLo(&OUTPORT, LED);
_delay_ms(100);
}
while (1)
{
// Do a frequency sweep in Hz
for(freq = 10000000; freq < 10001000; freq++)
{
sendFrequency(freq);
_delay_ms(1);
}
}
return 0;
}

void bitSetHi(volatile uint8_t *port, int bit)
{
*port |= ( 1<<bit );
}

void bitSetLo(volatile uint8_t *port, int bit)
{
*port &= ~( 1<<bit );
}

void sendFrequency(unsigned long frequency)
{
unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;
bitSetLo(&OUTPORT, LOAD); // take load pin low
int i;

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

bitSetHi(&OUTPORT, LOAD); // 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()
{
bitSetLo(&OUTPORT, CLOCK);
_delay_ms(1);
bitSetHi(&OUTPORT, DATA);
_delay_ms(1);
bitSetHi(&OUTPORT, CLOCK);
_delay_ms(1);
bitSetLo(&OUTPORT, DATA);
_delay_ms(1);
}

void outZero()
{
bitSetLo(&OUTPORT, CLOCK);
_delay_ms(1);
bitSetLo(&OUTPORT, DATA);
_delay_ms(1);
bitSetHi(&OUTPORT, CLOCK);
_delay_ms(1);
}


The AMQRP board has it's own regulator and has a 5V output so I'm powering the ATTiny85 from that power rather than a separate input. Here's a nifty spot reference. Simple enough even for me to build.

spotGen.jpg

1 comment:

Anonymous said...

You really don't want to use pow on uCs. They don't have floating point arithmetic in hardware and anything involving floats or doubles is very expensive.
This applies more so in this case, where you have a power of 2 that is used in an operation with a long, so it will be casted anyway to a long. So instead of pow(2,32) use 1 << 32 or 1 shifted to the left by 32.
You should read about binary representation, as it's very important when programming embedded.