Forum: Mikrocontroller und Digitale Elektronik AVR: Timer-Interrupt Frage


von Andreas (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

kurze Frage zu einem Timer, der einen Interrupt auslösen soll, es aber
nicht tut! Das kurze Programm ist aus einem Tutorial und soll auf dem
STK500 im Sekundentakt die LEDs zu Leuchten bringen. Aber weder auf dem
STK noch in der Simulation funktioniert das. Im Interrupt Handler
befindet sich ein Zähler (counter = r17), der dekrementiert werden
soll, aber das wird er nicht. Im Simulator wird dieses Register auf den
Startwert 32 gesetzt und da bleibt er dann. Vielleicht kann jemand
helfen.
Ach so, noch eine, kurze Frage: was sollen die vielen reti Befehle im
Initialisierungsteil für die Interrupthandler? Es ist doch nichts
angesprungen worden, also muß auch nicht zurück gesprungen werden.

Danke & Gruß,
Andreas

von Hannes L. (hannes)


Lesenswert?

Die RETIs haben schon ihren Sinn.

Denn der erste Teil des Programmspeichers ist die
Reset-/Interrupt-Sprungtabelle. Dabei wird die erste Zelle (Adresse 0)
beim Reset aufgerufen, die 2. Zelle bei einem INT0-Ereignis usw...
Nun hat aber jeder AVR eine etwas andere Hardware-Ausstattung. Daher
ist diese Sprungtabelle für jedem AVR anders. In deinem Fall ist der
benötigte Timer0-Interrupt an Adresse 6 (falls deine Tabelle stimmt,
ich habe sie jetzt nicht mit dem Datenblatt verglichen).
Das Ereignis "Timer0 läuft über" wird also von der Hardware erkannt
und als Reaktion darauf wird der aktuelle Programmcounterstand auf
Stack gelegt und Adresse 6 aufgerufen. Dort hat dann der Sprung zu
stehen, der in deine ISR (Interrupt-Service-Routine) springt, in der du
auf das Ereignis (Int) reagierst. RETI am Ende der ISR sorgt dafür, dass
der Programmcounter vom Stack geholt wird und das Programm an der alten
Stelle fortgesetzt wird.

Es ist daher wichtig, dass die Interrupt-Sprungtabelle nicht
durcheinander gerät. Eine einfache und übersichtliche Möglichkeit ist
es, Dummy-Befehle zum Füllen der unbenutzten Speicherzellen zu
benutzen. Dies ginge z.B. auch mit NOP. RETI hat aber den Vorteil, dass
ein versehentlich ausgelöster Interrupt (Programmierfehler durch
Missverstandenes Datenblatt) keinen Schaden anrichtet, sondern sauber
beendet wird.

Warum dein Programm nun nicht läuft, kann ich auf die Schnelle nicht
erkennen. Ich kann aber spekulieren:

- Benutzt du auch exakt den AVR-Typ, auf den die Int-Vektoren
  abgestimmt sind? (Wenn nicht, dann verpufft dein Int in
  irgendeinem RETI, weil die Vektoren nicht stimmen...)
- Stimmt deine Include-Datei mit dem verwendeten AVR überein?
  (wenn nicht, dann stimmen diverse Konstanten nicht...)
- Verstehst du den benutzten Programmcode?
  (wenn nicht, dann solltest du den Code analysieren und mit Hilfe
  des Datenblattes, des AVR-Instruction-Set und der Hilfe zum
  AVR-Assembler verstehen, ansonsten hast du schlechte Karten...)

Bit- & Bytebruch...
...HanneS...

von Andreas (Gast)


Lesenswert?

Hi,

erstmal vielen Dank für die ausführliche Antwort. Tatsächlich stand der
Einsprung in die Interrupt Service Routine an der falschen Stelle (an 7.
statt an 8.).
Leider funktioniert das trotzdem noch nicht. Der Timer wird mit einem
Vorteiler von 1024 initialisiert, zusätzlich wird noch Register 17 mit
dem Wert 32(dez) geladen. Dieses Register soll nun in der Interrupt
Routine dekrementiert werden. Insgesammt soll das bei einem Takt von 4
MHz eine Verzögerung von einer Sek. ergeben. Das Problem ist wie
vorher: der Simulator in AVR-Studio springt erst gar nicht in die
Interrupt Routine. Auf der MCU funktioniert es übrigens auch nicht.

Was ich noch merkwürdig finde, ist diese Service Routine selbst:

---------
timer0:
  dec counter
  brne restart
    ldi counter, loops
    com leds
    out PORTB, leds
  restart:
    ldi temp, start
    out TCNT0, temp
reti
----------

Hier wird counter (r17) also dekrementiert. Wenn r17 = 0, dann springe
zu restart. Und jetzt das, was ich nicht verstehe: wenn r17 != 0 ist,
wird zum nächsten Befehl nach brne gesprungen. Und hier wird counter
wieder mit 32 (.equ loops =32) geladen. So kann der doch gar nicht
dekrementiert werden, oder sehe ich das falsch?
Mal abgesehen davon, daß diese Interruptbehandlung nie angesprungen
wird. ;)

