LED watch not going to sleep

Hi everyone,

I'm trying to put my custom LED watch into deep sleep mode after it displays the time.

It uses an ATmega328P-MMHR (28-pin VQFN) with an M41T62 (8 pin LCC) real time clock, powered by a CR2016 coin battery. Time and day of the week are displayed on 33 multiplexed LEDs.

The watch only lasts for about 1-2 days before running out of power. I expected it to last longer if deep sleep is working, given that each time I press a button, only 3-4 leds are lit on and off very fast (creating persistence of vision) for about 2 seconds.

Can anyone see any problems with my code or if something is preventing the Atmega from going to sleep? Any advice is greatly appreciated.

NB. I use /// to distinguish my comments from commented out code (//).

#include "Wire.h"                /// I2C connections
#include "M41T62.h"              /// M41T62 real time clock
#include "PinChangeInterrupt.h"  /// pin change interrupt
#include "OneButton.h"           /// multi-button
#include <avr/sleep.h>
RTC_M41T62 RTC;  /// real time clock

#define NUM_COL 6   /// columns are anodes
#define NUM_ROW 6   /// rows are cathodes
#define COL_ON LOW  /// the following four lines of code control swtiching the multiplexed LEDs on and off
#define COL_OFF HIGH
#define ROW_ON HIGH
#define ROW_OFF LOW

const int colLED[NUM_COL] = { 2, 3, 4, 5, 6, 7 };      /// pins for anode (+) connections on ATmega328p, columns in LED matrix
const int rowLED[NUM_ROW] = { 0, 1, A0, A1, A2, A3 };  /// pins for cathode (-) connections on ATmega328p, rows in LED matrix
uint8_t display[NUM_ROW][NUM_COL];                     /// this array holds the current image to display

const int button1 = 9;      /// pin for button 1
const int button2 = 10;     /// pin for button 2
int buttonState;            /// the current reading from the input pin
int lastButtonState = LOW;  /// the previous reading from the input pin
int rowPin = 6;
int colPin = 6;

unsigned long lastDebounceTime = 0;  /// the last time the output pin was toggled
unsigned long debounceDelay = 50;    /// the debounce time; increase if the output flickers

bool myFlag1 = false;  /// create flag1 for pin change interrupt
bool myFlag2 = false;  /// create flag2 for pin change interrupt

bool deepSleepEnabled = false;
unsigned long displayStartMillis = 0;
bool displayActive = false;

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - setup + loop - - - - - - - - - - - - - - - -

void setup() {
  pinMode(button1, INPUT_PULLUP);            /// button1 pulled LOW when pressed
  pinMode(button2, INPUT_PULLUP);            /// button2 pulled LOW when pressed
  Wire.begin();                              /// I2C communication with the RTC
  RTC.begin();                               /// start RTC
  RTC.adjust(DateTime(__DATE__, __TIME__));  /// set RTC time to computer time

  for (int i = 0; i < NUM_COL; i++)  /// set all column pins to OUTPUT and OFF
  {
    pinMode(colLED[i], OUTPUT);        /// set column LEDs to output
    digitalWrite(colLED[i], COL_OFF);  /// turn all columns off
  }
  for (int j = 0; j < NUM_ROW; j++)  /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);        /// set row LEDs to output
    digitalWrite(rowLED[j], ROW_OFF);  /// turn all rows off
  }
  attachPCINT(digitalPinToPCINT(button1), setFlag1, CHANGE);  /// interrupt when button 1's state changes
  attachPCINT(digitalPinToPCINT(button2), setFlag2, CHANGE);  /// interrupt when button 2's state changes
}

