Arduino Multitasking problem

I've done a basic circuit that is consisting of a: servo, button, LED and 16x2 LCD.

Servo is doing a preprogrammed dancing rotine and while arm is doing its swings, I thought to try to pick up input from a push button. So, instead of delay(), I am making use of millis() function.

Thing is that, it works, but not so well. In order to get a code to pick up the push button's press, I need to press the button several times. Can somebody spot in the code what makes it so irresponsive to button's input?

/*
 * 3.7.10 - original code is modified so that:
 *
 *   - millis() is used instead of delay(). That way, precious clock cycles are not wasted, doing nothing.
 *   - a single button, with pull up resistor, and one variable are used to provide software & hardware 
 *     toggle switch,
 *
 *   - 
 */

// Sweep
// by BARRAGAN <http://barraganstudio.com> 

#include <Servo.h> 
#include <LiquidCrystal.h>
 
// initialise SERVO:
const int pinServoLED = 10; // pin for controling circuit mode LED. When green LED is ON, than
                            // servo should be running, when OFF servo should be motionless.
const int pinServo    =  9; // pin for controling servo #1.
const int inputPin    =  8; // pushbutton's Arduino input pin: button pressed, pin goes LOW.

Servo myservo;              // create servo object to control a servo 
                            // a maximum of eight servo objects can be created.
 
int posServo = 0;           // variable to store the servo position.

int modeCircuit = 0;        // circuit toggles from OFF to ON to OFF to ON state. And so on.

// initialize LCD:
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() { 
  
  // SERVO:
  pinMode(pinServoLED, OUTPUT);  // declare LED controling pin.
  digitalWrite(pinServoLED, LOW);
  
  pinMode(inputPin, INPUT);      // declare pushbutton on inputPin as input.
  
  myservo.attach(pinServo);      // attaches the servo on pinServo to the servo object 
  myservo.write(90);             // set servo to mid-point.
  
  // LCD: set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  
  // LCD: Print an initial message to the LCD.
  lcd.print("MODE= "); lcd.print(modeCircuit);
} 


void loop() { 
  
  unsigned long tNow;
  
  /*
  for(posServo = 0; posServo < 180; posServo += 20) // goes from 0 degrees to 180 degrees 
  {                                                 // in steps of 1 degree 
    myservo.write(posServo);                        // tell servo to go to position in variable 'posServo' 
    delay(500);                                     // waits 15ms for the servo to reach the position 
  } 
  
  for(posServo = 180; posServo>=1; posServo-=20)    // goes from 180 degrees to 0 degrees 
  {                                
    myservo.write(posServo);                        // tell servo to go to position in variable 'posServo' 
    delay(500);                                     // waits 15ms for the servo to reach the position 
  } 
  */

  // 3.7.10 - This is sort of dancing alghoritam, just to understand it properly.
  myservo.write(0);   timeGap(millis(), 375UL);
  myservo.write(90);  timeGap(millis(), 750UL);
  myservo.write(80);  timeGap(millis(), 350UL);
  myservo.write(90);  timeGap(millis(), 350UL);
  myservo.write(100); timeGap(millis(), 350UL);
  myservo.write(180); timeGap(millis(), 375UL); 
  myservo.write(90);  timeGap(millis(), 750UL);
  myservo.write(100); timeGap(millis(), 350UL);
  myservo.write(90);  timeGap(millis(), 350UL);
  myservo.write(80);  timeGap(millis(), 350UL);
  myservo.write(0);
} 


/*
 * checkCircuitMode() function takes care of the software switch variable modeCircuit that
 * regulates a behaviour of the circuit.
 */
void checkCircuitMode() {
  
  int intButtonState = digitalRead(inputPin);   // Check the inputPint, to see weather button was pressed.

  if ( intButtonState == LOW ) {                // When button is pressed, inputPin will go to LOW.

    if (modeCircuit == 0) {
      
      modeCircuit = 1; digitalWrite(pinServoLED, HIGH);  
    } else {
      
      modeCircuit = 0; digitalWrite(pinServoLED, LOW);
    }    
    
    // LCD: tell the circuit mode to everybody.
    lcd.clear(); lcd.print("MODE= "); lcd.print(modeCircuit);
  }

  return;
}


