Multiple Button Firing System

Hi Guys,

The project I'm working on uses an Arduino UNO R3, and has 4 buttons (non-toggle) to grip a ball using suction cups, then fire the ball out of the suction cups using a ram (powered by an actuator), the ball then bounces against a surface then the suction cups catch the ball on its return.

This project currently runs on a PLC but we're attempting to make it work on an arduino.

The buttons do 4 different things:

  • Start (Starts the vacuum cups, at which point the ball is placed into the cups)
  • Fire (Fires the actuator and disengages the vacuum cups)
  • Toggle (Switches between a continuous mode and a single fire mode)
  • Stop (Stops the process)

The start and fire buttons both have a loop each, as Start is expected to happen before all else, Then Fire.

Toggle and Stop both utilise the interrupt processes, so can be called at any time. I have posted the code below.

// Initialise Pins and Variables:::::::::::::::::::::::::::::::::::::::::::::::::
const int actuatorPin = 4;
const int vacuumPin = 5;
const int firePin = 6;
const int resetPin = 7;
const int startPin = 8;
const int ledPin = 9;
int bounceMode = LOW;                 // 1 = singlefire, 0 = multifire
int startRead = LOW;
int fireRead = LOW;
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


// Begin Program Setup:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
void setup() 
{
  Serial.begin(9600); // check the baud rate for the Pi
  digitalWrite(actuatorPin, LOW);
  digitalWrite(vacuumPin, LOW);
  digitalWrite(startPin, LOW);
  digitalWrite(ledPin, HIGH);
  attachInterrupt (0, toggle, RISING);    // Modechange Interrupt Handler (pin 2)
  attachInterrupt (1, STOP, RISING);          // STOP Interrupt Handler (pin 3)
}
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Fire Mode Interrupt Process:::::::::::::::::::::::::::::::::::::::::::::::::::
void toggle() 
{
  if (bounceMode==LOW){
    bounceMode=HIGH;
    Serial.println("Bounce Mode = Single Shot");
  }
  else{
    bounceMode=LOW;
    Serial.println("Bounce Mode = Continuous");
  }
  delay(1000);
}
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Start initial loop (detecting start button press):::::::::::::::::::::::::::::
void loop() {  
  startRead = digitalRead(startPin);
  if (startRead==HIGH){
    Serial.println("Vacuum = On");
    digitalWrite(vacuumPin, HIGH);        // Turns Vacuum on
    wait4fire();                          // This line to next function
  }
  else {
    startRead = LOW;
  }
}
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Function to wait for fire button Press::::::::::::::::::::::::::::::::::::::::
void wait4fire(){
  fireRead = digitalRead(firePin);
  if (fireRead==HIGH){
    Serial.println("Ball Fire Detected");
    if (bounceMode==LOW){
      continuous();                              // This line to next function
      Serial.println("Continuous Mode Enabled");
    }
    else{
      singleshot();                              // This line to next function
      Serial.println("Single Shot Mode Enabled");
    }
  }
  else{
    fireRead==LOW;
  }
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Function to perform continous bouncing:::::::::::::::::::::::::::::::::::::::
void continuous() {
  digitalWrite(vacuumPin, LOW);
  digitalWrite(actuatorPin, HIGH);
  delay(100);                       // Wait until actuator is at full extension
  digitalWrite(actuatorPin, LOW);
  delay(200);
  digitalWrite(vacuumPin, HIGH);
  delay(1000);                  // Look into this line if you want speed change
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Function to perform single shot bounce:::::::::::::::::::::::::::::::::::::::
void singleshot() {
  digitalWrite(vacuumPin, LOW);
  digitalWrite(actuatorPin, HIGH);
  delay(100);                       // Wait until actuator is at full extension
  digitalWrite(actuatorPin, LOW);
  delay(200);
  digitalWrite(vacuumPin, HIGH);
  wait4fire();
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// Interrupt Stop Function::::::::::::::::::::::::::::::::::::::::::::::::::::::
void STOP(){
  digitalWrite(vacuumPin, LOW);
  digitalWrite(vacuumPin, LOW);
  loop();
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

I have not yet hooked up the actuator and vacuum components to the arduino yet as we are waiting on some 24V relays, so I was hoping I could monitor the program through the serial input, However, after connecting up the wires via a breadboard, the serial monitor doesn't report anything back when the buttons are pressed.

I was hoping for a second set of eyes on my code to see if there were any glaring mistakes or anything missing?

The buttons are on a large pendant controller which I think is debounced. There is a primary earth signal from the controller, and each of the switches require a live input, which is then earthed upon a button press.

Hope you can help!

Bump...

A bit of progress today, the start and fire buttons are working, but neither of the interrupts (stop and toggle) work.

Is there a problem in how I've initiated the interrupts?

You cannot use Serial.print inside an ISR because it uses interrupts, nor should you call loop() as the recursion will quickly kill the program.

The best way to use ISRs is to have them set a flag indicating that the interrupt has occurred and then deal with the flag in the main program.

Thanks for the reply, I'll try modify the ISRs as explained and work the code a bit differently and paste the results here.

Thanks again for your help :slight_smile:

If you write it without using "delay()", you can probably get rid of the interrupts too.

AWOL:
If you write it without using "delay()", you can probably get rid of the interrupts too.

How would you go about doing this? I thought interrupts would be the only way to recognise the button presses from anywhere in the program.

Interrupts are one way, but if the processor isn't wasting all its time in "delay()" (have a look at the blink without delay example for clues), then it has plenty of time to monitor slow events, like buttons being pressed or switches being thrown.

Heimdallofasgard:

AWOL:
If you write it without using "delay()", you can probably get rid of the interrupts too.

How would you go about doing this? I thought interrupts would be the only way to recognise the button presses from anywhere in the program.

The basic techniques are covered in the blink without delay tutorial (http://arduino.cc/en/Tutorial/BlinkWithoutDelay). However, I tend to think that tutorial doesn't go far enough to help people asking these types of questions. What you want is a state machine, that says what state each of the things you are controlling are, and when to go to the next state. Every time through loop, you check whether it is time to go to the next state. If not, do nothing. If it is do the action, advance the state, and set the time to go to the next action N milliseconds from now.

Thanks for all the help guys, I've taken out most of the delay functions, just have to test them now.

One thing I was wondering though... once the stop button is pressed, it still calls an interrupt, but I want that interrupt to reset the program completely, is that possible?

Heimdallofasgard:
Thanks for all the help guys, I've taken out most of the delay functions, just have to test them now.

One thing I was wondering though... once the stop button is pressed, it still calls an interrupt, but I want that interrupt to reset the program completely, is that possible?

The simplest thing is just to have stop reset all of the state variables (or reset the state variables after shutting things down gracefully).

You could always wire the stop to the reset button, but you probably want to stop things more gracefully and not have to wait for the bootloader code to timeout waiting for new programs.

Stopping things "gracefully" would be preferred!

I can make the stop interrupt reset all the state variables, but after that, I just want it to go back to the main loop() function and wait there. But I can't call loop() directly (as someone said above)

If you implement a state machine then the first state would be waiting for the Start button to be pressed. If, when you press the Stop button, you set the state variable to the first state then it will effectively make the program go back to the start.

The first thing to do is to decide how many states you need, the actions to be taken whilst in each state, what the conditions are to move to another state and what that next state will be.

 // Initialise Pins and Variables:::::::::::::::::::::::::::::::::::::::::::::::::

const int actuatorPin = 4;

const int vacuumPin = 5;

const int firePin = 6;

const int resetPin = 7;

const int startPin = 8;

const int ledPin = 9;

int STOPFLAG = LOW;

int toggleFlag = HIGH;

int bounceMode = LOW;                 // 1 = singlefire, 0 = multifire

int startRead = HIGH;

int fireRead = HIGH;

long previousMillis = 0;

long interval = 1000;

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

 

// Begin Program Setup:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void setup()

{

  Serial.begin(9600); // check the baud rate for the Pi

  digitalWrite(actuatorPin, LOW);

  digitalWrite(vacuumPin, LOW);

  digitalWrite(startPin, HIGH);

  digitalWrite(firePin, HIGH);

  digitalWrite(resetPin, HIGH);

  digitalWrite(ledPin, HIGH);

  attachInterrupt (0, toggle, RISING);    // Modechange Interrupt Handler (pin 2)

 attachInterrupt (1, STOP, RISING);          // STOP Interrupt Handler (pin 3)

}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Fire Mode Interrupt Process:::::::::::::::::::::::::::::::::::::::::::::::::::

void toggle()

{

digitalWrite(toggleFlag, LOW);

}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Start initial loop (detecting start button press):::::::::::::::::::::::::::::

void loop() { 

  startRead = digitalRead(startPin);

  if (startRead==LOW){

    digitalWrite(vacuumPin, HIGH);        // Turns Vacuum on

    Serial.println("Vacuum = On");

    wait4fire();                          // This line to next function

  }

  else {

    startRead = HIGH;

  }

}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Function to wait for fire button Press::::::::::::::::::::::::::::::::::::::::

void wait4fire(){

  if (toggleFlag==LOW){

    if (bounceMode==LOW){

      bounceMode = HIGH;

      Serial.println("Bouncemode = Single Shot");

    }

    else{

      bounceMode = LOW;

      Serial.println("Bouncemode = Continuous");

    }

  }

  else{

    toggleFlag=HIGH;

  }

 

  fireRead = digitalRead(firePin);

  if (fireRead==LOW){

    Serial.println("Ball Fire Detected");

    previousMillis = millis();

    if (bounceMode==LOW){

      Serial.println("Continuous Mode Enabled");

      continuous();                              // This line to next function

    }

    else{

      Serial.println("Single Shot Mode Enabled");

      singleshot();                              // This line to next function

    }

  }

  else{

    fireRead==HIGH;

    wait4fire();

  }

}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Function to perform continous bouncing:::::::::::::::::::::::::::::::::::::::

void continuous() {

  digitalWrite(vacuumPin, LOW);

  digitalWrite(actuatorPin, HIGH);

  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > interval) {

    previousMillis = currentMillis;

  digitalWrite(actuatorPin, LOW);

  digitalWrite(vacuumPin, HIGH);

  }

  else {

  vacuumPin==LOW;

  actuatorPin==HIGH;

  }

  Serial.println("Bounce");

  continuous();

}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Function to perform single shot bounce:::::::::::::::::::::::::::::::::::::::

void singleshot() {

  digitalWrite(vacuumPin, LOW);

  digitalWrite(actuatorPin, HIGH);

  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > interval) {

    previousMillis = currentMillis;

  digitalWrite(actuatorPin, LOW);

  digitalWrite(vacuumPin, HIGH);

  }

  else {

  vacuumPin==LOW;

  actuatorPin==HIGH;

  wait4fire();

}

}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 

// Interrupt Stop Function::::::::::::::::::::::::::::::::::::::::::::::::::::::

void STOP(){

  digitalWrite(vacuumPin, LOW);

  digitalWrite(actuatorPin, LOW);

}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Here's the current code for completeness

Would a state machine work to read the stop and toggle inputs though? I'm trying to write one at the moment but can't really figure it out.

I can't see why not. Your program basically has 2 states

state 0 - waiting for the start or toggle buttons with the vacuum off
state 1 - start button has been pressed. Ball is in the cups waiting for fire, toggle or stop

When in state 0
Pressing start moves to state 1 and turns on the vacuum.
Pressing toggle changes the mode flag but stays in state 0 leaving the vacuum off

When in state 1
Pressing fire disengages the vacuum and fires the ball. If the mode is continuous the vacuum is turned on after firing the ball and the program stays in state 1. If the mode is single the vacuum is turned off after firing the ball and the program moves to state 0. Pressing toggle changes the mode flag and stays in state 1 with the vacuum on.
Pressing stop turns off the vacuum and moves to state 0

To do this you need to eliminate the use of all blocking code such as delay() so that key presses can be responded to in either state.