ATMEGA328PB wake with button & interrupt

I reworked the modified code to bury all the gory details of sleep on two functions, setupSleep() and gotoSleep().

I note that this code, like many examples, turns on and off interrupts. This can done crudely by turning off interrupts at a global level. Here we just managing the particular interrupt we are caring about.

/*
button or task complete -> go to sleep
button during sleep -> wake up
*/

# define ledPin     2  // activity LED
# define buttonPin  8  // Use D8 as the interrupt input pin

void setup(void) {
 Serial.begin(115200);

 pinMode(buttonPin, INPUT_PULLUP);        // Set D8 as input with pullup
 pinMode(ledPin, OUTPUT);    // Set the LED as output

 digitalWrite(ledPin, LOW);  // OFF

 setupSleep();

 Serial.println("Setup complete");
}


void loop(void) {
 bool sleepy = false;  // unless
 bool pressed;

 static int count = 0;
 static unsigned long timer;
 unsigned long now;

 now = millis();

 if (now - timer >= 77 && !sleepy) {
   timer = now;
   count++;
   digitalWrite(ledPin, digitalRead(ledPin) == HIGH ? LOW : HIGH);  // LED On

   if (count >= 50) {
     Serial.print("done for now - ");
     sleepy = true;
     count = 0;
   }
 }

 pressed = digitalRead(buttonPin) == LOW;
 if (pressed) {
   Serial.print("button induces sleep - ");

   count = 0;  // for next wakey wake time

   do {delay(20);}
     while (digitalRead(buttonPin) == LOW);   // wait for the fat finger to get off the button

     sleepy = true;
 }

 if (sleepy) {
     digitalWrite(ledPin, LOW);
     Serial.println("going to sleep");
     Serial.flush();

     goToSleep();  // off to never never land, wakes up right here when

     Serial.println("              now awake");
 }
}

// all sleep arcana

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

ISR(PCINT0_vect) {
 PCICR &= ~_BV(PCIE0);  // Disable the PCINT[7:0] interrupts
}

void setupSleep()
{
 // Turn off the ADC to save some power if you don't use it
 PRR = 0x01;
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // Set sleep mode to full power down.
}

void goToSleep()
{
 PCIFR = 0x07;            // Clear all the change on interrupt flags
 bitSet(PCICR, PCIE0);    // Enanble change interrupts on PCINT[7:0] pins
 bitSet(PCMSK0, PCINT0);  // Enable for PCINT0 (D8)
 sleep_bod_disable();     // Disble BOD; saves power
 sleep_mode();            // Enables sleep, puts cpu to sleep and disbles sleep when awoken

 // Zzzz. We are now ASLEEP, will stay here until interrupt occurs.

 Serial.println("\nwaking up...");

 do {delay(20);}
   while (digitalRead(buttonPin) == LOW);   // wait for the fat finger to get off the button
}

I did measure the UNO current, sleep here only means a reduction from 50 mA to 30 mA. I'll try it later on a prepared Pro Mini.

I did learn that the BOD currentcan be further reduced by changing some fuse(s), so if that is not something you are using a chance to squeeze a bit more out of your power source.

a7

It is working perfectly right now (as far as I can tell), I had to add a "which LEDs are on/off?" in the sleep so it came back to the same pattern as when it slept (this only affects a few of the patterns). This pattern recovery doesn't work for the "snake" one (yet?), it's not a big practical deal (as the pattern corrects when it cycles around which is pretty fast), but of course it's bugging me lol. (Although at one point I had forgotten somehow to put in the sleep mode so it wasn't sleeping, and this was madness.)

The program spends most of its time in each pattern function, as that's where I assign LED on/off times. This might be a bit odd (and might not be the best C++ way to do this, I like things to be Pythonic aka "the right way looks nice too"), but is at least an offshoot of how I built the code (first made one pattern, so, keeping it in that pattern was ok, then it was path dependency after that so I kept up that way of doing it). So I have to check for the Mode (pattern) button and the Power (sleep) button in each function's loop. But it works!

I am not super concerned about actual power savings as long as it is saving a little power, but also it gives the user/wearer a way to stop the blinking (they might not blinking all the time).

The code isn't perfectly commented but here it is (keeping in mind the snake() pattern doesn't recover from sleep for one cycle of its pattern, ooh and there might be a sleep issue in the fire() function which I see has two loops, hmm). It's likely I have used some things that are not best practice (coming from Python and R linear scripts, with a background in Pascal).

