Forum: Mikrocontroller und Digitale Elektronik Morgenmuffel: avr tiny45 quarz power-down sleep


von Bastler (Gast)


Lesenswert?

Guten Morgen!


Ich habe einen tiny45 samt 32kHz Quarz, der in den power-down versetzt 
wird, und anschließend mit einem pin-change interrupt aufgeweckt und 
direkt in den idle mode versetzt wird.

Fuse ist auf Low-Frequency Crystal Oscillator gesetzt (Verhalten 
unabhängig von startup time).

Der Quarz hat keine Lastkapazitäten (laut Datenblatt: Empfehlung 
Quarzhersteller beachten: 16pF), läuft aber bei reset trotzdem 
problemlos an (andere AVRs können das wohl auch ohne Lastkapazitäten 
gemäß Spezifikation).

Initialisierung
1
GIMSK  = 1<<PCIE;
2
 PCMSK  = 1<<PCINT2;
3
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
4
sei();

Aufwachen
1
ISR(PCINT0_vect)
2
{
3
  GIMSK &= ~(1<<PCIE);          
4
  set_sleep_mode(SLEEP_MODE_IDLE);
5
}

'Runterfahren in main endlos
1
sleep_mode();
2
3
if(irgendwas){
4
  GIMSK |= (1<<PCIE);
5
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
6
}


Das Ganze funktioniert auch nach dem einschalten, aber anschließend 
manuell ("irgendwas") nicht:

anschalten .. ok
power down .. ok
aufwecken durch pcint, pcint deaktivieren, sleep .. ok
irgendwas: pcint aktivieren, power down .. ok
aufwecken durch pcint .. error

So, warum wird der PCINT nicht mehr ausgeführt?

Ich habe irgendwo gelesen, dass Quarze eine lange Anschwingzeit haben. 
Setze ich die Fuses auf +64 ms gibt es keinen Unterschied.

Ich habe ausserdem gelesen, dass man den AVR nicht direkt nach dem 
Aufwecken wieder in einen sleep mode versetzen sollte, habe dazu aber 
nichts genaueres gefunden.

Datenblatt tiny45
http://www.atmel.com/dyn/resources/prod_documents/doc2586.pdf

http://www.mikrocontroller.net/articles/Sleep_Mode#Morgenmuffel

Vielleicht habt ihr ja einen Tipp parat..

von Andreas K. (a-k)


Lesenswert?

Bastler wrote:

> Ich habe ausserdem gelesen, dass man den AVR nicht direkt nach dem
> Aufwecken wieder in einen sleep mode versetzen sollte, habe dazu aber
> nichts genaueres gefunden.

Das trifft auf AVRs zu, die mit schnellem internen Takt arbeiten und nur 
den Timer2 mit 32KHz (asynchron) fahren. Was hier kaum der Fall sein 
kann, weil der Tiny45 m.W. nur komplett auf 32KHz geschaltet werden 
kann.

von Andreas K. (a-k)


Lesenswert?

Als Test kannst du mal den Tiny mit einem externen Takt an Stelle des 
Quarzes versorgen, um die Fehlerquelle Oszillator auszuschliessen.

Fehlende Lastkapazitäten können zu seltsamem Quarzverhalten führen. Und 
für die Frage, inwieweit AVR Oszillatoren im LF mode tatsächlich interne 
Lastkapazitäten haben, reicht ein Blick in den Hauptteil des Datasheets 
nicht aus (Mega8:ja), dazu muss man auch hinten in den Errata-Teil 
reinschauen (Mega8:ätsch!).

von Peter D. (peda)


Lesenswert?

Bastler wrote:
>
1
>   GIMSK |= (1<<PCIE);
2
>   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
3
>

Nun überleg mal, wenn genau dazwischen ein PC-Interrupt reinfährt.


Peter

von Bastler (Gast)


Lesenswert?

Dann schläft er wohl für immer friedlich mit deaktiviertem PCINT :-)

