Adding in a pause button to a loop

The code below is for a small project at home that uses a servo to push some parts on my workbench.

I have the code functioning, except for one piece: I want to add a pause button.

Presently my code does the following:

  1. Wait for RAM_TOP_SWITCH to be actioned. Turn on green LED while waiting.
  2. Once RAM_TOP_SWITCH has been actioned, turn on blue LED while waiting. Ignore future action on RAM_TOP_SWITCH until loop starts again.
  3. Wait for RAM_BOTTOM_SWITCH to be actioned.
  4. Once RAM_BOTTOM_SWITCH is actioned, turn off blue LED, action servo, and go back to Step 1.

I would like to have a pause button, designated as such below, that can be pressed between Step 1 and Step 2, and also between Step 3 and Step 4.

I tried writing a pause button (lines 51 to 65), and it's only partially functional. I have to use delays to avoid the code skipping ahead (I can only humanly press the button so fast). I'd rather not use delay if possible, either.

Ideally I would like the pause button to perform as follows:

  1. I press pause button (and can hold it down as long as I want).
  2. Pause state beings.
  3. Once I release pause button, do nothing. Remain in pause state.
  4. I press pause button again (and can hold it down as long as I want).
  5. Pause state ends.
  6. Main loop continues.
  7. I release pause button.

Any guidance? I've been out of school for nearly a decade, so I'm a bit rusty in this department. I've forgotten a lot of the novel creative solutions that I had to problems like this (so I end up just brute forcing most of it).

#include <Servo.h>

Servo my_servo; // servo object

// pins and such
#define RAM_TOP_SWITCH 2
#define RAM_BOTTOM_SWITCH 3
#define PAUSE_BUTTON 4

#define SERVO_PIN 5

#define GREEN_LED 6
#define BLUE_LED 7
#define RED_LED 8

// servo positions
int angle_home = 80; // pretty much parallel with sides of servo
int angle_push = 50; // might only need 60 with new arm

int top_switch_pushed = 0;
int button_state = 0;
int last_button_state = 0; 

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

  // servo
  my_servo.attach(SERVO_PIN);
  my_servo.write(angle_home); // Sets servo pusher arm to home

  // switches
  pinMode(RAM_TOP_SWITCH,INPUT_PULLUP);
  pinMode(RAM_BOTTOM_SWITCH,INPUT_PULLUP);

  // buttons
  pinMode(PAUSE_BUTTON,INPUT_PULLUP);
  
  // LEDs for system states
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
}

void loop() {

while(digitalRead(RAM_TOP_SWITCH) == HIGH){ //switch is open
    top_switch_pushed = 1;
    digitalWrite(GREEN_LED,HIGH); //Turns on green LED to indicate that the user can begin a ram cycle
    Serial.println("Ram Top Switch Waiting!");
    
    button_state = digitalRead(PAUSE_BUTTON);
    
    // Pause button function
    if(digitalRead(PAUSE_BUTTON)==LOW){
      pause_button_pushed = 1; 
      digitalWrite(RED_LED,HIGH);
      while(pause_button_pushed == 1){
        delay(1000);
        if(digitalRead(PAUSE_BUTTON)==LOW){
          pause_button_pushed = 0;
          digitalWrite(RED_LED,LOW);
          delay(1000);
        }
      }
    }
    If PAUSE_BUTTON is activated, sit in loop until it is activated again
  }

  if(digitalRead(RAM_TOP_SWITCH) == LOW){ //when top switch is actioned loop sits here until bottom switch is actioned
    top_switch_pushed = 0;
    digitalWrite(GREEN_LED,LOW); 
    Serial.println("Ram Top Switch Pressed!");
    while(top_switch_pushed == 0){
      digitalWrite(BLUE_LED,HIGH); //Indicates that servo will action unless otherwise paused
      // WANT TO PUT PAUSE FUNCTION HERE
      // If PAUSE_BUTTON is activated, sit in loop until it is activated again 
      if(digitalRead(RAM_BOTTOM_SWITCH) == LOW){
        digitalWrite(BLUE_LED,LOW); 
        Serial.println("Servo Running!");
        top_switch_pushed = 1; 
        my_servo.write(angle_push);
        delay(250);
        my_servo.write(angle_home);
      }
    }
  }
}

