Arduino DDS Controller

1. Introduction
One of my favourite devices seems to be direct digital synthesis (DDS) chips. Essentially they’re wide range (30 MHz or more) numerically controlled oscillators which are in turn controlled by a micro-controller or PC interface. I think it has to do with my frustration with analog LC type oscillators. At any rate, whenever I learn a new programming language or interface device, I wind up building a DDS controller. I’ve posted a controller with a Java based GUI and a USB interface here:  http://dbc-projects.net/java-dds-controller/.
Since I’ve been experimenting with Arduino/Python systems, I decided to implement an Arduino DDS interface controlled by a Python GUI. The Arduino code is written in embedded C developed on the Atmel Studio IDE. Hopefully I can re-use some of this code when I (eventually) get around to building a stand-alone DDS VFO.


2. The DDS Circuit
The DDS board I’m using is a PCB from an old QEX article called “Building A Direct Digital Synthesis VFO “. The design of this board is similar to the DDS boards found everywhere on the net. Interestingly, the blank PCBs that I purchased some years ago cost about the same as the smaller populated boards do today. The original project called for an AD9850 DDS (as do today’s small boards). I replaced this with an AD9851 which has a wider frequency range at the expense of higher current consumption. To handle the higher current, I replaced the original 78L05 regulator with a 7805. I’ve also scaled the output filter to allow for the higher frequencies.
The circuit adapted from the QEX article (without the rotary switch and LCD interface) is shown below:

3. The Arduino Firmware
The Arduino firmware was written in “pure” embedded C developed on Atmel Studio 7, and turned out to be fairly straightforward. The Python GUI sends a text string containing the desired frequency over the serial port. The firmware reads the text string, and sets the DDS accordingly. Including the main.c program, the whole thing consists of three modules.

1. ddsUtils Module
The ddsUtils module controls the frequency setting of the DDS. The DDS chip is programmed serially by hardwiring data bits D0, and D1 to VCC on the board. Data is shifted into the device via data bit D7 on the rising edge of the W_CLK signal. The FQ_UD signal is used to latch the data into the selected internal device registers once the the data transfer is complete.
The data control bits are shown in the following code:

// Setup control bits on Port B
#define W_CLK PB0 // Pin 8 - connect to AD9851 module word load clock pin (CLK)
#define FQ_UD PB1 // Pin 9 - connect to freq update pin (FQ)
#define DATA PB2 // Pin 10 - connect to serial data load pin (DATA)

#define pulseHigh(pin) {PORTB |= (1<<pin); PORTB &= (0<<pin); }
 
// Note: System clock approx: 30MHz * 6 = 180MHz
// exact value found by inspection
#define SYSCLK 179985600
#define PHASE_RES 4.294967296e9 // 2^32

Note that the AD9851 has an internal 6X REFCLK multiplier option which allows lower frequency oscillators to be used. In this case the external 30MHz clock oscillator is internally multiplied up to an internal system clock of 180MHz.
A byte of data is clocked to the DDS by W_CLK one bit at a time, LSB first as shown below:

 void writeDDSReg(char data)
{ 
 for (int i = 0; i < 8; i++, data >>= 1)
 {
 if (data & 0x01)
 {
 PORTB |= (1<<DATA);
 }
 else
 {
 PORTB &= (0<<DATA);
 }
 pulseHigh(W_CLK); // After each bit sent, CLK is pulsed high
 } 
}

The DDS controller is initialized by configuring the control pins, and selecting the serial mode by pulsing the W_CLK and FQ_UD pins. The device registers should also be cleared by writing a 40 bit word of 0’s as shown below:

void ddsInit(void)
{
 // Configure DDS control pins as outputs
 DDRB |= (1<<DATA)|(1<<FQ_UD)|(1<<W_CLK);
 PORTB &= 0xF0; // Set outputs low
 
 pulseHigh(W_CLK);
 pulseHigh(FQ_UD); // Enables serial mode on the AD9851
 
 // Flush residual data left in DDS core by writing
 // a 40 bit word of 0's
 // Clear the array
 for (int i = 0; i < 5; i++)
 {
 writeDDSReg(0); 
 }
 pulseHigh(FQ_UD); 
}

The actual data written to the DDS consists of a 32 bit frequency word, followed by an 8 bit control byte that selects the clock multiplier.

A floating point frequency value from the serial port is converted to a long (4 byte) frequency word then written to the DDS on a byte by byte basis. Finally, the control byte is sent.

void setFreq(float freqVal)
{
 long freq = (long)(freqVal * PHASE_RES / SYSCLK);
 
 for (int b = 0; b < 4; b++, freq >>= 8)
 {
 writeDDSReg(freq & 0xFF);
 }
 // Set phase to 0, power-up mode selected,
 // 6x REFCLK multiplier engaged.
 writeDDSReg(0x01);
 pulseHigh(FQ_UD);
}

2. stdio_setup Module
The stdio_setup module is responsible for configuring the Atmega328p USART, and setting the streaming parameters of the device. This allows the program to utilise the complier streaming I/O functions like gets(), puts(), or printf() once the device dependant basic functios are added.

The Atmega328p USART is initialized by declaring the microcontroller clock (16MHz), and desired baud rate. The actual baud rate is set by a macro in the compiler setbaud.h file.
The UartInit() routine itself then enables the device and configures the stop bits, parity etc.

define F_CPU 16000000UL
#define BAUD 9600
#define BAUD_TOL 2

#include <avr/io.h>
#include <stdio.h>
#include <util/setbaud.h>

// Declare I/O functions for the STDIO
int UartPutchar(char, FILE *stream);
int UartGetchar(FILE *stream);

