Forum: Mikrocontroller und Digitale Elektronik ATTiny45: Aufwachen mit PCINT klappt nicht


von Johannes (menschenskind)


Lesenswert?

Hallo,

Ich schicke den µC in den PowerDownModus und will ihn danach durch einen 
PinChangeInterrupt von 2 Tastern(also entweder der eine oder der andere)
wieder aufwecken.
In der while-Schleife werden die Taster über Peter Daneggers 
Entprellroutine abgefragt, deswegen deaktiviere ich den PCINT sobald er 
ausgelöst wurde und aktiviere den erst kurz bevor es ans Schlafen geht.
Das "flag" wird beim langen Drücken einer Taste auf 0 gesetzt.
Und noch während ich die Taste gedrückt halte wird bereits die ISR 
aufgerufen und danach reagiert meine Schaltung auf keinen Tastendruck 
mehr.

Habt ihr ne Idee, woran das liegen könnte?
Danke
der Hannes
1
.
2
.
3
uint8_t flag = 1;
4
5
ISR( PCINT0_vect){
6
  GIMSK &= ~(1<<PCIE);
7
  flag ^=1;
8
}
9
int main(void){
10
.
11
.
12
  /**** Configure external Interrupts ****/
13
  //GIMSK |= (1<<PCIE); // Enable Pin Change Interrupt
14
  PCMSK |= (1<<PCINT1)|(1<<PCINT2);
15
  
16
  sei();    // Enable global interrupts  
17
.
18
.
19
  while(1){
20
.
21
.
22
    if( flag == 0){
23
      set_sleep_mode(SLEEP_MODE_PWR_DOWN);
24
      GIMSK |= (1<<PCIE);
25
      sleep_mode();
26
    }
27
   }
28
}

von Mick (Gast)


Lesenswert?

Ich hab deinen Code nicht genau angeschaut. Aber das hier funktioniert:
1
bitSet(GIMSK, PCIE1);    //Turn on Pin Change interrupt
2
bitSet(PCMSK0, PCINT10); //Pin PCINT10
3
4
void sleepNow(){  
5
  cli();
6
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
7
  sleep_enable();
8
  sei();
9
  sleep_mode();
10
  sleep_disable();
11
}
12
13
ISR(PCINT1_vect){
14
  //flag handling
15
}

von Kirsch (Gast)


Angehängte Dateien:

Lesenswert?

Im Power-down Mode kann er ausschließlich durch ein Level-Interrupt an 
INT0 aufgeweckt werden

von Kirsch (Gast)


Angehängte Dateien:

Lesenswert?

Der Auszug aus dem Datenblatt ohne Transparents

von Kirsch (Gast)


Lesenswert?

Ok, Pin Change Interrupt geht anscheinend auch im Powerdown an jedem 
Pin.

Was mir jetzt auffällt, "uint8_t flag" ist nicht volatile.

von Peter D. (peda)


Lesenswert?

Ja, beim Sleep muß man sehr auf die genaue Reihenfolge achten.
Die wird im sleep_mode(); Macro aber nicht garantiert, daher führt das 
bei Benutzung regelmäßig zu Fehlfunktionen. Siehe auch der Warntext dazu 
in sleep.h.

Hier ein funktionierendes Beispiel, der 2. Beitrag enthält den 
verbesserten Code:
Beitrag "AVR Sleep Mode / Knight Rider"

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,
Das werde ich mal so in Deinem Beispielcode machen.

Allerdings hat es ja bereits schon funktioniert bei mir (mit 
sleepmode(); und set_sleep_mode();). Da hatte ich aber erst den 
LowLevelInterrupt INT0 benutzt und weil ich aber beide Taster zum 
Aufwecken nehmen will, auf PinChangeInterrupt gewechselt.

Ich schalte die "flag"-Variable um, wenn mit "get_key_long" ein langes 
Drücken festgestellt wurde.

von Johannes (menschenskind)


Lesenswert?

Ok, in Deinem Thread hab ich noch gelesen, dass Du die 
"get_key_busy"-Abfrage machst. Evtl. ist das ja der Schlüssel.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Also das Problem ist, sobald das lange Drücken des einen Tasters erkannt 
wird, dann geht der Controller schlafen. Wenn ich aber die Taste dann 
wieder loslasse, wacht er auf.
Müsste ich da in Deiner debounce.c noch was ändern?

Ich habe Deine Routine 1:1 übernommen. An einer anderen Stelle im Code 
verwende ich sie auch nochmal und da wird nach einem kurzen Tastendruck 
schlafen gegangen. Da gibt es keine Probleme.

: Bearbeitet durch User
von MWS (Gast)


Lesenswert?

