rocketscream / Low-Power

Low Power Library for Arduino
www.rocketscream.com
1.26k stars 345 forks source link

problem with using timed LowPower.powerDown and INT0 ISR #89

Closed nimasaj closed 4 years ago

nimasaj commented 4 years ago

Thanks for providing this library and publishing it in public. Using this library, it seems that periodic wakeup using PowerDown mode is incompatible with external interrupt on Atmega328P running on 3.3V-8MHz. I would like to keep Atmega338P in DeepSleep for a fixed interval time (14 minutes periods), but at the same time if an interrupt happens, it can wake up. I have to mention that running solely on deep sleep mode LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF) is working perfectly, but not working when using it with an ISR. By running following code, everything seem to work in the beginning but after the first period (14 minutes), the board doesn't go into DeepSleep mode and consumption increases to around 3.8mA. Do you have a suggestion/fix/hint for this issue?

update1. FYI, I'm using SoftwareSerial library in my code as well.

Sample code ```c #include "LowPower.h" #include const uint8_t Sleep = 14; //in minutes const int interruptPin = 2; // INT0 - PD2 boolean debug = false; volatile uint8_t counter = 0; volatile bool IntFlag = false; SoftwareSerial radio (SF_rxPin, SF_txPin); void setup() { pinMode(interruptPin, INPUT_PULLUP); if (debug) { Serial.begin(9600); } attachInterrupt(digitalPinToInterrupt(interruptPin), wakeUp, LOW); } void loop() { detachInterrupt(digitalPinToInterrupt(interruptPin));// disables INT0 so the wakeUp() code will not be executed when MCU is up. if(IntFlag) { //some code here } else { //Some code is running here to to measure sensor data } //Some code here to transmit measurements IntFlag=false; delay(500); sleepNow(); } void wakeUp() { static unsigned long last_millis = 0; unsigned long m = millis(); if (m - last_millis < 100) { //Do nothing - taking care of bouncing issue } else { IntFlag=true; counter++; } last_millis = m; } void sleepNow(){ if(debug){ Serial.print("Going to Sleep for "); Serial.print(Sleep); Serial.println(" minutes"); delay(500); } attachInterrupt(digitalPinToInterrupt(interruptPin),wakeUp, LOW);//when pin 2 gets LOW, run function wakeUp() Sleep_MCU(Sleep); } void Sleep_MCU(uint8_t Time) { for (uint8_t i = 0; i < (Time * 60 / 8); i++) { // Enter power down state for 8 s with ADC and BOD module disabled LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); } } ```
rocketscream commented 4 years ago

This is one of the biggest limitation on the older ATMega series which I didn't come across (and hence on radio silence on this issue) until very recently. If your source of wake up has a very short pulse (example your low pulse from UART) even in the range of ms, the MCU will not be able to determine what woke it up because upon waking up there will be a delay due to oscillator start up sequence (configured using the fuse and how long depends on the fuse settings). By the time the oscillator start up delay has been completed, your signal has probably already went HIGH. So, this causes it to skip your ISR function call where you would set the IntFlag. Possible remedy is to use a shorter delay oscillator start up setting on the fuse but I have tried down to 4800 bps using the shortest possible oscillator start up sequence, and even with that there are chances it skipped the pulse. If you have only the UART as the single source of wake up, you can skip setting and checking the flag altogether and that would work as the low pulse is still able to wake the MCU up but fall short of determining the cause which is probably not important in this case.