Eyeball Blinking and swiveling in a snowman

Hi all

I’ve got this project code that works great except when it doesn’t. It’s a selection of the “multiple things at once” pinned code from this forum.

Anywho, the code works great, does the thing nicely. However, once I put it onto a PIR sensor, it starts to get all weird.

Once the motion is triggered, it’s like the code has to “catch up” on the number of movements. it ignores all the timing and just does the motion as fast as possible until it catches up, then it does it at the speed it should do it. I’m sure it’s a simple fix that I just can’t wrap my head around.

Thanks!

#include <Servo.h>

Servo servo0; //Blinky
Servo servo1; //Eyeball

const int servo0MinDegrees = 60; // the limits to servo movement
const int servo0MaxDegrees = 145;
const int servo1MinDegrees = 70; // the limits to servo movement
const int servo1MaxDegrees = 110;

int servo0Position = 144;     // the current angle of the servo - starting at open.
int servo0SlowInterval = 100; // millisecs between servo moves
int servo0FastInterval = 50;
int servo0Interval = servo0SlowInterval; // initial millisecs between servo moves
int servo0Degrees = 3;       // amount servo moves at each step 
                            //    will be changed to negative value for movement in the other direction
unsigned long previousServo0Millis = 0;
unsigned long previousBlinkMillis = 0; 
int BlinkyCount = 0; // counts the blinks
int BlinkyInterval = 1000; //2.5 seconds between blinks

int servo1Position = 90;     // the current angle of the servo - starting at 90.
int servo1SlowInterval = 200; // millisecs between servo moves
int servo1FastInterval = 100;
int servo1Interval = servo1SlowInterval; // initial millisecs between servo moves
int servo1Degrees = 1;       // amount servo moves at each step 
                            //    will be changed to negative value for movement in the other direction
unsigned long previousServo1Millis = 0;

unsigned long currentMillis = 0; //sets starting time to zero, updates with each swing through the loop

int pirSensor = 8;
int motion = 0; //variable for reading pin status, 1 is true and 0 is false

void setup() {
  servo0.attach(3);  // attaches the servo on pin 3 to the servo object
  servo1.attach(4);  // attaches the servo on pin 4 to the servo object
  pinMode(pirSensor, INPUT);
  Serial.begin(9600);
 

}


void loop() { //update the time, call the functions.
  currentMillis = millis();
  CheckPIR();
  EyeballBlinky();
  EyeballSwivel();
  EyeballsHome();

}
  
void CheckPIR() {
//  static unsigned long pirTimeCheck = 0;
//  static unsigned long pirInterval = 5000;
  //needs to look for motion, see motion, then wait 5 seconds before checking again
  motion = digitalRead(pirSensor);
  //if (motion == 1) {   
    //Serial.println("I see you");
    //if (currentMillis - pirTimeCheck >= pirInterval) {
    //  pirTimeCheck += pirInterval;
    //  motion = digitalRead(pirSensor);
    //}
}
//}
//servo0
void EyeballBlinky() {
  //check blinky count, if 1 wait 5 seconds
//  if(BlinkyCount == 1) {
//    if (currentMillis - previousBlinkMillis >= BlinkyInterval) 
//    previousBlinkMillis += BlinkyInterval;
//    BlinkyCount = 0; //reset blink count to zero
//  }
  
  if(motion == 1) { //while the sensor is true, do the thing. Count the blinks. )&&(BlinkyCount == 0)
    Serial.println("Blinkin");    
    //does the thing
    if (currentMillis - previousServo0Millis >= servo0Interval) {
        // its time for another move
    previousServo0Millis += servo0Interval;
    servo0Position = servo0Position + servo0Degrees; // servoDegrees might be negative
    if (servo0Position <= servo0MinDegrees) {
          // when the servo gets to its minimum position change the interval to change the speed
       if (servo0Interval == servo0SlowInterval) {
         servo0Interval = servo0FastInterval;
       }
       else {
        servo0Interval = servo0SlowInterval;
       }
    }
    if ((servo0Position >= servo0MaxDegrees) || (servo0Position <= servo0MinDegrees))  {
          // if the servo is at either extreme change the sign of the degrees to make it move the other way
      servo0Degrees = - servo0Degrees; // reverse direction
          // and update the position to ensure it is within range
      servo0Position = servo0Position + servo0Degrees; 
    }
    servo0.write(servo0Position);
        // and record the time when the move happened
}
}
}

