Skip to content

Commit dd6ea93

Browse files
committed
Implement RAII-patterned token class for ForcedLightSleep begin()/end().
1 parent b8a95de commit dd6ea93

File tree

2 files changed

+87
-50
lines changed

2 files changed

+87
-50
lines changed

cores/esp8266/Esp.h

+30
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,34 @@ class EspClass {
293293

294294
extern EspClass ESP;
295295

296+
/// RAII helper token class for forcedLightSleepBegin()/End().
297+
/// Cast to bool to check that forced light sleep can commence.
298+
/// The forced light sleep is entered when the token goes out of scope.
299+
/// Call cancel() to prevent sleeping.
300+
class ESPForcedLightSleepToken {
301+
private:
302+
ESPForcedLightSleepToken() = delete;
303+
ESPForcedLightSleepToken(const ESPForcedLightSleepToken&) = delete;
304+
ESPForcedLightSleepToken& operator=(const ESPForcedLightSleepToken&) = delete;
305+
306+
bool isArmed = false;
307+
308+
public:
309+
ESPForcedLightSleepToken(uint32_t duration_us, void (*wakeupCb)()) {
310+
isArmed = ESP.forcedLightSleepBegin(10 * 1000 * 1000, wakeupCb);
311+
}
312+
~ESPForcedLightSleepToken() {
313+
if (isArmed) ESP.forcedLightSleepEnd(false);
314+
}
315+
operator bool() {
316+
return isArmed;
317+
}
318+
void cancel() {
319+
if (isArmed) {
320+
ESP.forcedLightSleepEnd(true);
321+
isArmed = false;
322+
}
323+
}
324+
};
325+
296326
#endif //ESP_H

libraries/esp8266/examples/ForcedLightSleep/ForcedLightSleep.ino

+57-50
Original file line numberDiff line numberDiff line change
@@ -26,70 +26,77 @@
2626
// you can use any GPIO for WAKE_UP_PIN except for D0/GPIO16 as it doesn't support interrupts
2727

2828
void IRAM_ATTR wakeupPinIsr() {
29-
// For edge-triggered IRQ.
30-
detachInterrupt(WAKE_UP_PIN);
31-
schedule_function([]() {
32-
Serial.println("GPIO went from HI to LO");
33-
});
29+
// For edge-triggered IRQ.
30+
detachInterrupt(WAKE_UP_PIN);
31+
schedule_function([]() {
32+
Serial.println("GPIO went from HI to LO");
33+
});
3434
}
3535

3636
void IRAM_ATTR wakeupPinIsrWE() {
37-
// Wakeup IRQs are available as level-triggered only.
38-
detachInterrupt(WAKE_UP_PIN);
39-
schedule_function([]() {
40-
Serial.println("GPIO wakeup IRQ");
41-
});
42-
wakeupPinIsr();
43-
// reattach falling edge IRQ in loop
37+
// Wakeup IRQs are available as level-triggered only.
38+
detachInterrupt(WAKE_UP_PIN);
39+
schedule_function([]() {
40+
Serial.println("GPIO wakeup IRQ");
41+
});
42+
wakeupPinIsr();
43+
// reattach falling edge IRQ in loop
4444
}
4545

4646
void wakeupCallback() {
47-
schedule_function([]() {
48-
Serial.println("wakeup callback was performed");
49-
});
50-
// return to falling edge IRQ, otherwise level-triggered IRQ with wakeup
51-
// would get called unexpectedly while awake.
52-
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
47+
schedule_function([]() {
48+
Serial.println("wakeup callback was performed");
49+
});
50+
// return to falling edge IRQ, otherwise level-triggered IRQ with wakeup
51+
// would get called unexpectedly while awake.
52+
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
5353
}
5454

5555
void setup() {
56-
Serial.begin(74880);
57-
while (!Serial)
58-
;
59-
delay(100);
60-
pinMode(LED_BUILTIN, OUTPUT); // activity and status indicator
61-
digitalWrite(LED_BUILTIN, LOW); // turn on the LED
62-
pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, interrupt for Forced Light Sleep
63-
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
56+
Serial.begin(74880);
57+
while (!Serial)
58+
;
59+
delay(100);
60+
pinMode(LED_BUILTIN, OUTPUT); // activity and status indicator
61+
digitalWrite(LED_BUILTIN, LOW); // turn on the LED
62+
pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, interrupt for Forced Light Sleep
63+
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
6464
}
6565

6666
using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
6767
oneShotYieldMs gotoSleep(2000);
6868

6969
void loop() {
70-
if (gotoSleep && ESP.forcedLightSleepBegin(10 * 1000 * 1000, wakeupCallback)) {
71-
// No new timers, no delay(), between forcedLightSleepBegin() and forcedLightSleepEnd().
72-
// Only ONLOW_WE or ONHIGH_WE interrupts work, no edge, that's an SDK or CPU limitation.
73-
// If the GPIO is in the state that will cause a wakeup on attaching the interrupt,
74-
// it cannot trigger a wakeup later, but any sleep duration will be honored.
75-
bool wakeupPinIsHigh = digitalRead(WAKE_UP_PIN);
76-
delayMicroseconds(5000);
77-
wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN);
78-
delayMicroseconds(5000);
79-
wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN);
80-
// the GPIO might still bounce to LOW after this but before sleep is full engaged,
81-
// disabling wakeup after all
82-
if (wakeupPinIsHigh) {
83-
attachInterrupt(WAKE_UP_PIN, wakeupPinIsrWE, ONLOW_WE);
70+
if (gotoSleep) {
71+
// No new timers, no delay(), while RAII ForcedLightSleepToken exists.
72+
// Only ONLOW_WE or ONHIGH_WE interrupts work, no edge, that's an SDK or CPU limitation.
73+
// If the GPIO is in the state that will cause a wakeup on attaching the interrupt,
74+
// it cannot trigger a wakeup later, but any sleep duration will be honored.
75+
bool wakeupPinIsHigh = digitalRead(WAKE_UP_PIN);
76+
{
77+
ESPForcedLightSleepToken token(10 * 1000 * 1000, wakeupCallback);
78+
if (token) { // if true, run user code to set up forced light sleep details
79+
// debouncing the wake up pin
80+
delayMicroseconds(5000);
81+
wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN);
82+
delayMicroseconds(5000);
83+
wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN);
84+
// the GPIO might still bounce to LOW after this but before sleep is full engaged,
85+
// disabling wakeup after all
86+
if (wakeupPinIsHigh) {
87+
attachInterrupt(WAKE_UP_PIN, wakeupPinIsrWE, ONLOW_WE);
88+
}
89+
digitalWrite(LED_BUILTIN, HIGH); // turn the LED off so they know the CPU isn't running
90+
if (!wakeupPinIsHigh) token.cancel();
91+
}
92+
// RAII token gets destructed, going to sleep if all went well
93+
}
94+
digitalWrite(LED_BUILTIN, LOW); // turn on the LED
95+
// retry immediately if the GPIO was found not ready for entering sleep
96+
if (wakeupPinIsHigh) {
97+
gotoSleep.reset();
98+
}
99+
// restore falling edge IRQ
100+
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
84101
}
85-
digitalWrite(LED_BUILTIN, HIGH); // turn the LED off so they know the CPU isn't running
86-
ESP.forcedLightSleepEnd(!wakeupPinIsHigh);
87-
digitalWrite(LED_BUILTIN, LOW); // turn on the LED
88-
// retry immediately if the GPIO was found not ready for entering sleep
89-
if (wakeupPinIsHigh) {
90-
gotoSleep.reset();
91-
}
92-
// restore falling edge IRQ
93-
attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING);
94-
}
95102
}

0 commit comments

Comments
 (0)