Forum: Mikrocontroller und Digitale Elektronik ATmega interne RTC, Power-save Problem


von Peter D. (peda)


Lesenswert?

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?

von Ingo Less (Gast)


Lesenswert?

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

von Sebastian S. (amateur)


Lesenswert?

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
von Ingo Less (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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
von Sebastian S. (amateur)


Lesenswert?

Kannst Du mal den genauen Typen verraten?

von Sebastian S. (amateur)


Lesenswert?

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.

von Ozvald K. (Gast)


Lesenswert?

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?

von Sascha W. (sascha-w)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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
}

von Oliver S. (oliverso)


Lesenswert?

Das erklärt aber immer noch nicht, woher der Stackoverflow kam.

Oliver

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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