If this was re-organized as a non-blocking state machine (google that) it would be a lot easier to add a pause. As written with the delays the pause can only happen at certain points in the process where you place code to read the pause button.

I suggest you start with these tutorials:

Once you understand what a state machine is and how to program one then you will have made a big step forward in your programming ability.
(I think everyone already knows what a state machine is, they just don't know it's called that. When in life you do a bit of one thing, put it down then do a bit of another thing, put that down and start something else, then go back to the first thing, that's a state machine).

There are other tutorials that also deal with the same and related concepts:

which controller board contain your project?

somewhat awkward but reasonable requirements. ironically the pause processing is most complicated. had to infer some requirements from the posted code.

using states rathers than while loops makes things more conventionally easier to follow. (could possible be made simpler)

only the pause button requires additional processing because it needs to be debounces and recognize each distinct press while the other buttons simply cause a state transition where that button is no longer monitored

#include <Servo.h>

Servo my_servo; // servo object

// pins and such
# define RAM_TOP_SWITCH      2
# define RAM_BOTTOM_SWITCH   3
# define PAUSE_BUTTON        4

# define SERVO_PIN           5
 
# define GREEN_LED           6
# define BLUE_LED            7
# define RED_LED             8

enum { Off = HIGH, On = LOW };

// servo positions
int angle_home = 80; // pretty much parallel with sides of servo
int angle_push = 50; // might only need 60 with new arm

enum { Idle, Pause, Active};
int state = Idle;
int statePause;

// -----------------------------------------------------------------------------
byte butPauseLst;

bool
pauseBut ()
{
    byte but = digitalRead (PAUSE_BUTTON);
    if (butPauseLst != but)  {   // state change
        butPauseLst  = but;
        delay (20);         // debounce
        if (LOW == but)      // pressed
            return true;
    }
    return false;
}

// -----------------------------------------------------------------------------
void loop ()
{
    switch (state) {
    case Idle:
        digitalWrite (GREEN_LED, On);
        if (digitalRead (RAM_TOP_SWITCH) == LOW)  {
            digitalWrite (GREEN_LED, Off);
            state = Active;
            Serial.println ("Idle -> Active");
        }

        if (pauseBut ())  {
            state      = Pause;
            statePause = Idle;
            Serial.println (" Idle -> Pause");
        }
        break;

    case Pause:
        digitalWrite (RED_LED, On);

        if (pauseBut ())  {
            digitalWrite (RED_LED, Off);
            state = statePause;
            Serial.println (" un-Pause");
        }
        break;

    case Active:
        digitalWrite (BLUE_LED,  On);

        if (pauseBut ())  {
            state      = Pause;
            statePause = Active;
            Serial.println (" Active -> Pause");
        }

        if (digitalRead (RAM_BOTTOM_SWITCH) == LOW){
            digitalWrite (BLUE_LED,  Off);
            my_servo.write (angle_push);
            delay (250);
            my_servo.write (angle_home);
            state = Idle;
            Serial.println ("Active -> Idle");
        }
        break;
    }
}

// -----------------------------------------------------------------------------
void setup () {
    Serial.begin (9600);

    // servo
    my_servo.attach (SERVO_PIN);
    my_servo.write (angle_home); // Sets servo pusher arm to home

    // switches
    pinMode (RAM_TOP_SWITCH,    INPUT_PULLUP);
    pinMode (RAM_BOTTOM_SWITCH, INPUT_PULLUP);

    // buttons
    pinMode (PAUSE_BUTTON,      INPUT_PULLUP);

    butPauseLst = digitalRead (PAUSE_BUTTON);

    // LEDs for system states
    pinMode (GREEN_LED,         OUTPUT);
    pinMode (BLUE_LED,          OUTPUT);
    pinMode (RED_LED,           OUTPUT);

    digitalWrite (GREEN_LED,    Off);
    digitalWrite (BLUE_LED,     Off);
    digitalWrite (RED_LED,      Off);
}

Briefly; Using the switch/case construct (commonly used to build an FSM) -

Thanks to everyone who replied.

@Delta_G , I watched a very thorough video on non-blocking state machines by a British model railroader (sp?). Very, very helpful indeed.

What I'm still trying to wrap my head around is the function of the pause button. In practice, I'm going to press this button, but with the code as I've written it, it will move to the exit the pause state faster than I can remove my finger from the button. I would like to have the button capable of being held down indefinitely, only to enter or exit the pause state on release. I have an idea forming in my head of using an internal if-loop within each state to do this, but it's a bit nebulous right now.

I also need to use the millis() timer to give the servo time to work, but that's pretty easy.

This is what I've got so far:

#include <Servo.h>

Servo my_servo; // servo object

// pins and such
#define RAM_TOP_SWITCH 2
#define RAM_BOTTOM_SWITCH 3
#define PAUSE_BUTTON 4

#define SERVO_PIN 5

#define GREEN_LED 6
#define BLUE_LED 7
#define RED_LED 8

int angle_home = 80;
int angle_push = 50;

int system_state; 
int system_state_paused;

void setup() {
  // servo
  pusher.attach(SERVO_PIN);
  pusher.write(angle_home);

  // switches
  pinMode(RAM_TOP_SWITCH,INPUT_PULLUP);
  pinMode(RAM_BOTTOM_SWITCH,INPUT_PULLUP);

  // buttons
  pinMode(PAUSE_BUTTON,INPUT_PULLUP);
  
  // LEDs for system states
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);

  system_state = 0;
  system_state_paused = 0; 
}

