Forum: Mikrocontroller und Digitale Elektronik ATmega8 und Sleep


von Christian Rötzer (Gast)


Lesenswert?

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

von mr.chip (Gast)


Lesenswert?

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.

von Christian Rötzer (Gast)


Lesenswert?

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...

von TravelRec. (Gast)


Lesenswert?

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.

von Christian Rötzer (Gast)


Lesenswert?

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 :-)

von Hannes L. (hannes)


Lesenswert?

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.

...

von Christian Rötzer (Gast)


Lesenswert?

Ja, nicht schlecht, aaaaber: Was ist denn, wenn INT0 schon auf 0 liegt,
bevor sleep aufgerufen wird? Praktisch in Listing "tiefschlaf" ab
Zeile 8...

von A.K. (Gast)


Lesenswert?

"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.

von Hannes L. (hannes)


Lesenswert?

> 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.

...

von Christian Rötzer (Gast)


Lesenswert?

@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>

von A.K. (Gast)


Lesenswert?

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."

von Christian Rötzer (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

> 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???

...

von Christian Rötzer (Gast)


Lesenswert?

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 :-)

von Hannes L. (hannes)


Lesenswert?

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
Noch kein Account? Hier anmelden.