play a song with PWM and DDS

hello,

i got an understanding problem. i want to play a song with the arduino Duemilanove http://www.arduino.cc/en/Main/ArduinoBoardDuemilanove. the clock speed of this board is 16 mhz. know i found a good example here http://forums.adafruit.com/viewtopic.php?t=6142 it works fine (so i guess), but i am curious about the calculation he made. i think he used a board with an 8 mhz clock speed, because of the calulation: the interrupt frequency is F_CPU / 256 (timer TOP value) / 2 (because timer counts up then down between interrupts). he ends up with 15625Hz and a .01 second is 156.25 interrupts.

my problem is: i used a clock speed of 16 Mhz and i use phase correct pwm (he does too) and this guy says http://www.arcfn.com/2009/07/secrets-of-arduino-pwm.html that phase correct pwm is off by one, so dont divide by 512 but by 510. so the calculation ends up to be: 16 000 000 / 510 = 31.372,54 and what i did is to recalculate the notetab like this note_calc (4899942950ULL * 855638016ULL / (uint64_t)F_CPU / 100000000ULL),// g1 changed this values 855638016ULL.

another thins i've done is to let the intcount variable count until 313, because of the new divider and the faster cpu clock.

i end up with this code, i think that it is correct, but it sound like crap.

(see next post)

#include <avr/io.h>              // I/O Definitionen
#include <avr/interrupt.h>       // Definitionen für Interrupts
#include <avr/pgmspace.h>        // Definitionen um PROGMEM zu verwenden

struct notestep {                // 24 bit Fixpunktwert
  uint8_t integer;
  uint16_t fraction;
};

// Namen der Noten und die dazugehörige Position in der Notentabelle,
// außerdem einen Enumtyp für eine Pause und um das Ende des Liedes zu markieren
enum {G_1, Gs1, A_1, As1, B_1, C_2, Cs2, D_2, Ds2, E_2, F_2, Fs2,
         G_2, Gs2, A_2, As2, B_2, C_3, Cs3, D_3, Ds3, E_3, F_3, Fs3,
         G_3, Gs3, A_3, As3, B_3, C_4, Cs4, D_4, Ds4, E_4, F_4, Fs4,
         G_4, Gs4, A_4, As4, B_4, C_5, Cs5, D_5, Ds5, E_5, F_5, Fs5,
         G_5, Gs5, A_5, As5, B_5, C_6, Cs6, D_6, Ds6, E_6, F_6, Fs6,
         G_6, Gs6, RST, END} notenames;

// Um die Notentabelle zu generieren wird die Frequenz der Note mit der Größe der Sinustabelle
// multipliziert. Danach wird das Ergbenis um 16 bit nach links verschoben (* 65536), um den
// Integer Wert zu isolieren und um Platz zu machen für den 16 bit Bruch. Dies sind die Werte
// des struct notestep, welcher verwendet wird, um auf die richtige Stelle in der Sinustabelle
// zu zugreifen, um den korrekten Ton zu spielen. Mit diesem Wert wird der Index inkrementiert,
// um an die nächste Stelle in der Sinustabelle zu gelangen, um den entsprechenden Ton, mit der
// entsprechenden Frequenz zu spielen. Am Ende wird Das Product noch durch die Interrupt-Frequenz
// geteilt (31372.55 Hz).
//
// F_CPU = 16 000 000 Hz
// F_CPU / 510 = 31372.55 Hz
//
// Die Interrupt-Frequenz ist 510, da der Timer sowohl hoch als auch wieder herunter zählt.
// Verdeutlicht man sich dies an einem Beispiel, wird klar, dass der Teiler nicht 512, sondern
// 510 sein muss. Stellt man sich vor der Timer zählt bis 3, dann würde der Zähler von 0 bis 3
// und wieder zurück gehen. Also 0 1 2 3 2 1 | 0 1 2 3 2 1 | ... daran sieht man, dass keine
// 8 Registerstellen pro Interrupt durchlaufen werden, sondern nur 6.
//
// (Notenfrequenz * 256 * 65536) / (F_CPU / 510) // 256 = 2^8, 65536 = 2^16
//
// daraus resultiert: (note_freq * 8707375104) / F_CPU