// For the ICA South Africa 2026 Conference Badge project.
// Xbox-like controller, with blinky LEDs where the controller buttons are.
// Summer / Fall 2025
// Sleep help from jim-p and alto777 (Arduino forum).

// Include Libraries
// For Sleep and Interrupt functionality.
#include <avr/sleep.h>
#include <avr/interrupt.h>


// Global Variables
// YRGB is the order of the lights, starting at noon and going clockwise (based on the buttons on an XBox 360 controller).
// SET PINS HERE (note difference between Arduino Metro board and custom board pin numbers).
// You can't use the simple pin numbers from the chip, you need to translate them since the Arduino IDE uses a different scheme.
// Here:  https://community.platformio.org/t/atmega328pb-arduino-pin-mappings/4248 
const int led_yellow = PD2; // ATMega328PB pin references.
const int led_red    = PD0;  
const int led_green  = PD1;  // Weird order due to circuit layout realities of the PCB and the ATMEGA328PB.
const int led_blue   = PD3;

// Different online schematics show different pin desginations. Beware!
const int button_onoff = 8; //12;  PB0  // Really is sleep, PULL UP RESISTOR (long story).
const int button_mode  = 9; //13;  PB1  // The mode button, PULL DOWN RESISTOR.

// Mode buttons setup here.
// https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection/
int button_state = 0;  // Variable for reading the pushbutton status.
int button_prev  = 0;  // LOW. For the previous state of the button, helps get one click not a human-length hold/click.

// Pattern varaibles.
const int num_patterns = 11; // How many pattens do I have.
int pattern = 1; // 1 to num_patterns for # patterns of LED blinking. Will start at this pattern number. 

// The ISR SUPER IMPORTANT, MCU and schematic specific. 
ISR(PCINT0_vect) {
	PCICR &= ~_BV(PCIE0);  // Disable the PCINT[7:0] interrupts
}


void setup() {
  pinMode(led_yellow, OUTPUT);
  pinMode(led_red, OUTPUT);
  pinMode(led_green, OUTPUT);
  pinMode(led_blue, OUTPUT);
  // Ports B, C, D, and E all have internal resistors that can be set.
  pinMode(button_mode, INPUT);          // Build has a pulldown resistor on it.   
  pinMode(button_onoff, INPUT_PULLUP);  // INPUT_PULLUP if I set it like that, is that chip-internal? (I have a resistor on the board.)
      // Input pullups essentially set the default state of the pin to HIGH or 1, so that the pin is not affected by interference.
      // Do not need a resistor in this case. (I have one on the board.)

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // Set sleep mode to full power down.

  init_off(); // Make sure all LEDs are set to LOW to start.
}

void loop() {
  if (pattern == 1)  blink();      // Starts on this one (unless changed above). 
  if (pattern == 2)  rotate();
  if (pattern == 3)  alternate(); 
  if (pattern == 4)  snake(); 
  if (pattern == 5)  rave();  
  if (pattern == 6)  rando(); 
  if (pattern == 7)  random_path();
  if (pattern == 8)  bounce();
  if (pattern == 9)  sideside(); 
  if (pattern == 10) fire(); 
  if (pattern == 11) fireworks(); 
} // main loop



// SLEEP FUNCTION hooray
void sleeper() {           // c/o jim-p (Arduino forum)
  bool yellow_on = (digitalRead(led_yellow) == HIGH);  // Have to reset the LEDs upon return from sleep.
  bool red_on    = (digitalRead(led_red) == HIGH);
  bool green_on  = (digitalRead(led_green) == HIGH);
  bool blue_on   = (digitalRead(led_blue) == HIGH);
  init_off();              // Turns all LEDs off, because it can catch them on at button press. 
	PCIFR = 0x07;            // Clear all the change on interrupt flags
	bitSet(PCICR, PCIE0);    // Enanble change interrupts on PCINT[7:0] pins
	bitSet(PCMSK0, PCINT0);  // Enable for PCINT0 (D8)
	interrupts();            // Make sure interrupts are enabled
	sleep_bod_disable();     // Disble BOD; saves power
	sleep_mode();            // Enables sleep, puts cpu to sleep and disbles sleep when awoken

	// We are now ASLEEP, will stay here until interrupt occurs (the interrrupt we just put on pin D8).
	//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
	// Now we are awake here.
  do {delay(20);}
    while (digitalRead(button_onoff) == LOW);     // Debounce, finger to get off the button. c/o alto777 (Arduino forum)
  if (yellow_on) digitalWrite(led_yellow, HIGH);  // Set the LEDs back to where they were (matters for some patterns).
  if (red_on) digitalWrite(led_red, HIGH);
  if (green_on) digitalWrite(led_green, HIGH);
  if (blue_on) digitalWrite(led_blue, HIGH);
}