//servo1
void EyeballSwivel() {
  if(motion == 1) { //while the sensor is true, do the thing.
     
    //does the thing
    if (currentMillis - previousServo1Millis >= servo1Interval) {
        // its time for another move
    previousServo1Millis += servo1Interval;
    
    servo1Position = servo1Position + servo1Degrees; // servoDegrees might be negative

    if (servo1Position <= servo1MinDegrees) {
          // when the servo gets to its minimum position change the interval to change the speed
       if (servo1Interval == servo1SlowInterval) {
         servo1Interval = servo1FastInterval;
       }
       else {
        servo1Interval = servo1SlowInterval;
       }
    }

    if ((servo1Position >= servo1MaxDegrees) || (servo1Position <= servo1MinDegrees))  {
          // if the servo is at either extreme change the sign of the degrees to make it move the other way
      servo1Degrees = - servo1Degrees; // reverse direction
          // and update the position to ensure it is within range
      servo1Position = servo1Position + servo1Degrees; 
    }
    
        // make the servo move to the next position
    servo1.write(servo1Position);
        // and record the time when the move happened

    }
  
  }
}

void EyeballsHome()  {
  if(motion == 0) {
    servo0.write(144);  //homes eyeballs.
    servo1.write(90);
  }
}

Is "motion" equal to 0 or 1 when the problem occurs? Try setting motion permanently to 0 and test. Then set motion permanently to 1 and test.

Results?

Paul

It was a 1.

I solved the problem with the advice of a software engineer friend:

The timing line I was using

if (currentMillis - previousBlinkMillis >= BlinkyInterval) {
previousBlinkMillis += BlinkyInterval;

was causing the timing of the program to be a bit "off" every cycle, which lead to the program trying to catch up and do everything super fast. I changed it to:

if (currentMillis - previousBlinkMillis >= BlinkyInterval) {
previousBlinkMillis = currentMillis;

and that solved all the timing problems (I changed all the interval timing to update to currentMillis, not trying to add an interval to the comparing time).

Thanks!

The timing line I was using

if (currentMillis - previousBlinkMillis >= BlinkyInterval) {
previousBlinkMillis += BlinkyInterval;

was causing the timing of the program to be a bit “off” every cycle

Explain, please.

TheMemberFormerlyKnownAsAWOL:
Explain, please.

Explain what? The code is wrong as the OP discovered.

TheMemberFormerlyKnownAsAWOL:
Explain, please.

by adding the interval to the time rather than just updating the time, it was causing it to miss by a few milliseconds each rotation. As each rotation happened, it got further and further off until it was just spinning away as fast as it could. Cleaning up the timing code solved the issue.

That’s …somewhat counterintuitive.

wackyworld:
The timing line I was using

if (currentMillis - previousBlinkMillis >= BlinkyInterval) {
previousBlinkMillis += BlinkyInterval;

This is the correct way to time an action at a regular interval. But the interval has to be long enough to let the action finish before setting a new “start time”. Otherwise it will behave as you describe: “… which lead to the program trying to catch up and do everything super fast”.

wackyworld:
I changed it to:

if (currentMillis - previousBlinkMillis >= BlinkyInterval) {
previousBlinkMillis = currentMillis;

This is typically used to make an even pause between actions, regardless of how long each action takes. If each action takes the same amount of time, they will start at a regular interval, but that is, in fact, a “special case”. As you add more code, the interval between starting actions will become longer and longer.