servo activated by pir motion sensor

Hello,

I’m starting to code for a project where I have a servo that is triggered by a motion sensor. It will activate the servo to move its arm, “shake” a little (move backwards and forwards) and then return to its position, delay and then get triggered again when motion is sensed.

Here is the Schematic:

Here it is what I want to achieve broken down into actions:

  • When powered up, the motion sensor calibrates.
  • set servo to mid point
    -delay (200)
  • When the motion sensor detects motion, it triggers the servo to turn clockwise 90 degrees.
  • delay (100)
  • Once the servo has turned 90degrees CW it should turn 5 degrees counter clockwise
  • delay (100)
  • Once the servo has turned 5 degrees CCW it should turn 5 degrees clockwise
  • delay (100)
  • Once the servo has turned 5degrees CW it should turn 90 degrees counter clockwise (thus returning to its starting position)
  • delay(900); (after it has completed the set of movements one it should not start another one for at least 5 seconds.)
  • If no motion is detected, the stepper should do nothing.
  • if motion is continued to be detected servo should start another out-and-back.
#include <Servo.h>

Servo myservo;

void setup()
{
  myservo.attach(9, 750, 2200);  // servo.attach(pin, min, max)
  myservo.writeMicroseconds(1500);  // set servo to mid-point
}

void loop() {
  myservo.write(1500);
  delay(200);
  myservo.write(2200);
  delay(100);
  myservo.write(2000);
  delay(100);
  myservo.write(2200);
  delay(100);
  myservo.write(1500);
  delay(900);
}

I have written this code to test the estimated movement and timings; I will need to slightly alter/adjust/refine these values when the circuit is installed.

I am attempting to code in small steps, as in not tackle the entire project in one go. So as I have the movement, next I am trying to switch out the delay for millis. I have read up on the blinkwithoutdelay and the running several things at the same time. But I’m struggling to incorporate it. Does anyone have any pointers?

Thanks,

Your circuit diagram looks a bit strange, with lacking grounds, but if the servo moves it seems to be okay.

You need a state machine, not several things at a time. Each of your listed steps becomes a distinct state, and after some time (BlinkWithoutDelay) the next state is activated.

Eventually you can write a ShakeHand task, using task macros. The task macros implement a non-blocking delay (taskDelay), so that your code does not need dramatic changes.

Hi,
Can you please edit your circuit diagram to include all your gnd wires please?

Thanks.... Tom... :slight_smile:

Hi sorry about that.

Have amended the circuit to include the GNDs.

Why powering your Arduino off 9V when you have 5V coming out of that buck converter?

And indeed, finite state machine is what you should be looking for.

The many Gnd connections on the regulator are confusing, also OVIN and OVOUT - what's all that?

Only one of these Gnds (GndOut) should go to the Arduino and all connected devices.

wvmarle:
Why powering your Arduino off 9V when you have 5V coming out of that buck converter?

The servo has a stall current of 3amps, so i'm supplying more than 3amps to it. The converter has a limit, so im supplying the converter with 9v 4amps via a wall plug.

The servo im running accepts a variable current between 4.8-6v. As im just testing at the moment i am supplying 5v, but in the future i will alter the supply from the converter to closer to 6v.

DrDiettrich:
The many Gnd connections on the regulator are confusing, also OVIN and OVOUT - what's all that?

The 9v (OVIN) goes into the convertor from the wall plug, this is the power source to everything. The OVOUT, is the original voltage from the wall plug that essentially bypasses the converter (although its connected to the converter) and powers the arduino. The Vout it the converted power thats safe to use for the arduino.

this is the converter.

Thanks wvmarle and DrDiettrich for pointing me towards the State Machine.

I have been reading the Demonstration code for several things at the same time and this tutorial that helps explains it.

I have come up with this code.

#include <Servo.h>

// ----CONSTANTS (won't change)

const int servoPin = 9; // the pin number for the servo signal
const int servoMinus = 1500; // the limits to servo movement
const int servoMaxus = 2100;

//------- VARIABLES (will change)

Servo myservo;

int servoPosition = 1500;     // the current angle of the servo - starting at 90.
int servomax = 2100; //servo position in us. The amount it moves at each step
int servomid = 2000; //servo position in us. The amount it moves at each step

int servoSlowInterval = 200; // millisecs between servo moves
int servoFastInterval = 100; // millisecs between servo moves
int servopause = 900; // millisecs between servo moves

int servoInterval = servoSlowInterval; // initial millisecs between servo moves

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousServoMillis = 0; // the time when the servo was last moved


void setup()
{
  myservo.attach(9, 900, 2100);  // servo.attach(pin, min, max)
  myservo.writeMicroseconds(1500);  // set servo to mid-point
}

