Delay() in main loop preventing ISR button detection

I am new to arduino. I hope someone can help me with this problem. I have looked for days over the web trying to find out how to create sequence using non blocking code so that the ISR button can work. However, different answers was confusing even more, I have tried "if" loop but that messes up the sequence of the activation of the digital output. Things randomly switches on unexpectedly. I fell in to this vicious cycle of confusion.

The sequence work with delay() and but the interrupt button fails to acknowledge due to delay in the main loop. How do I turn this to a none blocking code or something that I can understand in simple programming terms. May thanks.


// Digital input switches 
const int elt1      = 31;
const int elt2      = 32;
const int el1       = 33;
const int el2       = 34;
const int el3       = 35;
const int el4       = 36;
const int res       = 37;
const int push_button1    = 53;
const int motor_brake     = 52;
const int power_led       = 51;



// global variables
volatile int button_flag;
int last_button1_state = 0;
int current_button1_state;
unsigned long int current_time = 0;
unsigned long int previous_time = 0;
unsigned long int counter = 0;
unsigned long int interval = 1000;


void setup() {
  // setup pin modes


  pinMode(push_button1, INPUT);   // Push Button for enabling / dissabling programme
  pinMode(elt1, OUTPUT);   // Door Enable Switch (E1)
  pinMode(elt2, OUTPUT);            // E2 switch active high
  pinMode(el1, OUTPUT);           // DLS switch (E3)
  pinMode(el2, OUTPUT);           // DCS switch (E4)
  pinMode(el3, OUTPUT);
  pinMode(el4, OUTPUT);
  pinMode(res, OUTPUT);
  pinMode(power_led, OUTPUT);
 
  
  Serial.begin(115200);

  attachInterrupt(digitalPinToInterrupt(push_button1), ISR_button, RISING);
  current_button1_state = digitalRead(push_button1);

  //while(!Serial);     // wait for the serial connection

}

void loop() {
   // put your main code here, to run repeatedly:

if(current_button1_state == 0 && button_flag){

   if(button_flag == HIGH){

      last_button1_state = button_flag;

        Serial.print("Cycle count: ");
        Serial.println(counter);
        delay(5000);  // On power up allow the motor to run for 5 sec
        digitalWrite(motor_brake, HIGH);  // activate motor brake
        delay(4000);                      // Apply motor brake for 4 sec
        digitalWrite(motor_brake, LOW);   // de-activate motor brake
        delay(3000);                      // wait 3 seconds before the next seuence
        digitalWrite(el4, HIGH);          // toggle open door switch for 2 sec
        delay(2000);                      // wait 2 sec
        digitalWrite(el4, LOW);           // set EL4 low
        delay(4000);
        digitalWrite(motor_brake, HIGH);   // activate motor brake
        delay(4000);
        digitalWrite(motor_brake, LOW);   // de-activate motor brake
        delay(2000);
        digitalWrite(el2, HIGH);          // activate EL2
        delay(13000);                     // wait 13 sec for auto close
        digitalWrite(el2, LOW);
    

     counter++;

   } else {

          current_button1_state;
   }

   } else if (last_button1_state == 1 && button_flag == 0) {

     // Do something else in this block
     current_time = millis();
    while(current_time - previous_time >= interval){

      Serial.println("Button Deactive");
      digitalWrite(power_led, button_flag);
      previous_time = current_time;
      last_button1_state = button_flag;

     }
        //delay(1000);
   }


}
// Call function when switch is pressed.
void ISR_button(){

  if(current_button1_state == LOW){

        button_flag = !button_flag;
  }


}

Ok... You are indeed confused.

  1. delay() is blocking. Any code using delay() cannot be non-blocking code. You have to remove the delay().
  2. If your code was non-blocking, you would not have any need for an ISR. Using ISR for buttons is not necessary at all.

where is current_button_state set anywhere other than in setup()?

the ISR recognizes the change in the pin, but doesn't change the value of current_button_state

without the pin either tied HIGH or LOW with a resistor, the initial read could be random.

So the wanted functionality is you have a constant sequence of steps switching some IO-pins HIGH/LOW after different periods of time.

and whenever you press the button stop the sequence

This is best coded using lines of code that build a so called state-machine
state 1: wait 5000 milliseconds have passed by
state 2: switch motorbrake HIGH
state 3: wait 4000 milliseconds have passed by
state 4: switch switch motor-brake LOW
state 5: wait until 3000 milliseconds have passed by
state 6: switch e14 HIGH
state 7: wait 2000 milliseconds have passed by
state 8: switch e14 LOW
state 9: wait 4000 milliseconds have passed by
state 10: switch motorbrake HIGH
state 11: wait 4000 milliseconds have passed by
state 12: switch switch motor-brake LOW
state 13: wait 2000 milliseconds have passed by
state 14: switch e12 HIGH
state 15: wait 13000 milliseconds have passed by
state 16: switch e12 LOW and increase variable counter by one

