DDS Tone Generator

 

In this section we are going to learn about Direct Digital Synthesis by building a tone generator.

DDS or Direct Digital Synthesis is a technique that is simple to explain, but a little bit tricky to implement. It simply means that you generate an analog signal by first generating a digital signal, then use a digital to analog converter to convert the digital signal into analog. DDS has a lot of applications in various fields from communications to remote sensing. Examples include frequency sources for Sonobuoys, or as part of implementing a waveform division multiplexing (WDM) system for high speed optical communications. You can read more about these applications with Direct Digital Synthesis (DDS), Controls Waveforms in Test, Measurement, and Communications. Our use for DDS is pretty simple compared to these sorts of applications, we are simply going to use it as a tone generator, and a use a potentiometer to vary the frequency of that tone. This works in a similar manner to the tone generator that we did in the last tutorial where you have a waveform that is stored as values hard coded into a table, that waveform is played by iterating through the table and writing those table values to the Arduino’s DAC. We are going to do some things differently this time, in particular, we will use timer interrupts. Lets assemble the circuit, and upload the sketch, then I’ll explain things in more detail. This sketch was based off of the project “RCArduino Quick And Dirty Synth for Arduino Due”, which you can find out more about here. The amplifier circuit is the same as last time, but here is a quick run through. First hookup a potentiometer to analog input 0.

 

Next add the LM386, pins 2 and 4 are connected to GND, I shared the GND connection on the pot.

pin 6 of the LM386 is connected to the 5V port on the Arduino, pin 3 on the LM386 is the amplifier input.

Next add the 3 capacitors and 1o ohm resistor

Add the 2nd potentiometer, this is the “master volume” as described in the tone generator tutorial, and connect the speaker.



You may ask if we want to build a theremin, and we just built a tone generator, why not use it? The reason is that the previous tone generator is way too slow to do what we need, we could only get to 170 Hz with the previous example, a theremin needs to be able to produce sounds in the kHz range.
There are several things we are going to do differently to enable our sketch to run fast enough to produce kHz tones while simultaneously doing other things, like accepting input, and running a LCD display. We can describe a DDS tone generator very simply as coming up with a digital value, and then writing that value to the DAC port. How often do we need to update the DAC port so that we can make a high enough quality sound? The sampling rate for CDs is 44.1 kHz ( also for MP3’s), in other words when you play a CD, the data is read and played 44,100 times per second, if that’s good enough for a CD, it should be good enough for this project. In order to match this sampling rate, we can set up a timer interrupt such that the timer interrupt fires 44,100 times a second, then in the interrupt service routine we will update the value on the DAC port. How do we control the pitch of the tone being played? This can get a little tricky, to make it easy, first consider a square wave of 10Hz, all we need to do is count the number of times our timer interrupt occurs and toggle the value written to the DAC.
To get 10Hz we want to toggle the DAC every
44100 / 10 = 4410 interrupts.
To do this we need 2 global variables interruptCtr to count the number of interrupts, and numInterrupts to know when to stop. Varying our potentiometer will vary the number of interrupts (i.e. numInterrupts) we count before toggling the output.
What if we want a sin wave, not a square wave? The same idea will work with two additions, and that is to have a precomputed array of values that represents our sin wave ( call it sinTable[] ), and sineTableCtr to know what element of the sinTable[] to use. Then update the DAC port every time numInterrupts/sinTable.size() interrupts have occurred. Here is pseudo-code to make a 10Hz sin wave ( we are not actually going to use this approach).
Interrupt_Handler()
{
   If( interruptCtr >= numInterrupts / sinTable.size() )
     {
        analogWrite(DAC0, sinTable[sinTableCtr++])
     }
  interruptCtr++ // logic to reset counters
}
This was the idea I had in my head when I came up with the idea of building a Theremin with the Due, but it will run into problems when you try to play a very high note or try to have a large sinTable[]. Look at it this way. If you have 40 samples in your sinTable[], then you have a max freq of
44100 / 40 = 1100 Hz
if instead you only use 20 samples in your sine table, then you have a max frequency
44100 / 20 = 2205 Hz.
This just might work, 20 samples will probably sound like a sin wave, and a max freq of 2kHz will probably give enough range of pitch to make some music. We might also be able to increase the sampling rate. Then I found the “RCArduino Quick And Dirty Synth for Arduino Due” project, and learned about granular synthesis. It is such a clever and cool idea that I just had to use it instead of what I just described, and it very elegantly solves the push-pull problem of how many samples are in your sinTable[] vs the highest frequency that can be played. The way it solves it is to have a large number of samples in your sin table, like 600 samples, then when playing a low frequency it will play all the samples, but at a higher frequency it will not play them all, rather it will ‘skip’ over samples as it goes, for example it might play every 10th sample, this would then be as if your sineTable[] had 60 samples. This means that higher tones might not sound as ‘pure’ as the lower tones, but we’ll be able to play much faster. There are other benefits too. This really isn’t granular synthesis since we are not synthesizing (i.e. combining) anything, but that can be a future addition to the project.
How then do we manage to iterate over our sinTable[] this manner? bear with me for a minute, let’s count to 500 three different ways, that is using three different increments, as soon as you get to 500, start over. ( say the numbers aloud!)
    1) count to 500 one number at a time 1, 2, 3, 4, 5, 6,. . . . . 500 this takes 500 seconds
    2) count to 500, ten at a time 10, 20, 30, 40, 50, 60, . . . . 500 this takes 50 seconds, repeat 10 times and the total time is 500 seconds
    3) count to 500, a hundred at a time 100, 200, 300, 400, 500 this takes 5 seconds, repeat 100 times and the total time is 500 seconds
