I2S for PIC32MX/MZ – Sine wave generation using DDS

Skill level: Advanced, with C and 32-bit experience.

This tutorial requires an oscilloscope to confirm the resulting output!

In the previous tutorial, you have a general idea of interfacing your PIC32MX/MZ microcontroller to the Audio DAC.

This time, you learn to write a short, simple method to generate a sine wave using Digital Direct Synthesis (DDS) method, sample by sample to the DAC, and eventually output to the speaker. If you have an oscilloscope, you can probe the DAC outputs and view the signal on the screen too.

Digital Direct Synthesis (DDS)

Sine table:

If you are old enough to remember about the mathematical tables book (Malaysian language: buku sifir) or the slide rule, you notice that if you want the value of sin(θ) , you will have to look up the value in that page, and do some minor calculations.

Or, if you are not patient enough, there’s a formula for calculating angles:

(Source: http://people.math.sc.edu/girardi/m142/handouts/10sTaylorPolySeries.pdf)

Looks simple and straightforward, but why are we not doing it? The main reason is it takes way too much time to calculate the value. You have to add, subtract, multiply and divide a number of times before you get a sample. For more accuracy, the formula must be expanded, and more calculations are involved. It is not practical, especially on a microcontroller without floating point unit (PIC32MX and all the dsPIC30/33F). Plus, we need to generate these values within the common audio sample time too or else it will sound ‘slow’ or even bad.

So, coming back to the mathematical tables, if we want to get a certain value of sin(θ), we look it up in the page and calculate a bit of stuff. That is much quick than the formula.

But you will think: “How do I put the whole list of sin(θ) values inside the microcontroller? It looks endless!”

You don’t put all of the pre-calculated sin(θ) values inside the microcontroller’s flash. Since the flash is limited, let’s reserve, say, 4,096 samples for the values between sin(0°) to sin(360°).  We can write:

const short int waveTable[4096] = {...}

(The samples must be multiples of 2, for example 64, 256, etc.)

The resultant values must be 16-bit short. Using a spreadsheet program, you have to convert and round the values to become integers. You can download the sample spreadsheet and the header file for the sine table (called waveTable).

Accumulator and tuning words:

It’s great we have the sine table now. If we want to generate a sine wave, we can just keep incrementing the index by one every time there is an interrupt:

  • Sample 0 = waveTable[0]
  • Sample 1 = waveTable[1]
  • …more samples later…
  • Sample 4094 = waveTable[4094]
  • Sample 4095 = waveTable[4095]
  • Sample 0 = waveTable[0] (one period of sine wave is finished, and goes back to index 0)

That looks simple, but what if we want to have a different frequency for the sine wave? Should you write more code to make the counter wrap back to 0 after counting to 4095? Even if you can write the code, it may look clumsy and slow.

Well, you will have nothing to worry about, because there is another simpler method to do this without manually wrapping the counter or without worrying about how you are going to change the frequency.

Let’s start with a 32-bit unsigned variable. We call it accumulator.

unsigned int accumulator = 0x00000000;

Then, we declare another 32-bit unsigned variable. We call it tuning word. For this example,  a value of 0x123456 is inside that variable.

unsigned int tuningWord = 0x123456;

Try adding the value of tuningWord into the accumulator:

accumulator += tuningWord;

You “pinch off” (or in a proper word, truncate) the most significant 12-bits (because 0~4095 is a 12-bit value) from the accumulator and then this becomes the index of the sine table (waveTable).

Since it’s a 32-bit microcontroller which features fast bit-shifting capabilities (barrell shifter), might as well as just shift 20 bits to the left, leaving only the 12 bits.

output = waveTable[accumulator >> 20];

What would be the output? It will still be 0. Keep adding the tuningWord to the accumulator each sample, and you will see a pattern that the index number slowly increases, and then wraps back, and the process repeats.

If the value of the tuning word is small, the accumulator grows slowly, and the index number increases in a slow manner too. The resultant output will be a sampled sine wave which has a longer period.

Meanwhile, if the value of the tuning word is small, the accumulator grows faster, and the index number increases in a fast manner. The resultant output will be a sampled sine wave which has a shorter period.

Imagine the accumulator is a thin vessel with the value of the tuning word is the amount of liquid poured inside. The amount of liquid which is in the tuning word area is taken out. This liquid left is the number of the index in the wave table. Once it is looked up, the output is sent to the DAC, and it is presented in a sample (the dot) in the following graph.

For another sample, the tuning word value is added into the vessel again. The same process of taking out the remaining amount of liquid in the tuning word area to the wave table still applies. You are seeing the other dot in the graph.

Continuously doing all that, you will see more samples being output, which is eventually being seen as the sine wave. The greyed sine wave line in the graph is when the samples are being filtered by the audio DAC.

Eventually, the ‘vessel’, or the accumulator  is almost full, and one more amount of tuning word causes the accumulator to overflow, and wrap up the whole thing. It forms a whole period of sine wave again:

When this is done in a continuous fashion, a running sine wave is being output.

Note: Drawings not to scale!

If you want higher frequency of the sine wave, increase your tuning word, and if you want to have lower frequency, decrease your tuning word. It is because the amount of tuning word determines the size of the jumps in the wavetable.

The further the jumps in the wavetable, the shorter the period of the resulting sine wave, and the shorter the jumps in the wavetable, the longer the period of the resulting sine wave.

Here is a basic formula to calculate the M, or the tuning word:

  • fis the desired output frequency.
  • M is the tuning word.
  • fis the sampling frequency. In the tutorial it is 32,000 Hz as an example.
  • n is the number of bits in the accumulator. Here we specify 32 because it’s an unsigned int data type.

To summarize, here is the example DDS code inside the interrupt service routine:

void __ISR(_SPI1_VECTOR, ipl7AUTO) _IntHandlerDrvI2SInstance0(void)
{
if(channel) {
accumulator += tuningWord1;
SPI1BUF = wavetable[accumulator >> 20];
}
else {
SPI1BUF = 0x0000;
}
channel ^= 0x01;
IFS1bits.SPI1TXIF = 0;
}

Take note that the sine wave appears on the first channel. The other channel is muted, as it has no value loaded in the SPI1BUF.

Try some different values of the tuning word and probe the outputs with the oscilloscope. Connect it to a speaker with an amplifier after if you see a clean sine wave in the oscilloscope screen:

If you have a music instrument like a guitar or a piano, you can try hitting a note and compare the sound!

So you have just generated a sine wave using a 32-bit microcontroller with an audio DAC! This does not end here – you will know how to do this in a more efficient manner in the next tutorial!

Exercise 1: Try getting the value of M for 440Hz frequency with sampling rate of 32kHz, and try comparing this output to a note which is played on a music instrument.

Exercise 2: Add another sine wave with a different frequency on the other channel! Hint: Declare a new accumulator and another tuning word variables!

Exercise 3: Instead of a sine wave, the wave table is a square wave. Hint: 4096 samples, one half of the total 4096 is maximum value of the 16-bit signed value (32767), and another half of it is the ____________ value (_________). Use a spreadsheet software!

References:

1.) DDS Tutorial by Analog Devices: www.analog.com/media/en/training-seminars/tutorials/MT-085.pdf

2.) Schaum’s Mathematical Tables:     http://www.csd.uoc.gr/~hy215/tutorials/Formulas_and_Tables_Schaums.pdf

, , , ,

Related Post

I2S for PIC32MX/MZ – Application: Music Box

I2S for PIC32MX/MZ – Direct Memory Access (DMA)

I2S for PIC32MX/MZ – Introduction

Driving RainbowBits with Cytron’s sk1632!

Leave a Reply

Your email address will not be published. Required fields are marked *