Ich hatte die Stelle sogar schon böse angestarrt, allerdings habe ich 
irgendwie gedacht, dass set_sleep_mode den Rechner schlafen legt, und da 
wäre  interrupts ausschalten ja eher schlecht gewesen.

Danke jedenfalls.

Also besser so:
1
cli();
2
GIMSK |= (1<<PCIE);
3
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
4
sei();

von Bastler (Gast)


Lesenswert?

Ach so, den Tipp mit dem externen Takt habe ich auch befolgt; ich habe 
mangels Funktionsgenerator einen DS3234 benutzt. Der spuckt auch in 
Minimalbeschaltung  3276Hz aus (vcc, gnd, 32khz). Fehlerquelle 
ausgeschlossen ;-)

von Andreas K. (a-k)


Lesenswert?

Siehe Beispiel in:
http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html

Entscheidend dabei ist:

- Es darf vor dem SLEEP Befehle nicht vorkommen, dass der den Prozessor 
aufweckende Interrupt schon bearbeitet wird.

- Es darf daher im Assembly-Listing zwischen SEI und SLEEP kein weiterer 
Befehl zu sehen sein.

von Peter D. (peda)


Lesenswert?

Bastler wrote:

> Also besser so:
>
>
1
> cli();
2
> GIMSK |= (1<<PCIE);
3
> set_sleep_mode(SLEEP_MODE_PWR_DOWN);
4
> sei();
5
>

Jain, da ist noch ne Fallgrube im GCC.

Die Funktion sleep_mode() ist nämlich Bullshit, da sie nicht 
interruptfest ist.
Sie darf nie und auf keinen Fall verwendet werden !!!


Die einzig richtige Reihenfolge ist im sleep.h zu Anfang angegebene:
1
set_sleep_mode(<mode>);
2
cli();
3
if (some_condition)
4
{
5
  sleep_enable();
6
  sei();
7
  sleep_cpu();
8
  sleep_disable();
9
}
10
sei();


Peter

von Peter D. (peda)


Lesenswert?

Andreas Kaiser wrote:
> Siehe Beispiel in:
> http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html

Der Satz "As this combined macro might cause race conditions in some 
situations" ist ja wohl die glatte Untertreibung.

Er müßte richtig heißen.
"This combined macro definitively cause race conditions (sooner or 
later)"


Peter

von Andreas K. (a-k)


Lesenswert?

Peter Dannegger wrote:

> "This combined macro definitively cause race conditions (sooner or
> later)"

Yep. Das war von ein Paar Jahren mal ein Thema, damals war ich über 
dieses Unvermögen der alten Version gestolpert. Mein Alternative dazu 
sah ein bischen anders aus als die daraufhin integrierte, weil mir diese 
Zwangskopplung zweiter aufeinanderfolgender C Statements [sei() => 
sleep_cpu()] nicht behagt, lief aber auf das gleiche Ergebnis raus.

von Bastler (Gast)


Lesenswert?

Aha. Das geht für mich aus 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Sleep-Modes 
leider nicht so eindeutig hervor.

Also im Klartext heisst das, mein sleep_mode() in der endlosschleife der 
main muss durch
1
cli();
2
sleep_enable();
3
sei();
4
sleep_cpu();
5
sleep_disable();
6
sei();

ersetzt werden?

von Andreas K. (a-k)


Lesenswert?

1
cli();
2
sleep_enable();
3
sei();
4
sleep_cpu();
5
sleep_disable();
reicht aus, wenn die if()-Bedingung des Beispiels nicht nötig ist.

von Bastler (Gast)


Lesenswert?

Danke!

von Peter D. (peda)


Lesenswert?

Andreas Kaiser wrote:
>
1
> cli();
2
> sleep_enable();
3
> sei();
4
> sleep_cpu();
5
> sleep_disable();
6
7
>
> reicht aus, wenn die if()-Bedingung des Beispiels nicht nötig ist.