void loop() {

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
}


void servoSweep() {

  // this is similar to the servo sweep example except that it uses millis() rather than delay()

  // nothing happens unless the interval has expired
  // the value of currentMillis was set in loop()

  if (currentMillis - previousServoMillis >= servoInterval) { //this code will only become TRUE after millis() is at least “slowinterval” larger than the previously stored value of millis, in previousservoMillis. In other words, nothing in the if()-statement will execute until millis() gets 200 milliseconds larger than previousservoMillis.
    // its time for another move
    previousServoMillis += servoInterval;

    servoPosition = servoPosition + servomax; // is this correct? servoDegrees might be negative


    //code for an interval while at max position.
    if (servoPosition >= servomax) {
      // when the servo gets to its maximum position
      if (servoInterval == servoFastInterval) {   //delay before next action
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoFastInterval;
      }
    }

    // code to move move servo from max to mid position
    if (servoPosition == servomax)  {
      // if the servo is at its max position make it move to mid position
      servoPosition = servoPosition + servomid;

      if (servoInterval == servoFastInterval) {   //delay before next action
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoFastInterval;

      }
    }

    // code to move move servo from mid to max position
    if (servoPosition == servomid)  {
      // if the servo is at equal to its mid position make it move to max position
      servoPosition = servoPosition + servomax;

      if (servoInterval == servoFastInterval) {   //delay before next action
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoFastInterval;

      }
    }

    // code to move move servo from max to start position
    if (servoPosition == servomax)  {
      // if the servo is at its max position make it move to start position
      servoPosition = servoPosition + servoPosition;

      if (servoInterval == servopause) {   //delay before next action
        servoInterval = servopause;
      }
      else {
        servoInterval = servopause;

      }
    }


    // make the servo move to the next position
    myservo.write(servoPosition);
    // and record the time when the move happened
  }


}

I am attempting to achieve the same motion as in the code i put in the first post. But i cant get the servo working. I imagine there might be some issues. One of my first thoughts is, that i am attempting to control the servo using Micro Seconds rather than degrees and this may be conflicted in the code where i have stated the constants.

Any feedback on the structure of the state machine would be greatly appreciated.

Thanks in advance

Shouldn't you call servoSweep() in loop()?

