Forum: Mikrocontroller und Digitale Elektronik AVR Tiny Serie 2:Sleep Controller, sleep_cpu()


von Wulf D. (holler)


Lesenswert?

Es geht um Ein- und Ausstieg in den Power-Down (Standby wäre auch ok) 
bei einem AVR Tiny 1627, ist aus der Series 2.
Der soll aus dem Power-Down entweder auf Tastendruck (funktioniert) oder 
nach einer Zeitspanne wieder zurückkehren und ein paar Dinge 
verarbeiten.

Für das zyklische will ich die RTC bzw deren Mode "Periodic Interrupt 
Timer (PIT)" verwenden.
PIT verwende ich schon im aktiven Betrieb, nur soll der vor dem Einstieg 
in den Power down stark verlangsamt werden. Meine Erwartungshaltung 
wäre, dass beim ersten Interrupt des PIT die CPU wieder aufwacht.
Tut sie nicht: manchmal läuft sie ungebremst über sleep_cpu() hinweg, 
manchmal kommt die nie wieder da raus. Es sei denn, ich löse einen Port 
i/o Interrupt aus.

Initialisierung des Sleep Controllers:
1
SLPCTRL.CTRLA = SLPCTRL_SMODE1_bm;

Und hier der zyklische Power-Down, der definitiv nicht funktioniert:
1
if (  task & (1<<tasksleep)) 
2
  {
3
  PORTA.OUTCLR = LED;  
4
  RTC.CTRLA &= ~(1<< RTC_RTCEN_bp);
5
  RTC.PITCTRLA &=  ~(1<< RTC_PITEN_bp);
6
  RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | (1<< RTC_PITEN_bp);
7
  RTC.CTRLA |= (1<< RTC_RTCEN_bp);
8
  sleep_enable(); 
9
  sleep_cpu();
10
  sleep_disable();
11
  PORTA.OUTSET = LED;  
12
  RTC.CTRLA &= ~(1<< RTC_RTCEN_bp);
13
  RTC.PITCTRLA &=  ~(1<< RTC_PITEN_bp);
14
  while (RTC.PITSTATUS & RTC_CTRLBUSY_bm);
15
  RTC.PITCTRLA = RTC_PERIOD_CYC256_gc | (1<< RTC_PITEN_bp);
16
  RTC.PITINTFLAGS = RTC_PI_bm;
17
  RTC.CTRLA |= (1<< RTC_RTCEN_bp);
18
  task &= ~(1<<tasksleep);
19
  }
Ist etwas überladen, um den Fehler auf die Spur zu kommen. Leider 
vergeblich.
Vielleicht hat das schon mal jemand hinbekommen und kann mir einen Tipp 
geben.
Oder klarstellen, so gehts gar nicht, was mich wundern würde :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

habe die Serie nicht zur Hand, denke jedoch du musst noch "Standby" der 
jeweiligen Einheit aktivieren damit sie weiterläuft.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Nur mal vom Draufgucken:

Warum gibt es die Warteschleife auf RTC_CTRLBUSY_bm nur in einem der 
beiden Zweige?

Hast du auch eine ISR für den PIT-Interrupt? Solltest du ja ;-) Dann 
würde ich aber auch konsequenterweise das RTC_PI_bm da drin gleich 
löschen.

Habe den PIT schon benutzt, aber noch nicht, um aus dem Sleep 
aufzuwachen.

Achso, gibt es einen Grund, nicht gleich sleep_mode() zu nehmen?

: Bearbeitet durch Moderator
von Georg M. (g_m)


Lesenswert?

Beispielcode:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
uint8_t flag;
5
6
ISR(RTC_PIT_vect)
7
{
8
  RTC.PITINTFLAGS = RTC_PI_bm;                              // clear PIT flag
9
}
10
11
int main(void)
12
{
13
  RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;                         // 1024 Hz from OSCULP32K
14
  while(RTC.PITSTATUS > 0) {}                               // wait for RTC.PITCTRLA synchronization
15
  RTC.PITINTCTRL = RTC_PI_bm;                               // enable periodic interrupt
16
  RTC.PITCTRLA = RTC_PERIOD_CYC2048_gc | RTC_PITEN_bm;      // 2s, enable PIT
17
18
  PORTA.DIRSET = PIN3_bm;                                   // PA3 output (LED)
19
20
  SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc | SLPCTRL_SEN_bm;  // POWER-DOWN sleep mode, Sleep Enable
21
22
  sei();                                                    // enable interrupts
23
24
  while(1)
25
  {
26
    if(!flag)
27
    {
28
      PORTA.OUTSET = PIN3_bm;                               // PA3 on
29
      while(RTC.PITSTATUS > 0) {}                           // wait for RTC.PITCTRLA synchronization
30
      RTC.PITCTRLA = RTC_PERIOD_CYC128_gc | RTC_PITEN_bm;   // 125ms, enable PIT
31
      flag = 1;
32
    }
33
    else
34
    {
35
      PORTA.OUTCLR = PIN3_bm;                               // PA3 off
36
      while(RTC.PITSTATUS > 0) {}                           // wait for RTC.PITCTRLA synchronization
37
      RTC.PITCTRLA = RTC_PERIOD_CYC2048_gc | RTC_PITEN_bm;  // 2s, enable PIT
38
      flag = 0;      
39
    }
40
    __asm__ __volatile__ ("sleep" "\n\t" :: );
41
  }
42
}

von Peter D. (peda)


Lesenswert?

Ich würde mal vermuten, daß auch bei den neueren AVRs Sleep nur in 
Zusammenarbeit mit einem Interrupthandler funktioniert.

Und bei den klassischen AVRs muß man vor dem nächsten Sleep noch warten, 
bis die RTC-Register mit dem RTC-Takt synchronisiert sind, sonst wird 
der Interrupt mehrfach getriggert. Dafür sind die xx_Update_Busy Bits 
gedacht.

Das sleep_disable(); ist akademischer Furz (vollkommen überflüssig) und 
kann weg. Einmal sleep_enable(); ins Init und gut.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Achso, gibt es einen Grund, nicht gleich sleep_mode() zu nehmen?

Ja, (Race Conditions).

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Jörg W. schrieb:
>> Achso, gibt es einen Grund, nicht gleich sleep_mode() zu nehmen?
>
> Ja, (Race Conditions).

Welche können das realistisch sein? Vor dem Sleep hat man normalerweise 
eh nur die Peripherie aktiv, die einen daraus aufwecken soll. Auch sehe 
ich nicht, welchen Schaden ein zwischen den drei Teilschritten 
eintreffender Interrupt anrichten sollte.

Also ja, man kann das sleep_enable() auch ganz am Anfang machen und dann 
aktiv lassen (und nur noch sleep_cpu() benutzen), aber ich sehe keinen 
wirklichen Sinn drin, statt eines sleep_mode() die drei darin 
enthaltenen Teilschritte selbst einzeln hinzumeißeln.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Welche können das realistisch sein?

Z.B. wenn der Aufwachinterrupt sich selbst disabled und dann genau vor 
das sleep_cpu(); reinhaut.
Es gibt doch schon reichlich Threads mit dem "sleep forever Problem".

Wie schon gesagt, das sleep_disable(); hat absolut keine sinnvolle 
Funktion.
Jedes Programm verhält sich exakt gleich, wenn man es wegläßt. Es ist 
also nur vergeudeter Flash und CPU-Zyklen.

von Wulf D. (holler)


Lesenswert?

Veit D. schrieb:
> Hallo,
> habe die Serie nicht zur Hand, denke jedoch du musst noch "Standby" der
> jeweiligen Einheit aktivieren damit sie weiterläuft.

Ja richtig, ist gewährleistet. Init des Timers:
1
void timer_setup(void) /*Periodic Interrupt Timer (PIT) wird hier benutzt */
2
{  
3
RTC.CTRLA |=  RTC_RUNSTDBY_bm | (1<< RTC_RTCEN_bp) ;
4
RTC.PITINTCTRL = RTC_PI_bm; // Enable the interrupt
5
RTC.PITCTRLA |= RTC_PERIOD_CYC256_gc | (1<< RTC_PITEN_bp);
6
sei(); //enabling global interrupt
7
}

Peter D. schrieb:
> Ich würde mal vermuten, daß auch bei den neueren AVRs Sleep nur in
> Zusammenarbeit mit einem Interrupthandler funktioniert.
Es gibt eine ISR (RTC_PIT_vect).

> Das sleep_disable(); ist akademischer Furz (vollkommen überflüssig) und
> kann weg. Einmal sleep_enable(); ins Init und gut.
Danke für den Hinweis, dann muss ich mir den Sleep-Controller im Detail 
anschauen. Hatte die Sequenz nur irgendwo abgekupfert.

Jörg W. schrieb:
> Warum gibt es die Warteschleife auf RTC_CTRLBUSY_bm nur in einem der
> beiden Zweige?
Hast Recht, das ist inkonsequent. Im Datenblatt steht, man muss das 
busy-Flag beim Schreiben auf die PIT-Register abpollen. Müsste man dann 
immer tun. Wobei ich bisher ohne auskam.

Georg M. schrieb:
> Beispielcode: ...
Herzlichen Dank, schaue ich mir genau an.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Z.B. wenn der Aufwachinterrupt sich selbst disabled und dann genau vor
> das sleep_cpu(); reinhaut.

Das hat aber nicht viel mit sleep_mode() zu tun, außer dass das Fenster 
für diese race condition damit etwas größer wird. Am Ende ist das ein 
Designfehler.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Das hat aber nicht viel mit sleep_mode() zu tun

Doch, daß hat sehr viel damit zu tun. Schließlich ist sogar in der 
"sleep.h" ein Beispielcode für die korrekte Lösung angegeben.

