Successfully implementing sleep mode

I'm building a project that displays a random poem from an SD card to a LCD on button press. I reached the point where I wanted to enclose it and power it via batteries. I would like the Arduino to sleep until a button is pressed, wake up, display a random poem from the SD card, then return to sleep. So far I've been able to get the Arduino to go to sleep but on button press it prints out a poem too fast to read then returns to sleep. Any help is greatly appreciated.

// SPI
#include <SPI.h>

// interrupt library/pin
#include <avr/interrupt.h>
#include <avr/sleep.h>

// pin to for interrupt
int wakePin = 3;

// SD card chip select pin.
const byte chipSelect = 10;

// File system object. Make SdFat compatible with SD.
#include <SdFat.h>
SdFat SD;

// Directory file.
File root;
// Text file
File entry;
//number of files in root
int rootFileCount = 0;

// initialize the LCD library by associating any LCD interface pin
// with the arduino pin number it is connected to
#include <LiquidCrystal.h>
const int rs = 9, en = 2, d4 = 6, d5 = 7, d6 = 4, d7 = 8;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// number of LCD columns and rows
const byte lcdCols = 20;
const byte lcdRows = 4;

// index of poetry file to read on SD card
int threshold;

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    SysCall::yield();
  }
  Serial.println("Serial initialized. Initializing LCD display...");
  // set up the LCD's number of columns and rows:
  lcd.begin(lcdCols, lcdRows);
  // turn off LCD autoscrolling
  lcd.noAutoscroll();
  // set up switch pins
  pinMode(wakePin, INPUT_PULLUP);
  Serial.println("LCD initialized. Initializing SD card...");
  //Initialize at the highest speed supported by the board that is
  //not over 50 MHz. Try a lower speed if SPI errors occur.
  delay(1000);
  while (!SD.begin(chipSelect, SD_SCK_MHZ(50))) {
    SD.initErrorHalt();
  }
  Serial.println("SD card initialized. Opening root directory...");
  if (!root.open("/")) {
    Serial.println("Opening root directory failed.");
  }
  while (root.openNextFile()) {
    rootFileCount++;
  }
  Serial.print(rootFileCount);
  Serial.println(" files counted.");
  // generate a seed for the RNG and random starting colours
  randomSeed(analogRead(0));
  delay(1000);

}

void sleepNow()
{
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  noInterrupts ();          // make sure we don't get interrupted before we sleep
  sleep_enable ();          // enables the sleep bit in the mcucr register
  EIFR = bit (INTF0);       // clear flag for interrupt 0
  attachInterrupt (1, wakeUpNow, RISING);  // wake up on rising edge
  interrupts ();           // interrupts allowed now, next instruction WILL be executed
  sleep_cpu ();            // here the device is put to sleep
  detachInterrupt (1);      // stop this interrupt until next time
}

void loop() {
  Serial.println("going to sleep");
  delay(150);
  sleepNow();
}

void wakeUpNow()
{
  Serial.println("waking up");
  delay(1000);
  displayPoem();
}