// Redirect UartPutchar and UartGetChar to stdout and stdin 
static FILE the_stdio = FDEV_SETUP_STREAM(UartPutchar, UartGetchar, _FDEV_SETUP_RW);

void UartInit(void)
{
 
 // Redirect stdio to the_stdio
 stdout = &the_stdio;
 stdin = &the_stdio;
 
 // Set the baudrate via setbaud.h and the baud rate registers
 UBRR0H = UBRRH_VALUE;
 UBRR0L = UBRRL_VALUE;
 
 #if USE_2x
 UCSR0A |= (1<<U2X0);
 #endif
 
 // Enable the transmitter and receiver 
 UCSR0B = (1<<RXEN0) | (1<<TXEN0);
 
 // 8-bit, 1 stop bit, no parity, asynchronous UART
 UCSR0C = (1<<UCSZ01) | (1<< UCSZ00) | (0<<USBS0) |
 (0<<UPM01) | (0<<UPM00) | (0<<UMSEL01) |
 (0<<UMSEL00);
}

In order to use the compiler I/O streaming functions such as gets() and printf(), data in the USART must be redirecetd to the compiler I/O streams STDIN and STDOUT. This accomplished by setting up a FILE buffer with the FDEV_SETUP_STREAM() macro. The file buffer is then re-directed to stdout and stdin.

This method is necessary to allow for different configurations in the various Atmel processors. While this allows for some customization in the input and output functions, mine are pretty straightforward, although you do have to adhere to the format expected by the FDEV macro or risk a stern warning from the compiler.

The I/O functions are shown below:

int UartPutchar(char data, FILE *stream)
{
 while (!(UCSR0A & (1<<UDRE0))); // Wait until the buffer is empty
 UDR0 = data; // Send the data
 return 0;
}

int UartGetchar(FILE *stream)
{
 char data;
 
 while (!(UCSR0A & (1<<RXC0))); // Wait until data is available 
 data = UDR0; // Read the data 
 if (data == '\n') // If newline, signify the end of a string
 {
 return -1;
 }
 return(data); // Otherwise return the data
}

3. The main Module
What with using separate modules for the DDS and I/O functions the main.c routine becomes pretty straightforward. Essentially main.c initializes the DDS module and USART, then waits for data from the serial port and sets the DDS frequency accordingly.

The code is shown below:

int main(void)
{ 
 char inputString[50];
 float freqVal;
 
 ddsInit();
 setFreq(1e6); // Initialize the DDS frequency to 1 MHz
 UartInit();
 // Output a string to a terminal program
 // Not necessary but useful for debugging 
 printf("DDS Controller\r\n"); 
 
 
 while (1) 
 { 
 // Wait for data from the serial port
 gets(inputString);
 // Convert to a floating point value 
 freqVal = atof(inputString);
 // Set the DDS frequency
 setFreq(freqVal); 
 }
}

The complete Atmel Studio project and source code files can be found here: arduino_main.zip

4. The Python Script
The Python script, pyArdDDS.py is a TkInter based GUI, written in Python 3.4.3 for Windows, which configures a DDS frequncy word as as string, then uses pySerial to transmit serial frequency data to the Arduino DDS conroller.

The complete script can be found here: pyScope.zip

1. TkInter GUI
As shown in the screeen shot above, the main purpose of the GUI is to assemble a frequency word for the DDS, and send it over the serial port. The GUI can set frequencies from 1Hz to 30 MHz. Using the two slider bars, frequencies can be swept with resolutions fro 1 Hz to 100 KHz.

  • Frequencies can be manually entered by typing in the desired value into the Frequency input box and hitting the enter key. The value can be entered as a Hz or KHz value depending on the Hz/KHz button to the right. Pressing ‘Enter’ sends the data to the serial port and updates the positions of the slider bars to reflect the frequency value.
  • The value in the Frequency Input Box can be changed using the Coarse and Fine frequency control sliders. The two sliders control the resolution of the frequency changes based on the selected radio buttons below. The Coarse slider sets frequencies from 1KHz to 30000 KHz in steps of KHz, 10KHz, or 100KHz. The Fine slider sets frequencies from 1Hz to 999 Hz in steps of 1Hz, 10Hz, or 100Hz. The resulting output frequency is the sum of the Coarse and Fine sliders values giving frequencies of 1Hz to 30MHz in steps of 1Hz to 100KHz.
  • The frequency resolution radio buttons below the frequency control sliders control the step size of the Coarse and Fine frequency settings. Selecting the 1Hz, 10Hz, or 100Hz button will set the step size of the Fine slider while setting the 1KHz, 10KHz, or 100KHz button will set the step size of the Coarse slider.
  • The Reset button resets the GUI variables and the serial port. Frequency is set to 1MHz, in the frequency input box, and the sliders are set accordingly. Finally the 1KHz frequency resolution button is selected.
  • The Exit button simply closes the serial port and terminates the GUI

2. Serial Port Initialization
The serial port is initialized by checking a list of open ports on the PC. If a port is found connected to an Arduino, it is configured for 9600 baud and defaults to 8 data bits no parity.

If no Arduino serial port is found, an error is generated.

3. Setting the DDS Frequency
To set the DDS frequency, the floating point frequency value is converted to a string, and a carriage return/line feed is added, which is expected by the Arduino firmware. If a valid port has been found, the string is written.

4. __main__ Function
The ‘main’ function instantiates the the Gui class, then initializes the serial port. Finally the TkInter mainloop() function is called to service the Gui controls.

5. Conclusion
Using the Arduino to control a DDS module might be a bit of overkill, but its probably less expensive than using a separate USB or digital I/O module. As it stands, this is a reasonably good PC based signal generator, and its a good stepping stone to a standalone DDS VFO.

Someday.