// SECONDARY FUNCTIONS (these assist the main pattern functions).

// Helper functions that set LEDs to various states.
void init_off() { 
  digitalWrite(led_yellow, LOW);
  digitalWrite(led_red, LOW);
  digitalWrite(led_green, LOW);
  digitalWrite(led_blue, LOW);
}
void init_on() {
  digitalWrite(led_yellow, HIGH);
  digitalWrite(led_red, HIGH);
  digitalWrite(led_green, HIGH);
  digitalWrite(led_blue, HIGH);
}

// For the mode/pattern button, which pattern by number.
void increment() {
  pattern++; // Pattern is a global var so it is just set, not returned.
  if (pattern > num_patterns) pattern = 1;
  init_off(); // Because if you're in this function, the mode button was pushed, so start new pattern.
  delay(70); // Perhaps avoid bounce?
}

// If the LEDs are on turn them off and vice-versa, for the blink pattern.
void reverse_all() {
  if (digitalRead(led_red) == HIGH) init_off(); // If one is on, they are all on, turn all off.
  else init_on();
} // reverse_all

// These two are used by the rave function, if off, turn it on and return an "on until" time, etc. 
unsigned long rave_on(int which_led){
  digitalWrite(which_led, HIGH);
  unsigned long on_until = millis() + random(5, 50);  // times adjusted
  return on_until;
}
unsigned long rave_off(int which_led) {
  digitalWrite(which_led, LOW); 
  unsigned long off_until = millis() + random(5, 50);
  return off_until;
}

// All of these four turn one on and make sure the others are off.
void yellow_only() {
  digitalWrite(led_yellow, HIGH);
  digitalWrite(led_red, LOW);
  digitalWrite(led_green, LOW);
  digitalWrite(led_blue, LOW);
}
void red_only() {
  digitalWrite(led_yellow, LOW);
  digitalWrite(led_red, HIGH);
  digitalWrite(led_green, LOW);
  digitalWrite(led_blue, LOW);
}
void green_only() {
  digitalWrite(led_yellow, LOW);
  digitalWrite(led_red, LOW);
  digitalWrite(led_green, HIGH);
  digitalWrite(led_blue, LOW);
}
void blue_only() {
  digitalWrite(led_yellow, LOW);
  digitalWrite(led_red, LOW);
  digitalWrite(led_green, LOW);
  digitalWrite(led_blue, HIGH);
}

// Gamma Correction Function (for fading the LEDs).
int gamma_correct(int uncorrected_level) {
  // Convert 0-255 to 0.0-1.0 .
  float decimal_level = uncorrected_level / 255.0; // "If doing math with floats, you need to add a decimal point, otherwise it will be treated as an int."  = 0
  // Gamma adjust.
  float corrected_decimal = decimal_level * decimal_level * decimal_level;
  // Convert back to 0-255 metric and return it.
  return corrected_decimal * 255;
}


// PRIMARY FUNCTIONS these are the patterns (they may call a seconadry function for common actions).

void blink() {
  unsigned long start = millis();
  unsigned long now;
  init_on();

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - start >= 30) {   // beta timing for board
      reverse_all();  //
      start = millis();
    }
  } // while
} // blink


void rotate() {
  unsigned long now = millis();
  unsigned long y_off = now + 45;
  unsigned long r_off = now + 90;
  unsigned long g_off = now + 135;
  unsigned long b_off = now + 180;

  digitalWrite(led_yellow, HIGH);

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }

    if (now >= y_off) {
      digitalWrite(led_yellow, LOW);
      digitalWrite(led_red, HIGH);
      y_off = now + 180;
      r_off = now + 45;
    } 
    else if (now >= r_off) {
      digitalWrite(led_red, LOW);
      digitalWrite(led_green, HIGH);
      r_off = now + 180;
      g_off = now + 45;
    }
    else if (now >= g_off) {
      digitalWrite(led_green, LOW);
      digitalWrite(led_blue, HIGH);
      g_off = now + 180;
      b_off = now + 45;
    }
    else if (now >= b_off) {
      digitalWrite(led_blue, LOW);
      digitalWrite(led_yellow, HIGH);
      b_off = now + 180;
      y_off = now + 45;
    }   
  } // while
} // rotate