void displayPoem()
{
  // move to root directory beginning
  root.rewindDirectory();
  //generate random number up to file count and open the corresponding file
  threshold = random(0, rootFileCount);
  //loop through directory to open selected random poem
  int i = 0;
  while (i < threshold) {
    entry = root.openNextFile();
    i++;
  }
  // arrays with characters to display on each row of lcd screen
  char row0[lcdCols];
  char row1[lcdCols];
  char row2[lcdCols];
  char row3[lcdCols];
  // initialise as empty otherwise problems will occur when reading first line
  memset(row0, ' ', lcdCols);
  memset(row1, ' ', lcdCols);
  memset(row2, ' ', lcdCols);
  memset(row3, ' ', lcdCols);
  // read the file byte by byte and display it on the LCD screen
  byte b = 0;
  byte le;
  bool leFlag = false;
  while (entry.available()) {
    //if the next byte is a carriage return
    if (entry.peek() == 13) {
      leFlag = true;
      //ignore carriage return and line end symbols
      entry.read();
      entry.read();
    }
    else {
      // ignore any non basic ascii symbols
      while (entry.peek() < 32 || entry.peek() > 126) {
        entry.read();
      }
      //first row
      if (b < lcdCols) {
        row0[b] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols - 1 && entry.peek() != ' ') {
          //find the last instance of space in row0
          le = lineEnd(row0, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row1[c - le] = row0[c];
            row0[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 2 * lcdCols - 1 - le;
        }
        //second row
      } else if (b < lcdCols * 2) {
        row1[b - lcdCols] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols * 2 - 1 && entry.peek() != ' ') {
          //find the last instance of space in row1
          le = lineEnd(row1, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row2[c - le] = row1[c];
            row1[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 3 * lcdCols - 1 - le;
        }
        //third row
      } else if (b < lcdCols * 3) {
        row2[b - lcdCols * 2] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols * 3 - 1 && entry.peek() != ' ') {
          //find the last instance of space in row2
          le = lineEnd(row2, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row3[c - le] = row2[c];
            row2[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 4 * lcdCols - 1 - le;
        }
        //fourth row
      } else if (b < lcdCols * 4) {
        row3[b - lcdCols * 3] = entry.read();
      }
      b++;
    }
    // write the row bytes to the display
    if (b > lcdCols * 4 || leFlag == true) {
      lcd.clear();
      lcd.home();
      lcd.write(row0);
      lcd.setCursor(0, 1);
      lcd.write(row1);
      lcd.setCursor(0, 2);
      lcd.write(row2);
      lcd.setCursor(0, 3);
      lcd.write(row3);
      // time to wait between displaying lines
      delay(3500);
      memset(row0, ' ', lcdCols);
      memset(row1, ' ', lcdCols);
      memset(row2, ' ', lcdCols);
      memset(row3, ' ', lcdCols);
      b = 0;
      leFlag = false;
    }
  }
  // clear the screen and reset the cursor
  lcd.clear();
  lcd.home();
  entry.close();
}
// determine the position of where to break the display in a row of characters
byte lineEnd(char row[], byte rowLength) {
  byte le = 0;
  for (byte c = 0; c < rowLength; c++) {
    if (row[c] == ' ') {
      le = c;
    }
  }
  return le;
}

Your code is oddly structured, but at the risk of stating the obvious, have you tried to place a delay(10000) ten seconds at some strategic place?

Not ideal, but just to see if the code is flowing like you think.

Did you write this or start with a working example? If you followed some example, please post a link for us.

Have you read Nick Gammon's article on low power? Google will find it easily. You could pattern your existing functions into his framework easily.

Also - how are you planning to power up and down the SD card hardware and anything else attached to,your device?

a7

I'm relatively new to Arduino and have very little coding experience. I have tried placing a delay in between writing the rows to the LCD and it seems to just print lines from the poems for longer rather than pause after printing a row.

I started off following a project by a user named kzra. He made a poetry clock. I didn't want the clock functionality just the ability to print a random poem from a collection on an sd card. His Project

I got it to successfully switch from a welcome screen waiting for a button press to display a random poem, cycling through it in a readable manner, then going back to the welcome screen. I'll post the code for that below.

I will check his article on low power thank you.

I have not got that far yet I'm still relatively new to Arduino.

// SPI library to communicate with SD card
#include <SPI.h>

// SD card chip select pin.
const byte chipSelect = 10;

// File system object. Make SdFat compatible with SD.
#include <SdFat.h>
SdFat SD;

// Directory file.
File root;
// Text file
File entry;
//number of files in root
int rootFileCount = 0;

// initialize the LCD library by associating any LCD interface pin
// with the arduino pin number it is connected to
#include <LiquidCrystal.h>
const int rs = 9, en = 2, d4 = 6, d5 = 7, d6 = 4, d7 = 8;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// number of LCD columns and rows
const byte lcdCols = 20;
const byte lcdRows = 4;

//operating mode switch pin and state variables for screen switch
const int buttonPin = 3;
int buttonState;
int lastButtonState = LOW;
boolean hasChanged = true;
int WhichScreen = 1;

//debounce variables
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

// index of poetry file to read on SD card
int threshold;

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    SysCall::yield();
  }
  Serial.println("Serial initialized. Initializing LCD display...");
  // set up the LCD's number of columns and rows:
  lcd.begin(lcdCols, lcdRows);
  // turn off LCD autoscrolling
  lcd.noAutoscroll();
  // set up switch pins
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.println("LCD initialized. Initializing SD card...");
  // Initialize at the highest speed supported by the board that is
  // not over 50 MHz. Try a lower speed if SPI errors occur.
  delay(1000);
  while (!SD.begin(chipSelect, SD_SCK_MHZ(50))) {
    SD.initErrorHalt();
  }
  Serial.println("SD card initialized. Opening root directory...");
  if (!root.open("/")) {
    Serial.println("Opening root directory failed.");
  }
  //Serial.println("Root directory opened. Counting files...");
  while (root.openNextFile()) {
    rootFileCount++;
  }
  Serial.print(rootFileCount);
  Serial.println(" files counted.");
  // generate a seed for the RNG and random starting colours
  randomSeed(analogRead(0));
}

void loop() {
  if (hasChanged == true) {
    switch (WhichScreen) {
      case 1:
        {
          firstScreen();
        }
        break;

      case 2:
        {
          secondScreen();
        }
        break;
    }
  }
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == HIGH) {
        hasChanged = true;
        WhichScreen++;
      }
    } else {
      hasChanged = false;
    }
  }
  lastButtonState = reading;
  if (WhichScreen > 2) {
    WhichScreen = 1;
  }
}

