7 years, 2 months ago.

Best way to generate a pure sine wave

I'm trying to generate a pure sine wave at various frequencies between 100Hz and 500Hz for an audio project, but I'm having a hard time coming up with a working solution. My latest approach is using Direct Digital Synthesis techniques with an MCP4725 DAC, but due to performance constraints I can only pull off a sampling rate of 1kHz. Normally this would be fine since my highest target frequency is at or below the Nyquist frequency of 500Hz, but in practice I get serious aliasing throughout most of that frequency range. Attempts to remove the aliasing were unsuccessful, so I did some research and came across Fundamentals of Direct Digital Synthesis (DDS) by Analog Devices, which describes the aliasing I'm seeing as unavoidable and unfilterable:

Quote:

...the higher order harmonics of the fundamental output frequency in a DDS system will fold back into the baseband because of aliasing. These harmonics cannot be removed by the antialiasing filter.

Therefore, my only solution is to crank up the sampling rate to at least 2kHz, but then my microcontroller doesn't have enough time to run everything else and the whole system falls apart. So what's my best alternative solution? Analog Devices has some dedicated DDS chips such as the AD9837 that might work, but they're expensive and I'll need two of them to pull off stereo. PWM is not an option because as I've recently discovered, the LPC11UXX series has really crappy PWM hardware that's ill-suited to anything audio related...

I was having similar problem with aliasing and ditched DDS/NCO and just went with a square wave oscillator, running in a while(1) loop in the main function with the rest of the program being run via interrupts.A square wave is good enough for me. but for you wanting a sine wave, an option would be to put an 8 pole switched filter capacitor on the output of the square wave. A chip like the MAX7400 series. Can be driven by a 100 'times' oscillator eg 100Hz * 100 = 10,000Hz to 500Hz * 100 = 50,000Hz. There other chips/makes. Perhaps the 'crappy' (your words) PWM in the LPC11UXX can manage this. Although this reply is late for you, it might help someone else. This is my basic program, interrupts need to be added, and volume control via PWMing the DigitalOut's

// For LPC1768
// Copyright free
#include "mbed.h"

DigitalOut piezo1( p5 );
DigitalOut piezo2( p6 ); // piezo sounder connected between pins 5 and 6

void makeSound( int );

int main() {
    int freq;
    while(1) {
        for( freq = 100; freq < 4000; freq +=1 ) {
            makeSound( freq  );
        }
    }
}

void makeSound( int frequency ) {
    static int timeus, timeon, timeoff;
    timeus = 1000000 / frequency;
    timeon = timeus >> 1;
   // reduce/scale timeon for volume control here
    timeoff = timeus - timeon;
    piezo1 = 1; piezo2 = 0;
    wait_us( timeon );
    piezo1 = 0; piezo2 = 1;
    wait_us( timeoff );
}
posted by Rich Davies 28 Sep 2016

4 Answers

7 years, 2 months ago.

Depending on your microcontroller, you may be able to use timer-triggered DMA to your DAC (either on-chip or external via GPIO lines). This can give you very low CPU loading and allow for a high enough sample rate that your aliasing will not be at audible frequencies. Some DMA controllers will automatically wrap around to the start of your sample buffer when writing to the DAC.

Since you want the aliasing to be outside the audible range, if you sample at (say) 32kHz it'd require a sample buffer of 32kHz/100Hz = 320 samples.

The FRDM-KL25Z board, for instance, has an internal DAC that knows how to read from a data buffer and automatically wrap around to the start when it reaches the end of the buffer. It can be triggered by PIT (programmable interrupt timer) 0.

Using this feature on that board in MBED, you have to be aware that the PIT0 is being used by the MBED HAL as a prescaler for the microsecond ticker/timer routines. It is set to be running at 1MHz when a timer or ticker is created. As a result, if you take over PIT0 for DAC operations you will have to re-initialize it for use with the microsecond ticker/timer library routines. This re-initialization happens in the constructors of Ticker and Timer objects.

Accepted Answer

I considered this, but unfortunately my microcontroller (currently an LPC11U35, ultimately an LPC11U68) doesn't have DMA for the I2C bus or a built-in DAC. I could jump to an LPC1549, but I need two DACs and it only has one. The SPI bus would work better, but it's a bit crowded already since it's driving an OLED display and a Bluetooth radio. At this point I'm seriously considering offloading the audio to a second micro (like an LPC812) and controlling it using the UART.

posted by Neil Thiessen 08 May 2014
7 years, 2 months ago.

If you really need a pure sine than maybe an analogue voltage controlled oscillator is the best solution. An oldie like the XR2206 ($5) controlled by a DAC should work. You can use the square wave output to check the frequency. A faster solution than DDS+DAC may be pre-computing the sinewave, storing it in a table and use a 2kHz ticker to send it to a fast SPI DAC.

Doesn't need to be pure pure, just pure enough to sound decent in a pair of headphones. SPI is not an option in my case because I have an OLED display and Bluetooth radio competing for it already.

posted by Neil Thiessen 08 May 2014
7 years, 2 months ago.

Your quote is regarding folding of harmonics (2nd order, third order, etc), and not the alias of the fundamental tone you are making, that one can always be filtered if you are within Nyquist, although how realistic that is depends on the sample rate and frequency you are trying to make, which isn't very realistic in this case.

But the question is now mainly why your sample rate is limitted currently, what is limitting your performance? Without code that is hard to say.

The sample rate is limited because the project is fairly large and has a lot going on in it already. It just so happens that I have several other things running in a 1kHz RtosTimer, so it made sense to try to slot audio in there as well. If I cheat and run the I2C bus at 1MHz (Fm+), the I2C transaction alone takes 38.5μs * 2.

posted by Neil Thiessen 08 May 2014