writing this code uses the switch-case-break; statement
the break; is very important to make it work as intended

In your case The conditions to proceed to the next state is

  • either immitiately change to next state
    or
  • change to the next state after a number of milliseconds have passed by

I have written a tutorial about state-machines

best regards Stefan

I perfectly understand the delay() is blocking the rest of the operation as it goes down the sequences. I just do not know how the go on about programming an alternative routine so that it can also acknowledge the button activation to enable the jump in to a section of the routine. I have tried it with "while" loop also and it did the same thing.

The push button switch is tied to input low by 10K resistor and going through a hardware de-bouncing. That part works fine as I have tried it with blinking LED and also observed that on serial monitor. The current_button_state is not set at the moment as the button_flag is toggled and currently I am taking the button_flag to control my routine.

Thats interesting, How would I set the case variable? Do I set this as time, e.g if I want 1 sec , do I set first variable as time1 = 1000; when the function returns 1000 the switch case will activate?

No it is totally different.
I guess it will be useful for you to read at first how non-blocking timing works
I wrote a tutorial for that too

The essence is to rethink your strategy COMPLETELY.
Forget about your code being ‘time driven’… reimagine it as ‘event driven’

Things shouldn’t happen after a period of time, they should occur when the passage of certain interval “happens’. This is where a good understanding of millis() timing really makes your life easier.

More work, yes, but things happen when you want them to, not when the delay() wants them to.

I appreciate your response, are you able to give an example so that I can get my head around this idea?

The functionality of this program is

  1. say "Hello" (printing this message only ONCE)

  2. inform "I initiate counting" (printing this message only ONCE)

  3. counting up to 10 VERY fast (printing each number)

  4. say "good bye" (printing this message only ONCE)

  5. wait 5 seconds
    but not only waiting!
    in PARALLEL to the waiting a counter is counting up very fast
    in PARALLEL to the waiting print a message once per second

repeat this pattern.

// this demo-program shows how to use the switch-case statement
// to create a functionality as described at the bottom of this file

unsigned long myCounter;

const byte sayHello      = 0;
const byte startCounting = 1;
const byte countTo10     = 2; 
const byte sayGoodbye    = 3;
const byte wait5seconds  = 4;

byte myStateVar;

unsigned long waitingTime = 5000;
unsigned long WaitingTimer; // timer variable for non-blocking timing

unsigned long oneSecondTimer; // timer variable for non-blocking timing

void setup() {
  Serial.begin(115200);
  Serial.print( F("\n Setup-Start  \n") );
  myCounter = 0;
  // initialise state-variable to that state the state-machine 
  // shall start with
  myStateVar = sayHello; 
}


// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long &StartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - StartTime >= TimePeriod ){
    StartTime = currentMillis; // store timestamp when the new interval has started
    return true;               // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false; // return value false because LESS time than TimePeriod has passed by
}


void myStepChain() {
    
  switch (myStateVar) {
    case sayHello:
      Serial.println( F("Hello user!") );
      myStateVar = startCounting;
      break;

    case startCounting:
      myCounter = 0; // reset counter-variable
      Serial.println( F("I start counting very fast") );
      myStateVar = countTo10;
      break;     

    case countTo10:
      // there is no slowing down through non-blocking timing 
      // in this state. These lines get executed very fast
      // counting up to 10 in less than one MILLIsecond
      myCounter++;
      Serial.print( F("I'm counting up counter=") );
      Serial.println(myCounter);

      if (myCounter == 10) {
        myStateVar = sayGoodbye;
      }
      break;

    case sayGoodbye:
      Serial.println( F("goodbye see you next round in 5 seconds") );
      WaitingTimer = millis(); // store timestamp when the waiting STARTS
      myStateVar = wait5seconds;
      break;

    case wait5seconds:
      // this condition changes the state after 5000 milliseconds
      if ( TimePeriodIsOver(WaitingTimer,5000) ) {
        // more time than 5000 milliseconds has passed by
        Serial.println( F("5 seconds waited starting new cycle.. ") );
        Serial.println();
        Serial.println();
        myStateVar = sayHello; // reset to starting step to repeat
      }
      
      myCounter++; // counts up VERY fast because in a state-machine 
                   // code-execution is always fast
                   // if you need to execute only from time to time
                   // this is done by non-blocking timing like coded below
      // non-blocking timing:             
      if ( TimePeriodIsOver(oneSecondTimer,1000) ) {
        // more than 1000 milliseconds have passed by
        Serial.print( F("one second over") );
        Serial.print( F(" value of myCounter=") );
        Serial.println(myCounter);
      }
      break;            
  }    
  //delay(100);
}


