Moinmoin,
Ich bin auf der suche nach einem möglichst vollständigen Beispielcode
für einen 16-Bit-Timer im AtMega8.
Also konkret in main() den Timer vorbereiten und dann wie die
Interruptroutine aussieht. Wie hält man Timer in der Interruptroutine
wieder an, Wie weist man neuen Zählerwert zu und wie lässt man den dann
weiterlaufen. Ich möchte zuerst nur eine LED in definierten Abständen
blinken lassen oder einen Ton an einem Digitalport ausgeben, um die
Verwendung erstmal besser zu verinnerlichen.
Jan
Hallo Karl Heinz,
Vielen Dank für den Code!
1. Leider ist der Code zum Realisieren von einer Melodie an einem Pieper
über einen Digital-out noch nicht ganz vollständig, deshalb habe ich
noch eine kleine Nachfrage.
2. Ist der von dir genannte Code nicht ein Beispielcode für den 8-Bit
Counter oder sehe ich das falsch?
also erstmal zur Nachfrage:
Wie halte ich innerhalb der Serviceroutine den 16-Bit Counter an, weise
einen neuen Zählerstand zu und lasse den dann weiterlaufen.
Des weiteren muss ich innerhalb der ISR (Interrupt Service Routine)
zusätzlich die Interrupts deaktivieren, damit kein (wie nennt sich das
nochmal) overflow stattfindet. (Also kein neuer Interrupt obwohl der
alte noch nicht abgearbeitet ist.)
>Des weiteren muss ich innerhalb der ISR (Interrupt Service Routine)>zusätzlich die Interrupts deaktivieren, damit kein (wie nennt sich das>nochmal) overflow stattfindet. (Also kein neuer Interrupt obwohl der>alte noch nicht abgearbeitet ist.)
Nein, das macht der AVR für dich.
Dafür gibt es keine Prioritäten zwischen den Interrupts, die dafür
sorgen könnten, dass ein Interrupt mit höherer Priorität einen mit
niedriegerer unterbrechen kann (vgl. 8051).
@ 1001. Rahul
Achso. Naja aber trotzdem muss man wissen, wie man in den 16-Bit-Timer
innerhalb der 16-Bit-Timer-Interruptroutine eine neue Zählhöhe
reinschreibt, um wie schon erwähnt z.B. eine Melodie an einem Digitalen
Portpin auszugeben. Da liegt momentan mein Problem.
Außerdem halte ich es für Sinnvoll, dass dieser Beispielcode, wenn er
fertig ist und eine Melodie (z.B. Alle meine Entchen) ausgeben kann auch
in das Tutorial hier reinkommt weil das einfach Basics sind, die da
stumpf fehlen, obwohl das Tutorial immer so hoch gelobt wird.
>Achso. Naja aber trotzdem muss man wissen, wie man in den 16-Bit-Timer>innerhalb der 16-Bit-Timer-Interruptroutine eine neue Zählhöhe>reinschreibt,
So wie du es auch ausserhalb des int machst, vielleicht?
Wie sieht das mit den interrupts denn nun eigentlich aus...
Wenn sich dir CPU beispielsweise innerhalt von interrupt vom timer
befindet, was passiert dann wenn ein Interrupt von einem Digitaleingang
kommt? Garnicht? Wird der Interrupt komplett ignoriert? Oder wird er in
eine "warteschleife" gesetzt? Ich habe nur erfahrungen mit einem 8051
und da ist das offensichtlich komplett anders.
>da ist das offensichtlich komplett anders.
Da liegst du richtig.
Hier auf der Seite (oben links bei den Links) gibt es den Abschnitt
"AVR", dort gibt es Tutorien, die sich mit der Einführung zum AVR
befassen.
Das sollte eine Anlaufstelle für die sein, genauso wie der erste Teil
des Datenblattes deines Controller.
Das Tutorial ist mir bekannt. Aber ein Beispiel für die benutzung des
16-Bit Timers gibt es da trotzdem nicht. 8-Bit ist kein Problem. Das
steht da, aber 16-Bit ebend nicht und da der 16-Bit Timer nen ganzes
Stück komplizierter ist, ist das Tutorial so wie es jetzt ist für den
16-Bit Counter völlig unbrauchbar, weil das was bisher zum 16-Bit Timer
im Tutorial steht deutlich ausführlicher im Datenblatt steht. Aber auch
das was im Datenblatt steht ist noch kein brauchbares Beispiel. Was
daher in so ein Tutorial sollte, damit es sich vom Datenblatt
unterscheidet, ist ein vernünftiges Beispiel für den 16-Bit counter. Und
genau das versuche ich momentan (leider bisher mit wenig Erfolg) zu
schreiben.
>da der 16-Bit Timer nen ganzes Stück komplizierter ist
Wo das denn?
>Was daher in so ein Tutorial sollte, damit es sich vom Datenblatt>unterscheidet, ist ein vernünftiges Beispiel für den 16-Bit counter.
Dann weisst du ja, was du demnächst machen wirst...
>Und genau das versuche ich momentan (leider bisher mit wenig Erfolg) zu>schreiben.
Es gibt also schon Code.
Sowas postet man hier und lässt es dann von "uns" und anderen Experten
zerpflücken bis man keine Lust mehr auf controllern oder dieses Forum
hat ;-)
@Rahul
Okay. Dann sei hiermit mein bisheriger Code zur Diskussion freigegeben
;)
#define F_CPU 8000000UL
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
//#include <util/delay.h>
// ab avr-libc Version 1.2.0 möglich und empfohlen:
#include <stdint.h>
// veraltet: #include <inttypes.h>
ISR( TIMER1_OVF_vect )
{
PORTD ^= 0xFF;
}
int main (void) {
//Portkonfiguration
DDRD = 0b11111111; // Datenrichtung des Ports Bit im Register gesetzt
//76543210 // (1) für Ausgang, Bit gelöscht
// (0) für Eingang. Als Ausgang 0=GND 1=VCC
PORTD = 0b11100000; // Ausgang der letzten 5 Bits wird auf Masse
gezogen (LED's eingeschaltet)
//16 Bit Timer
// Die Bits: COM1a1 COM1a0 NA NA NA NA PWM11 PWM10
TCCR1A = 0b00000000;
//PWM11 und PWM10 = 0 => PWM-Betriebsart nicht aktiviert - Timer1
arbeitet als normaler Timer/Zähler
//COM1a1 und COM1a0 = 0 => OC1 wird nicht angesteuert
while(1) {
//Hier in Sleepmodus setzen so dass Interrupts aufwecken
}
/* wird nie erreicht */
return 0;
}
Die AVRs besitzen einen CTC-Modus, bei dem das Nachladen des Timers mit
einem Startwert entfällt. Der ist für solche Sachen wesentlich besser
geeignet, als der Overflow...
Was ist SEI ?
Aktuell siehts jetzt so aus:
ISR( TIMER1_OVF_vect )
{
PORTD ^= 0xFF;
//PORTD = ~PORTD;
}
int main (void) {
//Portkonfiguration
DDRD = 0b11111111; // Datenrichtung des Ports Bit im Register gesetzt
//76543210 // (1) für Ausgang, Bit gelöscht
// (0) für Eingang. Als Ausgang 0=GND 1=VCC
PORTD = 0b11100000; // Ausgang der letzten 5 Bits wird auf Masse
gezogen (LED's eingeschaltet)
//16 Bit Timer
// Die Bits: COM1a1 COM1a0 NA NA NA NA PWM11 PWM10
TCCR1A = 0b00000000;
//PWM11 und PWM10 = 0 => PWM-Betriebsart nicht aktiviert - Timer1
arbeitet als normaler Timer/Zähler
//COM1a1 und COM1a0 = 0 => OC1 wird nicht angesteuert
// Die Bits: ICNC1 ICES1 NA NA CTC1 CS12 CS11 CS10
// CTC1 CS12 CS11
// 0 0 0 angehalten
// 0 0 1 CPU-Takt
// 0 1 0 1/8
// 0 1 1 1/64
// 1 0 0 1/256
// 1 0 1 1/1024
TCCR1B = 0b00000011;
// Die Bits: TOIE1 OCIE1A NA NA TICIE NA TOIE0 NA
TIMSK = 0b10000000; //TOIE1 = 1 => Timer Overflow 1 Interrupt wird
ausgelöst
while(1) {
//Hier in Sleepmodus setzen so dass Interrupts aufwecken
}
/* wird nie erreicht */
return 0;
}
BTW: Wie postet man Quelltext so schön formatiert wie Heinz das gemacht
hat?
> Wie postet man Quelltext so schön formatiert wie Heinz das gemacht> hat?
OK, das steht nicht im Tutorial. Das geht mit "eckige Klammer auf C
eckige Klammer zu" und "eckige Klammer auf /C eckige Klammer zu"...
Test
<c>
#define F_CPU 8000000UL
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
//#include <util/delay.h>
// ab avr-libc Version 1.2.0 möglich und empfohlen:
#include <stdint.h>
// veraltet: #include <inttypes.h>
ISR( TIMER1_OVF_vect )
{
//PORTD ^= 0xFF;
PORTD = ~PORTD;
}
int main (void) {
//Portkonfiguration
DDRD = 0b11111111; // Datenrichtung des Ports Bit im Register gesetzt
//76543210 // (1) für Ausgang, Bit gelöscht
// (0) für Eingang. Als Ausgang 0=GND 1=VCC
PORTD = 0b11100000; // Ausgang der letzten 5 Bits wird auf Masse
gezogen (LED's eingeschaltet)
//16 Bit Timer
// Die Bits: COM1a1 COM1a0 NA NA NA NA PWM11 PWM10
TCCR1A = 0b00000000;
//PWM11 und PWM10 = 0 => PWM-Betriebsart nicht aktiviert - Timer1
arbeitet als normaler Timer/Zähler
//COM1a1 und COM1a0 = 0 => OC1 wird nicht angesteuert
// Die Bits: ICNC1 ICES1 NA NA CTC1 CS12 CS11 CS10
// CTC1 CS12 CS11
// 0 0 0 angehalten
// 0 0 1 CPU-Takt
// 0 1 0 1/8
// 0 1 1 1/64
// 1 0 0 1/256
// 1 0 1 1/1024
TCCR1B = 0b00000011;
// Die Bits: TOIE1 OCIE1A NA NA TICIE NA TOIE0 NA
TIMSK = 0b10000000; //TOIE1 = 1 => Timer Overflow 1 Interrupt wird
ausgelöst
sei();
while(1) {
//Hier in Sleepmodus setzen so dass Interrupts aufwecken
}
/* wird nie erreicht */
return 0;
}
</c>
Warum ziehst du dann nicht das unnütze Test-Posting zurück? Du darfst
das nämlich (für deine Postings). Außerdem hättest du es nach
Feststellung des Nicht-Funktionierens noch passend editieren können,
statt noch eins hinterher zu werfen.
Du willst also einen Melodiegenerator bauen. Sag das doch gleich.
Also: Für sowas würde ich 2 Timer benutzen.
Timer1, der 16 Bit Timer, wird im CTC Modus betrieben.
Seine Aufgabe ist es das Tonsignal zu erzeugen, indem ein
Pin getogeglt wird.
CTC: Clear Timer on Compare. Schau dir das unbedingt im
Datenblatt an! Das Prinzip ist Folgendes: Ein Timer zählt
ja durch bis er seinen Endstand erreicht hat. Dann kann er
einen Interrupt auslösen und fängt wieder bei 0 an. Beim
normalen Timerbetrieb ist der Endstand vorgegeben und er
Interrupt ist der 'Overflow-Interrupt'. Im CTC Modus kannst
du aber den gewünschten Endstand vorgeben und der Interrupt
ist auch ein anderer. Steht aber alles im Datenblatt.
Der Rest ist ein bioschen rumrechnerei: Welches muss der
Timer Endstand sein, damit sich am Output Pin die richtige
Frequenz ergibt.
Soweit zum ersten Timer.
Der zweite Timer ist dafür zuständig, dass die Endstände
für den Timer1 in der richgtigen Reihenfolge gesetzt werden.
Der läuft also durch und sorgt mit seinen Overflows für
das Basistiming aus dem dann letztendendes die Länge der
Achtel-, Viertel-, Halbe-, ... Noten und Pausen erzeugt
werden. Im Prinizp kannst du da die Vorlage von oben nehmen
und dich im Overflow Interrupt austoben. Einfach mitzählen
wieviele Overflows das jetzt waren. Die abzuspielende Note
gibt durch ihre Längenangabe vor, wieviele das sein müssen.
Bei erreichen dieser Anzahl, wird die nächste NOte geholt,
der Endstand für den Timer1 neu gesetzt und in einer globalen
Variablen vermerkt wieviele Overflows der Timer0 wieder abwarten
muss, bis die nächste Note drankommt.
Aber erst mal klein anfangen. Ich würde mal damit anfangen,
den Timer1 im CTC Modus zu betreiben und einen Ton hörbar
zu machen. Die Tonhöhe kann durchaus im Programm zunächst mal
ein fixer Wert wein, der inn das Compare-Register des Timer1
geschrieben wird. Alleine mit dem umprogrammieren dieses
Compare-Wertes kann man schon eine Menge Spass haben:
zb. In einer Schleife den Wert erhöhen und eine kurze Zeit
warten; oder andersrum; oder nach einer Wartezeit von vielleicht
500ms Zufallszahlen reinschreiben, oder ...