void standby_state(){
  if (system_state == 0){
    digitalWrite(GREEN_LED,HIGH);
    Serial.println("READY TO RAISE RAM")
    system_state = 1;
  }
}

void ready_state(){
  if (system_state == 1 && digitalRead(RAM_TOP_SWITCH) == LOW && digitalRead(PAUSE_BUTTON) == HIGH){
    digitalWrite(GREEN_LED,LOW);
    digitalWrite(BLUE_LED,HIGH);
    Serial.println("READY TO MOVE PUSHER")
    system_state = 2;
  }
}

void action_state(){
  if (system_state == 2 && digitalRead(RAM_BOTTOM_SWITCH) == LOW && digitalRead(PAUSE_BUTTON) == HIGH){
    digitalWrite(BLUE_LED,LOW);
    Serial.println("PUSHING")
    pusher.write(angle_push);
    pusher.write(angle_home);
    system_state = 0; 
  }
}

void pause_enter_state(){
  if (digitalRead(PAUSE_BUTTON)==LOW){
    digitalWrite(BLUE_LED,LOW);
    digitalWrite(GREEN_LED,LOW);
    digitalWrite(RED_LED,HIGH);
    Serial.println("PAUSED");
    system_state_paused = system_state;
    system_state = 3;
  }
}

void pause_exit_state(){
  if (digitalRead(PAUSE_BUTTON)==LOW && system_state == 3){
    digitalWrite(RED_LED,LOW);
    Serial.println("PAUSED");
    system_state = system_state_paused; 
  }
}

void loop() {
  
  currentMillis = millis();

  standby_state();
  ready_state();
  action_state();
  pause_enter_state();
  pause_exit_state();
}

looks like the code just executes each state function. there is no mechanism to process only the current state possibly by using system_state

do you see how in the code in post #6, state determines what processing to perform?

To be honest, when I was scrolling down the page, I didn't see anything beyond the first few lines you posted. I didn't realize there was more. :face_with_peeking_eye:

I do see what you're doing though! If the current path I'm on ends up in failure (highly likely), I'll look into the switch case method :slight_smile: Thank you for your input!

What board are you using?

you need to only execute the code for the current state