Toggle halt das Flag nicht
>  flag ^=1;
sondern setzte es in der ISR in einen definierten Zustand.
Wenn das Flag vor Aufruf der ISR durch irgendeinen Codeteil 0 wurde, 
dann ist's nachher <>0 und der Code in der While fühlt sich nicht mehr 
zuständig. Und dann ist halt Schluss, die ISR ist disabled.

Außerdem würde ich vor dem Enablen eines Interrupts
> GIMSK |= (1<<PCIE);
immer das entsprechende Interruptflag löschen, da sich der µC einen 
anstehenden Interrupt merkt und der kann noch von vorher, also nach dem 
Disablen des Interrupt stammen. Der wird dann sofort nach dem Enablen 
des entsprechenden Interrupts ausgeführt.

von Peter D. (peda)


Lesenswert?

Johannes H. schrieb:
> Wenn ich aber die Taste dann
> wieder loslasse, wacht er auf.

Das macht ja nix und ist in meinem Beispiel auch so. Jede Flanke weckt 
auf.
Aber es wird ja keine Aktion ausgeführt, d.h. die Variable "stop" ändert 
sich nicht und damit wird sofort wieder "try_sleep" aufgerufen.

von Johannes (menschenskind)


Lesenswert?

"flag" auf nen definierten Wert zu setzen hat's nicht gebracht.

@Peter: Aber was kann ich denn dann tun, um das Problem zu beheben? Im 
Kommentar zu get_key_busy steht doch "until key released".
Prellt der Taster eigentlich auch, wenn man ihn loslässt?

von Peter D. (peda)


Lesenswert?

Der PCINT reagiert auf jede Flanke, also auch auf Prellen. Deshalb macht 
der bei mir nur das Aufwachen, sonst nichts. Laß ihn leer, das schont 
Nerven.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,
Das Problem ist ja nicht das Aufwachen, sondern dass der µC schlafen 
geht WÄHREND die Taste noch runtergedrückt ist. Wenn ich die also 
loslasse wacht er ja gleich wieder auf.
Aber das Ziel ist ja dass er nach dem Loslassen immer noch schläft.

Als quick&dirty-Möglichkeit habe ich das jetzt so probiert, geht:
1
if(flag == 0){
2
      _delay_ms(500);
3
      try_sleep();
4
}
Aber das ist natürlich nicht failsafe...

von m.n. (Gast)


Lesenswert?

Johannes H. schrieb:
> Aber das Ziel ist ja dass er nach dem Loslassen immer noch schläft.

Das geht nicht mit PCINT, da jede Flanke den µC aufweckt.

Hier noch ein Beispiel mit PCINT und zwei Tastern, die auch noch 
entprellt werden und selbst gedrückt keine hohe Stromaufnahme 
verursachen.
Beitrag "Re: EIN-AUS mit Taster per Interrupt, ATtiny25 o.ä."

Wichtig ist, daß nur beim Drücken einer Taste eine Aktion ausgelöst 
wird. Beim Loslassen wird der µC zwar kurz wach, legt sich aber gleich 
wieder schlafen.

von Peter D. (peda)


Lesenswert?

Johannes H. schrieb:
> Als quick&dirty-Möglichkeit habe ich das jetzt so probiert, geht:

Ne, das ist grausam.
Ohne den kompletten Code, kann man natürlich nicht Deinen Fehler finden.
Machs doch so, wie in meinem Beispiel.
Das try_sleep() muß immer wieder aufgerufen werden, bis die Preller 
vorbei sind.

von Johannes (menschenskind)


Lesenswert?

