How to reduce current draw (sleep) with button on Pro Mini

I'm making an altitude recorder to go in a model rocket, so I'm trying to reduce weight as much as possible. I'm using an

  • Arduino Pro Mini, 328P, 3.3V 8MHz
  • SparkFun MPL3115A2 pressure sensor
  • SparkFun OpenLog
  • 240mAh Lipo battery

I could use a SPST switch in the power supply, but I'd rather use a single push button to control the power, or at least, to put the arduino to sleep and then wake it back up.

I looked at using the Narcoleptic library but there's a bug report saying it doesn't work on a 3.3V 328 arduino

I found this LowPowerExternalWakeup tutorial which sounded like what I wanted but it uses an "ArduinoLowPower.h" library that I can't find.

I found a different Low-Power library here but I can't seem to figure out its external interrupt example code - see below.

// **** INCLUDES *****
#include "LowPower.h"

// Use pin 2 as wake up pin
const int wakeUpPin = 2;

void wakeUp()
{
    // Just a handler for the pin interrupt.
}

void setup()
{
    // Configure wake up pin as input.
    // This will consumes few uA of current.
    pinMode(wakeUpPin, INPUT);   
}

void loop() 
{
    // Allow wake up pin to trigger interrupt on low.
    attachInterrupt(0, wakeUp, LOW);
    
    // Enter power down state with ADC and BOD module disabled.
    // Wake up when wake up pin is low.
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 
    
    // Disable external pin interrupt on wake up pin.
    detachInterrupt(0); 
    
    // Do something here
    // Example: Read sensor, data logging, data transmission.
}

I guess I don't understand how this code is supposed to execute. Why attach the interrupt in the loop? Won't that put the arduino back to sleep every time it goes through the loop?

Is there an easier way to do this that I'm missing? Thank you!

It is designed to wake up, do something and then (when loop is called again), go to sleep.

While you are awake there is no need for the interrupt.

If you need the tradition looping mechanism in your code, the "do something" could just be repeated calls to, say, myLoop. You could have your myLoop return something to indicate whether you want another loop service call or are done, in which case you let loop, er, loop and the whole thing goes to sleep again.

Or rejigger the functionality a bit. The main idea confusing here is that whilst awake, you don't need or want to think or worry about interrupts.

Very commonly found. Interrupts enabled only during sleep, waiting only to sense and react to the external stimulus.

HTH

a7

bfree42:
Why attach the interrupt in the loop? …

Just check the state of the sleep button at the start of loop() and sleep if pressed.

Re-jigging as @free42 says might make more sense than the example:

void setup()
{
  pinMode(sleepPin, INPUT_PULLUP);
}

void loop()
{
  checkSleepButton();   // if not pressed then we carry on as normal

  // do the normal awake stuff here
}

void checkSleepButton()
{
  if (digitalRead(sleepPin) == LOW)
  {
    // put go to sleep code here
    // zzzzzzzzz
    // Wakes up here, returns to loop() as if nothing's happened...
  }
}

Karma++ for clickable links and code tags.

I've got the code working but it's not behaving the way I expect. Can anybody tell me what I'm doing wrong or how to improve it?

First of all, I thought the arduino would sleep when it executes the LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); instruction but it doesn't, at least not right away. After that line, it executes another few lines before falling asleep. (More specifically, the code moves into a function and then falls asleep in the middle of printing characters to the Serial Monitor - see below.)

Second of all, after uploading the code the arduino goes to sleep before it finishes executing the setup code, even though the LowPower instruction doesn't appear until inside the loop function! (Is this something the LowPower library makes happen by default?)

Here's how it unfolds: I first upload the code. I expect to see "Hello World!" and then "Starting loop()" on the Serial Monitor since the first instruction is in setup, and the second occurs before the LowPower instruction. Instead the arduino immediately goes into low power mode and I only see this on Serial Monitor:

Now I press the Power button. The LED_BUILTIN turns on for just a split second. I see this on Serial Monitor...

⸮⸮llo World!
Starting loop()
Just passed LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF)
Inside do

...And the arduino goes back to sleep.

