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:
Wait for RAM_TOP_SWITCH to be actioned. Turn on green LED while waiting.
Once RAM_TOP_SWITCH has been actioned, turn on blue LED while waiting. Ignore future action on RAM_TOP_SWITCH until loop starts again.
Wait for RAM_BOTTOM_SWITCH to be actioned.
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:
I press pause button (and can hold it down as long as I want).
Pause state beings.
Once I release pause button, do nothing. Remain in pause state.
I press pause button again (and can hold it down as long as I want).
Pause state ends.
Main loop continues.
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.
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:
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);
}
@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.
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.
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 Thank you for your input!
#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);
}
}
}
}
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]);
}