(it doesn't handle pause. needs to recognize state change and debounce)

look this over

#include <Servo.h>

Servo my_servo; // servo object

// pins and such
# define RAM_TOP_SWITCH 2
# define RAM_BOTTOM_SWITCH 3
# define PAUSE_BUTTON 4

# define SERVO_PIN 5

# define GREEN_LED 6
# define BLUE_LED 7
# define RED_LED 8

int angle_home = 80;
int angle_push = 50;

int system_state;
int system_state_paused;

byte pauseButState;

// -----------------------------------------------------------------------------
void setup () {
    Serial.begin (9600);

    // servo
    my_servo.attach (SERVO_PIN);
    my_servo.write (angle_home);

    // switches
    pinMode (RAM_TOP_SWITCH,    INPUT_PULLUP);
    pinMode (RAM_BOTTOM_SWITCH, INPUT_PULLUP);

    // buttons
    pinMode (PAUSE_BUTTON, INPUT_PULLUP);

    // LEDs for system states
    pinMode (GREEN_LED, OUTPUT);
    pinMode (BLUE_LED,  OUTPUT);
    pinMode (RED_LED,   OUTPUT);

    system_state = 0;
    system_state_paused = 0;
}

// -----------------------------------------------------------------------------
enum { St_StdBy, St_Rdy, St_Act, St_Pause1, St_Pause2 };

// -------------------------------------
void standby_state ()
{
    digitalWrite (GREEN_LED,HIGH);
    digitalWrite (BLUE_LED,LOW);
    Serial.println ("READY TO RAISE RAM");
    system_state = St_Rdy;
}

// -------------------------------------
void ready_state ()
{
    digitalWrite (GREEN_LED,LOW);
    digitalWrite (BLUE_LED,HIGH);

    if (digitalRead (RAM_TOP_SWITCH) == LOW)  {
        Serial.println ("READY TO MOVE PUSHER");
        system_state = St_Act;
    }

    if (digitalRead (PAUSE_BUTTON)==LOW) {
        pauseButState = LOW;

        Serial.println ("Pause Pressed");
        system_state_paused = system_state;
        system_state = St_Pause1;
    }
}

// -------------------------------------
void action_state ()
{
    digitalWrite (BLUE_LED,LOW);

    if (digitalRead (RAM_BOTTOM_SWITCH) == LOW)  { 
        Serial.println ("PUSHING");
        system_state = St_StdBy;

        my_servo.write (angle_push);
        delay (500);
        my_servo.write (angle_home);
    }

    if (digitalRead (PAUSE_BUTTON)==LOW) {
        pauseButState = LOW;

        Serial.println ("Pause Pressed");
        system_state_paused = system_state;
        system_state = St_Pause1;
    }
}

// -------------------------------------
// PAUSE_BUTTON still LOW when this case entered
// need to wait for it to be released and debounced

void pause_state ()
{
    digitalWrite (BLUE_LED,LOW);
    digitalWrite (GREEN_LED,LOW);
    digitalWrite (RED_LED,HIGH);

    if (LOW == digitalRead (PAUSE_BUTTON))  {
        Serial.println ("Un-PAUSED");
        system_state = system_state_paused;
    }
}

// -----------------------------------------------------------------------------
void loop () {
    unsigned long currentMillis = millis ();

    switch (system_state)  {
    case St_StdBy:
        standby_state ();
        break;

    case St_Rdy:
        ready_state ();
        break;

    case St_Act:
        action_state ();
        break;

    case St_Pause1:
        pause_state ();
        break;
    }
}

Arduino Uno

#include <Servo.h>
Servo my_servo; // servo object

// pins and such
#define PAUSE_BUTTON 2 // ---- <<  -- ATTENTION ! pin is changed
#define RAM_TOP_SWITCH 3
#define RAM_BOTTOM_SWITCH 4

#define SERVO_PIN 5

#define GREEN_LED 6
#define BLUE_LED 7
#define RED_LED 8

// servo positions
int angle_home = 80; // pretty much parallel with sides of servo
int angle_push = 50; // might only need 60 with new arm

void setup() {
  // servo
  my_servo.attach(SERVO_PIN);
  my_servo.write(angle_home); // Sets servo pusher arm to home

  // switches
  pinMode(RAM_TOP_SWITCH, INPUT_PULLUP);
  pinMode(RAM_BOTTOM_SWITCH, INPUT_PULLUP);

  // buttons
  pinMode(PAUSE_BUTTON, INPUT_PULLUP);

  // LEDs for system states
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(PAUSE_BUTTON), Pause, FALLING );
}

void Pause() {
  while (!digitalRead(PAUSE_BUTTON));
  while (digitalRead(PAUSE_BUTTON));
}

void loop() {
  while (digitalRead(RAM_TOP_SWITCH)) { //switch is open
    digitalWrite(GREEN_LED, HIGH); //Turns on green LED to indicate that the user can begin a ram cycle
  }

  if (digitalRead(RAM_TOP_SWITCH) == LOW) { //when top switch is actioned loop sits here until bottom switch is actioned

    digitalWrite(GREEN_LED, LOW);
    digitalWrite(BLUE_LED, HIGH); //Indicates that servo will action unless otherwise paused
    while (digitalRead(BLUE_LED) ) {

      if (digitalRead(RAM_BOTTOM_SWITCH) == LOW) {
        digitalWrite(BLUE_LED, LOW);
        my_servo.write(angle_push);
        delay(250);
        my_servo.write(angle_home);
      }
    }
  }
}

buttone bounce. both conditions will be true whenever a button changes state

this works

bool
chkPauseBut ()
{
    byte but = digitalRead (PAUSE_BUTTON);
    if (pauseButState != but)  {        // state change
        pauseButState  = but;
        delay (20);                     // debounce

        if (LOW == but)
            return true;
    }
    return false;
}

capacitor needed

why add hardware when software can solve the problem, in this and many other cases.

recognizing a state change and adding a short delay works for most cases

1 Like

Thanks to everyone who contributed. Your help was greatly appreciated.

After some good head scratching, I was able to add the pause button with some if/while statements combined with a single pause case for the switch/case function.

I'm sure there's some clever way of using boolean statements to do a "button was pressed" and "button was released" logic, but I'm still wrapping my head around using those.

For brevity I've removed a lot of the other code.

//fires when pause button is pressed as long as system isn't firing the servo
if(digitalRead(PauseButton) == LOW && state != 3){ 

  // fires when pause button is released and system is not already in a pause state
  while(digitalRead(PauseButton)==HIGH && state !=4)
  paused_state = state;     //saves state from which user entered pause
  state = 4;               //changes the system to a paused state

 // fires when pause button is released only when system is in a paused state
  while(digitalRead(PauseButton)==HIGH && state == 4){
    state = paused_state; //returns state machine to state from which user entered pause state
  }
}

switch (state){
  case 1;
    // standby state
  break;

  case 2;
    // armed state
  break;

  case 3;
    // action state. servo fires here.
  break;

  case 4; 
    //pause state, alert user that system is paused
  break;
}

don't understand why you need to knwo when a button is released. you do need to track the state of the button which is simply whether it is pressed on or not. a button is pressed when it changes state and became pressed

a button check routine can return a boolean indicating that a button "became" pressed

it's conventional to check for stimuli (e.g. button press) within each state. look the following over

const byte PinButs [] = { A1, A2};
const int  Nbut       = sizeof(PinButs);
byte butState [Nbut];
enum { But, Pause };        // button idicies

bool
butChk (
int   idx )
{
    byte but = digitalRead (PinButs [idx]);
    if (butState [idx] != but)  {   // state change
        butState [idx]  = but;
        delay (20);                 // debounce

        if (LOW == but)
            return true;
    }
    return false;
}


// -----------------------------------------------------------------------------
const char *StateStr [] = { "Stdby", "Armed", "Action", "  Paused" };
enum { Stdby, Armed, Action, Paused };
int  state = Stdby;
int  statePaused;           // state to return to

// -------------------------------------
void
loop ()
{
    switch (state){
    case Stdby:
        if (butChk (But))  {
            state = Armed;
            Serial.println (StateStr [state]);
        }
        break;

    case Armed:
        if (butChk (But))  {
            state = Action;
            Serial.println (StateStr [state]);
        }
        if (butChk (Pause))  {
            statePaused = state;
            state       = Paused;
            Serial.println (StateStr [state]);
        }
        break;

    case Action:
        if (butChk (But))  {
            state = Stdby;
            Serial.println (StateStr [state]);
        }
        if (butChk (Pause))  {
            statePaused = state;
            state       = Pause;
            Serial.println (StateStr [state]);
        }
        break;

    case Paused:
        if (butChk (Pause))  {
            state       = statePaused;
            Serial.println (StateStr [state]);
        }
        break;
    }
}


// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < Nbut; n++)  {
        pinMode (PinButs [n], INPUT_PULLUP);
        butState [n] = digitalRead (PinButs [n]);
    }

    Serial.println (StateStr [state]);
}