Jörg W. schrieb:
> außer dass das Fenster
> für diese race condition damit etwas größer wird.

Ein "Sleep forever" würde ich nicht gerade als nur "etwas größer" 
bezeichnen.
Aber auch, wenn die Mainloop um einen zusätzlichen Timerzyklus 
verzögert, kann das schon Auswirkungen haben.
In einer BA eines MP3 Players habe ich mal gelesen: "Die Zeitanzeige 
kann bei häufiger Benutzung etwas nachgehen". Schönes Eingeständnis für 
Unfähigkeit.

von Peter D. (peda)


Lesenswert?

Wulf D. schrieb:
> Es gibt eine ISR (RTC_PIT_vect).

Und warum zeigst Du sie dann nicht?

Fehlersuche geht immer erheblich besser bei vollständigem Code.

von Wulf D. (holler)


Lesenswert?

Peter D. schrieb:
> Wulf D. schrieb:
>> Es gibt eine ISR (RTC_PIT_vect).
>
> Und warum zeigst Du sie dann nicht?
>
> Fehlersuche geht immer erheblich besser bei vollständigem Code.

Ist hier, hab ich für das Problem als irrelevant angesehen. Aber ja, 
hätte erwähnen müssen, dass es eine ISR gibt.
1
ISR (RTC_PIT_vect)    // ca 12ms
2
{
3
ticker++;
4
if (intr_count==TIMEIRQ) // ca 1/10 sec Takt
5
  {
6
  task |= (1<<task100ms);
7
  if (blink==2) PORTA.OUTTGL = LED;  // LED toggeln
8
  intr_count=0; //making intr_count=0 to repeat the count    
9
  if (intr_c10==9)
10
    {        // ca 1 sec Takt
11
    intr_c10=0;
12
    ++sec;
13
    sleepsec++;
14
    if (sleepsec==10)
15
      {
16
      task |= (1<<tasksleep);        
17
      sleepsec=0;
18
      }
19
    if (blink==1) PORTA.OUTTGL = LED;  // LED toggeln
20
    anz |= (1<<anzjetzt);
21
    if (klickrep>2 && klickinc<256) klickinc<<=1;
22
    else if (klickrep<2 && klickinc>1) klickinc>>=1;
23
    klickrep=0;
24
    }
25
  else intr_c10++;
26
  }
27
else  intr_count++;
28
RTC.PITINTFLAGS = RTC_PI_bm;
29
}

Habe Georgs Democode adaptiert und der Power-Down wird damit jedesmal 
zuverlässig betreten und verlassen!
Jetzt funktioniert zwar mein restlicher Code nicht mehr, aber das ist 
sicher irgend ein Randeffekt mit dem RTC. Finde ich sicher selbst raus, 
komme da aber erst morgen zu.

Wäre interessant woran es gelegen hat. Georgs Code ist sehr kompakt, 
werde ich noch rausfinden.

Hier die Adaption. Init:
1
SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc | SLPCTRL_SEN_bm;

Die Umschaltung des PIT mit sleep-Command.
1
if (  task & (1<<tasksleep)) 
2
  {
3
  PORTA.OUTCLR = LED;  
4
  while(RTC.PITSTATUS > 0) {} // wait for RTC.PITCTRLA synchronization
5
  RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc| RTC_PITEN_bm;
6
  __asm__ __volatile__ ("sleep" "\n\t" :: );
7
  PORTA.OUTSET = LED;  
8
  while(RTC.PITSTATUS > 0) {} // wait for RTC.PITCTRLA synchronization
9
  RTC.PITCTRLA = RTC_PERIOD_CYC256_gc | RTC_PITEN_bm;
10
  task &= ~(1<<tasksleep);
11
  }

von Wulf D. (holler)


Lesenswert?

Wulf D. schrieb:
> Jetzt funktioniert zwar mein restlicher Code nicht mehr, aber das ist
> sicher irgend ein Randeffekt mit dem RTC.
Nee, hatte nichts mit der RTC zu tun, die läuft.

Es war der ADC, der nach dem Power-Down neu gestartet werden muss.
Der ist so konfiguriert, dass nach erfolgter Konvertierung der beim 
Setzen seines Eingangs-Multiplexer automatisch eine neue Konvertierung 
startet.
Da passiert nach einem Power-Down nichts mehr. Muss neu angestoßen 
werden.
Also zum Code oben noch eine Zeile hinzugefügt:
1
ADC0.MUXPOS = uadc[adcindex].muxpos;
Die rechte Seite ist einfach eine Portnummer.

Vielen Dank für die Tipps, hat das Problem in kürzester Zeit gelöst!

Schaue morgen mal, was der entscheidende Punkt war.

von Georg M. (g_m)


Angehängte Dateien:

Lesenswert?

Wulf D. schrieb:
> PIT verwende ich schon im aktiven Betrieb

Beim laufenden Mikrocontroller braucht man den asynchronen PIT 
eigentlich nicht. Stattdessen kann ein TCB eingesetzt werden.

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.