this is analogous to how we are going to iterate through the sine table. The first way corresponds to a low frequency, you get through one “period” in 500 seconds. The second way corresponds to a higher frequency, you get through 10 “periods” in 500 seconds, the third way is higher still, you get through 100 “periods” in 500 seconds. The way we actually implement this is with two variables, an increment, and an accumulator. Every time we run the interrupt handler, which is 44,100 times per second we will add the increment amount to the accumulator. When the accumulator overflows we will have completed one full period. To vary the frequency of our output waveform, we vary the rate at which our accumulator overflows, we change the rate at which our accumulator overflows by changing the size of the increment that gets added to the accumulator every time our interrupt routine runs. That’s all we are doing in the above counting example, changing the increment size.
The current size of the accumulator is used to generate an index for our sinTable[]. To understand how the accumulator can be used to index the array, we need to cover fixed point arithmetic. There are 600 samples in the sineTable[], one approach would be to make the PhaseIncrement a decimal number, then we can play low frequencies by making our PhaseIncrement very small, like .001. The problem with this however is speed, doing computations with integers is much faster, and this is one of those somewhat rare projects where it matters. Instead of using decimal numbers (i.e. floats), we will use integers, but 600 indexes will not provide enough precision, what we need to do instead is multiply the number of sineTable[] indices by a large number, and use the result as our accumulator size. When the interrupt handler runs and PhaseIncrement is added to the accumulator, we can then divide the accumulator by a large number to scale it back down to the range o – 599. When doing this we are in effect representing a decimal with a fixed point integer representation.
The project that inspired me to take this approach has a very good explanation of fixed point math, check it out here. The Accumulator will be a very large number compared to the number of indices in our table, we scale the accumulator by multiplying / dividing it by a scaling factor, the fastest way to perform multiplication and division with integers is by doing a bit shift. Shifting left n times is the same as multiplying by 2n, and bit shifting right is the same as dividing by 2n. Upload this sketch, then we’ll go over how it works.
// DDS tone generator, based off of
//
// RCArduino Quick And Dirty Synth for Arduino Due// RCArduino Quick And Dirty Synth by RCArduino is licensed under a Creative Commons Attribution 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.
//
// These are the clock frequencies available to the timers /2,/8,/32,/128
// 84Mhz/2 = 42.000 MHz
// 84Mhz/8 = 10.500 MHz
// 84Mhz/32 = 2.625 MHz
// 84Mhz/128 = 656.250 KHz
//
// 44.1Khz = CD Sample Rate
// Lets aim for as close to the CD Sample Rate as we can get -
//
// 42Mhz/44.1Khz = 952.38
// 10.5Mhz/44.1Khz = 238.09 // best fit divide by 8 = TIMER_CLOCK2 and 238 ticks per sample
// 2.625Hmz/44.1Khz = 59.5
// 656Khz/44.1Khz = 14.88 // use for servo control 13120 / 65600 = .02 ( update servos every 20 milliseconds )

