AVR Tiny Serie 2:Sleep Controller, sleep_cpu()

#7901815
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 :-)

Moderator Persönliche Seite #7901838
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?

#7901850
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
}
#7901977
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.

Moderator Persönliche Seite #7901987
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.

#7901993
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.

#7902007
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.

#7902036
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.

#7902055
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
  }
#7902200
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.

#7902965
Lesenswert?

Georg M. schrieb:

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

Danke, guter Hinweis. Hatte ich aus anderen Anwendungen übernommen, die viele Timer/PWM benötigte. Stelle ich um, dann wird das sleep flexibler.

Habe jetz untersucht, woran mein Ansatz scheiterte.

  1. asm volatile ("sleep" "\n\t" :: ); durch sleep_cpu(); ersetzt. Funktioniert noch.
  2. die Synch while(RTC.PITSTATUS > 0) {} weggelassen. Funktioniert noch.
  3. Nur am Anfang mit SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc; den Sleep-Mode gesetzt, aber nicht enabled. Dann bei der Aktivierung die Sequenz
1
sleep_enable(); 
2
sleep_cpu();
3
sleep_disable();

benutzt. Das funktionierte nicht mehr. Keine Ahnung warum, ich denke der RTC läuft anschließend nicht mehr.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren