Bei den ATmegax8 kann man ja einfach einen 32kHz Quarz anschließen und sich dann mit T2 eine RTC programmieren. Das funktioniert auch sehr gut. Nun will man aber bei Stromausfall in Power-Save (1µA) gehen, um den AVR möglichst lange über einen Goldcap 1F zu versorgen. Und damit läuft man in ein Problem, wie macht man das möglichst schnell, um den Goldcap nicht noch lange mit 5mA (aktive Mode) leer zu saugen? Die Mainloop kann ja durchaus mal länger beschäftigt sein, ehe sie zu der Stelle mit dem Sleep gelangt. Ich überwache den Netzspannungsausfall mit einem Pin-Change Interrupt. Führt man darin das Sleep aus und erlaubt mit SEI die Interrupts wieder, damit die RTC weiter läuft, kriegt man einen wunderschönen Stacküberlauf. Hat jemand dafür eine schöne Lösung gefunden?
Peter D. schrieb: > kriegt man einen wunderschönen > Stacküberlauf. Das ist natürlich mies... Dann kannste wohl oder übel nur versuchen, schnellstmöglich in den Sleep zu kommen, indem du die Abfrage, ob er schlafen soll, nach jedem Task in der Main-loop abfragst. Keine elegante Lösung, aber scheint erstmal ein Workaround. Aber warum bekommst du einen Stackoverflow? Das kann ich nicht ganz nachvollziehen?!
Vielleicht spendierst Du dem µP einen großen Stützkondensator, der unabhängig von Deiner Power-Down Erkennung ist. So bekommt der µP noch Saft, in Höhe der Versorgungsspannung (meist Vcc), während der Umschaltung.
:
Bearbeitet durch User
Sebastian S. schrieb: > Vielleicht spendierst Du dem µP einen großen Stützkondensator Peter D. schrieb: > Nun will man aber bei Stromausfall in Power-Save (1µA) gehen, um den AVR > möglichst lange über einen Goldcap 1F zu versorgen.
Peter D. schrieb: > Ich überwache den Netzspannungsausfall mit einem Pin-Change Interrupt. > Führt man darin das Sleep aus und erlaubt mit SEI die Interrupts wieder, > damit die RTC weiter läuft, kriegt man einen wunderschönen > Stacküberlauf. Komisch. Und du bist sicher, daß du nicht ausversehen einen Level-Interrupt genommen hast? (falls ein ATmegax8 (???) sowas hat). Oliver
:
Bearbeitet durch User
Noch was: In vielen Fällen nutzt es (fast) nichts, den µP ins Bettchen zu schicken. Die gesamte Peripherie hängt ja noch dran. Ich war davon ausgegangen, dass der µP einen eigenen Back-Up Anschluss hat, der mit dem GoldCab verbunden wird. Es kann auch nichts schaden, wenn Du mal den Schaltplan ins Netz stellst. Eine "echte" Batteriestütze ist recht aufwändig, vor allem wenn die Peripherie nicht mitspielt.
Peter D. schrieb: > Ich überwache den Netzspannungsausfall mit einem Pin-Change Interrupt. > Führt man darin das Sleep aus und erlaubt mit SEI die Interrupts wieder, > damit die RTC weiter läuft, kriegt man einen wunderschönen > Stacküberlauf. Hast du das richtige Sleep Modus eingestellt? Aus dem Datenblatt: Power-save Mode: When the SM2..0 bits are written to 011, the SLEEP instruction makes the MCU enter Powersave mode. This mode is identical to Power-down, with one exception: If Timer/Counter2 is clocked asynchronously, that is, the AS2 bit in ASSR is set, Timer/Counter2 will run during sleep. The device can wake up from either Timer Overflow or Output Compare event from Timer/Counter2 if the corresponding Timer/Counter2 interrupt enable bits are set in TIMSK, and the global interrupt enable bit in SREG is set. Hast du in Pinchange Interrupt erst sleep, dann sei oder verkehrt?
Also wenn man in der Pin-Change ISR in den Sleep geht ohne zuvor die Interrupts freigegeben zu haben, dann sollte der mit T2 aufwecken die Pin-C.-ISR beenden und anschließend die T2-ISR ausführen. Dann müsste man allerdings den Sleep gleich in der T2-ISR wieder aktivieren. Zu beachten auch noch das: • If Timer/Counter2 is used to wake the device up from Power-save or ADC Noise Reduction mode, precautions must be taken if the user wants to re-enter one of these modes: If reentering sleep mode within the TOSC1 cycle, the interrupt will immidiately occur and the device wake up again. The result is multiple interrupts and wake-ups within one TOSC1 cycle from the first interrupt. If the user is in doubt whether the time before re-entering Power-save or ADC Noise Reduction mode is sufficient, the following algorithm can be used to ensure that one TOSC1 cycle has elapsed: a. Write a value to TCCR2x, TCNT2, or OCR2x. b. Wait until the corresponding Update Busy Flag in ASSR returns to zero. c. Enter Power-save or ADC Noise Reduction mode Also für Batteriebetrieb hab ich das als Uhr auch schon verwendet, da macht die eine oder andere μs nichts aus. Alles in allem ist dieses Feature nicht bis zu Ende gedacht. Sascha
Ich habe jetzt eine Lösung gefunden mit setjmp(), longjmp(), die funktioniert:
1 | bool do_sleep(void) |
2 | {
|
3 | if (POWER_OK_in) |
4 | return false; |
5 | TIMSK0 = 0; // Display Interrupt off |
6 | DIG_PORT = ~DIG_MASK; // Digits off |
7 | ADCSRA = 0; // Touch Interrupt off |
8 | TCCR2B = 1<<CS22 | 0<<CS21 | 1<<CS20; // dummy write |
9 | while (ASSR & 1<<TCR2BUB); // until T2 interrupt flag cleared |
10 | sleep_cpu(); |
11 | return true; |
12 | }
|
13 | |
14 | jmp_buf env; |
15 | |
16 | ISR(POWER_OK_vect) |
17 | {
|
18 | longjmp (env, 1); |
19 | }
|
20 | |
21 | int main(void) |
22 | {
|
23 | init(); |
24 | sei(); |
25 | while (1) |
26 | {
|
27 | if (setjmp (env)) |
28 | {
|
29 | if (do_sleep() == false) |
30 | {
|
31 | touch_init(); |
32 | display_init(); |
33 | }
|
34 | }
|
35 | if (do_sleep() == true) |
36 | continue; |
37 | // other (huge) main loop stuff
|
38 | }
|
39 | }
|
Das erklärt aber immer noch nicht, woher der Stackoverflow kam. Oliver
Sebastian S. schrieb: > Ich war davon ausgegangen, dass der µP einen eigenen Back-Up Anschluss > hat, der mit dem GoldCab verbunden wird. Die AVRs haben leider keinen extra VBATT-Pin. Sebastian S. schrieb: > Es kann auch nichts schaden, wenn Du mal den Schaltplan ins Netz > stellst. Muß ich noch zeichnen. Sebastian S. schrieb: > Eine "echte" Batteriestütze ist recht aufwändig, vor allem wenn > die Peripherie nicht mitspielt. Der ATmega48 wird über einen 78L05 aus 12V gespeist, mit einem BS170 als Rückstromschutz. Der 1F liegt direkt an VCC/AVCC. Die LED-Anzeigen werden vor dem Sleep abgeschaltet.
Oliver S. schrieb: > Das erklärt aber immer noch nicht, woher der Stackoverflow kam. Von hier:
1 | ISR(TIMER2_COMPA_vect) |
2 | {
|
3 | seconds++; |
4 | if (POWER_OK_in == false) |
5 | {
|
6 | sei(); |
7 | sleep_cpu(); |
8 | }
|
9 | }
|
Sascha W. schrieb: > Alles in allem ist dieses > Feature nicht bis zu Ende gedacht. Stimmt. Das Sleep sollte unter Interruptsperre vorgemerkt werden und erst nach dem nächsten Interrupt enable (SEI oder RETI) erfolgen. Ein Sleep unter Interruptsperre ergibt keinen Sinn.
Peter D. schrieb: > Ich habe jetzt eine Lösung gefunden mit setjmp(), longjmp(), die > funktioniert: >
1 | > ... |
2 | > jmp_buf env; |
3 | >
|
4 | > ISR(POWER_OK_vect) |
5 | > { |
6 | > longjmp (env, 1); |
7 | > } |
8 | >
|
9 | > int main(void) |
10 | > { |
11 | > init(); |
12 | > sei(); |
13 | > while (1) |
14 | > { |
15 | > if (setjmp (env)) |
16 | > { |
17 | > if (do_sleep() == false) |
18 | > { |
19 | >... |
20 | >
|
Auch wenn ich nach genauem Nachdenken feststellen muß, daß das hier wohl funktioniert, das sollte nicht jeder "nicht-Peda" mal schnell abzutippen. Der Fallstricke gibt es einige, die es zu umschiffen gilt. Zumindest würde ich da einige Kommentare dranhängen, damit mir auch morgen noch einfällt, warum das genau funktioniert.
Peter D. schrieb: > Bei den ATmegax8 Die Sache relativ einfach: Du gehst nicht in main() schlafen, sondern in der PCI-ISR. Das geht, weil es bei den klassischen AVR8 überhaupt kein Problem ist, eine ISR zu einem normalen Unterprogramm von main() zu machen. Einfach sei() aufrufen und gut isses. Man muss nicht mal das reti am Ende der ISR gegen ein ret tauschen, das spielt keine Rolle. Aber natürlich musst du vor dem Aufruf von sei() dafür sorgen, dass nicht weitere Instanzen des PCI-Interrupts entstehen können, der muss also ausgeknipst werden und darf erst wieder in irgendeiner zukünftigen Instanz des T2-Interrupts, der als alleiniger Wakeup-Trigger dienen sollte, reaktiviert werden, wenn halt der (gepollte) Status des entsprechenden Pins anzeigt: "power good". Nicht vergessen, vor Reaktivierung des PCI-Int einen eventuellen "pending" PCI-IRQ zu löschen.
c-hater schrieb: > reaktiviert werden, wenn halt der (gepollte) Status des > entsprechenden Pins anzeigt: "power good". Nicht vergessen, vor > Reaktivierung des PCI-Int einen eventuellen "pending" PCI-IRQ zu > löschen. Ergänzend: hier droht eine race condition, nämlich das Verpassen einer "power fail"-Meldung. Also nach der Reaktivierung des PCI-Int nochmals den Pegel prüfen. Zeigt der "power fail", ist die race eingetreten. In diesem Fall PCI-Int wieder deaktivieren, sei() aufrufen (diesmal also derselbe Trick im T2-Interrupt) und dann enter sleep. Und überhaupt: vor dem Versuch der Reaktivierung des PCI-Int in der T2-ISR ist natürlich erstmal zu prüfen, ob er überhaupt deaktiviert ist. Wenn nicht->nicht anfassen, alles läuft noch normal.
c-hater schrieb: > Das geht, weil es bei den klassischen AVR8 überhaupt kein Problem ist, > eine ISR zu einem normalen Unterprogramm von main() zu machen. Einfach > sei() aufrufen und gut isses. Ganz so einfach ist es nicht, d.h. war schief gelaufen. Ich hab jetzt noch ein Flag eingeführt, welches den Interrupt in einer Schleife aus sei/sleep/cli laufen läßt. Damit geht der Timerinterrupt immer sofort in Sleep. Das Flag wird bei Stromausfall gesetzt und somit keine weitere Instanz der Schleife erstellt. Bei Wiederkehr der Stromversorgung werden die abgeschalteten Einheiten neu initialisiert und die Schleife verlasen, d.h. das Main wird an der unterbrochenen Stelle wieder aufgenommen. Das Flag muß volatile sein, damit die Schleife es immer neu auf Änderung prüft. Durch das Flag werden auch kurze Störungen auf dem Pin-Change-Interrupt ignoriert.
1 | uint32_t seconds; |
2 | |
3 | void clock_init(void) |
4 | {
|
5 | ASSR = 1<<AS2; // 32kHz crystal |
6 | OCR2A = 127; |
7 | TCCR2A = 1<<WGM21; // Mode 2: CTC |
8 | TCCR2B = 1<<CS22 | 1<<CS21 | 0<<CS20; // 32kHz / 256 |
9 | TIMSK2 = 1<<OCIE2A; |
10 | PCMSK1 = 1<<POWER_OK_int; |
11 | PCICR = 1<<POWER_OK_ena; |
12 | set_sleep_mode(SLEEP_MODE_PWR_SAVE); |
13 | sleep_enable(); |
14 | }
|
15 | |
16 | ISR(POWER_OK_vect) |
17 | {
|
18 | static volatile bool pwr_save = false; |
19 | if (POWER_OK_in) // check input pin |
20 | {
|
21 | if (pwr_save) // if power returned |
22 | {
|
23 | touch_init(); |
24 | display_init(); |
25 | pwr_save = false; // mark power returned |
26 | }
|
27 | }
|
28 | else
|
29 | {
|
30 | if (pwr_save) // avoid stack overflow |
31 | return; |
32 | TIMSK0 = 0; // Display Interrupt off |
33 | DIG_PORT = ~DIG_MASK; // Digits off |
34 | ADCSRA = 0; // Touch Interrupt off |
35 | pwr_save = true; // mark power lost |
36 | do
|
37 | {
|
38 | while (ASSR & 1<<OCR2AUB); // until T2 updated |
39 | sei(); |
40 | sleep_cpu(); |
41 | // <- handle interrupt here
|
42 | cli(); |
43 | }
|
44 | while (pwr_save); |
45 | }
|
46 | }
|
47 | |
48 | ISR(TIMER2_COMPA_vect) |
49 | {
|
50 | OCR2A = 127; // dummy write |
51 | seconds++; |
52 | }
|
In der Mainloop führe ich kein weiteres Sleep aus, da die Ersparnis unerheblich ist. Ich muß daher auch nicht zwischen Idle und Power-Save umschalten.
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.