www.mikrocontroller.net

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


Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
GIMSK  = 1<<PCIE;
 PCMSK  = 1<<PCINT2;
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();

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

'Runterfahren in main endlos
sleep_mode();

if(irgendwas){
  GIMSK |= (1<<PCIE);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}


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

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

Vielleicht habt ihr ja einen Tipp parat..

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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!).

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bastler wrote:
>
>   GIMSK |= (1<<PCIE);
>   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
> 

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


Peter

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
cli();
GIMSK |= (1<<PCIE);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;-)

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Siehe Beispiel in:
http://www.nongnu.org/avr-libc/user-manual/group__...

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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bastler wrote:

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

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:

set_sleep_mode(<mode>);
cli();
if (some_condition)
{
  sleep_enable();
  sei();
  sleep_cpu();
  sleep_disable();
}
sei();



Peter

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas Kaiser wrote:
> Siehe Beispiel in:
> http://www.nongnu.org/avr-libc/user-manual/group__...

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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aha. Das geht für mich aus 
http://www.mikrocontroller.net/articles/AVR-GCC-Tu... 
leider nicht so eindeutig hervor.

Also im Klartext heisst das, mein sleep_mode() in der endlosschleife der 
main muss durch
cli();
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
sei();

ersetzt werden?

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
cli();
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
reicht aus, wenn die if()-Bedingung des Beispiels nicht nötig ist.

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke!

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas Kaiser wrote:
>

> cli();
> sleep_enable();
> sei();
> sleep_cpu();
> sleep_disable();

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

 cli();
 sleep_enable();
 sei();
 sleep_cpu();
 cli();
 sleep_disable();
 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

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
die Sequenz die ich immer benutze sieht so aus:
cli();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
GIMSK |= (1 << PCIE);
GFIR = (1 << PCIF);
sei();
sleep_cpu();
cli();
GIMSK &= ~(1 << PCIE);
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:
        MCUCR  = (1 << SE) | (1 << SM1);
        cli(); // asm volatile("cli");
        GIMSK  = (1 << PCIE);
        GIFR   = (1 << PCIF);
        sei(); // asm volatile("sei");
        sleep_cpu(); // asm volatile("sleep");
        GIMSK  = 0;

Gruß Hagen

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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:
>
>         MCUCR  = (1 << SE) | (1 << SM1);
>         cli(); // asm volatile("cli");
>         GIMSK  = (1 << PCIE);
>         GIFR   = (1 << PCIF);
>         sei(); // asm volatile("sei");
>         sleep_cpu(); // asm volatile("sleep");
>         GIMSK  = 0;
> 

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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Hagen Re (hagen)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.