I have chosen a more universal approach to make all timing adjustable
I added one function named "GearDownServRelSequence()"
which demonstrates how non-blocking timing can be coded.
I claim that using a function like TimePeriodIsOver() the one I'm using here is easier to apply than the "classical"
blink-without-delay-code
All timing-values use the same reference-point in time which is the detection of the buttonpress.
In the moment the buttonpress gets detected multiple things are executed
- a snapshot of time is taken
- set state-machine to first state that shall be executed
- an activation-flag is set to true to start executing the function that IS the state-machine
The state-machine proceeds from state to state with non-blocking timing and in the final state
- the activation-flag is set to false to stop executing
- as double-securing the state-machine is set to a state that does nothing
state-machines are coded in a way that the state-machine-function is
- entered
- runs down one time (there are NO loops inside the function)
- and then the function is left again
This means a quick jump in / jump out (of the function)
this is what enables to execute additional functions.
If all the additional functions work in the same way of quickly jump in / jump out
all functions can be called repeatedly => all functions are "served" in (almost) parallel
The repeating is done by the most outer loop which is
void loop()
Non-blocking timing is done through repeated checking how much time has passed by?
If the right amount of time has passed by take action
The checking how much time has passed by relative to a referencepoint in time is done within less than a millisecond = non-blocking
I have written a little tutorial that explains this principle on an everyday example of baking a pizza
So here is the modified code. the code compiles but I haven't tested it on real hardware.
So there might be still a bug inside of the code.
There are special experts for rating the easyness of understanding of explanation and comments.
These special experts are the beginners.
I appreciate it very much if you ask a lot of questions to this code.
With asking questions you give very good feedback how the explanations can be modified to become more easy to understand.
const byte OnBoard_LED = 13;
unsigned long GearDownStarted; // Timer-variables MUST be of type unsigned long
unsigned long GearUpStarted; // Timer-variables MUST be of type unsigned long
unsigned long NeutralStarted; // Timer-variables MUST be of type unsigned long
// naming convention
// GD = Gear Down
// GU = Gear Up
// SR = Servo first , Relay second
// RS = Relay first, Servo second
boolean GD_SR_Activated = false;
boolean GD_RS_Activated = false;
boolean GU_SR_Activated = false;
boolean GU_RS_Activated = false;
unsigned long GD_SR_delayUntilRelayOff = 200;
// + 100 means start servomove 100 milliseconds later than
// switching off the relay
unsigned long GD_SR_delayUntilServoBack = GD_SR_delayUntilRelayOff + 100;
// + 50 means switch on relay 50 milliseconds later than
// the servomove started
unsigned long GD_SR_delayUntilRelayON = GD_SR_delayUntilServoBack + 50;
/* Timing table
at 0 msec button is pressed
0 msec later move servo forward
200 msec later switch relay OFF
300 msec later move servo backward (200 + 100 = 300)
350 msec later switch relay ON (300 + 50 = 350)
*/
// constants for the state-machine
const byte GD_SR_idling = 0;
const byte GD_SR_ServoForward = 1;
const byte GD_SR_waitForSwitchOFF = 2;
const byte GD_SR_waitServoBack = 3;
const byte GD_SR_waitForSwitchON = 4;
byte GD_SR_State; // state-variable
#include <Servo.h>
Servo servo;
int pot = 90; // initial servo position (always return to 90 after servo has moved)
const int RELAY_PIN = 4; //switches off while moving the servo
const int buttonPin1 = 2; //go to 180 degrees
const int buttonPin2 = 3; //go to 0 degrees
const int buttonPin3 = 5; //go to 45 degrees
const int servoPin = 9;
boolean currentState1 = LOW;//stroage for current button state
boolean lastState1 = LOW;//storage for last button state
boolean currentState2 = HIGH;//stroage for current button state
boolean lastState2 = HIGH;//storage for last button state
boolean currentState3 = HIGH;//stroage for current button state
boolean lastState3 = HIGH;//storage for last button state
boolean pos = true;
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
PrintFileNameDateTime();
pinMode(buttonPin1, INPUT_PULLUP);
pinMode(buttonPin2, INPUT_PULLUP);
pinMode(buttonPin3, INPUT_PULLUP);
digitalWrite(RELAY_PIN, HIGH); // Set Relay to on state
pinMode(RELAY_PIN, OUTPUT);
// servo.write("what initial position is good?");
servo.attach(servoPin);
GD_SR_State = GD_SR_idling;
}
// function with state-machine for gear down
// first moving servo, second switching relay off
void GearDownServRelSequence() {
switch (GD_SR_State) {
case GD_SR_idling:
// just do nothing
break; // immidiately jump down to END-OF-SWITCH
case GD_SR_ServoForward:
GearDownStarted = millis(); // store snapshot of time in the moment the button gets pressed
servo.write(180); // tell servo to go to "180"
GD_SR_State = GD_SR_waitForSwitchOFF;
break; // immidiately jump down to END-OF-SWITCH
case GD_SR_waitForSwitchOFF:
if ( TimePeriodIsOver(GearDownStarted, GD_SR_delayUntilRelayOff) ) {
digitalWrite(RELAY_PIN, LOW); //relay off
GD_SR_State = GD_SR_waitServoBack;
break; // immidiately jump down to END-OF-SWITCH
}
case GD_SR_waitServoBack:
if ( TimePeriodIsOver(GearDownStarted, GD_SR_delayUntilServoBack) ) {
servo.write(90); // tell servo to go to "90"
GD_SR_State = GD_SR_waitForSwitchON;
break; // immidiately jump down to END-OF-SWITCH
}
case GD_SR_waitForSwitchON:
if ( TimePeriodIsOver(GearDownStarted, GD_SR_delayUntilRelayON) ) {
digitalWrite(RELAY_PIN, HIGH); //relay ON
GD_SR_State = GD_SR_idling;
GD_SR_Activated = false;
break; // immidiately jump down to END-OF-SWITCH
}
} // END-OF-SWITCH
}
void loop() {
//// down gear
currentState1 = digitalRead(buttonPin1);
if (!GD_SR_Activated) { // check if not activated "!" attention-mark is the not-operator
if (currentState1 == HIGH && lastState1 == LOW) { //if button has just been pressed
lastState1 = currentState1; // update variable "lastState1"
GD_SR_State = GD_SR_ServoForward; // set state-variable to first step of sequence
GD_SR_Activated = true; // set flag to true to enable executing the sequence
}
}
if (GD_SR_Activated) {
GearDownServRelSequence();
// last step of the sequence does
// GD_SR_Activated = false;
// GD_SR_State = GD_SR_idling;
}
//// up gear
currentState2 = digitalRead(buttonPin2);
if (currentState2 == HIGH && lastState2 == LOW) { //if button has just been pressed
delay(10);
if (pos == true) {
servo.write(0); // tell servo to go to "0"
digitalWrite(RELAY_PIN, LOW); //relay off
delay(200);
digitalWrite(RELAY_PIN, HIGH); //relay on
servo.write(90);
delay(150);
Serial.println("down");
pos = true;
}
}
lastState2 = currentState2;
///// neutral
currentState3 = digitalRead(buttonPin3);
if (currentState3 == HIGH && lastState3 == LOW) { //if button has just been pressed
delay(10);
if (pos == true) {
servo.write(45); // tell servo to go to "45"
delay(200);
servo.write(90);
delay(150);
Serial.println("neutral");
pos = true;
}
}
lastState3 = currentState3;
}
// ignore at first
// helper-function printing filename date and time to the serial monitor
void PrintFileNameDateTime() {
Serial.println( F("Code running comes from file ") );
Serial.println( F(__FILE__) );
Serial.print( F(" compiled ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.println( F(__TIME__) );
}
// ignore at first
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
// ignore at first
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
}
}
best regards Stefan