Hi. Sorry to say I'm not a programmer but am using some excellent code posted by user 'Photo-Tom' 8 years ago.
The code works perfectly well and I have no problem with my mechanics, circuit etc, it's all working nicely for a daft halloween project I'm building. The PIR sensor triggers my servo, moves it to position one, pauses, moves it to position 2 and returns to idle position to await the next 'HIGH' from the PIR.
To really make it special I'd like to add more servo positions to the sequence, I thought it would be easy (I've dabbled a bit with Basic, DOS etc many years ago) but I'm struggling to make any sense of it.
Here's the original code, I've since modified the timing a bit to suit my needs, but my question is 'how can I add more servo positions to the sequence?'
Apologies in advance if I've made any 'faux pas' in what I'm posting or how I've posted it, post #1 for me on this forum. Hoping someone can help me out.
Thanks, Rob
#include <Servo.h>
#define S_IDLE 1
#define S_MAXA 2
#define S_MID 3
#define S_MAXB 4
#define S_WAITING 5
#define S_FINISH 6
Servo myservo;
int PIR_SensorPin = 8;
unsigned long duration;
unsigned long lastMillis;
int calibrationTime = 30;
int state = S_IDLE;
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() {
if (millis() - lastMillis > duration) { // Step in sequence finished - time to take action!
lastMillis = millis();
switch (state) {
case S_MAXA:
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 200; // Duration of this position
state = S_MID; // Next step of the sequence - runs when duration has passed.
break;
case S_MID:
myservo.writeMicroseconds(2000); // turn servo to new position
duration = 200; // Duration of this position
state = S_MAXB; // Move to the next state
break;
case S_MAXB:
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 200; // Duration of this position;
state = S_WAITING; // Move to the next state
break;
case S_WAITING:
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 10000; // Wait for moving on.
state = S_FINISH; // Move to the next state
break;
case S_FINISH:
state = S_IDLE; // Done - time to look at the PIR sensor again.
break;
}
// General state: only look at the sensor when IDLE.
if (state == S_IDLE) {
if (digitalRead(PIR_SensorPin) == HIGH) {
state = S_MAXA;
duration = 0;
lastMillis = millis();
}
}
}
The first thing that I would do is to add a case for S_IDLE into the switch/case. As you are already using switch/case it makes no sense to use an if to test for a particular state outside of it
As to adding more servo positions, add more states to the switch/case with the appropriate values written to the servo
Add a "#define" (you should read about "enum" for easy "re-numbering" if needed)
#define S_WHAT 7
Then make another case...
case S_WHAT:
myservo.writeMicroseconds(1000); // WHAT position
duration = 1000; // Duration of this position
state = S_MAXB; // Move to the next state <== decide which is next state
break;
And copy/paste into your sketch.
By the way: this does nothing more than print dots for thirty seconds:
Serial.print("calibrating sensor ");
for (int i = 0; i < calibrationTime; i++) {
Serial.print(".");
delay(1000);
}
Thanks so much for the replies. To be perfectly honest I'd tried @xfpd's suggestion as a sort of 'cookie cutter' solution, this is about the limit of my coding ability, but it didn't change the sequence of sweeps. Thanks for the tips re the unneeded code BTW.
I've defined some additional 'BUMP's (the servo is supposed to bump a lid open and bang it shut again) and added them to the sequence with a unique 'WAITING' period between each that should return the servo to its start position.
Do the numbers have any significance when adding definitions? i.e. 'S_IDLE 1', is the number arbitrary? Also, does the sequence of defining have any effect or can definitions be in a disordered list? I'm just trying to rule a few things out. I'll tidy things up once I know I'm on the right track.
The revised code makes no difference to the servo movement. I purposely added long delays to make the sequence more obvious. It seems to skip over my added positions (BUMP and WAITING), almost as though my changes aren't updating the board, is this even possible? Weird.
Thanks again, Rob
#include <Servo.h>
#define S_IDLE 1
#define S_BUMPA 2
#define S_WAITINGA 3
#define S_MAXB 4
#define S_WAITING 5
#define S_FINISH 6
#define S_MAXA 7
#define S_BUMPB 8
#define S_BUMPC 9
#define S_MID 10
#define S_WAITINGB 11
Servo myservo;
int PIR_SensorPin = 8;
unsigned long duration;
unsigned long lastMillis;
int calibrationTime = 15;
int state = S_IDLE;
void setup()
{
Serial.begin(9600);
pinMode (PIR_SensorPin, INPUT); // Set pinMode
digitalWrite(PIR_SensorPin, LOW);
myservo.attach(9, 900, 2300); // 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() {
if (millis() - lastMillis > duration) { // Step in sequence finished - time to take action!
lastMillis = millis();
switch (state) {
case S_BUMPA:
myservo.writeMicroseconds(1650); // turn servo to new position
duration = 1000; // Duration of this position
state = S_WAITINGA; // Next step of the sequence - runs when duration has passed.
break;
case S_WAITINGA:
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 1000; // Wait for moving on.
state = S_BUMPB; // Move to the next state
break;
case S_BUMPB:
myservo.writeMicroseconds(1750); // return servo to netural position
duration = 20000; // Wait for moving on.
state = S_WAITINGB; // Move to the next state
break;
case S_WAITINGB:
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 5000; // Wait for moving on.
state = S_MAXA; // Move to the next state
break;
case S_MAXA:
myservo.writeMicroseconds(1900); // turn servo to new position
duration = 200; // Duration of this position
state = S_MID; // Next step of the sequence - runs when duration has passed.
break;
case S_MID:
myservo.writeMicroseconds(2000); // turn servo to new position
duration = 2000; // Duration of this position
state = S_MAXB; // Move to the next state
break;
case S_MAXB:
myservo.writeMicroseconds(2300); // turn servo to new position
duration = 1000; // Duration of this position;
state = S_WAITING; // Move to the next state
break;
case S_WAITING:
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 2000; // Wait for moving on.
state = S_FINISH; // Move to the next state
break;
case S_FINISH:
state = S_IDLE; // Done - time to look at the PIR sensor again.
break;
}
// General state: only look at the sensor when IDLE.
if (state == S_IDLE) {
if (digitalRead(PIR_SensorPin) == HIGH) {
state = S_MAXA;
duration = 0;
lastMillis = millis();
}
}
}
}
More than one state can point at the same state, just be aware to avoid creating an infinite loop.
Also... "cases" do not need to be in numerical order, just keep their values different.
The enum is an example of "keeping their values different." This is the simplest way. The top variable gets the value "0" unless assigned another value, and each successive variable gets assigned the next number. You can start with any number you want or skip numbers... just look up "enum c++" and you will get lots of information.
enum { // automatic numbering (enumeration)
S_IDLE, // 0
S_MAXA, // 1
S_MID, // 2
S_MAXB, // 3
S_WHAT, // 4 <== added
S_WAITING,// 5
S_FINISH // 6
};
#include <Servo.h>
Servo myservo;
int PIR_SensorPin = 8;
unsigned long duration;
unsigned long lastMillis;
int calibrationTime = 30;
int state = S_IDLE;
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() {
if (millis() - lastMillis > duration) { // Step in sequence finished - time to take action!
lastMillis = millis();
switch (state) {
case S_MAXA:
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 200; // Duration of this position
state = S_MID; // Next step of the sequence - runs when duration has passed.
break;
case S_MID:
myservo.writeMicroseconds(2000); // turn servo to new position
duration = 200; // Duration of this position
state = S_MAXB; // Move to the next state
break;
case S_MAXB:
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 200; // Duration of this position;
state = S_WAITING; // Move to the next state
break;
case S_WAITING:
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 10000; // Wait for moving on.
state = S_FINISH; // Move to the next state
break;
case S_FINISH:
state = S_IDLE; // Done - time to look at the PIR sensor again.
break;
case S_IDLE:
if (digitalRead(PIR_SensorPin) == HIGH) {
state = S_MAXA;
duration = 0;
lastMillis = millis();
}
break;
case S_WHAT:
myservo.writeMicroseconds(1000); // WHAT position
duration = 1000; // Duration of this position
state = S_MAXB; // Move to the next state <== decide which is next state
break;
}
}
}
Hmm. Some things to think about there. I will look at 'enum'.
Jim-p has rightly noted that my sketch will skip past my BUMPs and WAITING cases and go straight to MAXA. It seems to me that the sketch should run through successfully on the first cycle though, only skipping to MAXA on the second and later cycles. It doesn't, it goes straight to MAXA on cycle #1.
I'm at the office so can't test at the moment but my plan for this evening is to wipe out the existing sketch and rebuild it one case at a time, check the result, add the next case etc. Hopefully I can at least see where it falls over.
Thanks for all the help so far.
@whatiscode Consider this contribution. I've added comments, Serial output, and cleaned it up a bit. It can also be run in Wokwi, just substitute a button for the sensor input.
#include <Servo.h>
enum { // automatic numbering (enumeration)
S_IDLE, // 0
S_MAXA, // 1
S_MID, // 2
S_MAXB, // 3
S_WHAT, // 4 <== added, but not used in sequence right now
S_WAITING,// 5
S_FINISH // 6
};
Servo myservo;
int PIR_SensorPin = 8;
int ACTIVE = HIGH;
unsigned long duration; //=0 by default
unsigned long lastMillis; //=0 by default
//int calibrationTime = 30; //irrelevant code at this time
int state = S_IDLE;
void setup()
{
Serial.begin(9600);
Serial.println("Hello");
pinMode (PIR_SensorPin, INPUT_PULLUP); // Set pinMode
// digitalWrite(PIR_SensorPin, LOW); //serves no purpose
// because of the way Servo.h works, you should set the position before you enable the pin, but no one tells anyone this
myservo.writeMicroseconds(1500); // set servo to mid-point
myservo.attach(9, 900, 2100); // servo.attach(pin, min, max)
// the following is meaningless, code at this time
// Serial.print("calibrating sensor ");
// for (int i = 0; i < calibrationTime; i++) {
// Serial.print(".");
// delay(1000);
// }//ends calibration
Serial.println("S_IDLE"); //announce the starting state
}// ends setup
void loop() {
if (millis() - lastMillis > duration) { // Step in sequence finished - time to take action!
lastMillis = millis();
switch (state) {
case S_MAXA:
Serial.println("S_MAXA");
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 2000; // Duration of this position
state = S_MID; // Next step of the sequence - runs when duration has passed.
break;
case S_MID:
Serial.println("S_MID");
myservo.writeMicroseconds(2000); // turn servo to new position
duration = 2000; // Duration of this position
state = S_MAXB; // Move to the next state
break;
case S_MAXB:
Serial.println("S_MAXB");
myservo.writeMicroseconds(2100); // turn servo to new position
duration = 2000; // Duration of this position;
state = S_WAITING; // Move to the next state
break;
case S_WAITING:
Serial.println("S_WAITING");
myservo.writeMicroseconds(1500); // return servo to netural position
duration = 2000; // Wait for moving on.
state = S_FINISH; // Move to the next state
break;
case S_FINISH:
Serial.println("S_FINISH");
state = S_IDLE; // Done - time to look at the PIR sensor again.
duration = 0; // Wait for moving on.
break;
case S_IDLE:
if (digitalRead(PIR_SensorPin) == ACTIVE) { //start sequence if active
state = S_MAXA;
Serial.println(); //just ends line of ...
duration = 0; //move immediately to first state, no delay
lastMillis = millis();
}//ends if ACTIVE
else { //just cycling, so pump out another .
Serial.print(".");
delay(50);
}//ends else
break;
//the following is just a hangover from problem solving in thread
// case S_WHAT:
// Serial.println("S_WHAT");
// myservo.writeMicroseconds(1000); // WHAT position
// duration = 1000; // Duration of this position
// state = S_MAXB; // Move to the next state
// break;
}//end of switch statement
} //end of sequence finished compound
}//end of loop
You were right Jim-p (although I'm sure you already knew that) changed start state to BUMPA and it worked as hoped. It seemed illogical to me to have the start state at the end of the sketch, just showing my ignorance I guess.
Great, I can add as many sweeps and waits as I need now.
Thanks! Rob.