Using Millis to Execute Multiple Servo Sweeps

Hi all,

I'm trying to make a circuit which will make a dogs eyes light up in red, play a barking noise and make the jaw move up and down.

I have a working code for the first two items but I am struggling to do a code for a servo to move up and down multiple times. It's easy to get it to move once, but to go up and down x 4 without using a delay and messing up the rest of the code I am getting stuck on.

I've tried various methods and cannot yet seem to get something that works well so some advice would be very much appreciated.

Code is here:

//libraries and pin declarations

#include <SoftwareSerial.h>
#include <Servo.h>
#include <MD_YX5300.h>
SoftwareSerial mySerial(2,3);
MD_YX5300 mp3(mySerial);
#define LEDLeft 10
#define LEDRight 11
#define PIR 13
#define up 0
#define down 180

//servo declaration

Servo servo;

//values and variables to time the left LED

bool LEDLeftBool = false;
bool LEDLeftOn = false;
unsigned long LEDLeftPressTime = 0;
const int LEDLeftOnTime = 3000;

//values and variables to time the right LED

bool LEDRightBool = false;
bool LEDRightOn = false;
unsigned long LEDRightPressTime = 0;
const int LEDRightOnTime = 8000;

//values and variables to time the MP3 player

bool MP3Bool = false;
bool MP3On = false;
unsigned long MP3PressTime = 0;
const int MP3OnTime = 5000;

//values and variables to time the servo

  bool servoBool = false;
  bool servoOn = false;
  unsigned long servoPressTime = 0;
  const int servoOnTime = 1000;
     
void setup() {
  
  pinMode(LEDLeft, OUTPUT);
  pinMode(LEDRight, OUTPUT);
  pinMode(PIR, INPUT);
  mySerial.begin(9600);
  mp3.begin();
  mp3.setSynchronous(true);
  mp3.volume(30);
  servo.attach(6);
}
void loop() {

//left eye LED code
  
if (digitalRead(PIR) == HIGH && LEDLeftOn == false) {
  LEDLeftPressTime = millis();
  LEDLeftOn = true;
  LEDLeftBool = true;
  digitalWrite(LEDLeft, HIGH);
}

//Right eye LED code

 else if (digitalRead(PIR) == HIGH && LEDRightOn == false) {
  LEDRightPressTime = millis();
  LEDRightOn = true;
  LEDRightBool = true;
  digitalWrite(LEDRight, HIGH);
 }

 //MP3 Code
 
  else if (digitalRead(PIR) == HIGH && MP3On == false) {
  MP3PressTime = millis();
  MP3On = true;
  MP3Bool = true;
  mp3.volume(30);
  mp3.playTrack(2);
  }

//Servo Code
  
    else if (digitalRead(PIR) == HIGH && servoOn == false) {
  servoPressTime = millis();
  servoOn = true;
  servoBool = true;
    }
  //NEED A SERVO FUNCTION HERE WHICH GOES FROM 90 to 120 3 or 4 times


//if statements to end the various items
  
if (LEDLeftOn == true && millis() - LEDLeftPressTime >= LEDLeftOnTime) {
  digitalWrite(LEDLeft, LOW);
  LEDLeftOn = false;
}
 else if (LEDRightOn == true && millis() - LEDRightPressTime >= LEDRightOnTime) {
  digitalWrite(LEDRight, LOW);
  LEDRightOn = false;
 }
 else if (MP3On == true && millis() - MP3PressTime >= MP3OnTime) {
  mp3.volume(0);
  MP3On = false;
 }
  else if (servoOn == true && millis() - servoPressTime >= servoOnTime) {
  servo.write(90); //This part of the servo code can revert back to a standard position
  servoOn = false;
  }}

Many thanks
Ben

Use a counter and a 'switch' statement to perform different operations at each interval:

  if (servoOn && millis() - servoPressTime >= servoOnTime)
  {
    // Time to move the servo
    switch (ServoMoveCount)
    {
      case 0:
      case 2:
      case 4:
      case 6:
        servo.write(up);
        ServoMoveCount++;
        break;

      case 1:
      case 3:
      case 5:
      case 7:
        servo.write(down);
        ServoMoveCount++;
        break;

      default:
        ServoMoveCount = 0;
        servoOn = false;
        break;
    }
  }

Note: I would simplify the code by only checking the PIR sensor once:

  // Detect motion
  if (digitalRead(PIR) == HIGH)
  {
    //left eye LED code
    if (!LEDLeftOn)
    {
      LEDLeftOn = true;
      digitalWrite(LEDLeft, HIGH);
      LEDLeftPressTime = millis();
    }

    //Right eye LED code
    if (!LEDRightOn)
    {
      LEDRightOn = true;
      digitalWrite(LEDRight, HIGH);
      LEDRightPressTime = millis();
    }

    //MP3 Code

    if (!MP3On)
    {
      MP3PressTime = millis();
      MP3On = true;
      mp3.volume(30);
      mp3.playTrack(2);
    }

    //Servo Code
    if (!servoOn)
    {
      servoPressTime = millis();
      servoOn = true;
    }

  } // End of motion sensed

Note: The "xxxBool" variables don't seem to be used for anything so you can remove them.

OK thanks for the advice John I'll give it a go and see if I can get it working.

I'm guessing I'll need to define each case for example:

case 1 = 90:
case 2 = 120:

Thanks for the other two tips too, will help me keep it concise.

Thanks
Ben

I am not sure how to interpret your guess ... :wink:

Usually one would make up an array of enums and use them in @johnwasser's state machine somehow like this:

#include <Servo.h>

enum {
       WAIT,
       GO00,
       GO90,
       GO120,
       SWEEP4
} servo_states;

Servo aServo;  
byte state = WAIT;
byte SweepCount = 0;
int ServoPos = 0;
unsigned long servoMoveTime = 0;
const int servoOnTime = 10;
boolean servoOn = false;

struct Button_type {
      byte Pin;
      unsigned long lastButtonUsedTime = 0;
      byte lastButtonState = HIGH;
      boolean wasLOW = false;      
};

const byte NoOfButtons = 2;
Button_type Button[NoOfButtons];

int pos = 0;    

void setup() {
  Button[0].Pin = 4;
  Button[1].Pin = 5;
  for (int i = 0; i<NoOfButtons;i++) pinMode(Button[i].Pin, INPUT_PULLUP);

  aServo.attach(6);
  aServo.write(ServoPos);
  delay(1000);
}  

void loop() {
   HandleButtons();
   HandleServo();
}

void HandleButtons(){
   if (ButtonReleased(0)) {        
     servoOn = true;
     SweepCount = 0;
     if (ServoPos > 0) state = GO00;
                  else state = GO120;
  }
   if (ButtonReleased(1)) {          
     servoOn = true;
     SweepCount = 4;
     if (ServoPos > 0) {SweepCount++; state = GO00;}  // if you are not at Pos 0, go there first
                  else state = GO90;
  }

}

void HandleServo(){
 if (servoOn && millis() - servoMoveTime >= servoOnTime) {
    servoMoveTime = millis();
    // Time to move the servo
    switch (state) {
      case GO00:
          if (ServoPos <= 0) {
            if (SweepCount > 0) SweepCount--;
            if (SweepCount > 0) state = GO90;
                           else state = WAIT;
          }
          ServoPos--;
          aServo.write(ServoPos);
          break;
      case GO90:
          if (ServoPos >= 90) {
            if (SweepCount > 0) state = GO00;
                           else state = WAIT;
          }
          ServoPos++;
          aServo.write(ServoPos);
          break;
      case GO120:
          if (ServoPos >= 120) state = WAIT;
          ServoPos++;
          aServo.write(ServoPos);
          break;
      case SWEEP4:  
           break;
      case WAIT:  
      default:
        servoOn = false;
        state = WAIT;
        break;
    }
  }
}

// Check if Button is released and debounced
boolean ButtonReleased(byte No){
  int ButtonState = digitalRead(Button[No].Pin);          
  if (ButtonState != Button[No].lastButtonState) {
    Button[No].lastButtonUsedTime = millis();
    Button[No].lastButtonState = ButtonState;
    Button[No].wasLOW = (ButtonState == LOW);
  }
  if (millis()-Button[No].lastButtonUsedTime > 50 && Button[No].wasLOW) {  
     Button[No].wasLOW = false;                                 
     return true;                                    
  } else {                                           
     return false;                                   
  };   
}

If you like to see this simulated feel free to go to

https://wokwi.com/projects/326024025289523794

image

  • Start simulation
  • Press right button -> Goes from 0 to 120
  • Press right button again -> Goes from 120 to 0
  • Press left button when Servo at Pos 0 -> Sweeps four times between 0 and 90
  • Press left button when Servo at Pos 120 -> Goes to 0 and then sweeps four times between 0 and 90

It is just an example to show how to use a state machine and enums: Let the compiler make sure that each enum has its separate value. This way you can add or insert enums like needed without taking care of the values in your code.

No. That is not how 'case' labels works. They are correct as I wrote them. Cases 0, 2, 4, and 6 all do the same thing. Cases 1, 3, 5, and 7 all do the same thing.

Hi Ben,

it will become much easier to understand your code if you add a verbal description of the wanted code-behaviour.

I write my interpretation of what you have posted:

if a PIR-senor detects an object = PIR-senor-output goes high do three things:

1: switch on left LED (left eye of the dog)
2: switch on right LED (right eye of the dog)
3: start to playback the barking sound

after that
switch of left LED after a certain time
switch of right LED after a certain time (which can be different from the ontime of the left LED

stop playback after a certain time (which can be different from the ontime of the left / right LED )

in parallel to playing back the MP3-barking sound sweep a servo 4 times from 90 to 120 degrees and back 90-120--90-120--90-120--90-120

As you want to do things in parallel you need non-blocking timing.
You have a certain sequence of things that shall happen. This can be solved by using a state-machine as already suggested.

What is not yet explained is the basic behaviour of a state-machine.
a state-machine is code that does quickly jump-in - jump-out of a function.
The execution-time of the function is always short.
jump-in - do a small step - jump-out
This quickly jumping in/out enables to do mutliple things "in parallel".

The second important thing about state-machines is:
that always only a part of the code gets executed.
This code-execution is mutually exclusive. It is either part "A" or part"B" or part "C" that gets executed per one call of the function.
This realises the quickly jump-in / jump-out
ALL repeating and ALL looping is done by loop().

If the description above is correct I would divide your code into three functional parts

  1. switch_on_off_LEDS
  2. playBarking
  3. sweepServo
void loop() {
  switch_on_off_LEDS(); // quickly jump in/out to enable execution of the line below
  playBarking();  // quickly jump in/out to enable execution of the line below
  sweepServo(); // quickly jump in/out to enable looping through loop()
}

all three functions will work in a quickly jump_in / jump_out manner
working through its own small state-machine

best regards Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.