Demo
MIttels eines ATtiny2313 soll mit einem Piezoschallwandler oder Lautsprecher Töne erzeugt werden werden.
Dazu wird der 16-bit Timer/Counter1 mit Pulsweitenmodularor PWM verwendet. ( ATtiny2313 Seite 89ff.)
Ein Timer ist ein Zähler, der mit einem bestimmten Takt betrieben wird, durch Vergleich des Zählerwerts mit einstellbaren Werten und daraus folgeden Aktionen lassen sich einfach periodische Digitalsignale erzeugen, z.B. Töne.
Timer und Pulsweitenmodulator

Im ersten Schritt bekommt der Piezoschallwandler 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
- Den Zähler auf 0 und der Ausgang auf 1 setzen.
- Der Zähler zählt im µs Takt. Bei 500 wird der Ausgang auf 0 gesetzt.
- 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.
- OCR1A als TOP stellt die Perodendauer des Signals ein.
- OCR1B als Pulsweite stellt die Dauer der positiven Signals ein.
- Ausgang ist PB4 (OC1B).
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 |
Anschluß des Piezoschallwandlers
Der Schallwandler wird zwischen PB4 (OC1A) und GND angeschlossen. An der Pfostenleiste von PORTB sind dies die Pins 5 u. 9.
![]() |
![]() |
|
|||||||||||||||||||||||||
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
}
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
// *** 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 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 |
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.
// *** Melodie V1.1 (c) Oliver Mezger 3.7.2014 ***
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h> // Flashzugriffe laden
// Periodendauer von c' bis h'
const unsigned int PROGMEM 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};
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(&tone[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
}
}
Als Ausgabe alten Kopfhörer verwenden

Statt des Piezowandlers kann auch ein alter Kopfhörer Verwendung finden.
Dabei muss allerdings ein Vorwiderstand von 220 Ohm in Serie geschaltet werden:
- Die Kopfhörer haben typischerweise 30 Ohm Impendanz, damit wäre der Storm den der Portausgang liefern müsste grösser als die maximalen 40 mA.
- Viel gefählicher ist bei einem Rechtecksignal die Induktionsspannung in der Spule, die bei dem aprupten Signalwechsel entsteht.
Töne auf Tastendruck in C
// *** Piepser V1.2 (c) Oliver Mezger 3.7.2014 ***
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h> // Flashzugriffe laden
// C-Dur Frequenzen als Periodendauer
const unsigned int PROGMEM tone[]={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
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 = pgm_read_word(&tone[i])/2;
OCR1B = OCR1A/2;
_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
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 = pgm_read_word(&tone[i]); // Periodendauer
OCR1B = OCR1A/2; // Impulsdauer
DDRB |= 1<<PB4; // Sound an
}
}
if (keyOld == 0) // wenn keine Taste mehr gedrueckt
DDRB &= ~(1<<PB4); // Sound aus
}
}