Hier mal der komplette Code. Eigentlich dachte ich, dass ich es genau so 
wie Du in Deinem Beispiel mache.
Wenn das flag 0 ist wird try_sleep aufgerufen.
1
#include <util/delay.h>
2
#include <avr/interrupt.h>
3
#include <avr/io.h>
4
#include <avr/sleep.h>
5
6
#include "includes/config.h"
7
#include "includes/debounce.h"
8
#include "includes/light_ws2812.h"
9
10
struct cRGB LED_OUTPUT[1];
11
struct cRGB TEMP[1];
12
13
#define MODES 13
14
#define BRIGHTNESSES 6
15
#define PWM_MAX 255
16
17
volatile uint8_t flag = 1;
18
19
ISR( PCINT0_vect){
20
  flag = 1;
21
22
  GIFR = 0; // clear external interrupt flags
23
}
24
25
void try_sleep()
26
{
27
  if( get_key_busy( 1<<KEY1 ))
28
    return;          // still busy
29
  set_sleep_mode( SLEEP_MODE_PWR_DOWN);    // prepare power down
30
  GIMSK |= (1<<PCIE);      // awake interrupt on
31
  sleep_cpu();
32
  GIMSK &= ~(1<<PCIE);
33
34
}
35
36
int main(void){
37
  uint8_t mode_index = 1;
38
  uint8_t brightness_index = BRIGHTNESSES;  
39
  
40
  /**** Set Timer & Buttons for Debounce Functionality ****/
41
  KEY_DDR &= ~ALL_KEYS;
42
  KEY_PORT |= ALL_KEYS;
43
44
  TCCR0B = (1<<CS02)|(1<<CS00);         // divide by 1024
45
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
46
  TIMSK |= 1<<TOIE0;                   // enable timer interrupt  
47
  
48
  /**** Configure external Interrupts ****/
49
  PCMSK |= (1<<PCINT1)|(1<<PCINT2);
50
  
51
  
52
  /**** Power Reduction ****/
53
  PRR = (1<<PRUSI) | (1<<PRADC) | (1<<PRTIM1);
54
  
55
  sei();    // Enable global interrupts  
56
  
57
  initColors(); //can be deleted if direct initialization works
58
  
59
  TEMP[0]= colorStatic(0);
60
  ws2812_setleds(TEMP,1);  
61
  
62
  /************************************************************************/
63
  /* MAIN Action ;)                                                                     */
64
  /************************************************************************/
65
  while(1){
66
    
67
    if( get_key_short(1<<KEY0) ){
68
      if (mode_index == MODES)mode_index = 1;
69
      else mode_index += 1;            
70
    }
71
    
72
    if (get_key_long(1<<KEY0)) mode_index =0;
73
    
74
    if( get_key_short(1<<KEY1) ){
75
      if (brightness_index == 1)brightness_index = BRIGHTNESSES;
76
      else brightness_index -= 1;      
77
    }
78
    
79
    if (get_key_long(1<<KEY1)) flag ^= 1;
80
        
81
    switch (mode_index){
82
      case 0: LED_OUTPUT[0] = colorFade();break;
83
      case 1: if( flag == 1){
84
            LED_OUTPUT[0] = stroboscope();
85
             _delay_ms(10);
86
          }
87
          else LED_OUTPUT[0] = colorStatic(13);
88
          break;             
89
      case 2: LED_OUTPUT[0] = colorStatic(0); ws2812_setleds(LED_OUTPUT,1);try_sleep(); break;
90
      default: break;
91
    }
92
        
93
    if(mode_index >= 3){      
94
      if(flag == 1){
95
        LED_OUTPUT[0] = colorPulse(mode_index - 2);
96
        _delay_ms(20);
97
      }    
98
      else LED_OUTPUT[0] = colorStatic(mode_index-2);// Cycle colors starting from color "1" µC can be switched off here and woken up with interrupt
99
      
100
    }
101
    LED_OUTPUT[0].r  /= brightness_index;
102
    LED_OUTPUT[0].g  /= brightness_index;
103
    LED_OUTPUT[0].b  /= brightness_index;
104
    ws2812_setleds(LED_OUTPUT,1);
105
    
106
    if(flag == 0){
107
      _delay_ms(500);
108
      try_sleep();
109
    }
110
  }    
111
}

von Peter D. (peda)


Lesenswert?

Johannes H. schrieb:
> Hier mal der komplette Code.

Schau Dir meinen PCINT an, der ist leer. Das Flag wird nur durch 
Tasteneingaben geändert.
Aber bei Dir setzt jede Flanke das Flag zurück. Das kann nicht gehen.

von Johannes (menschenskind)


Lesenswert?

Hallo,
So bin jetzt wieder zurück aus dem Urlaub.

Ich hab's grad nochmal probiert: Auch wenn die ISR leer ist, wacht der 
µC nach dem Loslassen des Tasters gleich wieder auf.
Bzw. sogar wenn ich auf "get_key_long" triggere und den Taster die ganze 
Zeit gedrückt halte, geht der µC in den Schlafmodus und wacht im 
nächsten Moment wieder auf.

von Peter D. (peda)


Lesenswert?

Johannes H. schrieb:
> Auch wenn die ISR leer ist, wacht der
> µC nach dem Loslassen des Tasters gleich wieder auf.

Du must das Programm natürlich so schreiben, daß er nur die 
Tasteneingabe  auswertet und immer wieder schlafen geht, solange das 
Flag gesetzt ist.
Der Name try_sleep wurde schon mit Absicht so gewählt, d.h. es muß nicht 
beim ersten Versuch (Flanke, Preller) klappen.
Genau so ist es in meinem Beispiel gelöst.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Jetzt wird's misteriös bei mir.
Nachdem es partout nicht klappen wollte, hab ich die try_sleep mal so 
auskommentiert, dass der µC aus dem Schlafzustand nicht mehr aufwachen 
sollte:
1
void try_sleep()
2
{
3
  //if( get_key_busy( 1<<KEY1 )||get_key_busy(1<<KEY0) )
4
    //return;          // still busy
5
  set_sleep_mode( SLEEP_MODE_PWR_DOWN);    // prepare power down
6
  //GIMSK |= (1<<PCIE);      // awake interrupt on
7
  sleep_cpu();
8
  //GIMSK &= ~(1<<PCIE);
9
10
}

