Interrupt trouble with rapid sequence of button pushes

Hello, everyone. I sure could use some help! Thanks in advance.

THE PROBLEM: Button pushes too close together can produce unwanted response. See symptoms below.
Program works well if one does not crowd the button pushes too close together.
If one does, trouble shows up, and I would like it to be really hard (or impossible)
for the user to get the program to misbehave.

How can I make the program more robust?

Symptoms of bad behavior:
pb = one quick push and release of push button (time < .5 sec)
pbLong = one slow push and release of button (time > 1 sec)

*) closely spaced (t<<.5sec) pb’s will soon cause undesired response to slower paced pb’s
*) Can get mode 1 on button push w/no release, then mode 1 on release
*) If mode changes on button push w/no release, it’s always mode 1
*) That can happen multiple times in a row
*) A pb can give mode 1 on push and mode 2 or 3 on release
*) A pb can give mode 1 on release
*) That can happen multiple (unlimited?) times in a row
*) Mode 1 is occurring on push in that case
*) Then, pbLong can give mode 1 on push, mode 2 or 3 on release
*) After that, slow paced pb series gives desired mode 2/mode 3 alternation
*) ********A pbLong always gets things back on track ***************

Here is how the program should flow.
Program Flow:
Want to control program with a single push button. A short button push (pb, time<1sec) will
move the program between modes. There are 3 modes. Program starts in mode 1, then 2 and
3. Each pb after that alternated between mode 2 and 3. Mode 1 can be reached again with
a long button push (pbLong, time>=1sec). In mode 1 nothing happens. Mode 2 will run a series
of programs that move a kinetic sculpture in various ways. Mode 3 will allow manual control
of the sculpture. Each mode change is acknowledged with a different beep sound.

Program Flow stated differently:
mode 1 (starts here): waiting for push button push and release (PB) to start AP
(Action Program) sequence.
Return to mode 1 w/long PB (pbLong)
PB leads to mode 2
mode 2: run AP sequence.
PB leads to mode 3: manual control
Return to mode 1 w/(pbLong)
mode 3: Manual control
PB leads to return to mode 2
Return to mode 1 w/(pbLong)

Some wiring details:
Debouncing is done with hardware. A 100nF cap to ground from
pin D2; 4.7K ohm resistor from D2 to the push button; D2 internal pullup ON; other side of
switch to ground. I have examined the input signal on an oscilloscope: there is no bouncing.
Normally HIGH input has fall time of about 1mS, rise time 9mS.

I added audible feedback to button pushes. Pin 10 connects to a Radio Shack #273-0073
piezo transducer. Two different musical notes are used to differentiate between
entering mode 2 or 3. A descending 4 note sequence announces entering mode 1 (simplified
to one note here).

//Used in ISR (+elsewhere)***************************************************************
volatile int button = 2; //Push button is connected to pin 2 (interrupt 0)
volatile unsigned long timeButtonWentDown = 0;
volatile unsigned long timeButtonWentUp = 0;
volatile unsigned long pbLongThreshold = 1000000;  //1E6 microseconds: defines long button push
//which will send program back into mode 1 (waiting for button push to start action)
volatile boolean PBLong = true;
volatile unsigned long pbDeltaT = 0;//time between button down and button up
volatile boolean pbFlag = false;
//Used in ISR (+elsewhere)***************************************************************

int mode = 0;
boolean firstLoop = true;
int del = 300;  //Not sure how useful this is, or its relation to problem

// Interrupt Service Routine (ISR)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void pinChange ()
{
  if (digitalRead (button) == HIGH)
  {
    timeButtonWentUp = micros();
  }
  else
  {
    timeButtonWentDown = micros();
  }
  //test to see if button was pushed AND released. Only want action on release because
  //duration of time down determines if we have pb or pbLong
  if (timeButtonWentDown && timeButtonWentUp)  
  {
    pbFlag = true;//will be checked thruout pgm to see if mode change/beep are needed
    pbDeltaT = timeButtonWentUp - timeButtonWentDown;
    if (pbDeltaT > pbLongThreshold)
    {
      PBLong = true;//Long button push: Used to get back into Mode 1
    }
    timeButtonWentUp = 0;
    timeButtonWentDown = 0;
  }
} // Interrupt Service Routine (ISR)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