Wenn ich mir das genau ansehe, nein.
Das sleep_disable() ist ja auch nicht interruptfest.
Richtig müßte es so sein:
1
 cli();
2
 sleep_enable();
3
 sei();
4
 sleep_cpu();
5
 cli();
6
 sleep_disable();
7
 sei();

Wobei ich allerdings nicht den Sinn verstehe, warum man überhaupt die 
Modebits und das Enablebit getrennt setzen sollte.
Wenn man einen Mode setzt, impliziert das doch auch, daß man schlafen 
will.


Peter

von Andreas K. (a-k)


Lesenswert?

Der Gedanke dahinter ist wohl, dass man bei abgeschaltetem SE sich nicht 
versehentlich mal schlafen legt und damit stehen bleibt. Kann ich auch 
nur begrenzt nachvollziehen, aber was soll's.

Bleibt in der Sache zu berücksichtigen, dass man sich nur schlafen legen 
darf, wenn man sicher sein kann, dass das Event was einen aufwecken 
soll, auch noch eintreffen wird. Und nicht schon gerade eben 
eingetroffen ist.

Deshalb sieht sowas ohne dieses rein-raus mit SE oft ungefähr so aus wie 
im Beispiel skizziert:

cli
wenn es überhaupt noch einen Grund gibt sich schlafen zu legen, dann
  sei
  sleep
sei

von Hagen R. (hagen)


Lesenswert?

die Sequenz die ich immer benutze sieht so aus:
1
cli();
2
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
3
sleep_enable();
4
GIMSK |= (1 << PCIE);
5
GFIR = (1 << PCIF);
6
sei();
7
sleep_cpu();
8
cli();
9
GIMSK &= ~(1 << PCIE);
10
sei();

wobei ich dann aber meistens auf die WinAVR GCC Sleep Makros verzichte 
und selber die entsprechenden Register manuell setze, produziert 
expliziteren Source.

Wichtig ist das das PCIF in GFIR gelöscht wird nach dem Aktivieren des 
PCINTs.

verkürzt (mit bestimmten Rahmenbedingungen) sieht es dann so aus:
1
        MCUCR  = (1 << SE) | (1 << SM1);
2
        cli(); // asm volatile("cli");
3
        GIMSK  = (1 << PCIE);
4
        GIFR   = (1 << PCIF);
5
        sei(); // asm volatile("sei");
6
        sleep_cpu(); // asm volatile("sleep");
7
        GIMSK  = 0;

Gruß Hagen

von Andreas K. (a-k)


Lesenswert?

Wenn man den pin-change-int erst einschaltet, wenn man sich schlafen 
legt, dann sollte man sich schon sicher sein, dass der noch kommt und 
nicht grad schon war. Bei Tastern ist das meist weniger ein Problem, 
aber bei Ereignissen deren Abstand man absolut nicht vorhersagen kann, 
ist das durchaus ein Thema (z.B. bei einem I2C-Slave, der sich 
zwischendurch schlafen legt).

von Hagen R. (hagen)


Lesenswert?

korrekt, es hängt aber von der Zielstellung ab. In meinem Fall benutze 
ich den PCINT zum Aufwecken das AVRs aus dem Powerdown, das weitere 
Prozessing des PCINT Pins erfolgt dann nach dem Aufwachen des AVRs. 
Solange also der AVR nicht im Powerdown ist wird die am PCINT Pin 
angeschlossene Hardware anders ausgewertet. Solange diese HW noch aktiv 
ist wird auch nicht in den Powerdown gewechselt. Das ist meistens der 
Fall da man öfters noch andere Peripherie wie zb. Timer/ICP mit diesem 
Pin verknüpft hat und diese Peripherie wäre im Powerdown sowieso 
deaktiviert.

Gruß Hagen

von Peter D. (peda)


Lesenswert?

Hagen Re wrote:

