Hallo,
Ich bin gerade dabei einen uralten Quellcode (ca. 40 Jahre) auf aktuelle Hardware zu portieren.
Für meine ersten Tests verwende ich also einen Arduino Uno. Vom ganzen Projekt erscheint mir die Tonausgabe am schwierigsten. Habe lange versucht die ursprünglich verwendete Strategie nachzuprogrammieren. Ziemlich erfolglos. Was auch daran liegt, das für mich ein paar Zusammenhänge nicht plausibel sind. Sei's drum....
Auf der Suche nach einer Möglichkeit die Tonausgabe mit den ursprünglichen Definitionen umzusetzen bin ich auf folgende Seite gestoßen:
https://docplayer.org/21151374-Sound-mit-dem-atmega16.html
Und tatsächlich finde ich dort etwas, was der ursprünglichen Strategie ähnelt.
Leider ist sein Beispiel weder Vollständig noch stimmig.
Eine einfache Implementierung k¨onnte z.B. so aussehen:
Zitat von der betreffenden Stelle:
Auf diese Weise l¨asst sich zwar nur ein Rechtecksignal erzeugen, die Umsetzung ist dafur ¨
aber denkbar einfach und es werden keine zus¨atzlichen Anforderungen an die Hardware
gestellt. Da die Timerfrequenz der Frequenz des Tones entsprechen soll bedeutet dies dass
fur jeden Ton ein eigenes Timer-TOP definiert werden muss. ¨
Der Timer wird auf Fast PWM (WGM 14) und Compare Output Mode konfiguriert.
Damit ergibt sich als Timer-TOP das ICR1 Register. Der Wert hierfur lässt sich durch
folgende Formel errechnen.
Als Vergleichswert ergibt sich das OCR1A Register. Zu Beginn einer Timer-Periode (BOTTOM) wird der Ausgabepin OC1A auf HIGH-Pegel und bei Erreichen des ORC1A Wertes
auf LOW-Pegel gesetzt. Das OCR1A Register kann somit genutzt werden um die Hullkurve ¨
zu realisieren.
Eine einfache Implementierung k¨onnte z.B. so aussehen:
...
#define C 4778
#define D 4257
#define E 3792
...
uint8_t SONG[] = {C,D,E...,STOP}; // Array mit Noten
uint8_t LENGTH[] = {500,520,540...,STOP}; // Array mit Notenl¨angen
ISR(TIMER1_OVF_vect){
static uint8_t laenge;
static uint8_t count;
// wenn Note zu ende
if(laenge++ > notenLaenge){
// wenn Song zu ende
if(SONG[count] == STOP) {
TCCR1B &= ~(1<<CS11); // Timer aus
return;
}
// sonst neue Note
ICR1 = SONG[cnt]
OCR1A = 500; // zu beginn der note irgend eine Lautst¨arke
count++;
laenge = 0;
}
// sonst H¨ullkurve
else {
// tue irgendwas mit OCR1A
...
}
}
Habe das mal in einem Sketch ausprobiert. Leider ohne Erfolg. Entweder verstehe ich die Zusammenhänge nicht richtig, oder ich übersehe etwas. Für mich unstimmig ist folgendes:
Er möchte also, wie im Text beschrieben, mit OCR1A vergleichen. Im Quellcode verwendet er aber dann ISR(TIMER1_OVF_vect). Was meinem Verständnis nach eher für den Überlauf gedacht ist.
Weiter schreibt er, Der Timer wird auf Fast PWM (WGM 14) und Compare Output Mode konfiguriert. Ich nehme an er meint Mode 14. Meint er nun COM1A1 = 1 oder COM1A0 = 1?
Hier ist mein Sketch, den ich mal mit den vorhandenen Informationen zusammen geschrieben habe. Ich hoffe, jemand kann mir auf die Sprünge helfen...
#define speaker 9 // OC1A
#define C 4778
#define D 4257
#define E 3792
#define STOP 0
uint8_t SONG[] = { // Array mit Noten
C,
D,
E,
STOP
};
uint8_t LENGTH[] = { // Array mit Notenlängen
500,
520,
540,
STOP
};
ISR(TIMER1_OVF_vect){
static uint8_t laenge;
static uint8_t count;
// wenn Note zu ende
if(laenge++ > LENGTH[count])
{
// wenn Song zu ende
if(SONG[count] == STOP)
{
TCCR1B &= ~(1<<CS11); // Timer aus
return;
}
// sonst neue Note
ICR1 = SONG[count];
OCR1A = 500; // zu beginn der note irgendeine Lautstärke
count++;
laenge = 0;
}
else // sonst Hüllkurve
{
// tue irgendwas mit OCR1A
}
}
/*
gesucht: FOvf ≈ 20KHz - 50 Hz
FOvf
Prescaler | 8 bit | 9 bit | 10 bit | 16 bit
---------------------------------------------------------
1 | 78,1 kHz | 39 kHz | 19,5 kHz | 305 Hz
8 | 9,8 kHz | 4,8 kHz | 2,4 kHz | 38 Hz
64 | 1221 Hz | 610 Hz | 305 Hz | 4,7 Hz
256 | 305 Hz | 153 Hz | 76,3 Hz | 1,1 Hz
1024 | 76,3 Hz | 38,1 Hz | 19 Hz | 0,3 Hz
⇒ 16 bit / Prescaler 8
Mit diesen Parametern ergeben sich folgende Werte fur die Frequenzen:
FTimer = FCpu / 8 = 2.5 MHz
Fmin = FOVF = FTimer / (2^16 + 1) = 38 Hz
Fmax = FTimer / (2 + 1) = 833 kHz
ICR1 = FTimer / FTon
*/
// f_desired = system_clock / (prescaler * (1 + Top))
// Top = (system_clock / (prescaler * f_desired)) -1
// ICR1 = (16000000 / (8 * 1000)) -1 = 1999
void startTimer()
{
//cli(); // disable interrupts
TCCR1A = bit(COM1A1) | bit(WGM11); // Toggle OC1A on compare match, FastPWM(Mode14)
TCCR1B = bit(WGM13) | bit(WGM12) | bit(CS11); // FastPWM(Mode14), scale to clock / 8
TCNT1 = 0; // count back to zero
OCR1A = 500;
ICR1 = SONG[0];
TIMSK1 = bit(OCIE1A); // Output Compare A Match Interrupt Enable
//sei(); // allow interrupts
}
void stopTimer()
{
TCCR1B &= ~(1<<CS11); // stop timer
}
void setupTimer()
{
cli(); // Löschen des Global Interrupt Enable Bits (I) im Status
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
TIMSK1 = 0;
sei(); // Setzen des Global Interrupt Enable Bits (I) im Status
}
void setup() {
pinMode (speaker, OUTPUT);
setupTimer();
startTimer();
}
void loop() {
// put your main code here, to run repeatedly:
}
VG
Matze