The code after if (servoInterval... is crap.

Hi,

#include <Servo.h>

// ----CONSTANTS (won't change)

const int servoPin = 9; // the pin number for the servo signal
const int servoMinus = 1500; // the limits to servo movement
const int servoMaxus = 2100;

//------- VARIABLES (will change)

Servo myservo;

int servoPosition = 1500; // the current angle of the servo - starting at 90.
int servomax = 2100; //servo position in us. The amount it moves at each step
int servomid = 2000; //servo position in us. The amount it moves at each step

int servoSlowInterval = 200; // millisecs between servo moves
int servoFastInterval = 100; // millisecs between servo moves
int servopause = 900; // millisecs between servo moves

int servoInterval = servoSlowInterval; // initial millisecs between servo moves

unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long previousServoMillis = 0; // the time when the servo was last moved

void setup()
{
myservo.attach(9, 900, 2100); // servo.attach(pin, min, max)
myservo.writeMicroseconds(1500); // set servo to mid-point
}

void loop() {

currentMillis = millis(); // capture the latest value of millis()
// this is equivalent to noting the time from a clock
}

void servoSweep() {
etc...

The RED bit is the only code that runs.
All the void loop() does is store millis.
Tom.... :slight_smile:

Thanks for the feedback,

oh dear. i have missed the mark by miles.

I have been reading up on Finite State Machines. And found an example here

Below is the new code. I have written each individual action i want to execute; move servo to 2100, wait, move servo to 2000, wait, move servo to 2100, wait, move servo to 1500 as separate states that will be completed before moving on to the next.

I have not attempted to implement a pir motion sensor that triggers the the first state change in the code yet i want to make sure i an at least on the right track.

Heres the latest code:

#include <Servo.h>

#define S_IDLE 1
#define S_MAXA 2
#define S_WAITA 3
#define S_MID 4
#define S_WAITB 5
#define S_MAXB 6
#define S_WAITC 7

Servo myservo;

void setup()
{
  myservo.attach(9, 900, 2100);  // servo.attach(pin, min, max)
  myservo.writeMicroseconds(1500);  // set servo to mid-point
}
void loop()
{
  static int state = S_IDLE; // initial state is 1, the "idle" state.
  static unsigned long ts;  // To store the "current" time in for delays.
  switch (state)
  {
    case S_IDLE:
      // We don't need to do anything here, waiting for a forced state change.
      break;

    case S_MAXA:
      myservo.writeMicroseconds(2100);  // Turn servo to 2100 position
      ts = millis();  // Remember the current time
        state = S_WAITA;  // Move to the next state
      break;

    case S_WAITA:
      // If one second has passed, then move on to the next state.
      if (millis() > ts + 1000)
      {
        state = S_MID; // Move to the next state
      }
      break;

    case S_MID:
      myservo.writeMicroseconds(2000);  // Turn servo to 2000 position
      ts = millis();  // Remember the current time
        state = S_WAITB; // Move to the next state
      break;

    case S_WAITB:
      // If one second has passed, then move on to the next state.
      if (millis() > ts + 1000)
      {
        state = S_MAXB; // Move to the next state
      }
      break;

    case S_MAXB:
      myservo.writeMicroseconds(2100);  // Turn servo to 2100 position
      ts = millis();  // Remember the current time
        state = S_WAITC; // Move to the next state
      break;

    case S_WAITC:
      // If one second has passed, then move on to the next state.
      if (millis() > ts + 5000)
      {
        state = S_IDLE;
      }


  }
}

In your opinions, am i heading in the right direction now?

Thanks again,

\Yes, pretty much. This way a button can also terminate the sequence by setting the state to S_IDLE.

One thing to do in S_IDLE is return your servo to the idle position, so you can be sure it's there (in case the thing got interrupted).

One minor (but for long running projects essential) think, change your millis() tests to:

      if (millis() - ts > 1000)

This way thanks to integer magic overflows are handled transparently (millis() overflows after 49 days or so).

I'm used to using global variables rather than static ones, both ways should work.

A simplification, for one state per position instead of two:

S_...:
 if (millis() - ts > duration) {
   myServo.write(...); //new position
   ts = millis();
   duration = ...; //duration of this position;
 }
 break;

Thanks so much DrDiettrich and wvmarle,

I have implemented your suggestions, makes the code much more efficient. I have also added the PIR sensor that will activate the servo. I have arranged it so the first state SETUP calibrates the pir sensor and sets the servo to its neutral position. The IDLE state is the state the machine will come back top once completing the final state. This state will reset the servo to its original position and wait for a short duration before moving on to WAITING.

In the WAITING state i have written to keep the servo in neutral, and also, that if the sensor detects motion it should change state and move to the next state MAXA. This is the piece of code i'm unsure if i have got completely correct? Also, i have not declared a LOW state as i don't want anything to happen if its LOW except wait for it to become HIGH. is this ok?

Heres the code:

#include <Servo.h>

#define S_SETUP 1
#define S_IDLE 2
#define S_WAITING 3
#define S_MAXA 4
#define S_MID 5
#define S_MAXB 6

Servo myservo;
int PIR_SensorPin = 8;
unsigned long duration;

void setup()
{
  Serial.begin(9600);
  pinMode (PIR_SensorPin, INPUT);   // Set pinMode
  digitalWrite(PIR_SensorPin, LOW);
  myservo.attach(9, 900, 2100);  // servo.attach(pin, min, max)
  myservo.writeMicroseconds(1500);  // set servo to mid-point
}



void loop()
{
  static int state = S_IDLE; // initial state is 1, the "idle" state.
  static unsigned long ts;  // To store the "current" time in for delays.
  switch (state)
  {

    case S_SETUP: // sets servo to 1500 and calibrates pir motion sensor
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(1500); // turn servo to new position
        ts = millis();
        duration = 30000; //duration of this position;
        state = S_IDLE;  // Move to the next state
      }
      break;

    case S_IDLE:
      //pause before starting again
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(1500); // keep servo in netural position
        ts = millis();
        duration = 5000; //duration of this position;
        state = S_WAITING;  // Move to the next state
      }
      break;

    case S_WAITING: // wait here until the pir sensor senses motion
      myservo.writeMicroseconds(1500); // keep servo in netural position
      if (digitalRead(PIR_SensorPin) == HIGH) {
        state = S_MAXA;  // Move to the next state
      }
      break;

    case S_MAXA:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2100); // turn servo to new position
        ts = millis();
        duration = 1000; //duration of this position;
        state = S_MID;  // Move to the next state
      }
      break;

    case S_MID:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2000); // turn servo to new position
        ts = millis();
        duration = 1000; //duration of this position;
        state = S_MAXB;  // Move to the next state
      }
      break;

    case S_MAXB:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2100); // turn servo to new position
        ts = millis();
        duration = 1000; //duration of this position;
        state = S_IDLE;  // Move to the next state
      }
      break;


  }
}

Thanks,

the help so far is greatly appreciated.

The state should start at S_SETUP, I assume.
S_SETUP should not depend on a timeout, which also won't work well as long as duration is undefined.
The purpose of S_IDLE is unclear, IMO should be merged with S_WAITING.

