Forum: Mikrocontroller und Digitale Elektronik AtTiny mit Watchdog-Sleep und externem Interrupt


von Ralf S. (ralf_s480)


Lesenswert?

Hallo allemiteinander! Mein erstes Post hier, bitte seid nachsichtig :)
(ich hab auch brav gesucht und keine Lösung/Erklärung im Netz gefunden)

Ich habe ein recht triviales Problem: Ich möchte einen AtTiny85 in einen 
Low-Energy-Schlaf legen und über den Watchdog alle paar Sekunden 
aufwecken.
Zusätzlich soll der µC über einen externen Interrupt (ein Buttom) 
aufgeweckt werden können.
Beide Teile (Schlaf und Interrupt) laufen separat völlig tadellos, aber 
setzt man beides zusammen, läuft alles nicht so wie es soll.
Ich habe jetzt lange herumprobiert und nun auch eine Lösung gefunden, 
mir gefällt das aber nicht, weil ich nicht verstehe, WARUM der 
Workaround geht (bei sowas bin ich immer skeptisch, weil Pfusch-Lösungen 
oft böse Nebenwirkungen haben. Würde es gern verstehen)

Am Anfang vom Code ist ein define "UseWatchdog", damit kann man die zwei 
Verhalten umschalten.

Ich habe im Code an zwei Stellen meine Fragen reingeschrieben.
Hier nochmal kurz:

1.) Wenn ich den µC via Watchdog schlafen lege, reagiert er plötzlich 
nicht mehr auf den INT0 Interrupt. Mach ich die Pause via "delay" 
(Define am Anfang vom Code auskommentieren), dann funktioniert das.

Ich muss explizit über MCUCR und GIMSK den INT0 aktivieren. Warum warum 
warum??

2.) Und jetzt wird's völlig rätselhaft für mich:
In der Interrupt-Routine vom Button muss ich - wieder via GIMSK - den 
externen Interrupt ausschalten.
Mache ich das nicht, dann hängt der µC.
Was zum Geier ist da denn nun los?? Wieder: Ohne Watchdog ist das nicht 
nötig.

Könnt ihr mir helfen zu verstehen, was da los ist?

Hier der Code:
1
// Blink every 2 seconds, send proc to sleep in between
2
// If button is pressed -> wake up and blink 4 times quickly
3
4
#define UseWatchdog
5
6
#ifdef UseWatchdog
7
  #include <avr/sleep.h>
8
  #include <avr/power.h>
9
  #include <avr/wdt.h>
10
11
  #define CSleep2Seconds  0b00000111  // 1<<WDP0 | 1<<WDP1 | 1<<WDP2
12
#endif
13
14
#define pinLED 4      // physical pin 3, on-programmer LED
15
#define pinButton 2   // physical pin 7, on-programmer button
16
17
volatile bool IntButtonCalled = false;
18
19
void setup()
20
{
21
    pinMode(pinLED, OUTPUT);
22
    pinMode(pinButton, INPUT_PULLUP);
23
  
24
    // Attach interrupt to button
25
    int IntNr = digitalPinToInterrupt(pinButton);
26
    attachInterrupt(IntNr, IntHandlerButton, FALLING);
27
}
28
29
void loop()
30
{
31
    if (IntButtonCalled) {
32
        // Button pressed -> Blink 4 times
33
        for (int i=0; i<8; i++) {
34
            digitalWrite(pinLED, !digitalRead(pinLED));
35
            delay(50);
36
        }
37
        IntButtonCalled = false;
38
    } else {
39
        // Let LED blink once to show timer activity
40
        digitalWrite(pinLED, !digitalRead(pinLED));
41
        delay(10);
42
        digitalWrite(pinLED, !digitalRead(pinLED));
43
    }
44
    
45
#ifdef UseWatchdog
46
    SleepWatchdog(CSleep2Seconds);    //  Enter sleep mode
47
#else
48
    delay(2000);
49
#endif
50
}
51
52
void IntHandlerButton(void)
53
{
54
    IntButtonCalled = true;
55
#ifdef UseWatchdog
56
     // Why do I have to disable the external interrupt here?
57
     // If I don't the uC hang (LED stops blinking)
58
    GIMSK = 0;                    
59
#endif
60
}
61
62
#ifdef UseWatchdog
63
64
// Enter the arduino into sleep mode
65
void SleepWatchdog(int interval)
66
{
67
    // Why do I have to enable Int0 when WD-Timer is used?
68
    // Without wd-timer, the interrupt is enabled 
69
    MCUCR &= ~(_BV(ISC01) | _BV(ISC00));      // INT0 on low level
70
    GIMSK |= _BV(INT0);                       // enable INT0
71
72
    noInterrupts();
73
  
74
    wdt_reset();
75
      
76
    // This order of commands is important and cannot be combined
77
    MCUSR = 0;                          // reset Brown-out, Ext and PowerOn reset flags
78
    WDTCR |= 0b00011000;                // see docs, set (WDCE -> next set prescaler), (WDE -> Interrupt on timeout)
79
    WDTCR =  0b01000000 | interval;     // set WDIE, and appropriate delay
80
    ADCSRA &= ~_BV(ADEN);
81
  
82
    set_sleep_mode (SLEEP_MODE_PWR_DOWN); 
83
    
84
    sleep_bod_disable();
85
    interrupts();
86
    sleep_mode();    // Now enter sleep mode
87
}
88
89
// Watchdog Interrupt Service
90
ISR(WDT_vect)
91
{
92
    wdt_disable();    // Avoid that timer is called over and over again
93
}


