Two frequencies, one output

Hey folks,

Any guidance on the following would be very helpful.

I want to drive a speaker with two frequencies, one at around 1.1kHz and another at 1.3kHz. They need to be 'tuned' in the second and third decimal place, so it needs to be an adjustable range of 1.1kHz +/- .05 and such.

I've followed this guide: http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/, and have successfully got a single frequency sine wave outputting, and fairly cleanly too!

In my mind, it would make sense that adding a second wave and outputting both on one PWM pin should be fairly easy. However, even after some reading on Timer and interrupts, I'm not sure really how to go about this.

The final goal of this project is to control 4 speakers, each on one PWM pin, where the the frequencies is the same in each, but the phases can be modified.

To get two tones you would have two phase accumulators (say phaccu1 and phaccu2) and two dds tuning words (say tword_m1 and tword_m2). In the timer 2 interrupt service routine you then look up the table value for the upper 8 bits of each of the two phase accumulators as is done for the single tone. Divide each of these two values by 2 and add them together* to get the next output value to write to OCR2A.

    • more efficient would be to halve the value of each entry in the table sine256 . . .

The big problem is that the PWM output gives you 8 bits of resolution, that is a value between 0 and 255. For four signals you will only get 4 bits of resolution on the wave which is just numbers 0 to 15.
Pluse the fact that you have four additions and a divide by four will reduce the frequency of what you can generate.

If I'm reading this correctly what you are trying to accomplish is a 1.1khz and 1.3khz output simultaneously from one PWM output pin of an Arduino. I would say that this will be pretty much impossible to do. The PWM outputs are really only capable of generating one frequency at a time.

Combined frequencies become very complex and the harmonics created by square waves are radically different than that of sine waves. An absolutely pure sine wave has no harmonics where as all square waves have many harmonics. If I remember some of my old theory correctly, taking say a 1khz sine wave and combining it with its 3rd, 5th, 7th, 9th and so on odd harmonics you will get closer and closer to a square wave. Of course there's more to it than that including each next harmonic requiring a lower voltage than the previous. And I digress.

Anyway to get a proper combination of sine waves you would need to generate each individually and sum them after they are converted to sine waves with a mixer. A mixer can be as simple as using resisters or preferably buffer each output with an Op-Amp before summing them. The Op-Amps will stop leakage from output to output feeding back into the high impedance integrators giving you much cleaner sine waves.

As to creating four simultaneous frequencies with different phases that I can see as being doable but I would think the code may be quite complex

I would say that this will be pretty much impossible to do. The PWM outputs are really only capable of generating one frequency at a time.

Not true. I second the suggestion in reply #1. Timer 1 is capable of 16 bits resolution, which is more than adequate for two decent sine waves.

You could probably stick with the 8 bit table that you have and just add the two components together, to load into the 16 bit compare register. Lots of useful info here: http://web.csulb.edu/~hill/ee470/Lab%202d%20-%20Sine_Wave_Generator.pdf

jremington:
Not true. I second the suggestion in reply #1. Timer 1 is capable of 16 bits resolution, which is more than adequate for two decent sine waves.

Yes you are absolutely correct and I'm very wrong.

I do however stick to using separate outputs for the different frequencies because of the radical differences in the harmonics that will be created. Summing two square waves will produce far more harmonics than summing sine waves and will sound completely different.

His final goal seams to be to output the same frequency sine waves four times at different phases to four speakers. I don't believe that can be done with just one or even two PWM outputs.

Just a note you can get away with just one speaker and use a summing mixer as stated before with Op-Amps.

@Techno -- you should read up on DDS with PWM output. It is a form of digital to analog conversion and does not involve summing square waves. Also check out class D audio amplifiers, which use PWM for extremely efficient amplification.

Grumpy_Mike:
The big problem is that the PWM output gives you 8 bits of resolution, that is a value between 0 and 255. For four signals you will only get 4 bits of resolution on the wave which is just numbers 0 to 15.

This isn't quite right. For N tones on one pin you give up log2(N) bits resolution, so you get 7 bits of resolution with two tones and you get 6 bits of resolution with four tones.

technogeekca:
As to creating four simultaneous frequencies with different phases that I can see as being doable but I would think the code may be quite complex.

To get a frequency with a phase offset you index the sine256 lookup table with the phase accumulator plus the desired phase offset (sum modulo 256 to get circular addressing). The resultant value from the lookup is written to the OCRnA register for the associated PWM pin. I believe the atmega328p has six such pins so up to six phase offset signals can be generated with a pretty straightforward extension of the example code.

Thank you all for your input! I think I now understand how to implement this conceptually, I'll try at writing code for it and post here when I get that.

Probably, you will find "AVR314 application note" from atmel interesting to read.

I got it to work! Looking back, it's much simpler than I first thought, however at the beginning I didn't understand how the timer interrupts worked at all.

I now can play two different frequencies on one speaker, however I'm getting a weird sort of artifact noise, almost like a low scratchy frequency present. Seems like it might have to do with accounting for the new time the function takes, but I'm unsure.

