Arduino DDS(Direct Digital Synthesis)

 DDS stands for Direct Digital Synthesizer, also called Numerically Controlled Oscillator(NCO), and it is a method for generating analog waveforms using digital techniques. In a DDS system, a digital value called a tuning word is used to control the frequency and phase of an output waveform. The tuning word is typically a large integer value that represents the desired frequency as a fraction of the DDS clock frequency. The DDS generates a waveform by rapidly changing the output frequency according to the tuning word, resulting in a continuous waveform with the desired frequency and phase characteristics.

DDS technology offers several advantages over traditional analog waveform generation methods. It provides high frequency resolution, allowing for precise control of waveform frequency with fine increments. DDS also offers excellent frequency stability and accuracy, making it suitable for applications that require precise frequency control. Additionally, DDS allows for easy waveform modulation and frequency sweeping, making it versatile for various signal processing and modulation techniques. . A basic DDS consist of phase accumulator, a phase to amplitude converter(a sine table in ROM), a DAC(digital to analog converter) and a filter. The following shows the block diagram of DDS.

DDS block diagram
First tuning word(M) is calculated from the system clock frequency(fclk) and output frequency(fout) described by the following equation.

\( f_o = \frac{M f_{clk}}{2^N} \)  ------->(1)

therefore tuning word(M) is,

\(M= \frac{f_{clk} 2^N}{f_o}\)  ------->(2)

The tuning word can be described using the phasor diagram shown below.


 

The tuning word is applied to the Phase accumulator where it is added with the previous accumulated phase. The phase accumulator consists of a j-bit frequency register which stores a tuning word followed by a j-bit full adder and a phase register. The digital input phase increment word or tuning word is entered in the frequency register. At each clock pulse this data is added to the data previously held in the phase register. The phase increment word represents a phase angle step that is added to the previous value at each 1/fclk seconds to produce a linearly increasing digital value. The upper k bits of N bits are used to phase information to extract amplitude information from the ROM which stores the sine wave amplitudes. The sine wave amplitude is sent to digital to analog converter and subsequently filtered to get sine wave.

DDS with Arduino

Arduino is a widely used microcontroller platform that provides a simple and affordable way to create DIY electronics projects. It offers a rich ecosystem of hardware and software libraries that make it easy to interface with various sensors, actuators, and peripherals. Arduino boards are based on the Atmel AVR microcontroller or other microcontroller architectures, and they are programmed using the Arduino IDE (Integrated Development Environment), which simplifies the development process.

To implement DDS using Arduino, we need a microcontroller with digital-to-analog converter (DAC) capabilities to generate the analog output waveform such as Arduino Due. But we can also implement DDS without inbuilt DAC like using Arduino Nano or Arduino Uno by using pulse width modulation(PWM) and applying filter to the PWM output.

DDS with Arduino Uno

Here it is illustrated how to generate Sine Wave with Arduino by programming Arduino Uno as DDS. The output will be PWM signal which is filtered to get sine wave. To implement DDS with Arduino, we use one of the timer and configure it either Fast PWM mode or Phase correct Mode. Here we will configure Timer 2 in Phase Correct Mode. We first calculate the tuning word and setup timer2 overflow interrupt. The Timer 2 is configured to trigger the interrupt whenever the compare match is reach and overflow flag is set. Once the interrupt is triggered, the interrupt service routine(ISR) is executed. When ISR is executed, the calculated tuning word is added the the phase accumulator. Then the MSB 8 bits of the phase accumulator is extracted by bit shifting and this is used as index to select the sine wave amplitude stored in program memory. The corresponding sine wave PWM signal appears on the pin 11(PB3) of Arduino Uno. This is because the compare match output of Timer 2 compare match module A is used and for this module PB3 pin configured as the output internally in the ATmega328p microcontroller chip.

Arduino DDS code

The following is the Arduino DDS code.

 #include "avr/pgmspace.h"

// table of 256 sine values / one sine period / stored in flash memory
const unsigned char __ATTR_PROGMEM__ sine256[]  = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
  242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
  221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
  76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
  33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124

};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

byte bb;
int N = 32; //No. of bits of Phase Accumulator
double Fout;
//Fclk=16MHz/510=31372.549; formula for wave frequency in phase correct mode: Fcpu/5N N=1
const double Fclk=31376.6;      // measured

// variables used inside interrupt service declared as voilatile
volatile byte i;              // var inside interrupt
volatile byte j;             // var inside interrupt
volatile byte c4ms;              // counter incremented all 4ms
volatile unsigned long PhaseAcc;   // phase accumulator
volatile unsigned long TuningWord;  // dds tuning word M

void setup(){
  Serial.begin(115200);        // connect to the serial port
  Serial.println("Arduino DDS");

  pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output

  Setup_timer2();

  // disable interrupts to avoid timing distortion
  cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay() is now not available
  sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt

  Fout=1000.0;                    // initial output frequency 1000Hz
  TuningWord = pow(2,N)*Fout/Fclk;  // calulate DDS new tuning word 

}
void loop(){
	while(1){
     if (c4ms > 250) {                 // timer / wait fou a full second
      c4ms=0;
      Fout = analogRead(0);           // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz

      cbi(TIMSK2,TOIE2);              // disble Timer2 Interrupt
      TuningWord = pow(2,32)*Fout/Fclk;  // calulate DDS new tuning word
      sbi(TIMSK2,TOIE2);              // enable Timer2 Interrupt 

      Serial.print(Fout);
      Serial.print("  ");
      Serial.println(TuningWord);
    }

  }
 }