void alternate() {
  // Up and down, then left and right.
  const int alternate_delay = 50; // The millis delay for this function.  Changed for PCB.
  unsigned long ud_on = millis();  // Up Down when it has turned on.
  unsigned long lr_on = ud_on + alternate_delay; // Left Right on.
  unsigned long now;

  digitalWrite(led_yellow, HIGH);
  digitalWrite(led_green, HIGH);
  digitalWrite(led_red, LOW);
  digitalWrite(led_blue, LOW);

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - ud_on >= alternate_delay) {
      digitalWrite(led_yellow, LOW);
      digitalWrite(led_green, LOW);
      digitalWrite(led_red, HIGH);
      digitalWrite(led_blue, HIGH);
      ud_on = lr_on + alternate_delay; // e.g., 50 + 50
    }
    else if (now - lr_on >= alternate_delay) {
      digitalWrite(led_yellow, HIGH);
      digitalWrite(led_green, HIGH);
      digitalWrite(led_red, LOW);
      digitalWrite(led_blue, LOW);
      lr_on = ud_on + alternate_delay;
    }
  } // while
} // alternate


void snake() {
  // Grows and then shrinks    YRGB
  const int on_for    = 125;  // Times adjusted for PCB. 
  const int end_pause = 125;  // Pause before restarting the snake loop.

  unsigned long y_on  =  0;
  unsigned long r_on  = 25;
  unsigned long g_on  = 50;
  unsigned long b_on  = 75;
  unsigned long y_off = y_on + on_for;
  unsigned long r_off = r_on + on_for;
  unsigned long g_off = g_on + on_for;
  unsigned long b_off = b_on + on_for;
  unsigned long now   =  0; // This was why the start pattern was wonky.
  unsigned long start = millis();

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now >= y_on) {
      digitalWrite(led_yellow, HIGH);
      y_on = y_off + end_pause;
    }
    if (now >= r_on) {
      digitalWrite(led_red, HIGH);
      r_on = r_off + end_pause;
    }
    if (now >= g_on) {
      digitalWrite(led_green, HIGH);
      g_on = g_off + end_pause;
    }
    if (now >= b_on) {
      digitalWrite(led_blue, HIGH);
      b_on = b_off + end_pause;
    }
    if (now > y_off) {
      digitalWrite(led_yellow, LOW);
      y_off = y_on + on_for;
    }
    if (now > r_off) {
      digitalWrite(led_red, LOW);
      r_off = r_on + on_for;
    }
    if (now > g_off) {
      digitalWrite(led_green, LOW);
      g_off = g_on + on_for;
    }
    if (now > b_off) {
      digitalWrite(led_blue, LOW);
      b_off = b_on + on_for;
    }
    now = millis() - start; // Should time the function and add it to the "now".
  } // while
} // snake


void rave() {
  // This is random on all of them: random(min, max+1).
  unsigned long yellow_on_until  = 0;
  unsigned long yellow_off_until = 0;
  unsigned long red_on_until     = 0;
  unsigned long red_off_until    = 0;
  unsigned long green_on_until   = 0;
  unsigned long green_off_until  = 0;
  unsigned long blue_on_until    = 0;
  unsigned long blue_off_until   = 0;
  unsigned long now = 0;

  // Start all on with random rave on values.
  yellow_on_until = rave_on(led_yellow);
  red_on_until = rave_on(led_red);
  green_on_until = rave_on(led_green);
  blue_on_until = rave_on(led_blue);

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    // If on, should it be turned off? If off, should it be turned on?
    if (digitalRead(led_yellow) == HIGH) { if (now > yellow_on_until) yellow_off_until = rave_off(led_yellow); }
    else { if (now > yellow_off_until) yellow_on_until = rave_on(led_yellow); }

    if (digitalRead(led_red) == HIGH) { if (now > red_on_until) red_off_until = rave_off(led_red); }
    else { if (now > red_off_until) red_on_until = rave_on(led_red);  }

    if (digitalRead(led_green) == HIGH) { if (now > green_on_until) green_off_until = rave_off(led_green); }
    else { if (now > green_off_until) green_on_until = rave_on(led_green); }

    if (digitalRead(led_blue) == HIGH) { if (now > blue_on_until) blue_off_until = rave_off(led_blue); }
    else { if (now > blue_off_until) blue_on_until = rave_on(led_blue); }

  } // while
} // rave function