void setup ()
{
  digitalWrite (button, HIGH);  // internal pull-up resistor on interrupt pin
  attachInterrupt (0, pinChange, CHANGE);  // attach interrupt handler
  Serial.begin(9600);
  Serial.println ("Serial OK");
}  // end of setup


void loop ()
{
  if (firstLoop)//what to start out waiting in mode 1
  {
    delay(200);//with NO delay here, first pb's will give mode 1's.
    firstLoop = false;
    mode = 1;
    beep();

    PBLong = false;//otherwise will be true here, somehow
    pbFlag = false;//otherwise will be true here, somehow
  }
  switch (mode)
  {
    case 1:
      while (mode == 1)
      {
        if (pbFlag)
          mode = pbHandler();  //setMode;beep;return. Will leave case 1 if mode!= 1.
          Serial.print("Mode = ");
          Serial.println(mode);
          delay(del);//worse behavior w/o this delay!
      }
      break;
    case 2:
      while (mode == 2)
      {
        if (pbFlag)
        mode = pbHandler();  //setMode;beep;return
        Serial.print("Mode  = ");
        Serial.println(mode);
        delay(del);//worse behavior w/o this delay!
      }
      break;
    case 3:
      while (mode == 3)
      {
        if (pbFlag)
        mode = pbHandler();  //setMode;beep;return
        Serial.print("Mode   = ");
        Serial.println(mode);
        delay(del);//worse behavior w/o this delay!
      }
      break;
  }
}

void setMode()
{
    if (PBLong)
    {
      mode = 1;
    }
    else if (mode == 1 || mode == 3)
    {
      mode = 2;
    }
    else if (mode == 2)
    {
      mode = 3;
    }
    PBLong = false;
}

void beep()
{  
  if (mode == 1)
  {
    tone(10, 2637, 50);//pin 10, E7 pitch, mS duration
    delay(50);
    //commented out for troubleshooting/development...
//    tone(10, 1976, 100);//pin 10, B6 pitch, mS duration
//    delay(100);
//    tone(10, 1568, 100);//pin 10, G6 pitch, mS duration
//    delay(100);
//    tone(10, 1319, 300);//pin 10, E6 pitch, mS duration
//    delay(300);
//    noTone(10);
//    return;
  }
  else if (mode == 2)
  {
    tone(10, 1568, 25);//pin 10, G6 pitch, mS duration
    delay(25);
    noTone(10);
    return;
  }
  else if (mode == 3)
  {
    tone(10, 1976, 20);//pin 10, B6 pitch, mS duration
    delay(20);
    noTone(10);
    return;
  }
}

int pbHandler()
{
  setMode();
  beep();
  pbFlag = false;
  return mode;
}

Hello, again! I have been working to fix my problem, and have gotten the program to where I am satisfied with how it responds to button pushes. Hopefully my travails will benefit some of you. I am new to this, and it was a real struggle to get this to work!

I have added some tests in the interrupt service routine that have solved the problem. It makes for a long ISR compared to those I have seen as I recently educated myself a bit on the subject, but I don’t know how I can put the tests elsewhere in the program. I will gladly accept suggestions as to how to shorten the ISR if folks feel it needs to be done, but the program does work!

I will include the entire program again, as I have tweaked some items that are not in the ISR. I have included comments for the new tests in the ISR such that I hope you can follow what is going on. The test following the comment about getting occasional unwanted mode 1’s was tough for me to come up with!

Thanks for your time!

//Used in ISR (+elsewhere)***************************************************************
int button = 2; //Push button is connected to pin 2 (interrupt 0)
unsigned long tDown = 0;//time button was pushed
unsigned long tUp = 0;//time button was released
unsigned long pbLongThreshold = 1000000;  //1E6 microseconds: defines long button push
//which will send program back into mode 1 (waiting for button push to start action)
volatile boolean pbLong = false;
unsigned long pbDeltaT = 0;//time between button down and button up
volatile boolean pbFlag = false;
unsigned long tEnough = 200000;// microsec; min time btwn button pushes
//Will prevent response to a pb too soon after the previous pb.
unsigned long tUpPrev = 0;//time button was release previously
//Used in ISR (+elsewhere)***************************************************************

