Friday, October 10, 2008

BCD Switch direct entry DDS Oscillator

Wishing to free myself from using crystals in small transmitters, the DDS-60 board (with a AD9851 chip) seemed like the way to go, but how to do the frequency entry?

I've played with the excellent AVR Butterfly DDS controller, but it's a little fiddly for regular use.

BCD thumb switches seem like a nice approach as they remove the need for a display. Here's how it looks:

DSC01118.JPG


And here's my rather messy prototype board with the BCD switch wiring.

DSC01120.JPG


I wanted lots of I/O pins for scanning the BCD switch array (12 wires) and talking to the AD9851 DDS chip (3 wires) so I chose an Atmel ATmega32.

As frequency in the CPU isn't important, my design just uses the internal 8Mhz oscillator.

The code is written with avr-gcc, uses avr-libc and burnt with avrdude, all on a Mac. (Installed with Darwin ports).

I scan the BCD switches and if the value has changed I write to the DDS. There's a single LED that I flash when I write. (It's fun debugging code when you only have a single LED to let you know what's going on).

Note that there is a bug in MacPorts which won't build avr-libc. The tip about how to get past this is here.

The hard bit is driving the DDS chip and that code is here.

Update

Figured out a problem that might bite others, I was trying to use port c pins which were set for JTAG mode in the CPU fuse bytes, thanks to AVRFuse this was easily fixed.

I also realised that I have too many digits, so next step is to remove excess and put all this in a box. Done:

boxed vfo.JPG


Here's the code:


/* Scan 8 BCD switches and set a DDS to the frequency
Peter Marks VK2TPM
http://marxy.org
*/

#include <avr/interrupt.h>
#define F_CPU 800000UL
#include <util/delay.h>
#include <math.h> // for pow()

#define LED_BLINK_MS 500
#define SCAN_DELAY_MS 20

#define SCAN_LOW_PORT PORTA // low 8 digits scan output
#define READ_BCD_PORT PORTC // read BCD bits 0-3

#define LEDOFF PORTB &= ~(1<<2)
#define LEDON PORTB |= (1<<2)

// Pins used to talk to the DDS chip
#define DDS_LOAD 6
#define DDS_CLOCK 5
#define DDS_DATA 4

#define DDS_CLOCK_HZ 180000000UL

#define DDS_OUTPORT PORTD
#define DDS_OUTPORTDIRECTION DDRD

void ioInit();
unsigned long scanFrequency();
void blinkLed();
unsigned long tenPower(int decimal);
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()
{
ioInit();
blinkLed();

unsigned long lastFrequency = 0;
unsigned long newFrequency = 0;

while(1)
{
newFrequency = scanFrequency();
if(newFrequency != lastFrequency)
{
sendFrequency(newFrequency);
blinkLed();
lastFrequency = newFrequency;
}
}

return(0);
}

// Scan the BCD thumbwheels and return the frequency
unsigned long scanFrequency()
{
int i;
unsigned int readBcd = 0;
unsigned long frequency = 0;

for(i = 0; i < 8; i++)
{
SCAN_LOW_PORT = (1 << i);
_delay_ms(SCAN_DELAY_MS);
readBcd = PINC & 0x0f;
_delay_ms(SCAN_DELAY_MS);

if(i == 0)
frequency = readBcd;
else
frequency += (readBcd * tenPower(i));
}
SCAN_LOW_PORT = 0;
return frequency;
}

unsigned long tenPower(int decimal)
{
unsigned long result = 10;
int i;
if(decimal == 0)
return 1;
for(i = 0; i < decimal - 1; i++)
{
result *= 10;
}
return result;
}

void blinkLed()
{
LEDON;
_delay_ms(LED_BLINK_MS);
LEDOFF;
_delay_ms(LED_BLINK_MS);
}

void ioInit()
{
DDRA = 0xff; // enable output
DDRB = 0xff; // enable output
DDRC = 0xf0; // C is input on lower 4 bits
DDRD = 0xff; // D is for DDS control
}

// DDS control code
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_HZ;
bitSetLo(&DDS_OUTPORT, DDS_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(&DDS_OUTPORT, DDS_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(&DDS_OUTPORT, DDS_CLOCK);
_delay_ms(1);
bitSetHi(&DDS_OUTPORT, DDS_DATA);
_delay_ms(1);
bitSetHi(&DDS_OUTPORT, DDS_CLOCK);
_delay_ms(1);
bitSetLo(&DDS_OUTPORT, DDS_DATA);
_delay_ms(1);
}

void outZero()
{
bitSetLo(&DDS_OUTPORT, DDS_CLOCK);
_delay_ms(1);
bitSetLo(&DDS_OUTPORT, DDS_DATA);
_delay_ms(1);
bitSetHi(&DDS_OUTPORT, DDS_CLOCK);
_delay_ms(1);
}



The circuit

BCD DDS.png


The BCD switches have small diodes from each of the outputs 1,2,4,8 to a common bus which goes to a 4k7 pull down resistor and to input pins. (If I built this again, I'd reverse the diodes and use the CPU's internal pull-up resistors to save some wiring).

Output pins directly drive the common on each BCD swich to scan them with a brief +5V.

There's a 7805 3 terminal regulator to regulate 12V down to 5V for the CPU, the 12V line goes direct to the DDS-60. The input has a forward diode to protect against reverse polarity. One pin has an LED with a 1k resistor that flashes on power up and on each frequency change.

I brought out the programming pins on a little header. Also I had a go at a serial interface but haven't got it working yet.

Update

Hey, cheers to NT7S blog and thanks for the encouragement!

3 comments:

Alan Yates said...

This is great Peter, I'm glad you got that working you've been talking about doing it for a while now. Lots of diodes in the BCD switch scanning matrix, neat construction!

Peter B Marks said...

Thanks Alan,

Yes, I've been thinking about this for months but basically built it over the past 48 hours.

The diode matrix is built on a strip of veroboard on the back of the BCD switches.

Once I get it fully debugged I'm thinking of putting a serial interface on it for computer control as well, combine that with a logarithmic rf meter and you've got a really useful gadget.

Tomas ok4bx said...

Hallo Peter, thanks for sharing all those information. I have also DDS60 kit and I would like build one day QRP TRX on my own controlled from AVR cpu with my firmware. Now I am reading about programming in C for AVR and your article is great inpiration to me!

73! Tomas