void rando() {
  const int pause = 33;
  int prev   = 0;
  int prev_2 = 0;
  
  unsigned long now   = 0;
  unsigned long start = millis();

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - start >= pause) {
      start = millis();
      int roll = random(1,5); // Gives it a value so you can compare on next line.
      while (roll == prev_2) {roll = random(1,5);} // Reroll -- allows for 1 match but not more than that.
      prev_2 = prev; // Okay passed, so update these two.
      prev = roll;
      if (roll == 1) {
        yellow_only();
      }
      else if (roll == 2) {
        red_only();
      }
      else if (roll == 3) {
        green_only();
      }
      else if (roll == 4) {
        blue_only();
      }
    } // if now - start
  } // while true
} // rando


void random_path() {
  // Random, but only activates a neighbor (a "path"). 
  unsigned long last = 0;
  unsigned long now = 0; 
  const int pause = 40;           // adjusted for PCB
  int which = 1; // 1y 2r 3g 4b
  int upit = 0; // Int read as Boolean, random, "up it" up the value or not, beware wrap 1-4.

  digitalWrite(led_yellow, HIGH);
  last = millis();

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - last >= pause) {   // Time to move it one.
      // First get the new LED number.
      upit = random(0,2); // Returns 0,1 can be read as Boolean.
      last = millis(); // Update that.
      if (upit) {
        which++;
        if (which > 4) which = 1;
      }
      else { // Else don't up-it, lower it 1-4.
        which--;
        if (which < 1) which = 4;
      }
      if (which == 1) yellow_only();
      else if (which == 2) red_only();
      else if (which == 3) green_only();
      else blue_only();
    } // if time to move it one

  } // while true
} // random_path


void bounce() { // 1, then 2&4, then 3, then 2&4, then 1, etc
  // Similar to sideside function.
  int which_step = 1; // Which step of the pattern, so which is on and which is next (1, 2, 3, 4).
  unsigned long now = 0;
  unsigned long last = millis();
  const int pause = 33;  // adjusted

  yellow_only(); // start

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - last >= pause) {
      last = millis();
      which_step++;
      if (which_step > 4) which_step = 1;
      if      (which_step == 1) yellow_only();
      else if (which_step == 2) {digitalWrite(led_red, HIGH); digitalWrite(led_blue, HIGH); digitalWrite(led_yellow, LOW);}
      else if (which_step == 3) green_only();
      else if (which_step == 4) {digitalWrite(led_red, HIGH); digitalWrite(led_blue, HIGH); digitalWrite(led_green, LOW);}
    }
  } // while true
} // bounce


void sideside() { // u, r, d, r, u, l, d, l, u, etc, 1 2 3 4 for position, do where in pattern?
  int which_step = 1; // Which step of the pattern, so which is on and which is next.
    // 1u 2r 3d 4r 5u 6l 7d 8l 9u that is the start again so 1-8.
  unsigned long now = 0;
  unsigned long last = millis();
  const int pause = 33;  // adjusted

  yellow_only(); // start

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - last >= pause) {
      last = millis();
      which_step++;
      if (which_step > 8) which_step = 1;
      if      (which_step == 1) yellow_only();
      else if (which_step == 2) red_only();
      else if (which_step == 3) green_only();
      else if (which_step == 4) red_only();
      else if (which_step == 5) yellow_only();
      else if (which_step == 6) blue_only();
      else if (which_step == 7) green_only();
      else if (which_step == 8) blue_only();
    }
  } // while true
} // sideside