So I press the Power button again. Serial Monitor shows me...

Z⸮gTheThingLoop()
Exiting doingTheThingLoop()
Starting loop()
Just passed LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF)
Inside doingTheThingLoop()

...and this time the arduino stays on. The LED_BUILTIN stays lit. I can press the Blink button and the blinking starts and stops like it should. This Blink button is working just fine.

Now I click the power button again to turn it off. The arduino turns off and I see this on Serial Monitor:


I click the power button again to turn it back on. The arduino stays on. This is on Serial Monitor:

⸮⸮iting doingTheThingLoop()
Starting loop()
Just passed LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF)
Inside do

Here's my actual code.

/* Code to make arduino go into and out of low power mode.
 *  2 buttons attach to arduino. 
 *      Power button: 
 *          - makes arduino go into/come out of low power mode
 *          - push button connects pin 3 to ground
 *      Blink button:
 *          - makes an RGB LED start & stop flashing in sequence. 
 *          - push button connects Vcc to pin 2 with 10k resistor to ground
 *          
 *  RGB LED is common cathode, 100ohm resistors connect pins 7-9 to leads.
 */
 
#include "LowPower.h"

// Outputs for RGB LED
int greenPin = 7;
int bluePin = 8;
int redPin = 9;

//Pins for button interrupts.
const byte interruptPowerPin = 3;
const byte interruptBlinkPin = 2;

// Flags for the doingTheThingLoop().
volatile boolean makeBlinkFlag = false; // false == NO; true == YES
volatile boolean powerFlag = false; // false == NO; true == YES

void interruptPower()
{
  powerFlag =!powerFlag;
  if(powerFlag) //If power being turned on
  {
    digitalWrite(LED_BUILTIN, HIGH); //Turn on LED_BUILTIN
  }
}


void interruptBlink()
{
  makeBlinkFlag =!makeBlinkFlag;
}

void doingTheThingLoop()
{
  Serial.println("Inside doingTheThingLoop()");
  while(powerFlag) // If power is on, do next thing. If it's not, exit this function to return to loop.
  {
    //Serial.println("  PowerFlag == TRUE");
    while(makeBlinkFlag) //If the makeBlink button has been pressed, do these instructions.
    {
      Serial.println("    Now doing the thing!");
      digitalWrite(greenPin, HIGH);
      delay(500);
      digitalWrite(greenPin, LOW);
      digitalWrite(bluePin, HIGH);
      delay(500);
      digitalWrite(bluePin, LOW);
      digitalWrite(redPin, HIGH);
      delay(500);
      digitalWrite(redPin, LOW);
    }
  }
  Serial.println("Exiting doingTheThingLoop()");
}

void setup() {
  // Establish RGB LED leads
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
  pinMode(redPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(interruptBlinkPin, INPUT);
  
  attachInterrupt(digitalPinToInterrupt(interruptBlinkPin), interruptBlink, RISING);

  Serial.begin(9600);
  Serial.println("Hello World!");
  

}

void loop() {
  Serial.println("Starting loop()");
  pinMode(interruptPowerPin, INPUT_PULLUP); //Interrupt only works if pin goes LOW
  attachInterrupt(digitalPinToInterrupt(interruptPowerPin), interruptPower, LOW); // This only works for LOW
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

  //------This is where I expected the arduino to power off.------
  
  Serial.println("Just passed LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF)");
  doingTheThingLoop();

    digitalWrite(LED_BUILTIN, LOW);
}

Serial is interrupt driven, so it sends characters in the background while your code carries on.
If there are still characters in the Serial buffer when you go to sleep the output is truncated.
The remedy is to use Serial.flush() before sleeping, that way it will make sure the Serial buffer is empty before carrying on.

https://www.arduino.cc/reference/en/language/functions/communication/serial/flush

When you've tried that you might get a better idea of what is wrong (or right) with your code.

Martin-X:
Serial is interrupt driven, so it sends characters in the background while your code carries on.
If there are still characters in the Serial buffer when you go to sleep the output is truncated.
The remedy is to use Serial.flush() before sleeping, that way it will make sure the Serial buffer is empty before carrying on.

+1.