I'd suggest that you step through loop() manually, to find out what cannot work as intended.

Thanks for that DrDiettrich,

the IDLE was essentially resetinh the servo, so i put that on the end. It returns the servo to its original position and pauses before it starts again.

I also removed the SETUP and now calibrate the motion sensor in the void setup.

The circuit works, but with one issue. When i trigger the motion sensor, the servo goes through the motions and waits for 10 seconds (as required), but then then reruns the motions. It only reruns the motions once, even if i trigger the motion sensor multiple times during the 10 second pause in the final state. (i first thought it could be that the motion sensor was reading Highs when the servo was paused)

I dont want the servo to rerun the motions straight after the 10 seconds when no motion is present. I only want it to rerun the motions when motion is present.

Is there something i'm not seeing in the code that more experienced eyes might see?

#include <Servo.h>


#define S_WAITING 1
#define S_MAXA 2
#define S_MID 3
#define S_MAXB 4
#define S_RESET 5

Servo myservo;
int PIR_SensorPin = 8;
unsigned long duration;
int calibrationTime = 30;   

void setup()
{
  Serial.begin(9600);
  pinMode (PIR_SensorPin, INPUT);   // Set pinMode
  digitalWrite(PIR_SensorPin, LOW);
  myservo.attach(9, 900, 2100);  // servo.attach(pin, min, max)
  myservo.writeMicroseconds(1500);  // set servo to mid-point
   
    Serial.print("calibrating sensor ");
    for(int i = 0; i < calibrationTime; i++){
      Serial.print(".");
      delay(1000);
      }
}



void loop()
{
  static int state = S_WAITING; // initial state is 1, the "WAITING" state.
  static unsigned long ts;  // To store the "current" time in for delays.
  switch (state)
  {

    case S_WAITING: // wait here until the pir sensor senses motion
      myservo.writeMicroseconds(1500); // keep servo in netural position
      if (digitalRead(PIR_SensorPin) == HIGH) {
        state = S_MAXA;  // Move to the next state
      }
      break;

    case S_MAXA:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2100); // turn servo to new position
        ts = millis();
        duration = 200; //duration of this position;
        state = S_MID;  // Move to the next state
      }
      break;

    case S_MID:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2000); // turn servo to new position
        ts = millis();
        duration = 200; //duration of this position;
        state = S_MAXB;  // Move to the next state
      }
      break;

    case S_MAXB:
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(2100); // turn servo to new position
        ts = millis();
        duration = 200; //duration of this position;
        state = S_RESET;  // Move to the next state
      }
      break;

      case S_RESET:
      //pause before starting again
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(1500); // return servo to netural position
        ts = millis();
        duration = 10000; //duration of this position;
        state = S_WAITING;  // Move to the next state
      }
      break;


  }
}

Many Thanks,

I don't know when your PIR sensor resets its output, under which condition or after which delay. If you don't know that, too, how will you program a certain behaviour?

Check how long the PIR keeps its output high (this can be as much as a few minutes - there's probably a pot where you can adjust this).

Then about your state machine: you may stop it from going to S_WAITING state while the PIR sensor is still high, like this:

      case S_RESET:
      //pause before starting again
      if (millis() - ts > duration) {
        myservo.writeMicroseconds(1500); // return servo to netural position
        if ((digitalRead(PIR_SensorPin) == LOW { // Don't advance to the "waiting" stage when the sensor is still triggered.
          ts = millis();
          duration = 10000; //duration of this position;
          state = S_WAITING;  // Move to the next state
        }
      }
      break;

Thanks for the continued input wvmarle and DrDiettrich,

The PIR stays HIGH for about 3 seconds. It has a handy onboard led to identify this.

I implemented the code. If the PIR is triggered while the machine is running through other states it will cause the state machine to run through the sates again when the first run through has completed.

With the code instructing that if the PIR is still HIGH it won’t progress to S_WAITING. (its going to be in a busy room, so might not go LOW. I put the 10 second delay to give a pause between each run though)

I would like that if the PIR stays HIGH, it shouldn't mechanism until the state machine starts gets back to S_WAITING. Essentially, I want to pause reading the PIR until the first state. In other words, I only want the PIR to be read in the S_WAITING state. Regardless of the PIR reading HIGH or LOW in any other states.

What might be the best way to attempt to implement this? I have been reading up on it for the past day and cant find any examples.

Thanks,

Then read your PIR only when you're in S_WAITING state. Which it seems you do already (based on the code in #17), but you don't seem to have the 10 second delay implemented there (your comments are confusing in that matter: you say "duration of this position" while it's actually the next, as in the state you're setting in the line following that comment).