void fire() {
  // Start on blue, blink three times, fire aross to red, bounce back to y/g and b.
  const unsigned long pause_between = 175;
  // Seven blinks in the pattern. Hard code the entire thing why not.
  unsigned long starter   = 0;
  unsigned long step1_on  = 10; // B blinks three times.
  unsigned long step1_off = 20;
  unsigned long step2_on  = 30; 
  unsigned long step2_off = 40;
  unsigned long step3_on  = 50;
  unsigned long step3_off = 100;  // longer pause before firing  100 so far    times adjusted
  unsigned long step4_on  = 105;   // b fires
  unsigned long step4_off = 110;
  unsigned long step5_on  = 115;   // hits r
  unsigned long step5_off = 120;
  unsigned long step6_on  = 125;   // bounces back to y/g
  unsigned long step6_off = 130;
  unsigned long now;

  unsigned long start = millis();

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    now = millis();
    if (now - start >= starter) {digitalWrite(led_blue, HIGH); starter += (155 + pause_between);}
    else if (now - start > step1_on) {digitalWrite(led_blue, LOW); step1_on += (155 + pause_between);}
    else if (now - start > step1_off) {digitalWrite(led_blue, HIGH); step1_off += (155 + pause_between);}
    else if (now - start > step2_on) {digitalWrite(led_blue, LOW); step2_on += (155 + pause_between);}
    else if (now - start > step2_off) {digitalWrite(led_blue, HIGH); step2_off += (155 + pause_between);}
    else if (now - start > step3_on) {digitalWrite(led_blue, LOW); step3_on += (155 + pause_between);}
    else if (now - start > step3_off) {digitalWrite(led_blue, HIGH); step3_off += (155 + pause_between);}   // blue fires
    else if (now - start > step4_on) {digitalWrite(led_blue, LOW); step4_on += (155 + pause_between);}
    else if (now - start > step4_off) {digitalWrite(led_red, HIGH); step4_off += (155 + pause_between);}  // s4 done, red lights up
    else if (now - start > step5_on) {digitalWrite(led_red, LOW); step5_on += (155 + pause_between);}
    else if (now - start > step5_off) {digitalWrite(led_yellow, HIGH); digitalWrite(led_green, HIGH); step5_off += (155 + pause_between);}  // red done, y and g light up
    else if (now - start > step6_on) {digitalWrite(led_yellow, LOW); digitalWrite(led_green, LOW); step6_on += (155 + pause_between);}
    else if (now - start > step6_off) {
      digitalWrite(led_blue, HIGH); 
      step6_off += (155 + pause_between);
      int x = 200; // HIGH val for LED rounded down a bit.
      float y = 0.0;
      unsigned long previous_millis = millis();
      while (true){
        now = millis();
        button_prev = button_state; 
        button_state = digitalRead(button_mode);
        if (button_state == HIGH && button_prev == LOW) { // signal from mode button
          increment();
          return; // break out of the function completely
        }
        if (x <= 0) break; // should break out of this inner while but not return
        if (now - previous_millis >= 5) { 
          previous_millis = millis();
          x = x - 10;
          y = gamma_correct(x);
          analogWrite(led_blue, y);
        } 
      } // while inner true / button catch  
    } // If step_6 is completed.
  } // while true
} // fire


void fireworks() {
  // g pops, then y lights up and fades and r/b sparkle (back forth fast, and fade), like a firework. Requires PWM.
  unsigned long now;
  unsigned long start = millis();
  unsigned long previous_millis = millis(); // For fade cycle.
  const int fade_interval = 7;
  bool sparkle_lr = 1; // To alternate (sparkle) the two side LEDs.
  int x = 200; // For brightness of LED fade.
  int y; // Gamma corrected version of our good friend x.

  const int pause_between   = 150; // This is important or the green fires too soon. Also interacts with fade_interval and x.
  unsigned long step1_start = 50;  // Initial firing of the firework.
  unsigned long step1_end   = 57;
  unsigned long step2_start = 100;

  while (true) {
    if (digitalRead(button_onoff) == LOW) {     // if on/off button press, do sleep function call
      do {delay(20);}
        while (digitalRead(button_onoff) == LOW); 
      sleeper();
    }
    now = millis();
    button_prev = button_state; 
    button_state = digitalRead(button_mode);
    if (button_state == HIGH && button_prev == LOW) { // signal from mode button
      increment();
      return; // break out of the function
    }
    if (now - start > step1_start && now - start < step1_end) digitalWrite(led_green, HIGH);
    else if (now - start > step1_end) {
      digitalWrite(led_green, LOW);
    }
    if (now - start > step2_start && x > 0) { // If in step2, x is the LED fade.
      if (now - previous_millis >= fade_interval) {  // If so, time to +/- LEDs.
        sparkle_lr = !sparkle_lr;
        previous_millis = millis();
          x = x - 7;
          y = gamma_correct(x);
          analogWrite(led_yellow, y);
          if (sparkle_lr) {analogWrite(led_blue, y); digitalWrite(led_red, LOW);}
          else {analogWrite(led_red, y); digitalWrite(led_blue, LOW);}
      } // if it is time to +/- the LEDs
    }  // if in step2
    else if (x <= 0) {
      step1_start = step1_start + pause_between;  // If these step1 aren't here, they get out of sync due to x not being millis-centric. 
      step1_end   = step1_end + pause_between;
      step2_start = step2_start + pause_between;
      start       = start + pause_between;
      init_off();
      x = 200;
    }

  } // while true
} // fireworks

// END OF CODE

I'll read your code when I get to the lab.