#define note_calc(s) {(uint8_t)(s>>16),(uint16_t)(s&0xffff)}

const notestep notetab[] PROGMEM = {
  note_calc (4899942950ULL * 855638016ULL / (uint64_t)F_CPU / 100000000ULL),// g1
  note_calc (5191308719ULL * 855638016ULL / (uint64_t)F_CPU / 100000000ULL),// g#1
  note_calc (550000000ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),  // a1
  note_calc (582704702ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (617354127ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (654063913ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),  // c2
  note_calc (692956577ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (734161920ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (777817459ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (824068892ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (873070579ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (924986057ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (979988590ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (1038261744ULL * 855638016ULL / (uint64_t)F_CPU / 10000000ULL),
  note_calc (110000000ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),    // a2
  note_calc (116540940ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (123470825ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (130812783ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),    //c3
  note_calc (138591315ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (146832384ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (155563492ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (164813778ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (174614116ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (184997211ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (195997718ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (207652349ULL * 855638016ULL / (uint64_t)F_CPU / 1000000ULL),
  note_calc (22000000ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),      // a3
  note_calc (23308188ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (24694165ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (26162557ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),      // c4
  note_calc (27718263ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (29366477ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (31112698ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (32962756ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (34922823ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (36999442ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (39199544ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (41530470ULL * 855638016ULL / (uint64_t)F_CPU / 100000ULL),
  note_calc (4400000ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),        // a4
  note_calc (4661637ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (4938833ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (5232511ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),        // c5
  note_calc (5543653ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (5873295ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (6222540ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (6592551ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (6984565ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (7399888ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (7839909ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (8306094ULL * 855638016ULL / (uint64_t)F_CPU / 10000ULL),
  note_calc (880000ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),          // a5
  note_calc (932328ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (987767ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1046502ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),         // c6
  note_calc (1108731ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1174659ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1244508ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1318510ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1396913ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1479978ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1567982ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1661219ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),
  note_calc (1760000ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL),         // a6
  note_calc (1864655ULL * 855638016ULL / (uint64_t)F_CPU / 1000ULL)          // a#6
};

#ifndef DEFUALT_BPM
#define DEFAULT_BPM 150
#endif
#define DEFAULT_BEAT 6000 / DEFAULT_BPM

// Notenwerte
enum {W, dH, H, dQ, Q, dE, E, S} notelengths; // W = whole, H = half, Q = quarter, E = eighth, d = dotted, S = sixteenth

// Diese Tabelle enthält die Dauer der definierten Notenwerte. Wird die BPM-Anzahl
// verändert, müssen die Notenwerte neu berechnet werden.
uint16_t durtab[] = {
  DEFAULT_BEAT * 4,                  // ganze Note
  DEFAULT_BEAT * 3,                  // punktierte halbe Note
  DEFAULT_BEAT * 2,                  // halbe Note
  (DEFAULT_BEAT * 3) / 2,            // punktierte Viertelnote
  DEFAULT_BEAT,                      // Viertelnote
  (DEFAULT_BEAT * 3) / 4,            // punktierte Achtelnote
  DEFAULT_BEAT / 2,                  // Achtelnote
  DEFAULT_BEAT / 4                   // Sechzehntelnote
};

// Lied
uint8_t greensleeves[] PROGMEM = {
  A_4, Q,
  C_5, H, D_5, Q,
  E_5, dQ, F_5, E, E_5, Q,
  D_5, H, B_4, Q,
  G_4, dQ, A_4, E, B_4, Q,
  C_5, H, A_4, Q,
  A_4, dQ, G_4, Q, A_4, Q,
  B_4, H, Gs4, Q,
  E_4, H, A_4, Q,
  C_5, H, D_5, Q,
  E_5, dQ, Fs5, E, E_5, Q,
  D_5, H, B_4, Q,
  G_4, dQ, A_4, Q, B_4, Q,
  C_5, dQ, B_4, E, A_4, Q,
  Gs4, dQ, Fs4, E, Gs4, Q,
  A_4, dH,
  A_4, H, RST, Q,
  G_5, dH,
  G_5, dQ, Fs5, E, E_5, Q,
  D_5, H, B_4, Q,
  G_4, dQ, A_4, E, B_4, Q,
  C_5, H, A_4, Q,
  A_4, dQ, Gs4, E, A_4, Q,
  B_4, H, Gs4, Q,
  E_4, dH,
  G_5, dH,
  G_5, dQ, Fs5, E, E_5, Q,
  D_5, H, B_4, Q,
  G_4, dQ, A_4, E, B_4, Q,
  C_5, dQ, B_4, E, A_4, Q,
  Gs4, dQ, Fs4, E, Gs4, Q,
  A_4, dH,
  A_4, H, RST, Q,
  END, END, END, END
};
// Sinustabelle
const uint8_t sinetab[] PROGMEM = {
  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
};


volatile uint8_t* song_ptr = NULL;      // Pointer für die Position im Lied
volatile uint8_t song_flags;            // PLAY
#define S_PLAY 0
uint8_t song_note_tone;                 // Note an der Position im Lied
uint8_t song_note_time;                 // Notenwert an der derzeitigen Position
uint16_t song_note_duration;            // Herunterzählen der Dauer des aktuellen Notewertes in .01 sek Schritten

// Variablen um den Sound zu generieren
volatile notestep wavestep;             // Schrittweite für den aktuellen Ton
notestep phaseaccumulator;              // Phasenakkumulator
uint8_t wavephase;                      // Index in die Sinustabelle
uint8_t wavesample;                     // PWM Ausgabewert

// Variablen zum bestimmen des richtige Takts
volatile uint8_t bpm = DEFAULT_BPM;      // Schläge pro Minute
uint8_t intcount = 313;                  // Anzahl der Interrupts bis .01 sec vergangen ist
uint8_t correctvalue = 73;             // Anteil zu korrekten Zählung
uint8_t correctaccumulator;

// cbi P, b setzt das Bit b (0-7) im Port P = 0
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
// sbi P, b setzt das Bit b (0-7) im Port P = 1
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void setup()
{
  Serial.begin(115200);
  Serial.println("DDS Test");
  Serial.println(F_CPU);
  

  pinMode(11, OUTPUT);                   // PWM
  
  Setup_timer2();

  // deaktivieren des Interrupts um Störungen in der Zeitberechnung zu vermeiden
  cbi (TIMSK0,TOIE0);              // deaktiviere Timer0 !!! delay() ist nicht mehr verwendbar.
  sbi (TIMSK2,TOIE2);              // aktivieren des Timer2 Interrupt
}

void loop()
{
  song_ptr = &greensleeves[0];          // Zeiger auf den Begin des Liedes
  song_note_duration = 1;
  song_flags |= (1 << S_PLAY);          // Starte das Lied
  while (1);
}

void Setup_timer2() {

  // setze Timer2 Vorteiler auf : 1
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // setze Timer2 PWM Modus auf Phasen-korrekte PWM
  cbi (TCCR2A, COM2A0);
  sbi (TCCR2A, COM2A1);

  sbi (TCCR2A, WGM20);  // Modus 1  / Phasen-korrekte PWM
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
  
}


ISR(TIMER2_OVF_vect) { // Timer2 Überlauf

  if (song_flags & (1 << S_PLAY)) {        // wird ein Lied gespielt
    
    // Ton-Ausgabe
    OCR2A = wavesample;
    OCR2B = wavesample;
    
    Serial.print(intcount, DEC);
    
    // Zeitberechnung
    if (--intcount == 0) {              // zählen der Interrupts

      //correctaccumulator += correctvalue; // erhöhen der vergangenen .01 Sekunden
      //if(correctaccumulator < 100) {
      //      intcount = 156;
      //} else {
        //        correctaccumulator = correctaccumulator % 100;
      //      intcount = 156;
      //}

      intcount = 313;
  
      if (--song_note_duration == 0) {  // note finished ?
        song_note_tone = pgm_read_byte (song_ptr++) & 0x3f; // 0x3f = 63
        song_note_time = pgm_read_byte (song_ptr++) & 0x07; // 0x07 = 7
        song_note_duration = durtab[song_note_time];
        if (song_note_tone < RST) {
          wavestep.integer = pgm_read_byte (&(notetab[song_note_tone].integer));
          wavestep.fraction = pgm_read_word (&(notetab[song_note_tone].fraction));
        }
        else {
          wavestep.integer = 0;
          wavestep.fraction = 0;
        }
      }
    }

    // nächstes Sample
    if (song_note_duration > 4) {
      phaseaccumulator.fraction += wavestep.fraction;
      if (SREG & (1 << SREG_C))   {   // bei Überlauf
        phaseaccumulator.integer++;   // addiere 1 auf den Integer Wert
      }
      phaseaccumulator.integer += wavestep.integer;
      wavephase = phaseaccumulator.integer; // move integer part of accumulator to
                                            // phase pointer
      if (phaseaccumulator.fraction & 0x8000) // if accumulator fraction > 1/2 // 0x8000 = 32768
        wavephase++;         // round up
      wavesample = pgm_read_byte(&(sinetab[wavephase]));// get sample for next time.
    }
  }
  
}

sorry had to split it.
if someone could tell me what iam doing wrong, i would be happy.

thanks
peter

i end up with this code, i think that it is correct, but it sound like crap.

So it actually produces a tune? The quality of it is not going to be great, this is the sort of thing that demonstrates it is possible not that it is desirable. Can you post an audio file of the results so we can judge if the quality you get is what can be expected for this technique. It is possible that it is as good as it gets but without listening to the result it is hard to say.

hi, sorry that i hadn't done this before.

so here is the tune that the code from the link above produces. http://webuser.uni-weimar.de/~atix5565/arduino/forum.wav and here is the code for that http://webuser.uni-weimar.de/~atix5565/arduino/forum.pde

and now my tune and code http://webuser.uni-weimar.de/~atix5565/arduino/song_dds.wav (it is very muted, dont know why) http://webuser.uni-weimar.de/~atix5565/arduino/song_dds.pde

the only thing i've done is the racalculation with 16000000 MHz and 510 instead of 512.

thanks again. peter

How are you creating the wav files. When I try an play them I get a message saying the file is corrupt. What application can you play them in?

OK managed to listen to them when I swapped over from a PC to my mac. That first example is a bit better than I would expect. The second example is very low in volume and sounds like interrupts are firing off at the same time. Let's address the volume first. Are the two clips produced from exactly the same hardware setup?

yes they are created from the same hardware setup. i think i also amplified the signal so you can hear something. sorry for the wav problems, i also use a mac, so i didn't notice it.

I think you problem is that you are running the interrupts too fast. This is not allowing the PWM outputs to produce a complete cycle and hence the lack of volume as you are always cutting any signal short.

The line:- uint8_t intcount = 156; // count interrupts 156.25 = .01 sec

you have at:- uint8_t intcount = 313; // Anzahl der Interrupts bis .01 sec vergangen ist so you are running it at twice the rate.

yes i knwo, but i thought it is because he took an arduino with 8 MHz so so the calculation ends up to be: 8 000 000 / 512 = 15625 and so .01 sec are 156.25 interrupts. and i took an arduino board with 16 MHz and also i took 510 because of the phase correct PWM, so i come up with: 16 000 000 / 510 = 31372.55 and so .01 sec are 313.73 interrupts.

isn't that right?

thanks peter

That would only be right if the PWM frequency was halved in an 8MHz system. I think this is not the case, and the clock difference is compensated for in the setting of the timers, to give the same PWM frequency despite the lower clock speed.

so F_CPU is 16000000 in my case and my prescaler is 1. i dont got the point. if i have phase correct pwm then there are 510 register steps until in interrupts, so i have to divide the step count through the F_CPU, then i end up with my calculation.

can you give me a bit more, please? so maybe i miss something.

thanks peter

you should be ok at 16MHz since the clock frequency is built into the calculation. this code was a step along the way to completing another project that runs fine on a stock arduino. a 16MHz clock works better because you're not likely to hear the 30 whatever KHz frequency.

in case i have overlooked something, i'll take a look at the code once i've knocked some of the cobwebs out of my noggin.

just loaded orgOnan onto a duemillenova. sounds ok but seems to play the song at double speed at 16MHz. this can be corrected by using 12000 instead of 6000 in the calculation of DEFAULT_BEAT. the other, logically clearer, option would be to double int_count but, since it's an 8 bit variable, that won't really work.

ok, i see you tried doubling int_count. sadly, a uint8_t will not hold the value 313.