Interrupt from push button in a loop with a delay

Hi all,
I have a single push button and an led shield. I have the following code which works but not as smoothly as it should. I would like the cases specified in the loop to happen in a cycle each time the button is pressed it should move to the next case. Case 1 and 3 are a steady color while case 2 and 4 require a loop to alternate between a color and nothing. Is there a better way to do this with an interrupt? I know I can't use the delay function in an interrupt function so I am not sure how I would perform the alternating writes in case 2 and 4. Any ideas?

Thank you XD!

#include <Wire.h>
#include <HPRGB.h>
#include <Bounce.h>

HPRGB ledShield;            //initialize
#define BUTTON 2            //button
                            //debouncer
Bounce bouncer = Bounce( BUTTON,5 );
int count = 0;              //need a counter

void setup()
{
  pinMode(BUTTON,INPUT);    //initialize
  Serial.begin(9600);       //used for debugging
  ledShield.begin();        //mA of each output
  ledShield.setCurrent(350,350,350);
  ledShield.setFreq(600);   //set freq
  ledShield.stopScript();   //stop check sequence
  ledShield.eepromWrite();  //writes settings
  delay(100);               //needs this per spec
}

void loop()
{
  bouncer.update ( );       //check and read
  int value = bouncer.read();

  if (value == HIGH) {
    Serial.println("HIGH");
    delay(500);
    count = count + 1;      //increment
    int valuein = LOW;      //used for c2 and c4
    
    switch (count) {
     case 1:
       Serial.println("Pressed 1");
       ledShield.goToRGB(0,0,255);
       delay(1000);
       break;
     case 2:
       Serial.println("Pressed 2");
       while(valuein == LOW){
         ledShield.goToRGB(0,0,255);
         delay(1000);
         ledShield.goToRGB(0,0,0);
         delay(1000);
         bouncer.update ( );
         valuein = bouncer.read();
       }
       break;
     case 3:
       Serial.println("Pressed 3");
       ledShield.goToRGB(255,0,0);
       delay(1000);
       break;
     case 4:
        Serial.println("Pressed 4");
        while(valuein == LOW){
         ledShield.goToRGB(255,0,0);
         delay(1000);
         ledShield.goToRGB(0,0,0);
         delay(1000);
         bouncer.update ( );
         valuein = bouncer.read();
       }
       break;
    }
    
    if (count == 4) {
      count = 0;
    }
  }
}

You can try incorporating this in your code.

The interrupt is only used to raise a flag. Inside loop(), you check this flag on every iteration - if it has been set, "count" advances to the next mode. Otherwise, "count" remains unchanged and the current mode is executed again. Note that you don't need any pin reads with this approach. All input is handled by the interrupt.

volatile boolean interrupted = false;

void setup()
{
  ...
  attachInterrupt(0, handler, RISING);
}

void loop()
{
  if (interrupted)
  {
    count = (count + 1) % 4;
    interrupted = false;
  }
  
  switch (count)
  {
    ...
  }
}

void handler() 
{
  interrupted = true;
}

Based-on your code sample I have integrated it as shown below. I am seeing a couple oddities. On first press it cycles through case 1 and case 2 together. Then it keeps cycling through case 3 repeatedly. It won't go to case 4 and jumps back to case 1. Any ideas?

#include <Wire.h>
#include <HPRGB.h>
#include <Bounce.h>

HPRGB ledShield;            //initialize
                            //interrupt
volatile boolean interrupted = false;
int count = 0;              //need a counter

void setup()
{                           //interrupt for button
  attachInterrupt(0, handler, RISING);
  Serial.begin(9600);       //used for debugging
  ledShield.begin();        //mA of each output
  ledShield.setCurrent(350,350,350);
  ledShield.setFreq(600);   //set freq
  ledShield.stopScript();   //stop check sequence
  ledShield.eepromWrite();  //writes settings
  delay(100);               //needs this per spec
}

void loop()
{
  if (interrupted)
  {
    Serial.println("HIGH");
    count = (count + 1) % 4;
    interrupted = false;
    delay(500);
  }
  
  switch (count)
  {
     case 1:
       Serial.println("Pressed 1");
       ledShield.goToRGB(0,0,255);
       delay(500);
       break;
       
     case 2:
       Serial.println("Pressed 2");
       while(interrupted == false){
         ledShield.goToRGB(0,0,255);
         delay(1000);
         ledShield.goToRGB(0,0,0);
         delay(1000);
       }
       break;
       
     case 3:
       Serial.println("Pressed 3");
       ledShield.goToRGB(255,0,0);
       delay(500);
       break;
       
     case 4:
        Serial.println("Pressed 4");
        while(interrupted == false){
         ledShield.goToRGB(255,0,0);
         delay(1000);
         ledShield.goToRGB(0,0,0);
         delay(1000);
       }
       break;
  }
}

void handler() 
{
  interrupted = true;
}

Couple of them:

  • "count" changes 0 -> 1 -> 2 -> 3 -> 0. The switch labels should be 0 ~ 3, instead of 1 ~ 4
  • Having curly brackets in each case is always helpful (see below)
  • Finally, you should not need the two "while(interrupted == false){" loops. If there's been no interrupt, the main loop() will always repeat the same switch branch.
  switch (count)
  {
     case 0:
     {
       Serial.println("Pressed 1");
       ledShield.goToRGB(0,0,255);
       delay(500);
       break;
     }
     case 1:
     {
      ...

Hope this helps :slight_smile:

That fixed it, thank you!

I do notice on case 1 and case 3 it doesn't always get the interrupt on the first button press. I am assuming this is due to the delay? Is there a better was to do this so that it works every time regardless of when the button is pressed?

Indeed, the code does not react immediately. This also happens on the other two cases - 0 and 2, but they are shorter in duration (0.5s) and it's hardly noticeable.

Technically, the interrupts do happen and interrupted becomes true on the very first button press. But it won't switch to the next mode, until the current one completes.

You are right - it's all due to the delays. I'm not aware of a way to "stop" in the middle of a delay() and skip to the next instruction. You can work around this by replacing a delay(1000) with a loop of 100x delay(10). Check for interrupted and break the loop if true. This way the response time will be 0.01s instead of a 1s. However, you may start running into button bouncing issues...

Ah, good point about the bouncing if I were to do that.

Thanks for all your help, I will leave it as is for now. :grin: