AVR sleep with external interrupts fixes (#813)

Fixing #812, #811, #809
This commit is contained in:
Embedded Innovation
2017-04-01 15:05:57 +02:00
committed by Olivier
parent b132a8a81e
commit 3350d79fe6
+58 -54
View File
@@ -37,24 +37,31 @@ volatile uint8_t _wakeUp1Interrupt =
volatile uint8_t _wakeUp2Interrupt = volatile uint8_t _wakeUp2Interrupt =
INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback. INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback.
void wakeUp1() //place to send the interrupts void wakeUp1()
{ {
// Disable sleep. When an interrupt occurs after attachInterrupt,
// but before sleeping the CPU would not wake up.
// Ref: http://playground.arduino.cc/Learning/ArduinoSleepCode
sleep_disable();
detachInterrupt(_wakeUp1Interrupt); detachInterrupt(_wakeUp1Interrupt);
if (_wakeUp2Interrupt != INVALID_INTERRUPT_NUM) { // First interrupt occurred will be reported only
detachInterrupt(_wakeUp2Interrupt); if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt)
} {
_wokeUpByInterrupt = _wakeUp1Interrupt; _wokeUpByInterrupt = _wakeUp1Interrupt;
}
} }
void wakeUp2() //place to send the second interrupts void wakeUp2()
{ {
sleep_disable();
detachInterrupt(_wakeUp2Interrupt); detachInterrupt(_wakeUp2Interrupt);
if (_wakeUp1Interrupt != INVALID_INTERRUPT_NUM) { // First interrupt occurred will be reported only
detachInterrupt(_wakeUp1Interrupt); if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt)
} {
_wokeUpByInterrupt = _wakeUp2Interrupt; _wokeUpByInterrupt = _wakeUp2Interrupt;
}
} }
bool interruptWakeUp() inline bool interruptWakeUp()
{ {
return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM; return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM;
} }
@@ -87,7 +94,8 @@ void hwPowerDown(period_t period)
#endif #endif
// Enable interrupts & sleep until WDT or ext. interrupt // Enable interrupts & sleep until WDT or ext. interrupt
sei(); sei();
// Directly sleep CPU, to prevent race conditions! (see chapter 7.7 of ATMega328P datasheet) // Directly sleep CPU, to prevent race conditions!
// Ref: chapter 7.7 of ATMega328P datasheet
sleep_cpu(); sleep_cpu();
sleep_disable(); sleep_disable();
// restore previous WDT settings // restore previous WDT settings
@@ -108,46 +116,31 @@ void hwInternalSleep(unsigned long ms)
#ifndef MY_DISABLED_SERIAL #ifndef MY_DISABLED_SERIAL
MY_SERIALDEVICE.flush(); MY_SERIALDEVICE.flush();
#endif #endif
while (!interruptWakeUp() && ms >= 8000) {
hwPowerDown(SLEEP_8S); static const struct {
ms -= 8000; uint16_t ms;
} period_t period;
if (!interruptWakeUp() && ms >= 4000) { } wdtTimes[] = {
hwPowerDown(SLEEP_4S); { 8000u, SLEEP_8S },
ms -= 4000; { 4000u, SLEEP_4S },
} { 2000u, SLEEP_2S },
if (!interruptWakeUp() && ms >= 2000) { { 1000u, SLEEP_1S },
hwPowerDown(SLEEP_2S); { 500u, SLEEP_500MS },
ms -= 2000; { 250u, SLEEP_250MS },
} { 120u, SLEEP_120MS },
if (!interruptWakeUp() && ms >= 1000) { { 60u, SLEEP_60MS },
hwPowerDown(SLEEP_1S); { 30u, SLEEP_30MS },
ms -= 1000; { 15u, SLEEP_15MS },
} };
if (!interruptWakeUp() && ms >= 500) {
hwPowerDown(SLEEP_500MS); for (size_t i = 0; i < sizeof(wdtTimes)/sizeof(wdtTimes[0]); ++i)
ms -= 500; {
} while (ms >= wdtTimes[i].ms && !interruptWakeUp() )
if (!interruptWakeUp() && ms >= 250) { {
hwPowerDown(SLEEP_250MS); hwPowerDown(wdtTimes[i].period);
ms -= 250; ms -= wdtTimes[i].ms;
} }
if (!interruptWakeUp() && ms >= 125) { }
hwPowerDown(SLEEP_120MS);
ms -= 120;
}
if (!interruptWakeUp() && ms >= 64) {
hwPowerDown(SLEEP_60MS);
ms -= 60;
}
if (!interruptWakeUp() && ms >= 32) {
hwPowerDown(SLEEP_30MS);
ms -= 30;
}
if (!interruptWakeUp() && ms >= 16) {
hwPowerDown(SLEEP_15MS);
ms -= 15;
}
} }
int8_t hwSleep(unsigned long ms) int8_t hwSleep(unsigned long ms)
@@ -164,16 +157,26 @@ int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms)
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2,
unsigned long ms) unsigned long ms)
{ {
// ATMega328P supports following modes to wake from sleep: LOW, CHANGE, RISING, FALLING
// Datasheet states only LOW can be used with INT0/1 to wake from sleep, which is incorrect.
// Ref: http://gammon.com.au/interrupts
// Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt() // Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt()
// and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled! // and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled!
cli(); cli();
// attach interrupts // attach interrupts
_wakeUp1Interrupt = interrupt1; _wakeUp1Interrupt = interrupt1;
_wakeUp2Interrupt = interrupt2; _wakeUp2Interrupt = interrupt2;
if (interrupt1 != INVALID_INTERRUPT_NUM) {
// Attach external interrupt handlers, and clear any pending interrupt flag
// to prevent waking immediately again.
// Ref: https://forum.arduino.cc/index.php?topic=59217.0
if (interrupt1 != INVALID_INTERRUPT_NUM) {
EIFR = _BV(INTF0);
attachInterrupt(interrupt1, wakeUp1, mode1); attachInterrupt(interrupt1, wakeUp1, mode1);
} }
if (interrupt2 != INVALID_INTERRUPT_NUM) { if (interrupt2 != INVALID_INTERRUPT_NUM) {
EIFR = _BV(INTF1);
attachInterrupt(interrupt2, wakeUp2, mode2); attachInterrupt(interrupt2, wakeUp2, mode2);
} }
@@ -194,7 +197,8 @@ int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mo
} }
// Return what woke the mcu. // Return what woke the mcu.
int8_t ret = MY_WAKE_UP_BY_TIMER; // default: no interrupt triggered, timer wake up // Default: no interrupt triggered, timer wake up
int8_t ret = MY_WAKE_UP_BY_TIMER;
if (interruptWakeUp()) { if (interruptWakeUp()) {
ret = static_cast<int8_t>(_wokeUpByInterrupt); ret = static_cast<int8_t>(_wokeUpByInterrupt);
} }