Multitasking with functions?

Hello!

I have worked through the BlinkWithoutDelay, and even modified it to get two LEDs blinking alternately, without using delay. However, I've been staring and thinking and can't figure out how to use the same idea from BlinkWithoutDelay to work for my current project.

I have an IR Remote, red laser, and a servo. Here's my idea - when I press 1, 2, or 3 on the remote, the laser will turn on and be solid, or blink. Pressing 4 will start the Servo turning back and forth, and then pressing 5 will stop the servo. I would like it so that I can have the servo go back and forth independently of what the laser is doing (so I can have the servo rotate with the laser solid or blinking).

The code below works, almost. It can turn the laser on, blink the laser, and rotate the servo. However, here's the problem I hit: I turn the laser on (solid or blinking), but then when I start the Servo (by pressing '4' on my remote), the laser turns off and the servo starts turning. Also the reverse happens, if I have the laser off, and then start the Servo, if I try and have the laser blink, the delay (I am assuming) in the code only turns the laser on and off ONCE before rotating the servo again.

I believe the solution would be to use the BlinkWithoutDelay idea (using millis instead of delay) to have the servo and laser operate independently. OR maybe I just need to "re-layer" or "re-distribute" the code so that I can have them both running? Anyways, sorry for the long-winded explanation, here's the code!

#include <IRremote.h>
#include <Servo.h>

const int LEDpin = 4; // set up LED Pin [not used below, was earlier to confirm upload was working]
const int laserPin = 12;

// IR SENSOR SETUP ------------
int IRpin = 5;
IRrecv irrecv(IRpin); // create instance of 'irrecv'
decode_results results; //create instance of 'decode_results'
int remoteBtn = 0; // set variable to determine which button on remote was pressed
// -------------------------

// SERVO SETUP -------
Servo myservo; // create servo object to control a servo
int pos = 0;   // variable to store the servo position
int servoSweepvar = 0;  // variable to determine if the servo should be sweeping or not.
// -------------------------

long previousMillis = 0;        // will store last time laser was updated

void setup() {
  Serial.begin(9600);
  myservo.attach(9);    //attached the servo on pin 9 to the servo object
  pinMode(LEDpin, OUTPUT);
  pinMode(laserPin, OUTPUT);
  irrecv.enableIRIn();  // start the receiver
}

void loop() {
  unsigned long currentMillis = millis();  // THIS is as far as I could figure out the 'delay' fix, but don't know where to put the
                                                                        // currentMillis - previousMillis > interval stuff, I assume it'd be in my functions, not
                                                                        // here?

  if (irrecv.decode(&results)) {    // have we rec'd an IR signal?
  // Serial.println(results.value, HEX);     // this will show the RAW value of remote press
  translateIR();
  irrecv.resume();
  }

  remoteEvents();
}

// ----- FUNCTIONS BELOW

void translateIR(){
  switch(results.value) {
    case 0xFF6897: Serial.println(" 1"); remoteBtn = 1; break;
    case 0xFF9867: Serial.println(" 2"); remoteBtn = 2; break;
    case 0xFFB04F: Serial.println(" 3");    remoteBtn = 3;     break;
    case 0xFF30CF: Serial.println(" 4");    remoteBtn = 4; servoSweepvar = 1;    break;
    case 0xFF18E7: Serial.println(" 5");    remoteBtn = 5; servoSweepvar = 0;    break;
    case 0xFF7A85: Serial.println(" 6");    remoteBtn = 6;     break;
    case 0xFF10EF: Serial.println(" 7");    remoteBtn = 7;     break;
    case 0xFF38C7: Serial.println(" 8");    remoteBtn = 8;     break;
    case 0xFF5AA5: Serial.println(" 9");    remoteBtn = 9;     break;
    case 0xFF42BD: Serial.println(" *");    remoteBtn = 10;     break;
    case 0xFF4AB5: Serial.println(" 0");    remoteBtn = 0;     break;
    case 0xFF52AD: Serial.println(" #");    remoteBtn = 11;     break;
    default: Serial.println(" other button     ");
  } // end Case
  delay(500); // do not get immediate repeat
} //end translateIR