void loop() {
  if (myFlag1) {
    if (millis() - lastDebounceTime > debounceDelay) {
      lastDebounceTime = millis();
      displayStartMillis = millis();
      displayActive = true;
      myFlag1 = false;  /// Reset the flag
    }
  }

  if (displayActive) {
    if (millis() - displayStartMillis < 200) {
      displayTime();
    } else {
      displayActive = false;
      enterDeepSleep();  /// Function to enter sleep mode
    }
  }
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - time functions - - - - - - - - - - - - - - -

void displayTime() {
  DateTime now = RTC.now();  /// set "now" from the computer's time

  int hour = now.hour() % 12;
  if (hour == 0)
    hour = 12;
  int hourRow = (hour - 1) / 2;
  int hourColumn = (hour - 1) % 2;
  {
    ledOn(hourRow, hourColumn);
    delayMicroseconds(100);
    ledOff(hourRow, hourColumn);
  }

  int minute = now.minute();  /// 0...59
  int minuteRow;
  if (minute < 5)  /// zero minutes is located at the bottom
    minuteRow = 5;
  else
    minuteRow = (minute - 5) / 10;  /// start at 5 at the top
  /// A change of column for each 5 minutes: (m / 5)
  /// But the 10,20,30 are the right column: + 1
  /// There are two columns                : %2
  /// The start column is                  : 2
  int minuteColumn = 2 + ((minute / 5) + 1) % 2;
  {
    ledOn(minuteRow, minuteColumn);
    delayMicroseconds(100);
    ledOff(minuteRow, minuteColumn);
  }
  /// minute remainder 1...4
  int remainder = now.minute() % 5;
  if (remainder > 0)  /// leds off for seconds 0 and 5
  {
    remainder--;  /// row 0 for remainder 1
    ledOn(remainder, 4);
    delayMicroseconds(100);
    //delay(15);
    ledOff(remainder, 4);
  }
  /// ------------------------------
  /// day of the week
  /// ------------------------------
  int weekday = now.dayOfWeek() - 1;
  {
    ledOn(weekday, 5);
    delayMicroseconds(150);
    ledOff(weekday, 5);
  }
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - button functions - - - - - - - - - - - - - - -

void setFlag1() {
  myFlag1 = true;
}

void setFlag2() {
  myFlag2 = true;
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - sleep functions - - - - - - - - - - - - - - -

/// Add this function to handle sleep
void enterDeepSleep() {
  /// disable ADC
  ADCSRA = 0;

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  
  sleep_mode();  /// Enter sleep mode. Will wake up upon external interrupts

  /// Re-enable things here if needed after waking up
  sleep_disable();
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - main display functions - - - - - - - - - - - - - -

/// turn on specific LEDs
inline void ledOn(int row, int col) {
  digitalWrite(rowLED[row], HIGH);  /// row pin to +3V
  digitalWrite(colLED[col], LOW);   /// column pin to 0V
  //allOff();
}

/// turn off specific LEDs
inline void ledOff(int row, int col) {
  digitalWrite(rowLED[row], LOW);   /// row pin to 0V
  digitalWrite(colLED[col], HIGH);  /// column pin to +3V
}

/// turn on all LEDs
void allOn() {
  for (int i = 0; i < NUM_COL; i++) {
    digitalWrite(colLED[i], COL_ON);  /// all columns to 3V
  }
  for (int j = 0; j < NUM_ROW; j++) {
    digitalWrite(rowLED[j], ROW_ON);  /// all rows to 3V
  }
}

/// turn off all LEDs
void allOff() {
  for (int i = 0; i < NUM_ROW; i++) {
    digitalWrite(rowLED[i], ROW_OFF);  /// all row pins to 0V
  }
  for (int j = 0; j < NUM_COL; j++) {
    digitalWrite(colLED[j], COL_OFF);  /// all column pins to 0V
  }
}

Measure the deep sleep current with only the the ATmega328 in circuit and only its deep sleep code running ?

Thank you @srnet. The components are on a board so I cant isolate the mcu and I don't have serial monitor access for debugging.

#include "Wire.h"                // I2C connections
#include "M41T62.h"              // M41T62 real time clock
#include "PinChangeInterrupt.h"  // pin change interrupt
#include <avr/sleep.h>
#include <avr/power.h>
RTC_M41T62 RTC;  // real time clock

const byte NUM_COL = 6;  // columns are anodes
const byte NUM_ROW = 6;  // rows are cathodes
#define COL_ON LOW  // the following four lines of code control swtiching the multiplexed LEDs on and off
#define COL_OFF HIGH
#define ROW_ON HIGH
#define ROW_OFF LOW

const byte colLED[NUM_COL] = { 2, 3, 4, 5, 6, 7 };      // pins for anode (+) connections on ATmega328p, columns in LED matrix
const byte rowLED[NUM_ROW] = { 0, 1, A0, A1, A2, A3 };  // pins for cathode (-) connections on ATmega328p, rows in LED matrix

const byte button1 = 9;      // pin for button 1
unsigned long displayStartMillis = 0;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - setup + loop - - - - - - - - - - - - - - - -

void setup() {
  pinMode(button1, INPUT_PULLUP);            // button1 pulled LOW when pressed
  //pinMode(button2, INPUT_PULLUP);            // button2 pulled LOW when pressed
  Wire.begin();                              // I2C communication with the RTC
  RTC.begin();                               // start RTC
  //RTC.adjust(DateTime(__DATE__, __TIME__));  // set RTC time to computer time

  for (int i = 0; i < NUM_COL; i++)  // set all column pins to OUTPUT and OFF
  {
    pinMode(colLED[i], OUTPUT);        // set column LEDs to output
    digitalWrite(colLED[i], COL_OFF);  // turn all columns off
    pinMode(rowLED[i], OUTPUT);        // set row LEDs to output
    digitalWrite(rowLED[i], ROW_OFF);  // turn all rows off
  }
  allOn();
  delay(1000);
  allOff();

  attachPCINT(digitalPinToPCINT(button1), setFlag1, FALLING);
}

void loop() {
  enterDeepSleep();
  displayStartMillis = millis();
  while (millis() - displayStartMillis < 200)displayTime();
  allOff();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - time functions - - - - - - - - - - - - - - -

void displayTime() {
  DateTime now = RTC.now();  // set "now" from the computer's time

  byte hour = now.hour() % 12;
  if (hour == 0)    hour = 12;
  {
    ledOn((hour - 1) / 2, (hour - 1) % 2);
    delayMicroseconds(100);
    ledOff(hourRow, hourColumn);
  }

  byte minute = now.minute();  // 0...59
  int minuteRow;
  if (minute < 5)  // zero minutes is located at the bottom
    minuteRow = 5;
  else
    minuteRow = (minute - 5) / 10;  // start at 5 at the top
  // A change of column for each 5 minutes: (m / 5)
  // But the 10,20,30 are the right column: + 1
  // There are two columns                : %2
  // The start column is                  : 2
  byte minuteColumn = 2 + ((minute / 5) + 1) % 2;
  {
    ledOn(minuteRow, minuteColumn);
    delayMicroseconds(100);
    ledOff(minuteRow, minuteColumn);
  }
  // minute remainder 1...4
  byte remainder = now.minute() % 5;
  if (remainder > 0)  // leds off for seconds 0 and 5
  {
    remainder--;  // row 0 for remainder 1
    ledOn(remainder, 4);
    delayMicroseconds(100);
    //delay(15);
    ledOff(remainder, 4);
  }
  // ------------------------------
  // day of the week
  // ------------------------------
  byte weekday = now.dayOfWeek() - 1;
  {
    ledOn(weekday, 5);
    delayMicroseconds(150);
    ledOff(weekday, 5);
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - button functions - - - - - - - - - - - - - - -

void setFlag1() {
  PCICR = 0;  // cancel pin change interrupts
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - sleep functions - - - - - - - - - - - - - - -

void enterDeepSleep() {
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  sleep_enable();

  byte old_ADCSRA = ADCSRA;
  // disable ADC to save power
  ADCSRA = 0;

  power_all_disable ();  // turn off various modules

  PCIFR  |= bit (PCIF0) | bit (PCIF1) | bit (PCIF2);   // clear any outstanding interrupts
  enablePCINT(digitalPinToPCINT(button1));

  // turn off brown-out enable in software
  MCUCR = bit (BODS) | bit (BODSE);
  MCUCR = bit (BODS);
  sleep_cpu ();

  // cancel sleep as a precaution
  sleep_disable();
  power_all_enable ();   // enable modules again
  ADCSRA = old_ADCSRA;   // re-enable ADC conversion
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - main display functions - - - - - - - - - - - - - -

// turn on specific LEDs
void ledOn(byte row, byte col) {
  digitalWrite(rowLED[row], HIGH);  // row pin to +3V
  digitalWrite(colLED[col], LOW);   // column pin to 0V
  //allOff();
}

// turn off specific LEDs
void ledOff(byte row, byte col) {
  digitalWrite(rowLED[row], LOW);   // row pin to 0V
  digitalWrite(colLED[col], HIGH);  // column pin to +3V
}

// turn on all LEDs
void allOn() {
  for (int i = 0; i < NUM_COL; i++) {
    digitalWrite(colLED[i], COL_ON);  // all columns to 3V
    digitalWrite(rowLED[i], ROW_ON);  // all rows to 3V
  }
}

// turn off all LEDs
void allOff() {
  for (int i = 0; i < NUM_ROW; i++) {
    digitalWrite(rowLED[i], ROW_OFF);  // all row pins to 0V
    digitalWrite(colLED[i], COL_OFF);  // all column pins to 0V
  }
}
1 Like

I wrote my own binary pocketwatch using a Promini and an 8x8 led matrix for a display, and it runs for about three months on its batteries, so I understand what you are trying to do.

I'm currently writing code for a solar charge controller that sleeps most of the time. I've been leaning heavily on Nick Gammon's forum for guidance on sleep mode and interrupts. In a quick read of your code, I noticed that you don't disable interrupts during the critical section of setting up the sleep mode as Nick recommends, so maybe give that a try. I will also mention that I have found that using interrupts clobbers the serial port, making it difficult to get debug prints out, so you have to be prepared to experiment and use the display to indicate the results of your testing. It is very satisfying to figure out how to prove that a bit of code works and then figure out why it didn't. Happy bug hunting. :nerd_face:

1 Like
#include <Wire.h>
#include <M41T62.h>
#include <PinChangeInterrupt.h>
#include <avr/sleep.h>
RTC_M41T62 RTC;

#define NUM_COL 6
#define NUM_ROW 6
#define COL_ON LOW
#define COL_OFF HIGH
#define ROW_ON HIGH
#define ROW_OFF LOW

const int colLED[NUM_COL] = {2, 3, 4, 5, 6, 7};
const int rowLED[NUM_ROW] = {0, 1, A0, A1, A2, A3};
uint8_t display[NUM_ROW][NUM_COL];

const int button1 = 9;
const int button2 = 10;
int buttonState;
int lastButtonState = LOW;
int rowPin = 6;
int colPin = 6;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

bool myFlag1 = false;
bool myFlag2 = false;
bool deepSleepEnabled = false;
unsigned long displayStartMillis = 0;
bool displayActive = false;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  Wire.begin();
  RTC.begin();
  RTC.adjust(DateTime(__DATE__, __TIME__));
  for (int i = 0; i < NUM_COL; i++) {
    pinMode(colLED[i], OUTPUT);
    digitalWrite(colLED[i], COL_OFF);
  }
  for (int j = 0; j < NUM_ROW; j++) {
    pinMode(rowLED[j], OUTPUT);
    digitalWrite(rowLED[j], ROW_OFF);
  }
  attachPCINT(digitalPinToPCINT(button1), setFlag1, CHANGE);
  attachPCINT(digitalPinToPCINT(button2), setFlag2, CHANGE);
}

void loop() {
  if (myFlag1) {
    if (millis() - lastDebounceTime > debounceDelay) {
      lastDebounceTime = millis();
      displayStartMillis = millis();
      displayActive = true;
      myFlag1 = false;
    }
  }

  if (displayActive) {
    if (millis() - displayStartMillis < 200) {
      displayTime();
    } else {
      displayActive = false;
      enterDeepSleep();
    }
  }
}

void displayTime() {
  DateTime now = RTC.now();
  int hour = now.hour() % 12;
  if (hour == 0) hour = 12;
  int hourRow = (hour - 1) / 2;
  int hourColumn = (hour - 1) % 2;
  ledOn(hourRow, hourColumn);
  delayMicroseconds(100);
  ledOff(hourRow, hourColumn);

  int minute = now.minute();
  int minuteRow;
  if (minute < 5) minuteRow = 5;
  else minuteRow = (minute - 5) / 10;
  int minuteColumn = 2 + ((minute / 5) + 1) % 2;
  ledOn(minuteRow, minuteColumn);
  delayMicroseconds(100);
  ledOff(minuteRow, minuteColumn);

  int remainder = now.minute() % 5;
  if (remainder > 0) {
    remainder--;
    ledOn(remainder, 4);
    delayMicroseconds(100);
    ledOff(remainder, 4);
  }

  int weekday = now.dayOfWeek() - 1;
  ledOn(weekday, 5);
  delayMicroseconds(150);
  ledOff(weekday, 5);
}

void setFlag1() {
  myFlag1 = true;
}

void setFlag2() {
  myFlag2 = true;
}

void enterDeepSleep() {
  ADCSRA = 0; // Disable ADC
  // Turn off unnecessary modules to save power
  power_all_disable();

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();

  // Re-enable modules here if needed after waking up
  sleep_disable();
  // Re-enable necessary peripherals (e.g., ADC) if required
  // power_all_enable(); 
  // power_adc_enable();
  // power_timer0_enable();
  // ...

  // Turn off all LEDs before going back to sleep
  allOff();
}

void ledOn(int row, int col) {
  digitalWrite(rowLED[row], HIGH);
  digitalWrite(colLED[col], LOW);
}

void ledOff(int row, int col) {
  digitalWrite(rowLED[row], LOW);
  digitalWrite(colLED[col], HIGH);
}

void allOff() {
  for (int i = 0; i < NUM_ROW; i++) {
    digitalWrite(rowLED[i], ROW_OFF);
  }
  for (int j = 0; j < NUM_COL; j++) {
    digitalWrite(colLED[j], COL_OFF);
  }
}

Here are the key changes and optimizations made to the code:

  1. Power Management: power_all_disable() has been used to turn off unnecessary modules before entering deep sleep mode. You can selectively re-enable modules that you need after waking up.

  2. Disabling the ADC: ADC has been disabled with ADCSRA = 0 before entering sleep mode to save power.

  3. LED State: All LEDs are turned off explicitly before entering deep sleep mode.

  4. Button Debouncing: The debounce delay for buttons has been maintained as 50. Ensure that this value suits your button characteristics.

  5. RTC Usage: The RTC is used for displaying time and is actively turned off when in deep sleep mode, which saves power.

Make sure to double-check your specific hardware and requirements to further fine-tune power optimization settings as needed.

Thanks very much for the coding assistance @kolaha. Unfortunately, the watch has drained after two days again. I'm beginning to wonder if there might be somer hardware wiring situation that is preventing the watch from sleeping.
Or could the RTC be keeping it awake? I haven't played around with the RTC settings before but maybe it is sending interrupt signals to the mcu.

Thanks for the support, @Pogo. The watch is still draining after two days so I have't squashed that bug yet. Might be time to go back and have another look at Nick Gammon's forum.

Thanks for posting, @codingapacalyspe. I have tried ChatGPT for coding for months now, even paid for 4 but although it is good for small problems, I don't feel like the AI is quite good enough yet to deal with more complex code.

void allOff() {
  for (int i = 0; i < NUM_ROW; i++) {
    digitalWrite(rowLED[i], LOW);  // all row pins to 0V
    digitalWrite(colLED[i], LOW);  // all column pins to 0V
    pinMode(rowLED[i], INPUT);
    pinMode(colLED[i], INPUT); 
  }
}
void ledOn(byte row, byte col) {
  pinMode(rowLED[row], OUTPUT);        // set column LEDs to output
  pinMode(colLED[col], OUTPUT);        // set row LEDs to output
    
  digitalWrite(rowLED[row], HIGH);  // row pin to +3V
  digitalWrite(colLED[col], LOW);   // column pin to 0V
}
1 Like

Nick's column needs to be read carefully. The order that commands are entered is important in some sequences.

I wrote two versions of my last project, one without sleep mode and minimal interrupt sequences so that I could confirm that the hardware worked as I expected, and one with sleep mode and all the interrupt code included.

It was important to confirm that the hardware worked before looking for bugs in the interrupt code.

I see that you are using a module, so you can probably assume that there are no wiring errors.

Follow your interrupt code by hand, checking that tou set and clear flags in the right order and compare with Nic's code to be sure. You will kick youself when you find it, but you will find it.

Peter.

1 Like

You bloody ripper!! It has been four days and is still going as strong as the first day. It usually would have faded by this stage for sure.

The leds must have been drawing power the whole time and your code disconnects them until next use:

pinMode(rowLED[i], INPUT);
pinMode(colLED[i], INPUT); 

I can't thank you enough @kolaha. The power drain has been a massive hassle and the main thing preventing me from enjoying my watch properly so your help is super appreciated.

Thanks Peter. I'm in business now. It looks like the leds were drawing power and they needed to be disconnected each time after use. Appreciate the support.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.