Vielen Dank jetzt schon!!!

Ralf

Beitrag #5145825 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

Ralf S. schrieb:
> MCUCR &= ~(_BV(ISC01) | _BV(ISC00));      // INT0 on low level

Damit definierst du INT0 pegelgetriggert ...

> attachInterrupt(IntNr, IntHandlerButton, FALLING);

... und damit vermutlich flankengetriggert (kenne den Arduino-Kram 
nicht). Was im Powerdown mangels Takt nicht auslöst. Nur 
pegelgetriggerte Interrupts und Pin Change Interrupts kommen ohne Takt 
aus.

> In der Interrupt-Routine vom Button muss ich - wieder via GIMSK - den
> externen Interrupt ausschalten.

Wenn pegelgetriggert, dann löst der Interrupt so lange erneut aus, bis 
er abgeschaltet wird oder der Pin nicht mehr low ist.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

A. K. schrieb:

> ... und damit vermutlich flankengetriggert (kenne den Arduino-Kram
> nicht). Was im Powerdown mangels Takt nicht auslöst. Nur
> pegelgetriggerte Interrupts und Pin Change Interrupts kommen ohne Takt
> aus.

Das stimmt nicht ganz.

INT2 vieler ATMegas ist nicht PCINT, kann die Teile oft aber durchaus 
flankengetrieben aus den tiefsten (IO-taktlosen) Schlafzuständen 
aufwecken. Ist offensichtlich so eine Art Vorfahr der PCINT-Geschichte, 
nur besser, weil die Flanke konfigurierbar ist und der Interrupt 
exklusiv für einen Pin ist.

Beim Mega328(P) allerdings tatsächlich nicht, weil es da INT2 schlicht 
nicht gibt...

von (prx) A. K. (prx)


Lesenswert?

c-hater schrieb:
> Das stimmt nicht ganz.

Beim hier betrachteten ATtiny85 schon.

Wobei man bei solchen Details stets das Datasheet des verwendeten 
Controllers berücksichtigen sollte. Auch wenn Atmel einige Eigenschaften 
quer durch die AVR Reihe gleich zu implementieren scheint, sollte man 
sich nicht blind drauf verlassen.

von Ralf S. (ralf_s480)


Lesenswert?

A. K. schrieb:
> Ralf S. schrieb:
>> MCUCR &= ~(_BV(ISC01) | _BV(ISC00));      // INT0 on low level
>
> Damit definierst du INT0 pegelgetriggert ...
>
>> attachInterrupt(IntNr, IntHandlerButton, FALLING);
>
> ... und damit vermutlich flankengetriggert

Ok, stimmt. Das ist tatsächlich unterschiedlich.

> (kenne den Arduino-Kram
> nicht). Was im Powerdown mangels Takt nicht auslöst. Nur
> pegelgetriggerte Interrupts und Pin Change Interrupts kommen ohne Takt
> aus.

Hm. Zu den Pegel-Interrupts hab ich gleich zwei Fragen:

1.) Verstehe ich das richtig: Der Interrupt unterbricht die Ausführung 
in loop(), springt in die ISR, macht dort sein Ding, uns setzt dann die 
Ausführung in loop() an genau der Stelle fort, wo er unterbrochen wurde, 
oder?

2.) Kann in der ISR sofort noch mal der gleiche Interrupt ausgelöst 
werden? (und wenn ja: wie schnell). Sprich: Wenn ich den Pegel-Interrupt 
nehme, dann geht der Pin auf Low, die ISR wird angesprungen, dort das 
erste Kommando ausgeführt ... der Pegel ist ja noch immer LOW, wird da 
dann die Ausführung in der ISR unterbrochen und gleich noch mal der ISR 
angesprungen? (dann müsste doch recht flott der Stack volllaufen, oder?) 
Dann wär' der Pegel-Interrupt ja für nix.

Danke!

von Einer K. (Gast)


Lesenswert?

Ralf S. schrieb:
> Kann in der ISR sofort noch mal der gleiche Interrupt ausgelöst
> werden?
Nur wenn es der Programmierer erzwingt.