void loop() {
  myStepChain();
}



/* pre-ambel:  programming something more complex than  
   switch LED on wait 1 second switch LED off wait 1 second
   requires knowledge. More or less complex knowledge
   And this needs a rather big minimum of words to explain 
   That is the reason why this text has more than two lines
   
in writing programs it is always a good idea to write down
the functionality of the program in NORMAL WORDS.
This code uses the serial monitor to make visible what the code
is doing. So no additional hardware is required

The functionality of this program is 
1. say "Hello"  (printing this message only ONCE)
   
2. inform "I initiate counting" (printing this message only ONCE)

3. counting up to 10 VERY fast (printing each number)

4. say "good bye" (printing this message only ONCE)

5. wait 5 seconds 
  but not only waiting! 
  in PARALLEL to the waiting a counter is counting up very fast
  in PARALLEL to the waiting print a message once per second 

repeat this pattern.

This means the program is doing different things in a defined order
and is able to do a thing A in parallel to a thing B and in parallel 
to a thing C

This basic principle can be transferred to things like
- an LED is blinking while a display shows a message 
  "press button to start"


- a motor is switched on to run while in parallel a LED is blinking
  and the motor stops after X seconds

    
- a motor is switched on to run while in parallel a LED is blinking
  and the motor stops if a button is pressed / or a switch is closed


- a heating is switched on starting to heat and in parallel aquire
  new temperature-measurings until a certain temperature is reached 
  then switch of the heating
  while in parallel a humifier is switched on to increase humidity
  and in parallel new humidity-measurings are aquired until 
  a certain level is reached and then switch off

  where the times it takes do reach the right temperature-level
  and humidity-level can be very different


###### everyday analogon 
  The basic principle behind this is to make a servant watching 
  the scene and when certain things happen take a short action

  servant stay a little aside from the dining table and watch
  all guests dining
  if somebodies glas is empty go over and refill the glass
  keep an eye on the soup-pot if the pot is empty shout to the cook
  "bring a new pot"

  The servant is changing his focus very quickly from task to task
  this could be described as step in (a certain task) step out - repeat
  check if a glas is empty
  change to
  check soup-level
  change to 
  ....
  
  in opposite to: 
  walk over to the soup-pot fix your eyes on the soup-level and if 
  soup-level is low enough shout for the cook to bring a new pot

  This means your program is entering a first "task" 
  does a single step and then quickly leaves the first "task" 
  to enter the second "task"
  does a single step and then quickly leaves the second "task" 
  ....

  the repeating is taken to a higher level

loop() {
  task1(); // enter and quickly leave again 
  task2(); // enter and quickly leave again 
  task3(); // enter and quickly leave again 
  ...  
}

if each task requires multiple steps to be done
this is done by a switch-case-statement

like refilling a glas
- open bottle
- bring bottle into position to pour
- turn neck of the bottle down
- pour wine into the glas checking the level
- if glas is filled 
- turn neck of the bottle up
- close bottle
- move back to watching position

  after opening the bottle and bring bottle into position
  the servant is able to take a quick look to the soup-pot 
  seeing still enough soup
  OK let's just pour the wine
  or if soup-pot is empty shouting "cook bring a new pot with soup"
  
  Which means he is entering and leaving a task quickly to do 
  a small step of another task

  That is how multi-tasking programming works
  and it is done by using one BIG L----O----O----P

loop() {
  task1(); // enter and quickly leave again 
  task2(); // enter and quickly leave again 
  task3(); // enter and quickly leave again 
  ...  
}
  and quickly jumping in/out functions where each function has its
  own switch-case-statement to work through sequential steps
  that must be done in a defined order
*/

the "events" of the above posted code are

1 event: simply set switch-variable immidiately to a new value
2 event: simply set switch-variable immidiately to a new value
3 event: if counter reaches value 10 set switch-variable to a new value
4 event: if function TimePeriodIsOver(WaitingTimer,5000) returns value true
set switch-variable to a new value

ported to your sequence of steps
state 0: initial state store actual value of millis() in a variable that is then used as the timing variable myWaitTimer = millis();

state 1: check if TimePeriodIsOver(myWaitTimer , 5000) returns true
event if TimePeriodIsOver() returns true set switch-variable to state 2:

state 2:

  • switch motorbrake HIGH "event"
  • update timer-variable myWaitTimer = millis();
  • set switch-variable to state 3 (as everything of state2 is done with these 3 lines of code

state 3:

  • check if TimePeriodIsOver(myWaitTimer , 4000) returns true
    event if TimePeriodIsOver() returns true set switch-variable to state 4:

etc. etc. etc

best regards Stefan