I wanted to leave myself with a goes-to-sleep and wakes-up sketch that goes by the button as well task completion.

That does not block.

You prolly don't want or need to at this time, but all your effects could be rewritten to operate as non-blocking functions.

This would remove the unfortunate requirement that plays out in your sketch - strewing the button handling throughout the code, instead of being able to check once per loop pass.

FSMs (finite state machines) can be used here. A function doesn't own the processor for any significant time, rather it takes the next step when the next step's time has arrived. Usually the function does nothing (not time) or something that takes next to no time (light up the next LED in a chase, e.g.).

Many such functions can run "at the same time", all doing very little most of the time, but occasionally taking a relatively trivial (computationally insignificant) time.

The sleep stuff messes things up a bit, that's why I went to pains to separate things so the loop runs totally free, but we have sleep and waking up handled. At the top of the loop is a little machine that knows when to turn off or on the LED, and when to call for sleep. Control probably passes through this loop many thousands of times a second…

Here's a tutorial by @J-M-L, chatGPT assures me the this is a respected member of these fora:

I would be remiss not to point you at an abstract article on the IPO model for process control here:

and I must give a shout out to @paulpaulson, who brought it to my attention.

I had been sorta kinda doing things in this way, but did not have a name for it.

Not for this project perhaps, like I said, but keep it in mind for the next one.

a7

1 Like

Well, we all have our different ways of coding. Sure it could be optimized but if it's easy for you to follow why bother, it only uses 25% of the flash memory.