Ralf S. schrieb:
> Dann wär' der Pegel-Interrupt ja für nix.
Stell dir mal vor: Der Programmierer kann den Pegelinterrupt auch sogar 
deaktivieren.
Sollte er auch tun, zumindest dann solange, bis der Interrupt wieder 
gebraucht wird.

Ralf S. schrieb:
> Der Interrupt unterbricht die Ausführung
> in loop(),
Nicht nur in loop(), sondern überall, wo, oder besser, wenn es erlaubt 
ist.

von Ralf S. (ralf_s480)


Lesenswert?

Auch wenn deine Antwort auf mich sehr patzig wirkt (warum schreibst du 
eigentlich so von-oben-herab?), hat sie doch geholfen.
Ich hab mal das Datasheet durchforstet und poste mal schnell meine 
Erkenntnisse, nur für den Fall dass jemand mal das selbe Problem hat:

Arduino F. schrieb:
> Ralf S. schrieb:
>> Kann in der ISR sofort noch mal der gleiche Interrupt ausgelöst
>> werden?
> Nur wenn es der Programmierer erzwingt.

Scheinbar wird ein Interrupt-Flag (ich glaub in GIFR, weiss nimmer 
genau) gelöscht wenn der Interrupt-Handler beendet wird. Vorher muss man 
also nicht vor einem erneuten Auslösen Angst haben.

> Ralf S. schrieb:
>> Dann wär' der Pegel-Interrupt ja für nix.
> Stell dir mal vor: Der Programmierer kann den Pegelinterrupt auch sogar
> deaktivieren.
> Sollte er auch tun, zumindest dann solange, bis der Interrupt wieder
> gebraucht wird.

Für den ArduinoUNO (hab grad keinen Tiny zum Testen) hiess das für mich 
beim Auslösen vom Interrupt
    EIMSK &= ~_BV(INT0);
und dann halt das Bit wieder setzen wenn man den Interrupt potentiell 
wieder braucht.

Und zu meiner alten Frage:

Bei INT0 geht scheinbar nur der Level-Interrupt (und nicht FALLING) 
weil, siehe Seite 49 vom Datasheet:

Note that recognition of falling or rising edge
interrupts on INT0 requires the presence of an I/O clock, described in 
“Clock Systems and their Distribution” on
page 23.

Lg,
Ralf

von Einer K. (Gast)


Lesenswert?

Ralf S. schrieb:
> (warum schreibst du eigentlich so von-oben-herab?)
Patzig, ok, aber von oben?

Ich bin nicht "oben"!

Als Arduino Jünger werde ich hier oft von oben herab behandelt.
Das ist der ganz normale Ton hier.

Und "Patzig", weil das doch alles in den Datenblättern und in gefühlten 
1000 Tutorials zu finden ist.
Mir behagt das "Erst fragen und danach Datenblatt lesen" nicht so.

Ralf S. schrieb:
> Scheinbar wird ein Interrupt-Flag (ich glaub in GIFR, weiss nimmer
> genau) gelöscht wenn der Interrupt-Handler beendet wird. Vorher muss man
> also nicht vor einem erneuten Auslösen Angst haben.
Nicht ganz richtig!

Damit keine anderen Interrupts dazwischen funken, wird in ISRs 
jeglicher Interrupt gesperrt.
Suche mal nach: "avr sreg register"

von S. Landolt (Gast)


Lesenswert?

> INT0 ... Level-Interrupt ...

Auf demselben Pin wie INT0 liegt PCINT2, wie wäre es damit? Zwar 
triggert Letzterer, wenn ich mich recht erinnere, auf beide Flanken, 
aber das sollte man in Griff bekommen können.

von Dieter F. (Gast)


Lesenswert?

Ralf S. schrieb:
> (ein Buttom)

Ist das eine Krankheit :-)

Ralf S. schrieb:
> setzt dann die
> Ausführung in loop() an genau der Stelle fort, wo er unterbrochen wurde,
> oder?

Ja, wenn kein weiterer Interrupt (z. B. "Millis" beim Arduino) 
dazwischen funkt.

von Einer K. (Gast)


Lesenswert?

Dieter F. schrieb:
> Ralf S. schrieb:
>> setzt dann die
>> Ausführung in loop() an genau der Stelle fort, wo er unterbrochen wurde,
>> oder?
>
> Ja, wenn kein weiterer Interrupt (z. B. "Millis" beim Arduino)
> dazwischen funkt.

Zwischen 2 Interrupts, wird immer min. ein Maschinenbefehl des 
Hauptprogramms ausgeführt.
Es ist also gewährleistet, dass selbst bei Interrupt Dauerfeuer, die 
Hauptschleife weiter läuft. Und sei es auch nur im Schneckentempo.

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.