int mode = 0;
boolean firstLoop = true;//used to get into mode 1 with no button push first time thru LOOP
int del = 100;  //When = 0, got some failures to enter mode 1 after pbLong. Instead, would
//change from mode 2 to 3, or vice versa. Setting at 100 worked ok. Don't understand this yet.

// Interrupt Service Routine (ISR)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void pinChange ()
{
  
    if (digitalRead (button) == HIGH)
    {
      tUp = micros();
    }
    else
    {
      tDown = micros();
    }
    //Next: was getting  occasional unwanted mode 1's. Since tDown comes before tUp, 
    //having a tUp without a tDown indicates a problem. This makes it go away.
      if (tUp && !tDown)
      {
        tUp = 0;
      }
      //Next: if time between switch pulses is too small AND tUp is true, assign tUp to
      //tUpPrev and set tUp and tDown to zero to set the stage for the next pulse (which
      //is usually HIGH, and is LOW while the button is pressed)
      if ((tDown - tUpPrev < tEnough) && tUp)
      {
        tUpPrev = tUp;
        tUp = 0;
        tDown = 0;
      }
      //Next: if time between switch pulses is large enough AND tUp is true (button released), 
      //then we have a 'good' switch actuation so do the push button stuff.
      else if ((tDown - tUpPrev >= tEnough) && tUp)
      {
        pbFlag = true;//will be checked thruout pgm to see if mode change/beep are needed
        pbDeltaT = tUp - tDown;
        if (pbDeltaT > pbLongThreshold)
        {
          pbLong = true;//Long button push: Used to get back into Mode 1
        }
        
      //Set stage again for the next pulse as above.
        tUpPrev = tUp;
        tUp = 0;
        tDown = 0;
      }
} // Interrupt Service Routine (ISR)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


void setup ()
{
  digitalWrite (button, HIGH);  // internal pull-up resistor on interrupt pin
  attachInterrupt (0, pinChange, CHANGE);  // attach interrupt handler
  Serial.begin(9600);
  Serial.println ("Serial OK");
}  // end of setup


void loop ()
{
  if (firstLoop)//what to start out waiting in mode 1
  {
    firstLoop = false;
    mode = 1;
    beep();
  }
  switch (mode)
  {
    case 1:
      while (mode == 1)
      {
        if (pbFlag)
          mode = pbHandler();  //setMode;beep;return. Will leave case 1 if mode!= 1.
          delay(del);//worse behavior w/o this delay!
      }
      break;
    case 2:
      while (mode == 2)
      {
        if (pbFlag)
        mode = pbHandler();  //setMode;beep;return
        delay(del);//worse behavior w/o this delay!
      }
      break;
    case 3:
      while (mode == 3)
      {
        if (pbFlag)
        mode = pbHandler();  //setMode;beep;return
        delay(del);//worse behavior w/o this delay!
      }
      break;
  }
}

void setMode()
{
  if (pbLong)
  {
    mode = 1;
  }
  else if (mode == 1 || mode == 3)
  {
    mode = 2;
  }
  else if (mode == 2)
  {
    mode = 3;
  }
  pbLong = false;
}

void beep()
{  
  if (mode == 1)
  {
    tone(10, 2637, 50);//pin 10, E7 pitch, mS duration
    delay(70);
    //commented out for troubleshooting/development...
    tone(10, 1976, 50);//pin 10, B6 pitch, mS duration
    delay(100);
    tone(10, 1568, 50);//pin 10, G6 pitch, mS duration
    delay(100);
    tone(10, 1319, 250);//pin 10, E6 pitch, mS duration
    delay(300);
    noTone(10);
    return;
  }
  else if (mode == 2)
  {
    tone(10, 1568, 25);//pin 10, G6 pitch, mS duration
    delay(25);
    noTone(10);
    return;
  }
  else if (mode == 3)
  {
    tone(10, 1976, 20);//pin 10, B6 pitch, mS duration
    delay(20);
    noTone(10);
    return;
  }
}

int pbHandler()
{
  setMode();
  beep();
  pbFlag = false;
  return mode;
}