MezData-Logo

Musik mit Z-Eye (Pulsweitenmodulation)

Timer und Pulsweitenmodulator

Im ersten Schritt bekommt der Lautsprecher ein symetrisches Rechtecksignal im Bereich von 1-6 kHz.

1 kHz bedeutet 500µs an und 500µs aus. 10 kHz bedeutet 50µs an und 50µs aus. Der Timer wird mit dem Systemtakt (1 MHz) getaktet.

1kHz erzeugen

  1. Den Zähler auf 0 und der Ausgang auf 1 setzen.
  2. Der Zähler zählt im µs Takt. Bei 500 wird der Ausgang auf 0 gesetzt.
  3. Beim Wert 1000 gehe zu 1.

Die Timer/Zähler-Einheiten in dem µC können in verschiedene Modi eingestellt werden, für uns die die Betriebsart Fast PWM Mode siehe Seite 98ff. praktisch:
Der Timer zählt von 0 bis zu einem einstellbaren TOP Wert und springt dann wieder auf 0. Ein Ausgang kann beim Sprung von TOP auf 0 gesetzt und bei Erreichen eines einstellbaren Wertes OCR1A wieder rückgesetzt werden.

Zur Info: Konfiguration des Timers bei Initialisierung

Register Bit 7 6 5 4 3 2 1 0 Beschreibung
TCCR1A Bedeutung COM1A1 COM1A0 COM1B1 COM1B0 FOC1A FOC1B WGM11 WGM10 Ausgang OC1B bei 0 setzen und bei OCR1B löschen
Wert 0 0 1 0 0 0 1 1

Waveform Generation Mode: Fast PWM mit OCR1A als TOP

TCCR1B Bedeutung ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10
Wert 0 0 0 1 1 0 0 1 Keine Vorteilung Timer mit CPU-CLK (No prescaling)
TIMSK Bedeutung TOIE1 OCIE1A OCIE1B - ICIE1 OCIE0B TOIE0 OCIE0A Keine Interrups notwendig
Wert 0 0 0 0 0 0 0 0

Lautsprecher im Zylonenauge

Schema

Initialisierung 1 kHz Ton

#include <avr/io.h>
int main(){
  DDRB = 0b00010000; // PB4 als Ausgang
  TCCR1A = 0b00100011; // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  TCCR1B = 0b00011001; // Waveform Generation Mode: Fast PWM it OCR1A als Top, Timer mit CPU-CLK 
  OCR1A = 1000; // Timer 1ms
  OCR1B = 500;  // Impulslaenge 500us
  while (1); // Endlosschleife
}

Messen des Signals mit einem (Speicher-) Oszilloskop

Kontrollieren Sie das Signal mit einem Oszilloskop. Messen Sie die Periodendauer und die Signaldauer mit einem Frequenzzähler bzw. mit der entsprechenden Funktion des Speicher-Oszilloskops.

Falls Sie eine Abweichung feststellen diskutieren Sie die möglichen Ursachen, liegt gar ein Denkfehler bei der Ermittlung der Werte für OCR1A und OCR1B vor?

Initialisierung und 1 kHz Pieps und etwas mehr

Quellcode [Piepser.c]
// *** Piepser V1.0 (c) Oliver Mezger 16.9.2010
 
#include <avr/io.h>
#include <util/delay.h>
 
int main(){
  DDRB = 0b00010000; // PB4 als Ausgang
  TCCR1A = 0b00100011; // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  TCCR1B = 0b00011001; // Waveform Generation Mode: Fast PWM it OCR1A als Top, Timer mit CPU-CLK 
  OCR1A = 1000; // Timer 1ms
  OCR1B = 500;  // Impulslaenge 500us
  while (1){  // Spielwiese fuer Tonerzeugung
    for(int i = 2000;i>100;i--){
      OCR1A = i;
      for (int k = i/2; k>2;k--){
        OCR1B = k;
      //_delay_ms(1);
      }
    }
  }
}

Alle meine Entchen spielen

Eine einfache Tabelle mit den Tonhöhen fanden wir hier auf Wikipedia. Fluchs wurde eine Tabelle erstellt:

Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Ton c' cis' d' es' e' f' fis' g' as' a' b' h' c'' cis'' d'' es'' e'' f'' fis'' g'' as'' a'' b'' h''
Frequenz 264 275 297 317 330 352 367 396 422 440 475 495 528 550 594 ..
Solmisation Do Re Mi Fa So La Ti

Mittlwerweile ist das Thema so umfangreich vertreten, dass mein einfachster Link dieser ist : Gleichstufige_Stimmung

Trotz der const-Definition würden die Felder im Ram abgelegt und dieser wäre dadurch beinahe voll, deshalb wird mittels PROGMEM der Compiler angewiesen, die Daten im Flash zu speichern, zum Lesen braucht es dann spezielle Funktionen Programmspeicher (Flash), Data in Program Space.

Bei der Tonerzeugung wird eine Hüllkurve verwendet. Wenn in _delay_ms() keine Konstante eingetragen wird, steigt der RAM-Bedarf damatisch an, daher der Work-Around mit der For-Schleife.