> Wichtig ist das das PCIF in GFIR gelöscht wird nach dem Aktivieren des
> PCINTs.

Ist aber kein Muß.
Man würde nur sofort wieder aufwachen und dann die Sleepsequenz nochmal 
durchlaufen.
Ein Deadlock kann nicht passieren.


> verkürzt (mit bestimmten Rahmenbedingungen) sieht es dann so aus:
>
1
>         MCUCR  = (1 << SE) | (1 << SM1);
2
>         cli(); // asm volatile("cli");
3
>         GIMSK  = (1 << PCIE);
4
>         GIFR   = (1 << PCIF);
5
>         sei(); // asm volatile("sei");
6
>         sleep_cpu(); // asm volatile("sleep");
7
>         GIMSK  = 0;
8
>

Ja das sieht schön aus, direkte Zuweisungen statt umständliche 
Veroderungen.

Und die Freigebe des Weckinterrupts unter Interruptsperre direkt vor 
SEI,SLEEP, da kann ja nichts mehr schiefgehen.


Peter

von Peter D. (peda)


Lesenswert?

Andreas Kaiser wrote:
> Wenn man den pin-change-int erst einschaltet, wenn man sich schlafen
> legt, dann sollte man sich schon sicher sein, dass der noch kommt und
> nicht grad schon war.

Stimmt.
Wenn der nächste PCI erst kommt, nachdem man auf den vorherigen reagiert 
hat, dann gibts wieder einen Deadlock.

Also das PCIF nicht händisch löschen, sondern lieber nen 2.Schlafversuch 
riskieren, das ist universeller.



Peter

von Hagen R. (hagen)


Lesenswert?

>Also das PCIF nicht händisch löschen, sondern lieber nen 2.Schlafversuch
>riskieren, das ist universeller.

Mag sein, aber bei meinem letzten Projekt (Firefly in CodeLib) musste 
ich vor der Sleep Sequenz den PullUp am PCINT Pin de/aktivieren. Das 
führt dann par Takte später zum Auslösen des PCINT, deshalb lösche ich 
lieber das PCIF in GFIR.

Erschwerend kam hinzu das der AVR über den Watchdog eine grobe Wartezeit 
verbringen musste. Ein doppelt Auslösen des PCINT veränderte also die 
Gesamtwartezeiten des AVRs über den WDT. Mal dahingestellt das der WDT 
nicht sehr genau ist, aber der vor dem Powerdown eingestellte WDT 
Timeout sollte unabhängig doppelter (falscher) PCINTs eingehalten 
werden. Das erklärt also warum ich lieber das PCIF manuell lösche wenn 
ich vor dem aktivieren des PCINT auch noch die Pullups am zu 
überwachenden Pin aktiviere.

Übrigens löche ich das PCIF auch innerhalb der PCINT ISR am Ende dieser 
ISR sher häufig. Das hängt aber eben von der Zielsetzung ab. Ist der 
PCINT nur zum Aufwecken des AVRs gedacht dann meine ich sollte man das 
PCIF auch löschen.

Gruß Hagen

von Bastler (Gast)


Lesenswert?

Super, das Löschen von PCIF hat auch mein nächstes Problem gleich 
erschlagen!

Kann das sein, dass das Datenblatt (2586K–AVR–01/08) da Quatsch erzählt?

• Bit 5 – PCIF: Pin Change Interrupt Flag
When a logic change on any PCINT5:0 pin triggers an interrupt request, 
PCIF becomes set (one).
.....
Alternatively, the flag can be cleared by writing a logical one to 
it.

Ausserdem hätte ich gerne gewusst, warum Hagen Re den Interrupt nicht in 
der ISR löscht, sondern danach.

Danke.

von Andreas K. (a-k)


Lesenswert?

Bastler wrote:

> Alternatively, the flag can be cleared by writing a logical one to

Doch, das stimmt so.

