Ir Sensor Activated Servo With Millis

Hey all! I'm currently using arduino to create an animatronic, and am having an issue. It's supposed to be when the ir sensor is triggered, the servo goes to 160 degrees, than 60, and continues doing that for about 20 seconds. I also have 2 other things that will be on for a couple seconds when the sensor is activated, but that part of the code works great. My current issue is that the servo only moves if the ir sensor is currently triggered, but as mentioned I want it to move for 20 without needing to hold my hand over it. I have tried using the method that uses elapsedmillis to make the servo go for a certain amount of time, but with that method, the servo moves for 21 seconds, stops, and never moves again even if the sensor is triggered. That method also messed up the timing of the 2 other things. Essentially i want the servo to go for 21 seconds when the sensor is triggered using millis. Whatever works works, so if i have to make the servo loop go a million times that's totally fine.

Code:


#include <Servo.h>
int position = 90 ;
boolean forward = false ;
unsigned long ts = millis () ;
#define DELAY 40
int servoPin = 8;
Servo servo;
# define PinSensor   9
# define PinThing1   12
# define PinThing2   13

enum { Off = LOW, On = HIGH};

// -----------------------------------------------------------------------------
void setup ()
{

  pinMode (PinSensor, INPUT_PULLUP);

  pinMode (PinThing1, OUTPUT);
  pinMode (PinThing2,    OUTPUT);

  digitalWrite (PinThing1, Off);
  digitalWrite (PinThing2,    Off);
  Serial.begin(9600);
  servo.attach(8);
}

// -----------------------------------------------------------------------------
struct Timer {
  byte           pin;
  unsigned long  duration;
  bool           on;
};

Timer timers [] = {
  { PinThing1, 5000 },
  { PinThing2,    2000 }
};

#define N_TIMERS    (sizeof(timers)/sizeof(Timer))

// -----------------------------------------------------------------------------
void loop ()
{
  static unsigned long msecLst;
  unsigned long msec = millis ();

  // enable timed outputs
  if (digitalRead (PinSensor) == LOW)  {
    if (millis () - ts >= DELAY)
    {
      ts += DELAY ;   // setup timestamp for next time.
      if (forward)
      {
        servo.write (-- position) ;  // progress the servo
        if (position == 60)  // test for reverse
          forward = false ;
      }
      else
      {
        servo.write (++ position) ;  // progress the servo
        if (position == 160)  // test for reverse
          forward = true ;
      }
    }

    msecLst = msec;
    for (unsigned n = 0; n < N_TIMERS; n++)  {
      timers [n].on = true;
      digitalWrite (timers [n].pin, On);
    }
  }

  Timer *t = timers;
  for (unsigned n = 0; n < N_TIMERS; n++, t++)  {
    if (t->on && (msec - msecLst) > t->duration)  {
      digitalWrite (t->pin, Off);
      t->on = false;
    }
  }
}

Thank you all so much for your help! I've only been doing this for a couple of months so don't be suprised if it's an obvious error lol

You need to detect when the button becomes pressed rather than when it is pressed
See the StateChangeDetection example in the IDE

Hey! tried this and now not only does the servo not work, but the led only stays on if my hand is in front of the sensor. If i use it with elapsed millis the servo works, but again only once, and the led does not work at all. Code:

#include <Servo.h>

int position = 90 ;    // state variable
boolean forward = false ;   // state variable
unsigned long ts = millis () ;   // time accounting.
#define DELAY 40
int servoPin = 8;
Servo servo;
int SensorCounter = 0;   // counter for the number of button presses
int SensorState = 0;         // current state of the button
int lastSensorState = 0;     // previous state of the button
# define PinSensor   9
# define PinThing1   12
# define PinThing2   13

enum { Off = LOW, On = HIGH};

// -----------------------------------------------------------------------------
void setup ()
{
  servo.attach(8);
  pinMode (PinSensor, INPUT_PULLUP);

  pinMode (PinThing1, OUTPUT);
  pinMode (PinThing2,    OUTPUT);

  digitalWrite (PinThing1, Off);
  digitalWrite (PinThing2,    Off);
  Serial.begin(9600);
}

// -----------------------------------------------------------------------------
struct Timer {
  byte           pin;
  unsigned long  duration;
  bool           on;
};

Timer timers [] = {
  { PinThing1, 5000 },
  { PinThing2,    2000 }
};

#define N_TIMERS    (sizeof(timers)/sizeof(Timer))