Quellcode [zMelodie.ino]
// *** Zylonen-Melodie V1.0 (c) Oliver Mezger 9.2.2020 ***
 
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>  // Flashzugriffe laden
// Periodendauer von c' bis h'
const unsigned int PROGMEM ton[] ={F_CPU/264,F_CPU/275,F_CPU/297,F_CPU/317,F_CPU/330,F_CPU/352,F_CPU/367,
F_CPU/396,F_CPU/422,F_CPU/440,F_CPU/475,F_CPU/495};
 
const unsigned char kurve[]  = {1,2,3,5,6,7,8,9,10,11,12}; // Huellkurve Exponenten
//const unsigned char kurve2[] = {5,1,2,6,3,6,8,9,10,12};
const unsigned char PROGMEM melodie[]  ={0,2,4,5,7,7,9,9,9,9,7,9,9,9,9,7,5,5,5,5,4,4,7,7,7,7,0}; // alle meine Entchen
const unsigned char PROGMEM lange[]    ={2,2,2,2,4,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,4,4,2,2,2,2,4}; // Tonlaengen
 
int main(){
  unsigned char d,i,j,l;
  unsigned int k;
  DDRB = 0b00010000;        // PB4 als Ausgang
  TCCR1A = 0b00100011;      // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  TCCR1B = 0b00011001;      // Waveform Generation Mode: Fast PWM it OCR1A als Top, Timer mit CPU-CLK 
  OCR1A = 1000;             // Timer 1ms
  OCR1B = 500;              // Impulslaenge 500us
  _delay_ms(100);           // Testton nach Initialisierung
  while(1)                  // Endlosschleife
    for (i=0;i<sizeof(melodie);i++){ // spiele die Melodie
      k = pgm_read_word(&ton[pgm_read_byte(&melodie[i])])/2; // hole die Periodendauer, erhoehe um 1 Oktave
      OCR1A = k;              // Periodendauer einstellen
      for(j=0;j<sizeof(kurve);j++){ // Huellkurve anwenden
        OCR1B = k>>kurve[j];  // Impulsdauer einstellen
      for (d=0;d<pgm_read_byte(&lange[i]);d++) // Tonlaenge
        _delay_ms(20);
    }
  _delay_ms(10);          // Pause bis naechster Ton
  }
}
 

Sounds auf Tastendruck

Quellcode [zSounds.ino]
// *** zSounds V1.0 (c) Oliver Mezger 9.2.2020 ***
 
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>  // Flashzugriffe laden 
// C-Dur Frequenzen als Periodendauer
const unsigned int PROGMEM ton[]={F_CPU/264,F_CPU/297,F_CPU/330,F_CPU/352,F_CPU/396,F_CPU/440,F_CPU/495,F_CPU/528};
unsigned char keyOld = 0;       // alter Tasten-Zustand
unsigned char keyEnter,keyExit; // gedrueckte und losgelassene Tasten
 
void spaceSound(){        // Unterprogramm fuer Testzwecke
  TCCR1A = 0b00100011;      // ton an
  for(int i = 2000;i>100;i--){
    OCR1A = i;
    for (int k = i/2; k>2;k--){
      OCR1B = k;
    }
  }
  TCCR1A = 0b00000011;      // ton aus
}
 
void tonleiter(){         // Unterprogramm fuer Testzwecke
  TCCR1A = 0b00100011;      // ton an
  for(int i=0;i<=7;i++){
    OCR1A = pgm_read_word(&ton[i])/2;
    OCR1B = OCR1A/2;
    _delay_ms(200);
  }
  TCCR1A = 0b00000011;      // ton aus
}
 
void keyCheck(){          // Tastaturabfrage mit Flankendedektion 
  unsigned char keyTest,tmp;
  keyEnter = 0, keyExit = 0;
  keyTest = ~PIND >>4; // Einlesen und zurechtschieben
  if (keyOld != keyTest){       // hat sich was getan
    _delay_ms(10);              // Prellen abwarten
    tmp = ~PIND >>4;   // nochmal Einlesen und zurechtschieben
    if (tmp == keyTest){        // ist es stabil?
      keyEnter = (~keyOld) & keyTest; // steigende Flanke !alt und neu
      keyExit = keyOld & (~keyTest);  // fallende Flanke alt und !neu
      keyOld = keyTest;
    }
  }
}
 
int main(){
  unsigned char i;
  DDRB = 0b00010000;        // PB4 als Ausgang
  PORTD = 0x7f;             // PullUps an
  //TCCR1A = 0b00100011;      // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  TCCR1B = 0b00011001;  // Waveform Generation Mode: Fast PWM it OCR1A als Top, Timer mit CPU-CLK 
  while (1){                // Endlosschleife
    keyCheck();             // Tastaturabfrage
    if (keyEnter&1){      // wurde Taste gedrueckt?
      tonleiter();
    }
    if(keyEnter&2){
      spaceSound();
    }
  }
}