Bin für alle Hinweise dankbar. Es geht mir nur um's Verständnis.

Gruß,
Andreas

von Mark H. (haemi)


Lesenswert?

Salve,

falsch. "brne" verzweigt dann, wenn das Zero-Flag nicht gesetzt ist
("branch if not equal (zero)"). Das Zero-Flag wird bei "inc" und
"dec" dann gesetzt, wenn das Ergebnis gleich Null ist. Also wird
counter dann neu geladen, wenn er gleich Null war.

Hoffe, das hat zur Klärung beigetragen. :)

Mark

von Andreas (Gast)


Lesenswert?

Hallo nochmal,

ja, Deine Richtigstellung hat zur Klärung beigetragen. War ein blöder
Fehler meinerseits. Das ganze funktioniert jetzt so, wie es soll.

Allerdings nur auf dem Controller, nicht in der Simulation. Kann es
sein, daß dort die Interrupts oder die Timer nicht richtig simuliert
werden?

Andreas

von Andi K. (Gast)


Lesenswert?

Klar geht das auch im Simulator. Habs gerade ausprobiert.
Nur um das X-1000 Fache langsammer oder gar Millionen Fache im
AutoStep-Mode.
Setz mal zum Simulieren den Prescaler auf 64 (011) oder weniger, setze
einen Breakpoint beim ersten Befehl in der ISR und klick auf Run oben
in der Leiste.

Dir ist schon klar, das man bei normaler Anwendung das Statusregister
(SREG) und die verwendeten Register in der ISR am Anfang sichern und am
Ende zurückholen sollte (Stack)?

Versuche möglichst schnell, mit Variablen im SRAM zu arbeiten denn mit
reservierte Register als "Variablenträger" kommt man nicht weit,
abgesehen vom Tiny11/12/15 etc., die haben sowieso keinen RAM.

MfG
Andi

von Andreas (Gast)


Lesenswert?

Hi Andi,

auch Dir vielen Dank für die Tips! Mir ist bisher noch gar nicht
aufgefallen, daß es im AVR-Studio auch sowas wie eine Stopuhr gibt. Und
die läuft in Microsekunden. Kein Wunder, daß ich beim Simulieren des
Programms keine Änderungen feststellen konnte. :)

Was das Sichern der Register angeht, das habe ich schon öfter in
diversen Programmen gesehen, aber warum ist das so wichtig? Der
Stackpointer wird beim Sprung in die ISR doch sowieso gesichert und
wenn ich darauf achte, keine Register doppelt zu verwenden, sollte es
doch kein Problem geben, oder?

Zu den Variablen: bei kleineren Programmen reichen eigentlich die
Register. Und wenn ich mich nicht irre, muß bei Verwendung des SRAMs
der Inhalt erst in die Register kopiert werden, wo die Werte dann
manipuliert werden können, und anschließend muß zurück kopiert werden.
Den Aufwand (zeitlich und auch sonst) würde ich nur machen, wenn die
Register nicht reichen.

Danke & Gruß,
Andreas

von Hannes L. (hannes)


Lesenswert?

Es geht um das Statusregister. Dieses enthält die Flags, die von
Operationen und Vergleichen verändert werden und den bedingten Sprüngen
(BRxx) als Bedingung dienen.
Wenn nun das Hauptprogramm von einem Int in dem Moment unterbrochen
wird, wo die Flags zwar verändert, aber noch nicht "ausgewertet"
sind, dann verändert der Int diese Flags und das Hauptprogramm reagiert
auf falsche Flags, springt also in die Irre (rechnet falsch?). Deshalb
muss das SREG in der ISR gesichert und wiederhergestellt werden wenn
die ISR Befehle enthält, die das SREG beeinflussen.

Mit den Variablen in Registern bin ich auch noch etwas großzügig
(solange die Register reichen), das kommt aber daher, dass ich mit dem
AT90S1200 begann und auch oft mit Tiny12/15 hantiere.

Inzwischen habe ich aber auch festgestellt, dass sich Programme
leichter in (wiederverwendbare) Module aufteilen lassen, wenn man die
Variablen konsequent im SRAM hält. Dafür habe ich aber auch gelernt,
den Controller nicht unnötig in Warteschleifen zu schicken. Das holt
die durch SRAM-Zugriffe vertrödelte Zeit mehrfach wieder raus.

...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.