void firstScreen()
{
  Serial.println("Displaying welcome screen.");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Press the button for"));
  lcd.setCursor(0, 1);
  lcd.print(F("a random poem.."));
}

void secondScreen()
{
  Serial.println("Button pressed.. displaying random poem...");
  // move to root directory beginning
  root.rewindDirectory();
  //generate random number up to file count and open the corresponding file
  threshold = random(0, rootFileCount);
  //loop through directory to open selected random poem
  int i = 0;
  while (i < threshold) {
    entry = root.openNextFile();
    i++;
  }
  // arrays with characters to display on each row of lcd screen
  char row0[lcdCols];
  char row1[lcdCols];
  char row2[lcdCols];
  char row3[lcdCols];
  // initialise as empty otherwise problems will occur when reading first line
  memset(row0, ' ', lcdCols);
  memset(row1, ' ', lcdCols);
  memset(row2, ' ', lcdCols);
  memset(row3, ' ', lcdCols);
  // read the file byte by byte and display it on the LCD screen
  byte b = 0;
  byte le;
  bool leFlag = false;
  while (entry.available()) {
    //if the next byte is a carriage return
    if (entry.peek() == 13) {
      leFlag = true;
      //ignore carriage return and line end symbols
      entry.read();
      entry.read();
    }
    else {
      // ignore any non basic ascii symbols
      while (entry.peek() < 32 || entry.peek() > 126) {
        entry.read();
      }
      //first row
      if (b < lcdCols) {
        row0[b] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols - 1 && entry.peek() != ' ') {
          //find the last instance of space in row0
          le = lineEnd(row0, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row1[c - le] = row0[c];
            row0[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 2 * lcdCols - 1 - le;
        }
        //second row
      } else if (b < lcdCols * 2) {
        row1[b - lcdCols] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols * 2 - 1 && entry.peek() != ' ') {
          //find the last instance of space in row1
          le = lineEnd(row1, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row2[c - le] = row1[c];
            row1[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 3 * lcdCols - 1 - le;
        }
        //third row
      } else if (b < lcdCols * 3) {
        row2[b - lcdCols * 2] = entry.read();
        // prevent words from splitting over lines
        if (b == lcdCols * 3 - 1 && entry.peek() != ' ') {
          //find the last instance of space in row2
          le = lineEnd(row2, lcdCols);
          //shift all text after the last space to the row beneath
          for (byte c = le; c < lcdCols; c++) {
            row3[c - le] = row2[c];
            row2[c] = ' ';
          }
          //update the index position to account for the shift;
          b = 4 * lcdCols - 1 - le;
        }
        //fourth row
      } else if (b < lcdCols * 4) {
        row3[b - lcdCols * 3] = entry.read();
      }
      b++;
    }
    // write the row bytes to the display
    if (b > lcdCols * 4 || leFlag == true) {
      lcd.clear();
      lcd.home();
      lcd.write(row0);
      lcd.setCursor(0, 1);
      lcd.write(row1);
      lcd.setCursor(0, 2);
      lcd.write(row2);
      lcd.setCursor(0, 3);
      lcd.write(row3);
      // time to wait between displaying lines
      delay(3500);
      memset(row0, ' ', lcdCols);
      memset(row1, ' ', lcdCols);
      memset(row2, ' ', lcdCols);
      memset(row3, ' ', lcdCols);
      b = 0;
      leFlag = false;
    }
  }
  // clear the screen and reset the cursor
  lcd.clear();
  lcd.home();
  entry.close();
  WhichScreen++;
}
// determine the position of where to break the display in a row of characters
byte lineEnd(char row[], byte rowLength) {
  byte le = 0;
  for (byte c = 0; c < rowLength; c++) {
    if (row[c] == ' ') {
      le = c;
    }
  }
  return le;
}

I have thought about this (I happen to be on a low power mission just now myself!): I don't see how what you posted was working at all in the manner you describe. That's the trouble with this code stuff.

Do you have a meter capable of showing current at the uA range?

What Arduino are you using? My comments apply to the UNO and Pro Mini. There are hardware impedements to achieving good sleep, as well as the aforementioned power issue with the SD card circuitry.

How is your button wired? The code suggests you have a button on wakePin to ground. I don't think it would make any difference, but FALLING would usually be seen where you have RISING.

Your "go to sleep" section

void sleepNow()
{
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  noInterrupts ();          // make sure we don't get interrupted before we sleep
  sleep_enable ();          // enables the sleep bit in the mcucr register
  EIFR = bit (INTF0);       // clear flag for interrupt 0
  attachInterrupt (1, wakeUpNow, RISING);  // wake up on rising edge
  interrupts ();           // interrupts allowed now, next instruction WILL be executed
  sleep_cpu ();            // here the device is put to sleep
  detachInterrupt (1);      // stop this interrupt until next time
}

Is split between talking about interrupt 0 and interrupt 1. It is safer to use

 digitalPinToInterrupt(wakePin)

instead of 1. In both the call to attach and detach, viz:

  attachInterrupt(digitalPinToInterrupt(wakePin), wakeUpNow, FALLING); 

Pin 3 is interrupt 1, so you need

EIFR = bit (INTF1);

Make wakePin a constant, so add const like

const int wakePin = 3;

in the declaration. It should not matter, it just adds a bit of bullet proofing.

This is the odd part

void loop() {
  Serial.println("going to sleep");
  delay(150);
  sleepNow();
}

void wakeUpNow()
{
  Serial.println("waking up");
  delay(1000);
  displayPoem();
}

Remove

  detachInterrupt (1);      // stop this interrupt until next time

from sleepNow()

and use

void wakeUpNow()
{
    sleep_disable();
    detachInterrupt(digitalPinToInterrupt(wakePin)); 
}

as the ISR and then loop() would be

void loop() {
    Serial.println("going to sleep");

    sleepNow();

// asleep, waiting to be woked up

    Serial.println("Awake again!");

    displayPoem();

    delay(10000);   // let it sit here for 10 seconds
}

With the exception of lacking sleep_disable(), and the change to using digitaPinToInterrupt, it's what you had but re-arragned a bit.

Anyway, try the changes here. Then the fun begins, because at a glance you sleep might not be very sound, that is to say the sleeping current may be so high (relatively) that your batteries will drain sooner than later.

HTH

a7

I have a meter capable of uA ranges.

I'm prototyping this on an UNO but the final build will be on a Pro Mini.

After implementing the changes you suggested it works successfully. The arduino successfully sleeps and wakes up on button press, displays a random poem from the SD card then returns to sleep after the 10 second delay!

Thanks for your help! I will have to figure out how to conserve power with my LCD screen and SD card reader next. I've also considered wiring a switch in series with my battery pack to switch battery power on and off to the entire circuit and whether this would be a more practical approach.

Yay!

I did another thing w/o using low power at all, too late just now but 2morrow will share.

The pro mini is way easier than the UNO to slim down power diet wise, the SD and LCD can be dealt with but are sorta a PITA.

L8R

a7

I appreciate all your input!

For my current project I need to wake up on pushbutton and wake up periodically, twice a day mediated by an RTC. So messing with low power modes, switching peripherals on and off &c.

If all you need is wake up on pushbutton, this on/off switch


is one I have used. It is obsolete, but there’s this update

click the link to review all products in the category.

It’s very easy to use, so (but?) no fun at all if you think you’ve been having fun getting as far as you have with you current approach.

Such a switch will power up on pushbutton, and can be turned off from an output on your Arduino.

It presents only one new challenge, and that is selecting a random peom, as the program will start from scratch (setup()) and do the exact same thing every time if you don't figure out how to make it not do that.

But anyone here can help with that should the time come.

a7

I will look into those thanks! Currently I have some of these:

I was going to wire one in series with my 3 aa batteries and use on/off switch to toggle the entire project on and off. I might check and see how much power the LCD pulls when the backlight is turned off.

Also I was doing some research on the SD card reader. The author of the library SDFat, on another forum post, said "Most SD cards automatically go to a low power mode when no SPI clock is applied."

It surprised me to learn just now from my friend google that some brands of SD cards do better than others. And if you are scrounging uA as a necessity or just for the challenge, some can take a lot of power even in a shut down mode. Relatively.

Which is why you'll find ppl using a circuit of some kind to turn them off off.

Those switches look nice. The advantage of the Pololu device, or similar circuits one can build for themselves, is the ability to go ON with a pushbutton, and stay on until the program itself reaches out and turns itself OFF.

a7

Oooh I see why the Pololu device would be useful in my situation. Thanks I will have to pickup a few!