/*
 * timeGap(unsigned long  tOld, unsigned long tGap);
 *
 *   tOld - time gap's start time  [miliseconds],
 *   tGap - length of the time gap [miliseconds],
 *
 * Using delay() function is not very productive. Because of it, a whole microprocessor
 * gets halted, doing nothing, receiving no inputs, unitl the specified delay passes. It
 * lays to waste usefull microprocessor time, during which something can be done.
 *
 */
void timeGap(unsigned long  tOld, unsigned long tGap) {
  
  unsigned long tCurrent;
  
  // During the time gap, Arduino can do few usefull things:
  do {
    
    // Here, from this line to the end of the loop, you just put things you want to be
    // done doring the gap time. Much better than delay(), since something is done.
    
    checkCircuitMode();        // Check weather circuit mode change button was pressed.
    
    tCurrent = millis();
    
  } while ((tCurrent - tOld) < tGap);
  
  tOld = tCurrent;
  
  return;
}

Here is the pretty picture, as well:

Could it be a contact bounce problem? Where the button sometimes reports an even number of contact bounces, keeping the modecircuit variable the same thing, as far as your eye can tell?

I've had this problem with things I've made, but I just used the bounce library because I was too lazy to write my own code to do software debouncing.
[edit]Oh wait, nevermind. That is not the main problem. The main problem is that for however long the button is pressed, the code will keep toggling the mode variable. So it relies on chance for you to release the button at the right time.[/edit]

hi, thanks for the reply,

What is bouncing / debouncing?

Ignore that for now. Check out my edit to that post.

The bounce library can also be used to solve both problems I mentioned.
You could also check out the state change detection tutorial.
And contact bounce is when the contacts inside the switch chatter and bounce a bit before completely closing or opening.

phenomenal, you are right 100%.

I am coming from 'static' programming background. I've never had done this type of real time programming and I always forget to take a event timing properly into account.

Glad I could help.
:slight_smile:

Here is revised and much improved code. Code supports multitasking (sort off), has a button that changes servo's mode of operation and LED that shows which mode is on. It is just a good educational excercise, not a solution to a problem.

/*
 * 3.7.10 - original code is modified so that:
 *
 *   - millis() is used instead of delay(). That way, precious clock cycles are not wasted 
 *     doing nothing. 
 * 
 *     Change from using delay() to millis() required more complex code, but made that up with
 *     ten times more responsiveness code.
 *
 *   - a single button, with pull up resistor, and one variable are used to provide software & 
 *     hardware toggle switch,
 *
 *   - 
 */

// Sweep
// by BARRAGAN <http://barraganstudio.com> 

#include <Servo.h> 
#include <LiquidCrystal.h>
 
// Initialise SERVO:
const int pinServoLED = 10; // Pin for controling circuit mode LED. When green LED is ON, than
                            // servo should be running, when OFF servo should be motionless.
const int pinServo    =  9; // Pin for controling servo #1.

Servo myservo;              // Create servo object to control a servo a maximum of eight
                            // servo objects can be created.
 
int posServo = 0;           // Variable to store the servo position.

int modeCircuit = 0;        // Circuit toggles from OFF to ON to OFF to ON state. And so on.

// Initialize PUSH-BUTTON:
const int inputPin          =  8; // Circuit Mode Change Button's Arduino input pin: button 
                                  // pressed, pin goes LOW.
const int delayBtnDebounce  =  5; // Amount of time, in milliseconds, waiting for button to stop
                                  // bouncing and creating false signals on button input pin.
int stateCircuitModeButton_0;     // Code needs to know the previous state of the button.

// Initialize LCD:
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);


void setup() { 
  
  // SERVO:  
  myservo.attach(pinServo);        // attaches the servo on pinServo to the servo object 
  myservo.write(90);               // set servo to mid-point.
  
  // BUTTON:
  pinMode(inputPin, INPUT);        // declare pushbutton on inputPin as input.
  stateCircuitModeButton_0 = HIGH;

  // LED:
  pinMode(pinServoLED, OUTPUT);    // declare LED controling pin.
  digitalWrite(pinServoLED, LOW);  // initally green LED should be OFF.

  // LCD:
  lcd.begin(16, 2);                // set up the LCD's number of columns and rows.
  lcd.print("MODE= "); lcd.print(modeCircuit); // print initial message to the LCD.
} 