Aber: nach erneutem Drücken geht's einfach weiter.

Wie kann das sein? Das PCINT-Bit im GIMSK wird ja gar nicht gesetzt.

> Du must das Programm natürlich so schreiben, daß er nur die
> Tasteneingabe  auswertet und immer wieder schlafen geht, solange das
> Flag gesetzt ist.
Aber die Abfrage geht doch nach "if( flag == 0)" direkt zum 
"try_sleep"...
Allerding hast Du in Deiner Abfrage noch "get_key_short( 1<<KEY0 );". 
Das verstehe ich noch nicht ganz.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Also zum einen hatte ich vergessen während der Initialisierung 
"sleep_enable" zu setzen (da hat "sleep_cpu" natürlich nichts 
bewirkt)...
Zum anderen kommt von "get_key_busy" IMMER nicht Null zurück, d.h. der 
µC wird nie schlafen geschickt.

Hast Du nen Idee, wie sich das beheben lässt?

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Ich hab noch mal in Deinem Knightriderbeispiel geschaut und mal den Code 
der initialen "get_key_busy" verwendet, also
1
return (~KEY_PIN | key_state) & key_mask;

Damit gibt die Funktion nun auch nur einen Wert ungleich 0 zurück, wenn 
der Schalter tatsächlich gedrückt wird, allerdings treten nun noch 
andere Problem auf. Ich vermute, weil die VerODERung mit "key_press" 
fehlt.

Kannst Du mir bitte nochmal helfen, warum Deine aktualisierte Variante
1
return ((KEY_PIN ^ key_state) | key_press) & key_mask;
 bei mir nicht funktioniert?

von Peter D. (peda)


Lesenswert?

Stell mal einen compilierbaren Code (0 Warnings, 0 Errors) als Anhang 
(Zip) rein.
Mit Schnipselchen kann man keine Fehler suchen.

von Johannes (menschenskind)


Angehängte Dateien:

Lesenswert?

Hier, bitteschön.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Ich wollte noch mal nachfragen, ob Du schon Zeit hattest mal in den Code 
reinzugucken.
Falls Dir das zu viel ist, kennst Du noch jemand anders aus dem Forum, 
der sich mit Deinem Code etwas auskennt? Bzw. würde ich einfach im 
zugehörigen Thread was posten.

von Peter D. (peda)


Lesenswert?

Kannst Du mal das "nicht funktioniert" beschreiben.

Ich weiß nicht, wie lange Deine Mainloop dauert, es werden ja einige 
Unterfunktionen aufgerufen.
Die long/short Unterscheidung funktioniert nur dann einwandfrei, wenn 
die Mainloop immer kürzer als die short-Zeit dauert.
Wenn das nicht geht, muß man diese Unterscheidung mit in den 
Timerinterrupt legen.

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,
Naja, ich hab zwischen einigen Unterfunktionen 10 bzw. 20 ms delay 
eingebaut.

Die long/short Unterscheidung hat ja ohne Probleme funktioniert.
Durch die "get_key_busy"-Abfrage sind erst die Probleme gekommen, weil 
dessen Rückgabewert immer ungleich 0 ist und "try_sleep" dadurch gleich 
wieder verlassen wird.

von Johannes (menschenskind)


Lesenswert?

Update:
Nachdem ich mich nun eingehender mit"get_key_busy" beschäftigt habe, 
habe ich diese nun so modifiziert:
1
return ((~KEY_PIN | key_state) | key_press) & key_mask;
Das scheint zu funktionieren.

von Peter D. (peda)


Lesenswert?

So ich hab nochmal in Deinen Code geschaut.
Der Unterschied ist, daß ich das low-activ gleich in die Definition 
gepackt habe.
1
#define KEY_PIN      ~PINB    // Input low active
Damit ist dann KEY_PIN high aktiv.
Und damit ergibt
1
(KEY_PIN ^ key_state)
0, wenn der entprellte Zustand gleich dem (high)-Eingang ist, also das 
Entprellen abgeschlossen ist.
Ich finde das schöner, da dann das ~ nur einmal in der Definition stehen 
muß.

von Johannes (menschenskind)


Lesenswert?

Ach daher kam's.
Also noch mal vielen Dank für Deine Analyse! :)

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.