MezData-Logo Creative Commons License 1456 Lösungen Button :AVR: Musik mit Piezoschallwandler

MIttels eines ATtiny2313 soll mit einem Piezoschallwandler Musik erzeugt werden werden. Dazu wird der 16-bit Timer/Counter1 mit Pulsweitenmodularor PWM verwendet. [ ATtiny2313] Seite 88ff.

Demo


Auch auf YouTube..

Timer und Pulsweitenmodulator

Im ersten Schritt soll der Piezoschallwandler ein symetrisches Rechtecksignal im Bereich von 1-6 kHz bekommen. Die Möglichkeit komplexere Signale zu erzeugen soll offen gehalten werden.
1 kHz bedeutet 500µs an und 500µs aus. 10 kHz bedeutet 50µs an und 50µs aus. Der Timer wird daher mit 1 MHz, dem Systemtakt getaktet.

Gewählt 1 MHz mit Vorteiler 1:1.

Betriebsart Fast PWM Mode siehe Seite 98ff. 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.

OCR1A als TOP. OCR1B als Pulsweite. Ausgang ist PB4 (OC1B).

Einstellungen 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

Anschluß des Piezoschallwandlers

D0 1 2 D1
D2 3 4 D3
Piezo D4 5 6 D5
D6 7 8 D7
Piezo GND 9 10 VCC
Piezoschallwandler ATtiny2313 STK200 I/O Port B

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
}

Initialisierung und 1 kHz Pieps und etwas mehr in C

Quellcode [Quellcode]
// *** 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);
      }
    }
  }
}

Eine kleine Melodie in Assembler

Eine Tabelle mit den Tonhöhen finden wir auf Wikipedia. Fluchs 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

Die Tonhöhen und die Melodie werden im Flash-ROM abgelegt.

Quellcode [Quellcode]
; *** PiepserAsm 0.6 (c) Oliver Mezger 12.10.2010
; Spielt eine Melodie aus dem Programmspeicher
; fuer melodieIndex<-0 bis ende
;  Periodendauer = tone[melodie[melodieIndex]]
;  Impulslaenge = Periodendauer / 2

.include "tn2313def.inc"

.equ cpuClk = 1000000   ; Taktfrequenz
.def null = R2
.def tmp = R16
.def tmpH = R17
.def melodieIndex = R18
.def warte = R19
.def aussen = R20
.def innen = R21

reset:
  ldi tmp, low(RAMEND)  ; Stackpointer initialisieren
  out SPL,tmp
  ldi tmp, 0b00010000   ; PB4 als Ausgang
  out DDRB, tmp
  ldi tmp, 0b00100011   ; Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  out TCCR1A, tmp
  ldi tmp, 0b00011001   ; Waveform Generation Mode: Fast PWM mit OCR1A als Top
  out TCCR1B,tmp        ; Timer mit CPU-CLK
  ldi warte, 150        ; Tonlaenge 300ms
  
spielInit:
  ldi melodieIndex,0
spiel:
  ldi ZH, high(melodie*2) ; lade Tabellenanfang melodie[]
  ldi ZL, low(melodie*2)
  add ZL,melodieIndex     ; addiere melodieIndex
  adc ZH,null             ; addiere Uebertrag
  lpm tmp,Z               ; lade den Ton
  lsl tmp                 ; nimm mal 2 da toene 2 Byte haben
  ldi ZH, high(tone*2)    ; lade Tabellenanfang tone[]
  ldi ZL, low(tone*2)
  add ZL, tmp             ; addiere ton
  adc ZH,null             ; addiere Uebertrag
  lpm tmp,Z+              ; lade low von tone[ton]
  lpm tmpH,Z              ; lade high
  out OCR1AH, tmpH        ; speichere zuerst High
  out OCR1AL, tmp         ; speichere dann Low
  lsr tmpH                ; /2 durch rechtsschieben Bit 0 in Carry
  ror tmp                 ; teile durch 2 aber schiebe Carry oben rein
  out OCR1BH, tmpH        ; speichere zuerst High
  out OCR1BL, tmp         ; speichere dann Low
  rcall waitWarte         ; warte
  rcall waitWarte         ; warte
  inc melodieIndex        ; naechster Ton
  cpi melodieIndex,(endeMelodie-melodie)*2-1 ; Assembler berechnet Laenge der Melodie
  brge spielInit          ; wieder zum Anfang
  rjmp spiel

waitWarte:                ; warte ms warten
  mov aussen,warte
loop_aussen:
  ldi innen,250
loop_innen:
  dec innen
  nop
  brne loop_innen
  dec aussen
  brne loop_aussen
  ret

; Tabelle mit Periodendauer von c' bis h'' mit Halbtoenen
tone: .dw cpuClk/264,cpuClk/275,cpuClk/297,cpuClk/317,cpuClk/330,cpuClk/352
      .dw cpuClk/367,cpuClk/396,cpuClk/422,cpuClk/440,cpuClk/475,cpuClk/495
      .dw cpuClk/2/264,cpuClk/2/275,cpuClk/2/297,cpuClk/2/317,cpuClk/2/330,cpuClk/2/352
      .dw cpuClk/2/367,cpuClk/2/396,cpuClk/2/422,cpuClk/2/440,cpuClk/2/475,cpuClk/2/495
; Tabelle mit Melodie
melodie: .db 0,2,4,6,8,3,4,2,12,20,4,7,2,10,15,23
endeMelodie: ; Label fuer Ende der Melodie

Alle meine Entchen in C

Die const-Felder werden im Ram abgelegt und ist dadurch beinahe voll, leider ist der erste Versuch mittels PROGMEM die Daten im Flash zu speichern und wieder zu lesen gescheitert [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.
Ich beginne den AVR-C-Compiler zu hassen, er ist erschreckend dumm und fabriziert unerwarteten und fehlerhaften Maschienencode -Werkzeug mit Tücken! [AVR Toolchain Bugs]
Weiter gehts mit Daten im Flash: [Programmspeicher (Flash)] [Zugriff string Konstante im PROGMEM eleganterer, einfacherer Weg?]

Quellcode [Quellcode]
// *** Melodie V1.0 (c) Oliver Mezger 12.10.2010 ***

#include <avr/io.h>
#include <util/delay.h>
// Periodendauer von c' bis h'
const unsigned int tone[] ={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};
/*,F_CPU/2/264,F_CPU/2/275,F_CPU/2/297,F_CPU/2/317,
F_CPU/2/330,F_CPU/2/352,F_CPU/2/367,F_CPU/2/396,F_CPU/2/422,F_CPU/2/440,F_CPU/2/475,F_CPU/2/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 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 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 = tone[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<lange[i];d++) // Tonlaenge
        _delay_ms(20);
    }
    _delay_ms(10);            // Pause bis naechster Ton
  }
}

Töne auf Tastendruck in C

Quellcode [Quellcode]
// *** Piepser V1.1 (c) Oliver Mezger 12.10.2010 ***

#include <avr/io.h>
#include <util/delay.h>
// C-Dur Frequenzen als Periodendauer
const unsigned int tone[]={1000000/264,1000000/297,1000000/330,1000000/352,1000000/396,1000000/440,1000000/495,1000000/528};
unsigned char keyOld = 0;       // alter Tasten-Zustand
unsigned char keyEnter,keyExit; // gedrueckte und losgelassene Tasten

void spaceSound(){        // Unterprogramm fuer Testzwecke
  for(int i = 2000;i>100;i--){
    OCR1A = i;
    for (int k = i/2; k>2;k--){
      OCR1B = k;
    }
  }
}

void tonleiter(){         // Unterprogramm fuer Testzwecke
  for(int i=0;i<=7;i++){
    OCR1A = tone[i]/2;
    OCR1B = tone[i]/4;
    _delay_ms(200);
  }
}

void keyCheck(){          // Tastaturabfrage mit Flankendedektion 
  unsigned char keyTest,tmp;
  keyEnter = 0, keyExit = 0;
  keyTest = ~PIND & 0b01111111; // Einlesen und zurechtschieben
  if (keyOld != keyTest){       // hat sich was getan
    _delay_ms(10);              // Prellen abwarten
    tmp = ~PIND & 0b01111111;   // 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;
    }
  }
}

unsigned char tasteInWert(unsigned char n){ // Zuordnung einer Taste zu einem Ton
  switch (n){
    case 128: return 0;
    case 64: return 1;
    case 32: return 2;
    case 16: return 3;
    case 8: return 4;
    case 4: return 5;
    case 2: return 6;
    case 1: return 7;
    default: return 8;
  }
}

int main(){
  unsigned char i;
  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
    keyCheck();             // Tastaturabfrage
    if (keyEnter > 0){      // wurde Taste gedrueckt?
      i = tasteInWert(keyEnter); // wandle in Tonzahl um
      if (i<8){             // wenn gueltiger Ton
        OCR1A = tone[i];    // Periodendauer
        OCR1B = tone[i]/2;  // Impulsdauer
        DDRB |= 1<<PB4;     // Sound an
      }
    }
    if (keyOld == 0)        // wenn keine Taste mehr gedrueckt
      DDRB &= ~(1<<PB4);    // Sound aus
  }
}