//******************************************************************
// timer2 setup: set prescaler=1,Phase Correct PWM,  16000000/510 = 31372.55 Hz clock
void Setup_timer2() {

// Timer2 Clock Prescaler to 1
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Timer2 PWM Mode set to Phase Correct PWM
  cbi (TCCR2A, COM2A0);  // clear Compare Match
  sbi (TCCR2A, COM2A1);

  sbi (TCCR2A, WGM20);  // Mode 1 Phase Correct PWM
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
}

//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// Fout = (M*Fclk) / (2 exp 32)
// runtime : 8 microseconds ( inclusive push and pop)
ISR(TIMER2_OVF_vect) {
  PhaseAcc = PhaseAcc + TuningWord; // soft DDS, phase accu with 32 bits
  i = PhaseAcc >> 24;     // use upper 8 bits for phaseaccu as frequency information
                         // read value fron ROM sine table and send to PWM DAC
  OCR2A=pgm_read_byte_near(sine256 + i);    

	// increment variable c4ms all 4 milliseconds
  if(j++ == 125) {  
    c4ms++;
    j=0;
   }   

}
 

This Arduino code implements a Direct Digital Synthesis (DDS) waveform generator using an Arduino board. DDS is a technique used to generate precise and stable analog waveforms using digital hardware. The code generates a sine wave output with a frequency that can be controlled using a potentiometer connected to analog input pin 0.

Here's a brief overview of the code:

  1. The sine waveform values are stored in an array called sine256, which contains 256 values that represent one period of a sine wave. These values are stored in flash memory using the __ATTR_PROGMEM__ attribute to conserve RAM space.

  2. The code defines some macros cbi and sbi which are used to clear and set bits in special function registers (SFRs) respectively. These macros are used to configure the hardware timers and pins on the Arduino board.

  3. The Setup_timer2() function sets up Timer2 of the Arduino board to generate a Phase Correct Pulse Width Modulated (PWM) signal with a frequency of approximately 31.4 kHz (calculated as 16 MHz divided by 510). This timer is used as the clock source for the DDS waveform generation.

  4. The setup() function is the setup routine that initializes the Arduino board. It configures the serial communication with a baud rate of 115200 and sets up the PWM output pin (pin 11) for DDS waveform generation.

  5. The loop() function is the main loop of the program. It reads the value of a potentiometer connected to analog input pin 0 using the analogRead() function, which gives a value ranging from 0 to 1023 representing the voltage on the analog pin. This value is used to adjust the output frequency of the DDS waveform.

  6. Inside the loop() function, a counter c4ms is used to wait for 1 second before updating the output frequency. When the counter reaches 250, the potentiometer value is read and used to calculate a new tuning word for the DDS waveform generation. The tuning word is then updated in the TuningWord variable, which determines the frequency of the output waveform.

  7. The TuningWord value is calculated using the formula TuningWord = pow(2, 32) * Fout / Fclk, where Fout is the desired output frequency in Hertz and Fclk is the clock frequency of the DDS waveform generator (31.4 kHz in this case).

  8. The DDS waveform generation is done inside an interrupt service routine (ISR) that is triggered by Timer2 overflow. The ISR updates the phase accumulator PhaseAcc with the TuningWord value, which determines the phase of the output waveform. The current value of the phase accumulator is used as an index to look up the corresponding value from the sine256 array, and this value is written to the PWM output pin (pin 11) using the analogWrite() function, which generates a Pulse Width Modulated (PWM) signal with a duty cycle proportional to the value written. This generates a sine waveform on the PWM output pin with a frequency determined by the TuningWord value.

In the above code Phase Acc is declared as volatile unsigned long. The "unsigned long" data type typically represents an unsigned integer that is at least 32 bits in size, depending on the platform and compiler used. On most modern systems, "unsigned long" is typically 32 bits or 64 bits in size, with 32 bits being the most common. The "volatile" keyword is used to indicate that the value of the variable may change unexpectedly, possibly due to external factors outside the control of the program, such as hardware events or interrupts. This informs the compiler that the variable should not be optimized or cached in registers, and that every read or write operation on the variable should directly access its memory location.

Implementation and Test

Shown below is picture of Arduino DDS build with the help of breadboard. 

Arduino DDS
And the following shows the circuit diagram for testing the Arduino DDS.

Direct Digital Synthesis (DDS) Arduino animation
 The circuit diagram shows a 10KOhm potentiometer connected to analog pin A0 which is used to control the frequency of the output sine wave signal. At the sine wave PWM output pin 11 two stage RC low pass filter are used to convert the PWM signal to sine wave. The cutoff frequency is 1.59 KHz which was computer used the low pass filter online calculator.

Next, upload the Arduino sketch to your Arduino board and observe the generated waveform on your output channel, such as an oscilloscope or an amplifier. You can adjust the potentiometer to change frequency of the sine wave.

Following shows the signal waveform at the output of the filter and the frequency spectrum. 

signal waveform


Video demonstration of Arduino DDS

The video demonstrate how the Arduino works as DDS to generate sine wave signal, and how to adjust the potentiometer to change the output frequency in real-time.


Conclusion

In this blog post, we have explored how Direct Digital Synthesis (DDS) can be implemented using an Arduino microcontroller. DDS provides a powerful and flexible method for generating precise waveforms with high resolution and frequency accuracy. With the availability of DDS libraries for Arduino, it is easy to implement DDS in DIY electronics projects for various applications, such as communications, signal processing, audio synthesis, and instrumentation. Experimenting with DDS using Arduino opens up a world of possibilities for creating custom waveforms and exploring different signal processing techniques. Happy experimenting with DDS and Arduino!

 References and Further Readings

[1] Programming Arduino Mega in CTC mode

[2] Arduino 8MHz Variable Frequency Generator

[3] LM358 based Voltage Controlled Oscillator(VCO) 

 

Post a Comment

Previous Post Next Post