// 84Mhz/44.1Khz = 1904 instructions per tick

// include the library code:

// the phase accumulator points to the current sample in our wavetable
uint32_t ulPhaseAccumulator = 0;
// the phase increment controls the rate at which we move through the wave table
// higher values = higher frequencies
volatile uint32_t ulPhaseIncrement = 0;   // 32 bit phase increment, see below

// full waveform = 0 to SAMPLES_PER_CYCLE
// Phase Increment for 1 Hz =(SAMPLES_PER_CYCLE_FIXEDPOINT/SAMPLE_RATE) = 1Hz
// Phase Increment for frequency F = (SAMPLES_PER_CYCLE/SAMPLE_RATE)*F
#define SAMPLE_RATE 44100.0
#define SAMPLES_PER_CYCLE 600
#define SAMPLES_PER_CYCLE_FIXEDPOINT (SAMPLES_PER_CYCLE<<20)
#define TICKS_PER_CYCLE (float)((float)SAMPLES_PER_CYCLE_FIXEDPOINT/(float)SAMPLE_RATE)

// to represent 600 we need 10 bits
// Our fixed point format will be 10P22 = 32 bits

// We have 521K flash and 96K ram to play with
// Create a table to hold the phase increments we need to generate midi note frequencies at our 44.1Khz sample rate
#define MIDI_NOTES 128
uint32_t nMidiPhaseIncrement[MIDI_NOTES];

// Create a table to hold pre computed sinewave, the table has a resolution of 600 samples
#define WAVE_SAMPLES 600
// default int is 32 bit, in most cases its best to use uint32_t but for large arrays its better to use smaller
// data types if possible, here we are storing 12 bit samples in 16 bit ints
uint16_t nSineTable[WAVE_SAMPLES];

void setup()
{
  Serial.begin(9600);
  createNoteTable(SAMPLE_RATE);
  createSineTable();

  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);		 // disable write protection for pmc registers
  pmc_enable_periph_clk(ID_TC6);	 // enable peripheral clock TC6

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC2,/* channel */0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC2, 0, 238); // sets <> 44.1 Khz interrupt rate 
  TC_Start(TC2, 0);

  // enable timer interrupts on the timer
  TC2->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[0].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register

  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC6_IRQn);

  // this is a cheat - enable the DAC
  analogWrite(DAC1,0);
}

void loop()
{
  // read analog input 0 drop the range from 0-1024 to 0-127 with a right shift 3 places,
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  Serial.println(ulInput);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TC6_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 0);

  ulPhaseAccumulator += ulPhaseIncrement;   // 32 bit phase increment, see below

  // if the phase accumulator over flows - we have been through one cycle at the current pitch,
  // now we need to reset the grains ready for our next cycle
  if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
  {
   // DB 02/Jan/2012 - carry the remainder of the phase accumulator
   ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
  }

  // get the current sample  
  uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>20]; 

  // we cheated and user analogWrite to enable the dac, but here we want to be fast so
  // write directly 
  dacc_write_conversion_data(DACC_INTERFACE, ulOutput);
}

// create the notes we want to be abale to play, i.e. a list of frequencies
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0; unMidiNote < 128; unMidiNote++)
  {
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
  }
}

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2);
    Serial.println(nSineTable[nIndex]);
  }
}

If you are new to programming or new to Arduino programming, this code might be a bit more advanced than what you’re used to seeing, don’t get discouraged if you don’t understand every little detail, all the necessary code to build the project is provided, so just get enough to understand the basics. The function createSinTable() is a function that actually creates a table to represent the waveform we are going to use. Sin(x) is a function that varies from 0 to 1, but since we need to use integers we need to rescale it to the range 0 to 4095 ( b/c we have a 12 bit DAC).

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2);
    Serial.println(nSineTable[nIndex]);
  }
}

The function createNoteTable() creates an array named nMidiPhaseIncrement[]. The entries of this array are integers, each integer is a phase Increment that represents a certain frequency or note, to get the frequency, we use the midi formula