void loop() { 
  
  unsigned long tNow;
  
  /*
  for(posServo = 0; posServo < 180; posServo += 1) // goes from 0 degrees to 180 degrees 
  {                                                // in steps of 1 degree 
    myservo.write(posServo);                       // tell servo to go to position in variable 'posServo' 
    delay(15);                                     // waits 15ms for the servo to reach the position 
  } 
  
  for(posServo = 180; posServo>=1; posServo-=1)    // goes from 180 degrees to 0 degrees 
  {                                
    myservo.write(posServo);                       // tell servo to go to position in variable 'posServo' 
    delay(15);                                     // waits 15ms for the servo to reach the position 
  } 
  */

  // This is sort of servo dancing alghoritam, just to understand properly how servo works.
  
  switch (modeCircuit) {
    
    // Big step wiggle:
    case 0:
    
      lcd.clear(); lcd.print("BIG wiggle");
      
      myservo.write(0);   timeGap(millis(), 375UL);
      myservo.write(90);  timeGap(millis(), 750UL);
      myservo.write(180); timeGap(millis(), 375UL); 
      myservo.write(90);  timeGap(millis(), 750UL);
      myservo.write(0);
      
      break;
    
    // Small step wiggle:
    case 1:
    
      lcd.clear(); lcd.print("SMALL wiggle");
      
      myservo.write(90);  timeGap(millis(), 350UL);
      myservo.write(80);  timeGap(millis(), 350UL);
      myservo.write(90);  timeGap(millis(), 350UL);
      myservo.write(100); timeGap(millis(), 350UL); 
      myservo.write(90);  timeGap(millis(), 700UL);
      myservo.write(80);  timeGap(millis(), 700UL);
      myservo.write(90);  timeGap(millis(), 700UL);
      myservo.write(100); timeGap(millis(), 700UL);
      myservo.write(90);
      
      break;
  }
  
}   // void loop()


/*
 * checkCircuitModeButton() 
 *
 * Here we takes care of the software toggle switch variable modeCircuit that regulates a behaviour
 * of the circuit.
 */
void checkCircuitModeButton() {

  // Wait for the button to stop bouncing and causing random signals.
  ///delay(delayBtnDebounce);                              
    
  // Check the inputPint, to see weather button was pressed.
  int stateCircuitModeButton_1 = digitalRead(inputPin); 

  // When button is pressed, inputPin will go to LOW.
  if ( stateCircuitModeButton_1 == LOW && stateCircuitModeButton_0 == HIGH ) {

    // This will prevent repeated circuit mode toggle if button is pressed for 
    // a longer period.
    stateCircuitModeButton_0 = LOW;
    
    // Toggle circuit mode once.
    if (modeCircuit == 0) {
      
      modeCircuit = 1; digitalWrite(pinServoLED, HIGH);  
    } else {
      
      modeCircuit = 0; digitalWrite(pinServoLED, LOW);
    }    

    // LCD: tell the world about servo step mode change.   
    lcd.setCursor(0, 1); 
    lcd.print("new mode= "); lcd.print(modeCircuit);
  }
    
  // If Circuit Mode button was unpressed, next check will be against 
  // stateCircuitModeButton_0 = HIGH.
  if ( stateCircuitModeButton_1 == HIGH && stateCircuitModeButton_0 == LOW ) {
    
    stateCircuitModeButton_0 = HIGH;
  }
    
  return;
}


/*
 * timeGap(unsigned long  tOld, unsigned long tGap);
 *
 *   tOld - time gap's start time  [miliseconds],
 *   tGap - length of the time gap [miliseconds],
 *
 * Using delay() function is not very productive. Because of it, a whole microprocessor
 * gets stopped, doing nothing, receiving no inputs, unitl the specified delay passes. 
 * delay() function lays to waste usefull microprocessor time, during which something 
 * else can be done.
 *
 * In this example, we will check weather Circuit Mode Change Button was pressed, during
 * the time gap.
 */
void timeGap(unsigned long  tOld, unsigned long tGap) {
  
  unsigned long tCurrent;
  
  // During the time gap, Arduino can do few usefull things:
  do {
    
    // Here, from this line to the end of the loop, you just put things you want to be
    // done doring the gap time. Much better than delay(), since something is done.
    
      checkCircuitModeButton();  // Check weather circuit mode change button was pressed.
    
    tCurrent = millis();
    
  } while ((millis() - tOld) < tGap);

  return;
}