Oh reading my code, I might advise against that (it's not overly amazing), I just shared it because it's the right thing to do when knowledge sharing.

I think you are noting that my background is more linear coding (Python and R), and I don't have the hang of event loops, but I can make it work (with a bit of help).

Next year I think I would NOT do two buttons, just I am copying the look of a game console controller (specifically the Xbox 360), so needed items in locations on the PCB that matched the controller to some extent. In the future I will use a shape where I can dictate the number of components, and I should try a different shape each year, so, no need for such complexity in the future ("it looks easy to do this" newbie mistake). (This is vaguely #badgelife but I am not a hardware hacker.)

But yes I will try to get the code... more pythonically C++/Arduino next year! (So I will read those links you provided, thanks! And yes I need to learn about FSMs, which I don't know anything about.)

Some time ago, I published a minimal ATmega328P sleep example https://arduino.stackexchange.com/questions/92021/arudino-receiving-interrupt-command-before-entering-sleep-mode-causing-it-not-to/92045#92045
The interrupt service routine is empty (although it has to be present). It is, however, for an edge triggered interrupt and not a pin change interrupt. This solution also may be interesting for some applications. Since the ISR is empty, there is no need to cycle the enable/disable of the interrupt.

/*
   Sleep mode demonstration (Uno/Nano ATmega328P)
   based on https://www.gammon.com.au/power Sketch J 
   but stripped down for demonstration purposes to exclude some optimisation (BOD and ADC etc.)
   See also: https://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html

   Connect a button switch between pin 2 and ground
   Press the button. It wakes, blinks the led and then sleeps.

*/

#include <avr/sleep.h>

const byte LED = 13;  // built in led

void wake()
{
  // nothing here. All done in the loop
}

void setup()
{
  Serial.begin( 115200 ) ;
  pinMode( LED, OUTPUT );
  pinMode( 2, INPUT_PULLUP ) ; 
  attachInterrupt ( digitalPinToInterrupt(2), wake, FALLING);
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
}

void loop()
{
  Serial.println( "waking . . . ") ;

  // your own code could go here instead 

  for ( int i = 0; i < 4; i++ ) {
    digitalWrite (LED, HIGH);
    delay (50);
    digitalWrite (LED, LOW);
    delay (50);
  }
  delay(3000) ;
  

  Serial.println( "Sleeping. . . " ) ;
  Serial.flush() ;
  delay(20) ; // allow message to be written

  sleep_mode();
  // *** we wake here ***
}
1 Like

Thanks @6v6gt.

The trouble for me began when I was going for one button to both wake up and go to sleep.

I couldn't (haven't yet) combine traditional button handling (or a button library) with the interrupt wake up.

Turning off that interrupt in the interrupt routine let me skip past contact bounce at the wake up press.

I have conceived a slightly more elaborate wake-up and sleep scheme that uses an FSM to track awake, going to sleep and sleeping, fully separating the matter from all the normal "I am fully awake" stuff going on. It gets rid of the hacky wait for the fat finger to get off the button thing.

a7

I took a few minutes cruising about your code. I did not go blind. In fact, I find value in reading code whether it is a joy to do or somewhat of a brain-teaser.

Many of your processes are already event loops - there isn't a legit loop in the sketch that isn't while (true) - so making them good citizen FSMs would be straightforward. You'd just have to deal with the prologue code what gets done before the infinite loop.

You are right to think the fire() is not one of them. So that's a bit more of a challenge.

Your timing is done in a manner that I have not seen. What this says is that you have programming skills, but haven't read much of this kind of code and as we have discussed, haven't seen a few essential concepts in action.

So along with the abstract stuff, you will enjoy learning more of the basic language features. Wherever you are coming from, arrays must have been a thing there; you can be sure they are no less valuable here.

I picked one of your effects thinking I would demonstrate. By the time I finished, there was zero resemblance to your original… let's just say that's what happened. I think all the effects could be boiled down and simplified.

Here's my rotate() function, which is called like thousands of times a second and occasionally moves to the next LED in a list of LEDs to illuminate sequentially.

Try it here

The code:

// https://wokwi.com/projects/448153462893255681
// https://forum.arduino.cc/t/atmega328pb-wake-with-button-interrupt/1414866

const byte ledYellow = 10;
const byte ledRed    = 8;
const byte ledGreen  = 9;
const byte ledBlue   = 11;

void setup() {
 Serial.begin(9600);
 Serial.println("\nWake up!\n");
}

void loop() {

 rotate(); // take one step in the rotate pattern if it is time

// this clogs the serial buffer and reduces the speed
// but shows the free running nature of the loop
//  static int counter;
//  Serial.print(counter); counter++;
//  Serial.println(" loop.");
}

void rotate() {
// sequence of pins
 const byte ledPins[] = {
   ledYellow, ledRed, ledGreen, ledBlue,
 };

// compiler alculates lenth of the sequence
 const byte lenth = sizeof ledPins / sizeof *ledPins;


 static byte pc;
 static unsigned long lastTime;

 unsigned long now = millis();

 if (now - lastTime < 333) return;  // not time yet!

 digitalWrite(ledPins[pc], LOW);
 pc++; if (pc >= lenth) pc = 0;
 digitalWrite(ledPins[pc], HIGH);

 lastTime = now;
}

HTH

a7

1 Like

A finite state machine solution to handling sleep/wakeup is fine if the application is already developed as a finite state machine or can easily be adapted to one.

However, if you are retrofitting sleep/wakeup functionality into an application where there are lots of delay statements, blocking while loops etc., as indeed the OP's application is, then that would appear to preclude a finite state solution and the options are more limited.

Similar to that already shown/mentioned is to have an interrupt on a button which sets a global flag to indicate that a request to sleep is pending and adding function calls at opportune places in the code to check if a sleep request is pending and, if so, act on it. Also, with the same interrupt being used for the wakeup. I think a "hacky" wait here, probably involving a check that the button is no longer pressed, and cleanup of the flag that got us here, is then inevitable.

Another approach would be to use the sleep/wakeup button to control a latch on the power supply. At the software level, this would be easy to retrofit into a complex application (such as that of the OP) but implies a hardware change (say at least a couple of transistors). It would also mean that any environment created during the runtime of the application, which was not preserved in non-volatile memory, would be lost. That is, a fresh start each time.

And the last resort is a simple on/off switch.

Indeed. Back-fitting sleep into a sketch that blocks all over without a rewrite is harder.

If we are going to use a hack, I would go with the solution that overrides delay().

Start here

take the link in that post. Or search these for myDelay.

A further hack reduces code changes:

void myDelay(unsigned long t)
{
  delay(t);
}

# define delay myDelay

The body of myDelay can decide what to do when called; here it just obediently does the delaying. But button down might mean something else… I've used it for a 10-times slow down, e.g.

I said we were hacking. If you want real hacks, add setjmp() and longjmp() to the toolkit. I'll try to find the sketch I wrote that demonstrates.

a7

I'd probably have to see an example to understand how that can be applied.

The rule I personally apply is never to write code which requires such desperate measures and the sort of user who may need these, due to their style of programming, would be hopelessly out of their depth applying such tricks.

Of course, with RTOS as on the ESP32 for example, the user can write as much blocking code as he/she wants. When the going gets rough, a new task can be defined to monitor the button presses etc. or other activities which would only occasionally be respected in the original task. It is quite simple so that may be an interesting alternative to unraveling a lot of blocking code and quickly sending the user on their way.

1 Like