Thursday, October 1, 2009

1-bit PIC sine wave generator

Above is the digitally synthesized sine wave.

This is one of those “because I can” projects. Can I generate a sine wave using a simple PIC employing no PWM and no DAC, no software timers or interrupts, just turning an output pin on and off? We all know it can be done because our music players boast of the 1-bit DACs inside.

Simplistically, we know if our output pin puts out 5 volts when ON and 0 volts when OFF, a 50% duty cycle should give us an average output voltage of 2.5. But the actual method is a little cleverer than that, which is what makes it fun. I picked it up from Glen Leinweber, VE3DNL, a great ham tinkerer in the realms of RF and programming.
The sequence of ones and zeroes is first calculated “off line” using a simple program in a high level language, QBASIC in my case. Variables track the integral under the sine (actually cosine in my case) curve and the integrated value of all the bits generated previously. If the value of the integrated bits is less than that of the cosine curve, the next bit is made a 1 and if it is more, the next bit is a 0. In this way, the average value of the bit stream is tracking the value of the cosine wave as closely as possible. Glen says this technique is an example of something called “sigma-delta coding”.
There are some practical considerations related to our MCU chip, a 12F629 in my case. My processor is running at 1 MHz and it can toggle my output pin in one execution cycle or 1 microsecond. Naturally, I want my bits as narrow as possible for fine resolution. The limitation is on the amount of program memory I have, which is 1024 bytes for the 12F629. The program will simply output all the bits for one cycle and then repeat. So the period of the cycle determines the program’s length. Say I wanted a 1,000 Hz output. The period is 1 millisecond, so I’d need roughly 1,000 instructions plus a little overhead. You can see that lower pitched tones require more memory as their period is longer.
I wanted to do an “A” musical note at 440 Hz but it wouldn’t fit so I wound up at “D” (587 Hz). Even this wouldn’t fit without a little trickery since its period is 1,703 microseconds. Turns out that there are long streams of 1s or 0s where the sine wave is at its positive and negative peaks. So I can save memory by calling delay routines repetitively. Actually, one routine with multiple entry points is even better.
Want to write a ~1000 line program by staring at a printout of 1,703 ones and zeros and writing the code to produce them? Me neither. So my QBASIC program also gets to write the bulk of the source code. After the bit sequence is stored in an array, the program then examines it for repeats and writes the code to implement the bit sequence efficiently.
I also wanted a way to turn the output on or off in response to user input (telegraph key?) on another pin. To do this, I just wrote the code to sense the pin and if its state tells me to turn the sound off, I configure the output pin to be an input instead of an output. Each time through the code, it reads the control pin and configures the output pin accordingly. The sound generating code continues to run as always. I had to count the number of cycles this code took and then insert it in place of an equal number of “time wasting” cycles in an area where the output pin is not changing.
OK, say I get it running. How do I look at the output and verify that I’ve achieved my goal? If I just look at the output pin with my o’scope, I’ll see a pulse train of varying density. So just as I did in software, I have to integrate the pulse train in hardware by connecting a resistor and capacitor to the pin. Here’s the tricky part: the R/C network is a low pass filter, and with enough filtering even a square wave can be turned into a perfect sine. So to make sure I’m not “cheating”, I make sure the corner frequency of my filter is much higher than my fundamental frequency. The proof is in the pictures, with a shot of several cycles showing a nice sine wave, but a close-in zoom of part of the cycles show the jaggies caused by the individual bit transitions.

In this "magnified" view, you can see the jaggies that betray the sine wave's digital origin

A fun variation might be to do a more complex waveform – say the sum of sine waves of 1,000 Hz and 1,500 Hz. The period would be that of the difference frequency, 500 Hz and the resultant would be something you couldn’t fake with low pass filtering. Combining two musically related notes would be even better – easier on the ears.
One more relevant bit of info. Most PICs these days include high speed and fairly accurate internal oscillators you can use and save the price of a crystal and two I/O pins. The thing is pretty accurate, but has frequency trimming registers if you want to get closer. My ‘D’ note started off 8 Hz low with the factory value and I was able to trim it to within 1 Hz.
What’s it good for? Don’t you hate that question? But I guess it’s a stable audio sine wave source for the price ($2 or so?) of a simple PIC chip. Not too flexible though – to change the frequency you have to re-run your QBASIC program to generate revised source code and then re-program the chip.
Update: I decided I should make my PIC program and my QBASIC program accessible, which was easier to do on my web page than in this blog. So go to my site using this link and near (or at) the bottom of the table of stuff is the link to my 1-bit sine page. Scroll down through all the text you've already seen and you see the links to the two files.