void remoteEvents(){      // Start the remote Events for the laser
  if (servoSweepvar == 1) {
    servoSweepStart();
  } else if (servoSweepvar == 0) {
    servoSweepStop();
  }
  switch (remoteBtn) {
      case 1:
        steadyLaser();
        break;
      case 2:
        blinkingLaser();
        break;
      case 3:
        blinkingfasterLaser();
        break;
      case 0:
        offLaser();
        break;
      // default:
      //   offLaser(); 
      //   break;
  }
}


void remoteEventsServo(){
  switch(servoSweepvar){
    case 0:
      servoSweepStop();
      break;
    case 1:
      servoSweepStart();
      break;
  }
}


void steadyLaser(){
  digitalWrite(laserPin, HIGH);
}

void blinkingLaser(){
  digitalWrite(laserPin, HIGH);
  delay(450);
  digitalWrite(laserPin, LOW);
  delay(450);
}

void blinkingfasterLaser(){
  digitalWrite(laserPin, HIGH);
  delay(250);
  digitalWrite(laserPin, LOW);
  delay(250);
}

void offLaser(){
  digitalWrite(laserPin, LOW);
}


void servoSweepStart(){
  servoSweepvar = 1;                   // This sets servo sweep to '1' for the looping
  for(pos = 5; pos < 175; pos +=1) {   // goes from 5 degrees to 175 degrees in steps of 1 degree
    myservo.write(pos);                // tell servo to go to position in variable 'pos'
    delay(15);                         // wait 15ms for the servo to reach the position
  }
  for (pos = 175; pos >= 6; pos -=1) { // goes from 175 degrees to 6 degrees
    myservo.write(pos);
    delay(15);
  }
}

void servoSweepStop(){
  servoSweepvar = 0;
  myservo.write(pos);   // return servo to 5 degrees 
}

If there's an example online of what I'm trying to do, please let me know what I should look up and I can research it from there. I may have too many "functions within functions" or something?

Thanks for any help or ideas.

Hi- I don't have time right now to look at that in detail, but you will probably benefit from looking at this video. It expands the Blink Without Delay approach to do two things in concert: he uses two LEDs but I'm pretty sure you could adapt it to be your LED and servo.

Also it would help you (I think) to start thinking about state machines, mentioned in the video, and so why not have a look at this thread where I implement a turnstile. The idea with a finite stste machine is that the system is always in a known state, and from that state known and defined events cause transitions to other legal states.

HTH...

The laser turns off because the delay-driven blink code finishes with the laser off and while the delay-filled sweep code runs, the blink code does not. That sketch spends most of its time in delays, I'm surprised that you can get a remote signal to it unless the remote receiver runs by itself or something.

You may be able to run BWD (Blink Without Delay) and even stretch it to run 2 leds but you didn't get the point of BWD at all.

Here is a much better, more complete yet beginner level explanation of what BWD only shows:

How to do multiple things at once ... like cook bacon and eggs

If actually reading that for understanding, working through and running the code to verify your understanding doesn't give you a different view then bring up your BWD and be prepared to use the terms that Nick does in that blog to learn more about it and how that can apply to your current project. If it becomes clear that you skipped through Nick's blog then I can't help you.

My extended demo of BWoD in the first post in the Thread several things at a time may help to illustrate the use of millis() and the use of variable to keep track of the states of things.

...R

Thanks everybody, I'll review y'alls posts/info and come back with questions if I can't figure it out. I appreciate it! :slight_smile:

Hooray! Thank you everyone for your replies, it helped a lot - special thanks to Robin2, I used your sketch and now have my servo sweeping back and forth. I can turn the servo on, by pressing 4, and then turn on a laser (or have it blink)! :grin:

I only have to more questions:

  1. Is this the most efficient way to set up the sketch? It seems to me to be a little verbose (although admittedly, I think I have a few extra variables I don't need).

  2. The Servo will sweep and I can then turn on a laser. However, when I press '5' to turn it off, my lasers stop as well. I assume this is because I'm using case to determine which button I pressed, and in doing so, I 'negate' having turned the laser on with another button. Is there a way to fix this, so I can independently turn the servo on and off without affecting the laser? Would I need to combine a switch, or create a new function with switch to look at 4 and 5 separately? I am having trouble visualizing how I'd do that, so thank you for any help!

Here's my current code:

#include <IRremote.h>
#include <Servo.h>


const int LEDpin = 4; // set up LED Pin [not used below, was earlier to confirm upload was working]
const int laserPin = 12;

// IR SENSOR SETUP ------------
int IRpin = 5;
IRrecv irrecv(IRpin); // create instance of 'irrecv'
decode_results results; //create instance of 'decode_results'
int remoteBtn = 0; // set variable to determine which button on remote was pressed
// -------------------------

// SERVO SETUP -------
Servo myservo; // create servo object to control a servo
int pos = 0;   // variable to store the servo position
int servoSweepvar = 0;  // variable to determine if the servo should be sweeping or not.
const int servoMinDegrees = 10;
const int servoMaxDegrees = 170;
int servoPosition = 90; // starting position of Servo
int servoSlowInterval = 80;
int servoFastInterval = 10;
int servoInterval = servoSlowInterval;
int servoDegrees = 1; // how many degrees to increment the Servo


int laserDelay; // set a delay variable for the laser blink
const int remoteDelay = 500;
const int servoDelay = 15;
// -------------------------

byte laserState = LOW;
byte laserSlowState = LOW;
byte laserSlowerState = LOW;


unsigned long currentMillis = 0;
unsigned long previousSlowLaserMillis = 0;
unsigned long previousSlowerLaserMillis = 0;
unsigned long previousRemoteMillis = 0;
unsigned long previousServoMillis = 0;

void setup() {
  Serial.begin(9600);
  myservo.attach(9);    //attached the servo on pin 9 to the servo object
  pinMode(LEDpin, OUTPUT);
  pinMode(laserPin, OUTPUT);
  // pinMode(PIRpin, INPUT); //I removed the PIR pin
  irrecv.enableIRIn();  // start the receiver
}

void loop() {
  currentMillis = millis();
  
  if (irrecv.decode(&results)) {    // have we rec'd an IR signal?
  // Serial.println(results.value, HEX);     // this will show the RAW value of remote press
  translateIR();
  irrecv.resume();
  }
  remoteEvents();
}

// ----- FUNCTIONS BELOW

void translateIR(){
  if (currentMillis - previousRemoteMillis > remoteDelay){  // do not get immediate repeat remote reading
    previousRemoteMillis = currentMillis;
  switch(results.value) {
    case 0xFF6897: Serial.println(" 1"); remoteBtn = 1; break;
    case 0xFF9867: Serial.println(" 2"); remoteBtn = 2; break;
    case 0xFFB04F: Serial.println(" 3");    remoteBtn = 3;     break;
    case 0xFF30CF: Serial.println(" 4");    remoteBtn = 4; servoSweepvar = 1;    break;
    case 0xFF18E7: Serial.println(" 5");    remoteBtn = 5; servoSweepvar = 0;    break;
    case 0xFF7A85: Serial.println(" 6");    remoteBtn = 6;     break;
    case 0xFF10EF: Serial.println(" 7");    remoteBtn = 7;     break;
    case 0xFF38C7: Serial.println(" 8");    remoteBtn = 8;     break;
    case 0xFF5AA5: Serial.println(" 9");    remoteBtn = 9;     break;
    case 0xFF42BD: Serial.println(" *");    remoteBtn = 10;     break;
    case 0xFF4AB5: Serial.println(" 0");    remoteBtn = 0;     break;
    case 0xFF52AD: Serial.println(" #");    remoteBtn = 11;     break;
    default: Serial.println(" other button     ");
  } // end Case
} //end translateIR
}

void remoteEvents(){      // Start the remote Events for the LASER
  if (servoSweepvar == 1) {
    servoSweep();
  } else { servoSweepStop(); }
  switch (remoteBtn) {
      case 1:
        steadyLaser();
        break;
      case 2:
        laserDelay = 500;
        blinkingLaser();
        break;
      case 3:
        laserDelay = 250;
        blinkingLaser();
        break;
      case 0:
        offLaser();
        break;
      case 4:
        servoSweep();
        break;
      case 5:
        servoSweepStop();
        break;
  }
}

void steadyLaser(){
  if (laserState == LOW){
    laserState = HIGH;
  }
  digitalWrite(laserPin, laserState);
}

void blinkingLaser(){
  if(currentMillis - previousSlowLaserMillis >= laserDelay){
    previousSlowLaserMillis = currentMillis;
 // if the LED is off turn it on and vice-versa:
    if (laserSlowState == LOW) { laserSlowState = HIGH; }
    else { laserSlowState = LOW; }
  digitalWrite(laserPin, laserSlowState);
}
}

void offLaser(){
  digitalWrite(laserPin, LOW);
}

void servoSweep(){
  servoSweepvar = 1;
  if (currentMillis - previousServoMillis >= servoInterval){
    previousServoMillis += servoInterval;
  servoSweepvar = 1;                   // This sets servo sweep to '1' for the looping
  servoPosition = servoPosition + servoDegrees;
  if (servoPosition <= servoMinDegrees) {
    if (servoInterval == servoSlowInterval) {
      servoInterval = servoFastInterval;
    }
    else {
      servoInterval = servoSlowInterval;
    }
  }
  
  if ((servoPosition >= servoMaxDegrees) || (servoPosition <= servoMinDegrees)) {
    servoDegrees = - servoDegrees;
    servoPosition = servoPosition + servoDegrees;
  }
  myservo.write(servoPosition);
  
  }
}

void servoSweepStop(){
  servoSweepvar = 0;
  myservo.write(servoMinDegrees);   // return servo to 5 degrees 
}

Thanks for any ideas and again, thanks for all your help, this is a very educational project for me!

Don't worry about verbose if the code is easy to understand.

You are using SWITCH/CASE to call various functions and as you suspect pressing a different button affects everything.

I would be inclined to include in the servo function the code for deciding if the servo should work and ditto for the laser function. That way each activity can be governed independently. And if the decision code for the servo is complicated I would probably add to the verbosity by giving it a function of its own.

...R

I edited the REMOTE code to add a the variable for the ServoSweep:

  switch(results.value) {
    case 0xFF6897: Serial.println(" 1"); remoteBtn = 1; servoSweepvar = 1;break;
    case 0xFF9867: Serial.println(" 2"); remoteBtn = 2; servoSweepvar = 1;break;
    case 0xFFB04F: Serial.println(" 3");    remoteBtn = 3;servoSweepvar = 1;     break;
    case 0xFF30CF: Serial.println(" 4");    remoteBtn = 4; servoSweepvar = 1;    break;
    case 0xFF18E7: Serial.println(" 5");    remoteBtn = 5; servoSweepvar = 0;    break;
    case 0xFF7A85: Serial.println(" 6");    remoteBtn = 6;     break;

and it works, I can blink/steady the laser and the servo still runs, so it works in that way. ...Now I just need to figure out how to do them separately, since I can turn the servo off, and it starts again with 1,2,3 instead of waiting for me to press 4...

I'll just have a think and troubleshoot a lot, thanks for your help! XD XD