/*
*
* DDS Sine Generator mit ATMEGS 168
* Timer2 generates the  31250 KHz Clock Interrupt
*
* KHM 2009 /  Martin Nawrath
* Kunsthochschule fuer Medien Koeln
* Academy of Media Arts Cologne

*/

#include "avr/pgmspace.h"

// table of 256 sine values / one sine period / stored in flash memory
PROGMEM const unsigned char sine256[]  = {
 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
 242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
 221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
 76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
 33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124
};

PROGMEM const unsigned char sine256_2[]  = {
 63, 65, 66, 68, 69, 71, 73, 74, 76, 77, 79, 80, 82, 83, 85, 86, 88, 89, 90, 92, 93, 95, 96, 97, 99, 100, 101, 102, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
 118, 119, 119, 120, 121, 121, 122, 122, 123, 124, 124, 124, 125, 125, 126, 126, 126, 126, 126, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 126, 126, 125, 125, 124, 124, 124, 123,
 122, 122, 121, 121, 120, 119, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 102, 101, 100, 99, 97, 96, 95, 93, 92, 90, 89, 88, 86, 85, 83, 82, 80, 79,
 77, 76, 74, 73, 71, 69, 68, 66, 65, 63, 62, 60, 59, 57, 55, 54, 52, 51, 49, 48, 46, 45, 43, 42, 40, 39, 38, 36, 35, 33, 32, 31, 29, 28, 27, 25, 24, 23, 22, 21, 19, 18, 17, 16, 15, 14, 13,
 12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 
 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31, 32, 33, 35, 36, 38, 39, 40, 42, 43, 45, 46, 48, 49, 51, 52, 54, 55, 57, 59, 60, 62
};

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

int ledPin = 13;                 // LED pin 7
int testPin = 7;
int t2Pin = 6;
byte bb;

double dfreq;
double dfreq2;

// const double refclk=31372.549;  // =16MHz / 510
const double refclk=31376.6;      // measured

// variables used inside interrupt service declared as voilatile
volatile byte icnt;              // var inside interrupt
volatile byte icnt2;              // var inside interrupt
volatile byte icnt1;             // var inside interrupt
volatile byte c4ms;              // counter incremented all 4ms
volatile unsigned long phaccu;   // pahse accumulator
volatile unsigned long phaccu2;   // pahse accumulator
volatile unsigned long tword_m;  // dds tuning word m
volatile unsigned long tword_m2;  // dds tuning word m

void setup()
{
 pinMode(ledPin, OUTPUT);      // sets the digital pin as output
 Serial.begin(115200);        // connect to the serial port
 Serial.println("DDS Test");

 pinMode(6, OUTPUT);      // sets the digital pin as output
 pinMode(7, OUTPUT);      // sets the digital pin as output
 pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output

 Setup_timer2();

 // disable interrupts to avoid timing distortion
 cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay() is now not available
 sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt

 //sbi (TIMSK1,TOIE1);              // enable Timer2 Interrupt

 dfreq=523;                    // initial output frequency = 1000.o Hz
 dfreq2=659;
 tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word 

}
void loop()
{
 while(1) {
    if (c4ms > 1) {                 // timer / wait fou a full second
     c4ms=0;
     
     //dfreq=analogRead(0);             // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz
//      dfreq = dfreq - 1;
//      dfreq2 = dfreq2 - 1;
//      if(dfreq < 900) {
//        dfreq = 1300;
//        dfreq2 = 1150;
//      }
     
     cbi (TIMSK2,TOIE2);              // disble Timer2 Interrupt
     tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word
     tword_m2=pow(2,32)*dfreq2/refclk;  // calulate 2nd DDS new tuning word
     sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt 

     Serial.print(dfreq);
     Serial.print("  ");
     Serial.println(tword_m);
   }

  sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
  cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
 }
}
//******************************************************************
// timer2 setup
// set prscaler to 1, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock
void Setup_timer2() {
// Timer2 Clock Prescaler to : 1
 sbi (TCCR2B, CS20);
 cbi (TCCR2B, CS21);
 cbi (TCCR2B, CS22);

 // Timer2 PWM Mode set to Phase Correct PWM
 cbi (TCCR2A, COM2A0);  // clear Compare Match
 sbi (TCCR2A, COM2A1);

 sbi (TCCR2A, WGM20);  // Mode 1  / Phase Correct PWM
 cbi (TCCR2A, WGM21);
 cbi (TCCR2B, WGM22);
}

//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// FOUT = (M (REFCLK)) / (2 exp 32)
// runtime : 8 microseconds ( inclusive push and pop)
ISR(TIMER2_OVF_vect) {

 sbi(PORTD,7);          // Test / set PORTD,7 high to observe timing with a oscope

 phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits
 phaccu2=phaccu2+tword_m2;
 icnt=phaccu >> 24;     // use upper 8 bits for phase accu as frequency information
                        // read value fron ROM sine table and send to PWM DAC
 icnt2=phaccu2 >> 24;
 OCR2A= pgm_read_byte_near(sine256_2 + icnt) + pgm_read_byte_near(sine256_2 + icnt2);    

 if(icnt1++ == 125) {  // increment variable c4ms all 4 milliseconds
   c4ms++;
   icnt1=0;
  }   

cbi(PORTD,7);            // reset PORTD,7
}