Hallo zusammen, ich versuche gerade für den ATmega8 eine Routine zu schreiben, die den AVR in den Sleep-Modus schickt. Über den INT0 soll per Levelinterrupt geweckt werden. Das funktioniert soweit auch alles. Es gibt da nur ein Problem: Der Interrupt wird ausgelöst, wenn INT0 auf 0-Pegel geht. Bevor man nun die Sleep-Anweisung losschickt, muß man den globalen Interrupt aktivieren. Dieser Interrupt deaktiviert nach aufwachen unmittelbar wieder den INT0-Interrupt. Wenn nun der Pegel aber schon vor Aufruf der Sleep-Anweisung auf 0 geht, wird demnach vor Aufruf der Sleep-Anweisung der Interrupt ausgelöst, dann die Sleep-Anweisung ausgeführt und somit kann man den AVR gar nicht mehr wecken. Wie ist denn hier die anerkannte Vorgehensweise? Grüße Christian
Hallo Ich würde es so machen: INT0: WENN flag nicht gesetzt PIN = 1 SONST tue etwas... MAIN: flag = 0 interrupt aktivieren sleep flag = 1 Alternativ könnte man auch die Flankenwechsel auswerten anstatt ein bestimmter Pegel.
Was meinst Du mit PIN = 1? Evtl.: WENN flag nicht gesetzt tue nix SONST tue etwas... Quasi ein Sub-Interrupt-Enable. Hm, das könnte gehen... Ich habe jetzt gerade mal ausprobiert, im Interrupt nichts zu tun, und unmittelbar nach der Sleep-Anweisung den Interrupt wieder auszuschalten: // Global interrupt enable sei(); // Now, goto sleep sleep_mode(); // Immediately turn off interrupts cli(); Ich programmiere in (GC)C und daher vergeht zwischen dem Aufwachen und dem Cli soviel Zeit, dass der Interrupt (wegen Level-Interrupt, was anderes geht nicht) viermal auslöst. Wahrscheinlich werde ich einfach damit leben...
Alternative: in ASM programmieren... ;-) Das deaktivieren des Int0 würde reichen, dazu muß vor dem Sleep aber auch das zugehörige Flag durch Schreiben einer "1" gelöscht werden, damit der Prozi überhaupt erst einschläft und nicht gleich wieder in den Interrupt läuft, weil die Int0-Leitung zwischendurch mal "gewackelt" hat.
Tja, da kann ich Dir nur recht geben. Eine wirklich saubere Lösung scheint's aber nicht zu geben. Eine schweinische hätte ich noch: Im Zuge der Abarbeitung des INT0-Interrupts nachsehen, ob die Rücksprungaddresse schon "hinter" den sleep-Befehl zeigt. Nur in diesem Fall disablet sich der INT0 selber. Wie gesagt, schweinisch, sowas würde ich nieeeeee programmieren :-)
Alle meine Programme nutzen einen Timer-Interrupt. Fast alle meiner Programme gehen in der Mainloop in den Sleep-Zustand und werden vom Timer-Int wieder geweckt. Dazu ist Sleep-Mode 'Idle' erforderlich, damit die Timer weiter laufen können. Soll der Controller nun in der 'Tiefschlaf' (Power-Down-Mode) versetzt werden, dann ist der Low-Level-Interrupt zum Wecken scharf zu machen (vorher das Interrupt-Flag in GIFR löschen, indem man das Bit setzt) und der Sleepmode auf Power-Down umzuschalten:
1 | tiefschlaf: ;AVR in Standby-Betrieb schalten um Strom zu |
2 | sparen |
3 | ldi wl,down ;Sleep auf Power-Down |
4 | out mcucr,wl ;umschalten (Strom sparen) |
5 | ldi wl,1<<int0 ; |
6 | out gifr,wl ;Flag vom vorhergehenden Ext-Int löschen |
7 | out gimsk,wl ;externen Interrupt einschalten |
8 | out tccr1,null ;Timer1 deaktivieren |
9 | ldi wl,taste ;PullUp nur für Interrupt-Eingang |
10 | out tap+2,wl ;aktivieren und Lautsprecher auf L-Pegel |
11 | rjmp mainloop1 ;schlafen legen... |
Die Mainloop schickt den AVR dann statt in den Halbschlaf (Idle) in den Tiefschlaf zum Strom sparen:
1 | mainloop: ;Hauptschleife |
2 | sbrc tfl,klintas ;Klingel-Taster betätigt gewesen? - nein... |
3 | rjmp start ;ja, abarbeiten... |
4 | sbrc tfl,stoptas ;Stop-Taster betätigt gewesen? - nein... |
5 | rjmp stop ;ja, abarbeiten... |
6 | sbrc tfl,busy ;Soundausgabe aktiv? nein... |
7 | rjmp mainloop2 ;ja... |
8 | dec aus ;Timeout runterzählen |
9 | breq tiefschlaf ;AVR ausschalten... |
10 | mainloop1: |
11 | sleep ;bis zum nächsten Interrupt schlafen |
12 | rjmp mainloop ;nochmal |
Ein Low-Level am ext-Int löst den Interrupt aus. In dieser ISR wird der Sleepmode sofort auf 'Idle' zurückgestellt und der externe Interrupt deaktiviert, er soll schließlich nur einmal pro Wecken auslösen:
1 | EXT_INT0: ;ISR externer Interrupt |
2 | ldi wl,idle ;Sleep auf IDLE |
3 | out mcucr,wl ;umschalten |
4 | out gimsk,null ;ext.Int ausschalten |
5 | ldi wl,tasten ;PullUps für alle Tasten |
6 | out tap+2,wl ;aktivieren |
7 | clr num ;Soundnummer voreinstellen |
8 | reti ;fertig... |
Die gezeigten Fragmente stammen aus einem Programm für Tiny15, das einen Türgong mit mehreren Melodien realisiert. ...
Ja, nicht schlecht, aaaaber: Was ist denn, wenn INT0 schon auf 0 liegt, bevor sleep aufgerufen wird? Praktisch in Listing "tiefschlaf" ab Zeile 8...
"Wenn nun der Pegel aber schon vor Aufruf der Sleep-Anweisung auf 0 geht, wird demnach vor Aufruf der Sleep-Anweisung der Interrupt ausgelöst." Ganz so dusselig war Atmel nicht. SEI aktiviert Interrupts erst nachdem der nächste Befehl gestartet wurde. Somit ist die Folge SEI SLEEP narrensicher, weil zwischen SEI und SLEEP kein Interrupt reinpasst. Geht auch in C, dafür empfiehlt sich allerdings die neueste WinAVR Version.
> Ja, nicht schlecht, aaaaber: Was ist denn, wenn INT0 schon auf 0 > liegt, > bevor sleep aufgerufen wird? Das kommt im Normalfall nicht vor. Und wenn es vorkommt, dann wird der AVR wieder geweckt und geht wieder in den Normalbetrieb, fragt also in der Timer-ISR die Taster ab und tut seine Arbeit. Erst wenn der Timeout-Zähler abgelaufen ist (nach 256 Timer-Interrupts) wird dann wieder in den Tiefschlaf geschaltet. Wurde in dieser Zeit aber der Starttaster erneut betätigt, dann startet das Programm einen neuen Ausgabezyklus, in dem bei jedem Timer-Interrupt der Timeout-Zähler gelöscht wird, so dass dieser während der Ausgabe nicht ablaufen kann. Daher kann der Int0 beim Ablaufen des Timeouts garnicht low sein. ...
@A.K. Wenn das stimmt, was Du sagst ist's in der Tat kein Problem. Ich kann die Stelle im AVR Instruction Set aber nicht finden. Das müßte ich glatt ausprobieren! @HanneS <Prinzipienreiter on> Wenn es doch vorkommt, wird Dein INT0-Interrupt weitere INT0-Interrupts ausschalten. Dann wird sleep ausgeführt. Der AVR wird dann nicht mehr aufwachen. <Prinzipienreiter off>
So schwer zu finden? Aus der AVR Instruction Set Reference (ja, die gibt es! ~150 Seiten), über SEI: "Sets the Global Interrupt Flag (I) in SREG (Status Register). The instruction following SEI will be executed before any pending interrupts."
Wie gesagt: "Ich kann die Stelle im AVR Instruction Set nicht finden". Damit wollte ich sagen, dass ich ins AVR Instruction Set geschaut habe. Es mag daran liegen, dass meines nur 133 Seiten aufweist. <Bin mal kurz bei Atmel> Aha. 150 Seiten. Na also, da steht's ja! Mit Beispiel! Bezieht sich exakt auf meine Situation :-) Damit erkläre ich die Sache für hinreichend geklärt. Danke an alle! Grüße Christian
> Wenn es doch vorkommt, wird Dein INT0-Interrupt weitere INT0- > Interrupts > ausschalten. Dann wird sleep ausgeführt. Der AVR wird dann nicht > mehr > aufwachen. Das sehe ich anders. - Vor Einschalten des ext-Int wird das Interrupt-Flag gelöscht. Somit kann ein alter Int nicht greifen. - Sleep wird am Ende der Mainloop aufgerufen, und zwar immer, auch wenn der AVR arbeitet, nur ist dann der Mode 'Idle' ausgewählt. - Wird direkt vor Sleep der ext-Int ausgelöst, dann wird noch inner- halb der ISR der Sleepmode auf 'idle' umgeschaltet, worauf der Timer seinen Takt bekommt und den AVR wecken kann. Es herrscht also Normalbetrieb. - Wie um alles in der Welt soll der von dir befürchtete Zustand eintreten??? ...
Dann seh ich's genauso! Der Timer tritt dem AVR quasi in den Hintern, wenn er im Idle mode ist. Dann ist es ja in Ordnung :-)
Ich habe mir nochmal die ersten Beiträge durchgelesen und bin auf Dieses gestoßen: > Bevor man nun > die Sleep-Anweisung losschickt, muß man den globalen Interrupt > aktivieren. Dieser Fall tritt praktisch nie auf. Der globale Interrupt wird bei mir grundsätzlich bereits am Ende der Reset-Routine aktiviert. Denn jedes meiner Programme braucht mindestens einen Timer-Interrupt zur Steuerung des gesamten Programmablaufs. Und daher ist (bei den neueren Programmen) die Mainloop so gestaltet, dass sie nach jedem Interrupt solange durchlaufen wird, bis alle (per Jobflags verwalteten) Jobs und noch anstehenden (bereits entprellten) Tastendrücke abgearbeitet wurden. Danach geht die Mainloop in den Sleep-Zustand, aus dem ein Interrupt (meist der Timer) den AVR wieder aufweckt und nach der ISR die nächste Runde der Mainloop auslöst. Dies ist bei mir Normalzustand für ein arbeitendes Programm. Meist ist das Abarbeiten der Jobs fertig, bevor der nächste Interrupt auftritt. Wenn nicht, dann finden eben mehrere Interrupts statt, bis das Programm wieder in der Mainloop und im Sleep landet. Der globale Interrupt ist also immer (außerhalb von ISRs) aktiv, abgesehen von einigen Ausnahmen, bei denen der globale Interrupt mal kurzzeitig gesperrt wird, wie z.B. EEPROM-Schreibzugriff. ...
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.