f=2^{(p-69)/12} \times 440\,\text{Hz}

but we need to convert these frequencies into phase increments. Let’s back up a second and consider how a given frequency is played. Frequency is cycles per second, which is also the number of accumulator overflows per second. f = cycles / sec = #overflows / sec Our interrupt fires 44,100 times per second, and each time adds the phase increment to the accumulator, until it overflows, thus we can write the #overflows per second as f = cycles / sec = #overflows /sec = (PhaseIncrement * 44,100) / AccumulatorSize Then we can write the PhaseIncrement PhaseIncrement = ( freq * AccumulatorSize ) / 44,100 The (AccumulatorSize / 44,100) is captured in TICKS_PER_CYCLE

#define SAMPLE_RATE 44100.0
#define SAMPLES_PER_CYCLE 600
#define SAMPLES_PER_CYCLE_FIXEDPOINT (SAMPLES_PER_CYCLE<<20)
#define TICKS_PER_CYCLE (float)((float)SAMPLES_PER_CYCLE_FIXEDPOINT/(float)SAMPLE_RATE)

Finally to populate the nMidiPhaseIncrement[] array we need to multiply our calculated frequency by TICKS_PER_CYCLE

// create the notes we want to be able to play, i.e. a list of frequencies
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0; unMidiNote < 128; unMidiNote++)
  {
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
  }
}

Here is a table of notes with corresponding frequency and Phase Increment.

Note Freq PhaseIncrement
0 8.18 116638.73
1 8.66 123574.43
2 9.18 130922.55
3 9.72 138707.61
4 10.30 146955.59
5 10.91 155694.02
6 11.56 164952.07
7 12.25 174760.63
8 12.98 185152.44
9 13.75 196162.18
10 14.57 207826.59
11 15.43 220184.60
12 16.35 233277.46
13 17.32 247148.86
14 18.35 261845.09
15 19.45 277415.21
16 20.60 293911.18
17 21.83 311388.05
18 23.12 329904.14
19 24.50 349521.26
20 25.96 370304.88
21 27.50 392324.35
22 29.14 415653.17
23 30.87 440369.20
24 32.70 466554.91
25 34.65 494297.71
26 36.71 523690.18
27 38.89 554830.42
28 41.20 587822.36
29 43.65 622776.09
30 46.25 659808.29
31 49.00 699042.53
32 51.91 740609.76
33 55.00 784648.71
34 58.27 831306.35
35 61.74 880738.40
36 65.41 933109.83
37 69.30 988595.42
38 73.42 1047380.37
39 77.78 1109660.84
40 82.41 1175644.71
41 87.31 1245552.18
42 92.50 1319616.57
43 98.00 1398085.06
44 103.83 1481219.52
45 110.00 1569297.41
46 116.54 1662612.70
47 123.47 1761476.79
48 130.81 1866219.65
49 138.59 1977190.85
50 146.83 2094760.73
51 155.56 2219321.69
52 164.81 2351289.42
53 174.61 2491104.37
54 185.00 2639233.14
55 196.00 2796170.11
56 207.65 2962439.04
57 220.00 3138594.83
58 233.08 3325225.39
59 246.94 3522953.58
60 261.63 3732439.30
61 277.18 3954381.69
62 293.66 4189521.47
63 311.13 4438643.38
64 329.63 4702578.85
65 349.23 4982208.73
66 369.99 5278466.28
67 392.00 5592340.22
68 415.30 5924878.08
69 440.00 6277189.66
70 466.16 6650450.78
71 493.88 7045907.16
72 523.25 7464878.61
73 554.37 7908763.39
74 587.33 8379042.93
75 622.25 8877286.75
76 659.26 9405157.69
77 698.46 9964417.47
78 739.99 10556932.57
79 783.99 11184680.44
80 830.61 11849756.15
81 880.00 12554379.32
82 932.33 13300901.56
83 987.77 14091814.33
84 1046.50 14929757.21
85 1108.73 15817526.77
86 1174.66 16758085.86
87 1244.51 17754573.50
88 1318.51 18810315.38
89 1396.91 19928834.94
90 1479.98 21113865.13
91 1567.98 22369360.89
92 1661.22 23699512.30
93 1760.00 25108758.64
94 1864.66 26601803.12
95 1975.53 28183628.65
96 2093.00 29859514.42
97 2217.46 31635053.55
98 2349.32 33516171.72
99 2489.02 35509147.00
100 2637.02 37620630.76
101 2793.83 39857669.88
102 2959.96 42227730.26
103 3135.96 44738721.77
104 3322.44 47399024.61
105 3520.00 50217517.28
106 3729.31 53203606.25
107 3951.07 56367257.31
108 4186.01 59719028.85
109 4434.92 63270107.09
110 4698.64 67032343.44
111 4978.03 71018294.00
112 5274.04 75241261.52
113 5587.65 79715339.76
114 5919.91 84455460.53
115 6271.93 89477443.54
116 6644.88 94798049.21
117 7040.00 100435034.56
118 7458.62 106407212.50
119 7902.13 112734514.61
120 8372.02 119438057.69
121 8869.84 126540214.19
122 9397.27 134064686.88
123 9956.06 142036588.01
124 10548.08 150482523.04
125 11175.30 159430679.51
126 11839.82 168910921.05
127 12543.85 178954887.09

