Sometime ago, my friends at Embedded Dreams asked me if I was interested in doing the firmware for one of their kits – the “Luny Sound Delirium“, which is a cool digital software synthesizer with 2 oscillators, a lot of knobs and endless input possibilities. Since this seemed a lot of fun, and I never did anything audio related before at a microcontroller level, I said yes and started working on it.
That’s when I discovered the Direct Digital Synthesis (DDS) algorithm and how to use it to create a digital signal generator.
This is an example of it’s usage, to create a sine wave up to 15KHz. It uses an Arduino (and its PWM), a voltage divider, a RC filter (which removes the strong noise components at the PWM frequency and at odd harmonics of that frequency), a coupling capacitor (remove the DC component) and a headphone jack (to plug it in the microphone of the laptop).

Oscillator schematic
The following sketch generates a 440Hz frequency sinewave (audio sample), but you can try different frequencies in the sketch or better yet, add a frequency control knob using one of the analog inputs of the Arduino so you can control it while is playing.
/*
* DDS - Direct digital synthesis example for sinewave generation.
*
* The implementation of the DDS relies upon integer
* arithmetic. The size of accumulator is N-bits.
* Assuming that the period of the output signal is
* 2*pi rad, the maximum phase is represented by 2^N.
* During one sample period the phase increases by the
* phase increment which lead us to:
* - outputFrequency(phaseIncr) = (samplingFrequency / 2^N) * phaseIncr (1)
*
* The later can be rewritten:
* - phaseIncr = outputFrequency * resolution
*
* where resolution is:
* - resolution = 2^N / samplingFrequency (2)
*
* Also:
* - N=log2(samplingFrequency / stepFrequency)+0.5 (3)
* - maximumOutputFrequency = samplingFrequency / 2
*
* For a better analog reconstruction of the signal we use:
* - maximumOutputFrequency=(samplingFrequency / 4) (4)
*
* According to (4) and for a maximum output frequency of
* 15KHz, a 62.5KHz sampling frequency was chosen.
* with (3) we can derive the accumulator as 16 bits wide
* (for sake of commodity), and according to (2) we'll
* have a resolution of 1.0486. Since we're using fixed
* point arithmetic, we'll scale the resolution by a
* factor of 2^16 (to maintain some accuracy):
* - N=16, resolution=(2^16 * maximumOutputFrequency) / (samplingFrequency)
*
* The minimum frequency generated is for phaseIncr=1 :
* - minimumFrequency = samplingFrequency/2^N
*/
#include <avr/pgmspace.h>
#include <avr/sleep.h>
// Sinewave lookup table for waveform generation
static const uint8_t sineTable[] PROGMEM =
{
0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,
0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,
0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,
0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,
0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,
0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,
0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,
0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc,
0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,
0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3,
0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,
0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83,
0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,
0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51,
0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,
0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27,
0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,
0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a,
0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,
0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,
0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08,
0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,
0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23,
0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,
0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c,
0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,
0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c
};
// PWM output (OCR1A)
int pwmPin = 9;
// 16 bit accumulator
uint16_t phaseAccumulator = 0;
// 16 bit delta
uint16_t phaseIncrement = 0;
// DDS resolution
const uint32_t resolution = 68719;
// wavetable lookup index(upper 8 bits of the accumulator)
uint8_t index = 0;
// Desired output frequency (let's set it to 440Hz)
uint16_t frequency = 440;
// TIMER1 will overflow at a 62.5KHz(Sampling frequency).
// Updates the OCR1A value and the accumulator.
// Computes the next sample to be sent to the PWM.
ISR(TIMER1_OVF_vect)
{
static uint8_t osc = 0;
// Send oscillator output to PWM
OCR1A = osc;
// Update accumulator
phaseAccumulator += phaseIncrement;
index = phaseAccumulator >> 8;
// Read oscillator value for next interrupt
osc = pgm_read_byte( &sineTable[index] );
}
// Configures TIMER1 to fast PWM non inverted mode.
// Prescaler set to 1, which means that timer overflows
// every 16MHz/256 = 62.5KHz
void initPWM(void)
{
// Set PORTB1 pin as output
pinMode(pwmPin, OUTPUT);
// 8-bit Fast PWM - non inverted PWM
TCCR1A= _BV(COM1A1) | _BV(WGM10);
// Start timer without prescaler
TCCR1B = _BV(CS10) | _BV(WGM12);
// Enable overflow interrupt for OCR1A
TIMSK1 = _BV(TOIE1);
}
// Translates the desired output frequency to a phase
// increment to be used with the phase accumulator.
// The 16 bit shift is required to remove the 2^16
// scale factor of the resolution.
void setFrequency( uint32_t frequency )
{
uint64_t phaseIncr64 = resolution * frequency;
phaseIncrement = phaseIncr64 >> 16;
}
void setup(void)
{
// Initialise fast PWM
initPWM();
// Enable sleep mode
set_sleep_mode(SLEEP_MODE_IDLE);
// Set phase increment according to
// desired output frequency
setFrequency( frequency );
// Enable global interrupts
sei();
}
void loop()
{
// Go to sleep
sleep_mode();
}
You can also add more lookup tables for different waveforms like triangle, toothsaw, ECG, square, or even some noise. Or use more than one oscillator and mix them.
For details on the algorithm check the Wikipedia entry on DDS and Direct Digital Synthesis: A Tool for Periodic Wave Generation (Part 1) .
11 Comments
1 Thomas wrote:
Could you post values for the components that you list in the schematic
? Thanks!
2 Noto Yota wrote:
Hi, i’ve working with this piece of code to create a DDS oscillator.
I’m relatively new to the AVR timers.
Could you explain a little bit more how this works?
Is it for example possible to have this working with a timer with prescale?
I have one oscillator running on a duemilanove and can do nothing more. If I try to add some more, for example volume, it all breaks due to timing errors.
I found that using a prescale lowers the load on the processor, but i cannot see in this code how to keep the waveforms correct.
Nice work.
Regards,
Patrick
3 mouro wrote:
Hi Patrick,
i’m using the PWM(+Low Band Fitler) to generate the analog output instead of using an 8bit DAC. Timer1 is configured to 8-bit fast PWM, no prescaler – clock io = cpu clock, so it’s running at 16MHz and overflows every 256 clock cycles (62.5kHz). And this is the frequency used to update the output signal.
You can decrease the sampling frequency to gain more processing time. Keep in mind though that the sampling frequency should be at least the double of the maximum frequency you want to generate (Nyquist theorem).
To decrease the sampling frequency, you can either change the PWM mode to 9 or 10bit PWM mode, thus gaining more time for background processing or you can select a different clock source to TIMER1 instead of using no prescaler. Each possibility has drawbacks.
If I do recall properly, the Atmega168 TIMER1 has 4 clock sources with prescaler: clkio/8, clio/64, clkio/256, clkio/1024. If you use clkio/8, TIMER1 will overflow every 7.815kHz (16000000/8*256) which allow you to reconstruct signals up to 3.9kHz which isn’t that good for audio applications.
With a 10 bit fast PWM no prescaling, the sampling frequency would be (16000000/1024*1) 15.625kHz thus allowing analog reconstruction of signals up to 7.8125 kHz. However this would require the need of 10 bit lookup tables instead of 8bit tables thus increasing the memory footprint.
“If I try to add some more, for example volume, it all breaks due to timing errors.”
You can add a potentiometer at the output stage to control the volume, no need for CPU power for that.
If you need another oscillator, you can use OCR1B to generate a waveform output on OC1B pin, using the same timer. This is a good option for example, if you want stereo output.
“I found that using a prescale lowers the load on the processor, but i cannot see in this code how to keep the waveforms correct.” – I forgot to answer this one.
If you use a prescaler you’re changing the sampling frequency. For the sketch to continue to work properly you need to recompute the dds ‘resolution’ according to the sampling frequency.
resolution = 2^16 * 2^16/ (samplingFrequency)
cheers
Hope this helps,
mouro
4 Alessandro wrote:
Hi Mouro,
first i want to thank you for sharing the article and the sketch!
When i started looking for a way to produce a sine wave out of an arduino i found lots of posts and discussions, but yours was the most precise and easy to understand, at least for a DDS newbie as i am!
By the way i started tweaking here and there, and produced an 8-note keyboard with an octave switch succesfully.
Now i just wanted to add a waveform switch: i added a square wave wavetable to the sketch and have it working by changing the value in
osc = pgm_read_byte( &squareTable[index] );
but still i can’t find how to make it dynamic, i mean, having a trimpot or something like that to make me switch between different wavetables…
i tried different ways and googled the ISR(TIMER1_OVF_vect) part of the sketch to try and understand that part better, but i’m stuck, so that’s the reason i’m writing here.
Maybe you can have any suggestion or explain how this part of the code, where the sineTable is used, works?
Thanks a lot in advance and keep up the good work!
alessandro
5 mouro wrote:
Hi Alessando,
For defining which wavetable you want to use, you can either use a
digital pin (for 2 different waveforms) , ‘n’ digital pins (for 2^n
different waveforms) or a analog signal with different waveforms
mapped onto it.
If you only use two wavetables, you can use one digital pin for this purpose:
(…)
// … main loop
if (digitalRead(waveSelectorPin) == HIGH)
waveSelector = SINE_WAVE;
else
waveSelector = SQUARE_WAVE;
(…)
If using more than 2 wavetables and you need the digital pins for
other stuff, just use an analog pin.
(…)
// main loop
int waveSelector_analog = analogRead(waveSelectorPin);
if (waveSelector_analog < ANALOG_TOP_SINE)
waveSelector = SINE_WAVE;
else if (waveSelector_analog < ANALOG_TOP_SQUARE)
waveSelector = SQUARE_WAVE;
....
else (waveSelector_analog < ANALOG_TOP_TOOTHSAW)
waveSelector = TOOTHSAW_WAVE;
(...)
The values ANALOG_TOP_TOOTHSAW, ANALOG_TOP_SQUARE, ANALOG_TOP_SINE,
etc , depend on the number of wavetables you use, and how you map them
in the analog signal. for instance, using 3 tables you'll have:
ANALOG_TOP_SINE = 1023/3
ANALOG_TOP_SQUARE = 1023/2
ANALOG_TOP_SINE = 1023/1
so values between [0, 1023/3[ are for sinewave selection, [1023/3,
1023/2[ squarewave selection, and [1023/2, 1023] for toothsaw wave.
(please note that this is one way of doing this.)
Now you should use the 'waveSelector' value on the ISR to select the table:
ISR(TIMER1_OVF_vect)
{
static uint8_t osc = 0;
// Send oscillator output to PWM
OCR1A = osc;
// Update accumulator
phaseAccumulator += phaseIncrement;
index = phaseAccumulator >> 8;
// Read oscillator value for next interrupt
switch(waveSelector)
{
case SQUARE_WAVE:
osc = pgm_read_byte( &squareTable[index] );
break;
case SINE_WAVE:
osc = pgm_read_byte( &squareTable[index] );
break;
….
case TOOTHSAW_WAVE:
osc = pgm_read_byte( &toothsawTable[index] );
break;
}
}
hope this hels,
regards
6 J M Wilkes wrote:
Could you email the values of the components in the oscillator schematic that generates a 440Hzfrequency sinewave?
Thanks
7 mouro wrote:
The components I used were spare ones, I don’t remember the values by heart.
1) You can replace R1 and R2 with a linear POT to adjust the volume (for instance 10Kohm); Keep in mind that the line input should be kept in the 1V Peak to Peak range.
2) The low pass filter components R3 and C1 could be 1Kohm and .01uF for a cuttof frequency of ~16KHz.
3) The AC coupling capacitor you actually don’t need it since the computer line input already has AC coupling builtin.
I haven’t tried these values, but I hope this helps.
8 wings wrote:
This sketch would not compile without errors (Arduino Uno).
Removed & from line 107 and program compiles and runs ok.
Regards,
wings
9 wings wrote:
Odd, my comment was posted incorrectly. Should read:
Removed “&” from line 107…
wings
10 wings wrote:
Looks like the comment cannot be posted correctly, so here goes again a little differently: On line 107, remove the ampersand, the text “amp”, and the semicolon in order to compile on an Arduino Uno.
wings
11 mouro wrote:
hey wings, thanks for bringing that up. When I first posted it was correct but I guess the code plugin changed it.
cheers
One Trackback