// -----------------------------------------------------------------------------
void loop ()
{ SensorState = digitalRead(PinSensor);

  static unsigned long msecLst;
  unsigned long msec = millis ();

  // enable timed outputs
  if (digitalRead (PinSensor) == LOW)  {

    for (unsigned n = 0; n < N_TIMERS; n++)  {
      timers [n].on = true;
      digitalWrite (timers [n].pin, On);
    }
  }

  Timer *t = timers;
  for (unsigned n = 0; n < N_TIMERS; n++, t++)  {
    if (t->on && (msec - msecLst) > t->duration)  {
      digitalWrite (t->pin, Off);
      t->on = false;

      // compare the buttonState to its previous state
      if (SensorState != lastSensorState) {
        // if the state has changed, increment the counter
        if (SensorState == HIGH) {
          // if the current state is HIGH then the button went from off to on:
          SensorCounter++;
          Serial.println("on");
          Serial.print("number of button pushes: ");
          Serial.println(SensorCounter);
        } else {
          // if the current state is LOW then the button went from on to off:
          Serial.println("off");
        }
        // save the current state as the last state, for next time through the loop
        lastSensorState = SensorState;


        if (SensorCounter % 1 == 0) {
            if (millis () - ts >= DELAY)
            {
              ts += DELAY ;   // setup timestamp for next time.
              if (forward)
              {
                servo.write (-- position) ;  // progress the servo
                if (position == 60)  // test for reverse
                  forward = false ;
              }
              else
              {
                servo.write (++ position) ;  // progress the servo
                if (position == 160)  // test for reverse
                  forward = true ;
              }
            }
          }
        }
      }
    }
  }

You are saving the current sensor state as the previous sensor state only when the sensor state has changed. That is not what the StateChangeDetection example does.

You need to do it before every read of the sensor

How would i do that? Sorry I'm dumb lmao. Also would you know why the led isn't working?

Look at where it is done in the example

I'm not entirely sure what you mean by this, Where in the program is this? I want the program to run every time the ir sensor is activated, not just the first time, and from my understanding of it statechangedetection only detects if it changes once, not every time. The servo moves a tiny bit with this program, but barely at all.

Here is the StateChangeDetection example stripped to the bare bones with added comments

const int  buttonPin = A3;    // the pin that the pushbutton is attached to

int buttonState = HIGH;         // current state of the button
int lastButtonState = HIGH;     // previous state of the button

void setup()
{
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(115200);
}

void loop()
{
  buttonState = digitalRead(buttonPin);   //read the current button state
  if (buttonState != lastButtonState)     //if it has changed since the last time it was read
  {
    if (buttonState == LOW)               //and is now LOW
    {
      Serial.println("input became LOW");
    }
    delay(50);                            //quick and dirty way to debounce
  }
  lastButtonState = buttonState;   //save the current button state ready for the next check for a change
}

So from my understanding, i need to change this? right now with this code the elapsedmillis timer is not working

Frankly I suggest that you restructure your sketch into a state machine so that the code for each state can be isolated to make it easier to see what is going on

As I understand it the states would be
WAIT_FOR_TRIGGER
MOVE_TO_160_DEGREES //repeat moves and waits for 20 seconds or a fixed count
WAIT_A_BIT //using millis() for non blocking timing
MOVE_TO_60_DEGREES
WAIT_A_BIT //using millis() for non blocking timing

Meanwhile the code can do whatever it likes in loop() as long as it does not block execution of the code. Does that sound right ?

Yep, that's essentially what I'm trying to do

This is the sort of program structure that I had in mind

//https://forum.arduino.cc/t/ir-sensor-activated-servo-with-millis/947216
enum states
{
  WAIT_FOR_TRIGGER,
  MOVE_TO_160_DEGREES,
  WAIT1,
  MOVE_TO_60_DEGREES,
  WAIT2
};

const unsigned long wait1Period = 5000;
const unsigned long wait2Period = 5000;
const unsigned long movementPeriod = 20000;
unsigned long currentTime;
unsigned long shortPeriodStartTime;
unsigned long movementPeriodStartTime;

byte currentState = WAIT_FOR_TRIGGER;
const byte triggerPin = A3;

void setup()
{
  Serial.begin(115200);
  pinMode(triggerPin, INPUT_PULLUP);
  Serial.println("waiting for trigger");
}

void loop()
{
  currentTime = millis();
  switch (currentState)
  {
    case WAIT_FOR_TRIGGER:
      if (digitalRead(triggerPin) == LOW)
      {
        currentState = MOVE_TO_160_DEGREES;
        movementPeriodStartTime = currentTime;
      }
      break;
    case MOVE_TO_160_DEGREES:
      Serial.println("move to 160 degrees");  //<<<<<servo code goes here
      shortPeriodStartTime = currentTime;
      currentState = WAIT1;
      break;
    case WAIT1:
      if (currentTime - shortPeriodStartTime >= wait1Period)
      {
        currentState = MOVE_TO_60_DEGREES;
      }
      checkMovementPeriod();
      break;
    case MOVE_TO_60_DEGREES:
      Serial.println("move to 60 degrees");  //<<<<<servo code goes here
      shortPeriodStartTime = currentTime;
      currentState = WAIT2;
      break;
    case WAIT2:
      if (currentTime - shortPeriodStartTime >= wait1Period)
      {
        currentState = MOVE_TO_160_DEGREES;
      }
      checkMovementPeriod();
      break;
  };
  //any other non blocking code required goes here
}

void checkMovementPeriod()
{
  if (currentTime - movementPeriodStartTime >= movementPeriod)
  {
    Serial.println("movement  period ended");
    Serial.println("waiting for trigger");
    currentState = WAIT_FOR_TRIGGER;
  }
}