The setup() function is very similar to what was done in the timer interrupt tutorial, so I won’t cover it here. In the loop section we only need to do two things. First we need to read the current input using the potentiometer, then we need to convert the potentiometer input into an integer, this integer will be the index into the PhaseIncrement[] array, which has 128 values. So what we need to do is convert a 10 bit number ( from the ADC) into a 7 bit number ( b/c we have 128 possible midi notes) we do this with a bit shift of 3 to the right.

void loop()
{
  // read analog input 0 drop the range from 0-1024 to 0-127 with a right shift 3 places,
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  Serial.println(ulInput);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];
}

TC6_handler() is the name of the function that serves as the interrupt service routine for the timer interrupt. The first thing we need to do is call TC_GetStatus() this will clear the interrupt , and allow it to fire again. Next we need to check if we’ve completed one period of our waveform in which case we will need to reset the phase accumulator.

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TC6_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 0);

  ulPhaseAccumulator += ulPhaseIncrement;   // 32 bit phase increment, see below

  // if the phase accumulator over flows - we have been through one cycle at the current pitch,
  // now we need to reset the grains ready for our next cycle
  if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
  {
   // DB 02/Jan/2012 - carry the remainder of the phase accumulator
   ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
  }

Next we’re going to set the variable that will be the waveform output, this is the actual piece of the waveform that comes out of our sinTable[]. We will bit shift the phase accumulator variable 21 times to the right to scale it back down to the 0 – 599 range, and use it to index our sineTable[].

  // get the current sample  
  uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>20];

The last thing we need to do is actually write the value to the DAC port, we want to do this quickly so we won’t use the analogWrite(), which has some extra overhead but instead we will use the function the dacc_write_conversion_data() to write to the DAC directly.

  // we cheated and user analogWrite to enable the dac, but here we want to be fast so
  // write directly 
  dacc_write_conversion_data(DACC_INTERFACE, ulOutput);
}

http://www.youtube.com/watch?v=hHqLSYU-1l4&amp;feature=youtu.be That’s probably the hardest part of this project, if you’ve made it far, its all downhill from here!

Intro->SOS->LCD Tutorial->Servo Tutorial->Timer Interrupts->Hacking Servos->Tone generator->DDS Tone Generator->Theremin 1->Theremin 2->RTTL Songs

 Posted by at 3:55 pm

  2 Responses to “DDS Tone Generator”

  1. Thanks for your great tutorial, but I’ve a question.
    Why do you shift by 21 instead of 20 ?
    uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>21];
    and Why don’t you go back to 0 in case of overflow ?

    // if the phase accumulator over flows – we have been through one cycle at the current pitch,
    // now we need to reset the grains ready for our next cycle
    if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
    {
    // DB 02/Jan/2012 – carry the remainder of the phase accumulator
    ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
    }
    In fact in doing so, you only use half of your sine period
    Thanks !

    • You are correct. This was just a typo that slipped in somehow, I had it correct on the other pages, but not this one. It is fixed now.

      Thanks!

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)


Time limit is exhausted. Please reload CAPTCHA.