Es gibt bei AVRs nur wenige Interrupts, bei denen es in der ISR nötig 
ist, sie explizit zu löschen. Bei den meisten geschieht dies automatisch 
entweder bereits im Rahmen des Aufrufs der ISR oder durch Zugriff in der 
ISR auf ein Transferregister.

von Bastler (Gast)


Lesenswert?

Hi, ich meinte den ersten Teil des Absatzes:
Wenn ein PCINT auftritt, müsste PCIF doch 0 (zero) werden !?

Danach habe ich Unverständliches gefaselt und meinte das Deaktivieren 
des Interrupts durch GIMSK  = 0;

Ich mache das bislang direkt in der PCINT-ISR. Allerdings gibt es ja nur 
zwei Fälle:

In der ISR:
wenn der PCINT nur zum aufwachen benutzt wird, muss sleep nur einmal im 
programm vorkommen.

Nach sleep:
flexibler. Der Befehl muss ggf. nach jedem sleep aufgerufen werden.

von Andreas K. (a-k)


Lesenswert?

Bastler wrote:

> Wenn ein PCINT auftritt, müsste PCIF doch 0 (zero) werden !?

Wenn der Pinzustand wechselt, wird PCIF 1. Wenn die ISR aufgerufen wurd, 
wird PCIF automatisch 0.

von Hagen R. (hagen)


Lesenswert?

>Danach habe ich Unverständliches gefaselt und meinte das Deaktivieren
>des Interrupts durch GIMSK  = 0;

Du musst das nicht so machen wie ich es oben gezeigt habe und kannst 
GIMSK auch wenn nötig in der PCINT-ISR löschen. In meinem Fall wurde der 
PCINT benutzt um zb. am Pin PB2 durch einen 1Wire-Bootloader den AVR aus 
dem Powerdown zu wecken und danach sofort in der PCINT-ISR den 
Bootloader im AVR über den Reset Vektor aufzurufen. Dh. AVR geht in den 
Powerdown, dann und nur dann darf er über zb. PB2 an dem der 1Wire 
Bootloader zum PC hängt, aufgeweckt werden. Sobald der AVR nicht im 
Powerdown ist sollte in meinem Projekt der PCINT des Bootloaders auf 
keinen Fall reagieren. Du kannst dir ja in der CodeLib hier im Forum mal 
mein FireFly (Glühwürmchen im Rotkohlglass) Projekt anschauen.
Wenn die Zielsetzung deines Projektes es erlaubt das GIMSK innerhalb der 
ISR zu ändern dann solltest du das auch nutzen. Denn Code in ISRs ist 
auf Grund der fehlenden Interruptprioritäten im AVR auch gleichzeitigt 
geschützt. Wenn es möglich ist versuche ich generell die komplette 
Programlogik, also die Statemachine, innerhalb von ISRs zu erledigen. 
Das vereinfacht den Handler in Main(). Allerdings geht das meistens 
nicht so einfach.

Es hängt also von der konkreten Zielstellung ab.
Beim PinChange sollte man eben par Punkte beachten:
1.) um unnötige doppelte Triggerungen zu vermeiden lösche ich in der ISR 
das PCIF Flag nochmals explizit am Ende dieser ISR. Denn in der 
Zischenzeicht könnte ja nochmals ein PinChange aufgetreten sein. Das 
sollte man immer dann machen wenn der PinChange quasi nur zum Auslösen 
einer anderen Aktion benötigt wird, also unabhängig davon ob mehrere 
PinChange Events eintreffen soll nur das erste PinChange Event eine 
Aktion auslösen.
2.) aktiviert man den PinChange dann sollte man auch das PCIF löschen. 
Besonders wenn man zb. den PullUp am Pin vorherig eingeschaltet hat. Ich 
habe die Erfahrung gemacht das in diesem Fall par takte nach der 
Aktivierung des Pullups der PCINT auslösst.

Gruß Hagen

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.