-
Okay let's start.
Let me know when you need a rest, we can carry on the next day.
-
Attached is the skeleton Sketch I mentioned.
For now, we will use the Arduino UNO as our target, this sketch will work on the ESP32 with some GPIO changes.
-
We always start our projects with a schematic showing our component interconnections.
-
Wire up the components to your UNO as seen above.
-
Upload the sketch below, set serial monitor to 115200, report back what you see on the LEDs, push the Switch on GPIO #2.
-
You may need to get the hd44780.h Library before your LCD will work:
GitHub - duinoWitchery/hd44780: Extensible hd44780 LCD library
-
Do not worry about the code for now, we will cover things a bit at a time.
//
//================================================^================================================
// B a s i c S k e l e t o n S k e t c h
//
// https://forum.arduino.cc/t/arduino-millis-and-indexing-code/1376338/17
//
// LarryD
//
// Version YY/MM/DD Comments
// ======= ======== ========================================================================
// 1.00 23/01/14 Running code
//
//
//
//
// Notes:
//
//
//
//
//================================================
#include <Wire.h>
//Use I2C library: https://github.com/duinoWitchery/hd44780
//LCD Reference: https://www.arduino.cc/en/Reference/LiquidCrystal
#include <hd44780.h> //main hd44780 header
//NOTE:
//hd44780_I2Cexp control LCD using I2C I/O expander backpack (PCF8574 or MCP23008)
//hd44780_I2Clcd control LCD with native I2C interface (PCF2116, PCF2119x, etc...)
#include <hd44780ioClass/hd44780_I2Cexp.h> //I2C expander i/o class header
//If you do not know what your I2C address is, first run the "I2C_Scanner" sketch
//OR
//run the "I2CexpDiag" sketch that comes with the hd44780 library
//hd44780_I2Cexp lcd(0x3F);
hd44780_I2Cexp lcd(0x27);
//================================================
#define LEDon HIGH //PIN---[220R]---A[LED]K---GND
#define LEDoff LOW
#define PRESSED LOW //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED HIGH
#define CLOSED LOW //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED HIGH
#define ENABLED true
#define DISABLED false
#define RELAYon LOW
#define RELAYoff HIGH
//================================================
#define ONE_SECOND 1000ul //milliseconds
#define ONE_MINUTE (ONE_SECOND * 60ul) //60 seconds in one minute
#define ONE_HOUR (ONE_MINUTE * 60ul) //60 minutes in one hour
#define ONE_DAY (ONE_HOUR * 24ul) //24 hours in one day
// c l a s s m a k e T I M E R
//================================================^================================================
//
/*
//========================
makeTIMER toggleLED =
{
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
MILLIS/MICROS, 500ul, ENABLED/DISABLED, YES/NO, A0-A5
//.SpeedAdjustPin defaults to 0 i.e. no speed adjustment is used
//if .SpeedAdjustPin = A0-A5, a potentiometer on this pin adjusts the TIMER's speed (for diagnostics)
//class static flag "makeTIMER::normalFlag" can be used to ENABLE/DISABLE adjustable TIMER speed,
//ENABLE = normal speed, DISABLED = potentiometer controls TIMER speed
};
TIMER functions we can access:
toggleLED.checkTIMER();
toggleLED.enableRestartTIMER();
toggleLED.restartTIMER()
toggleLED.disableTIMER();
toggleLED.expireTimer();
toggleLED.setInterval(100ul);
Static variable access
makeTIMER::normalFlag = ENABLED/DISABLED //defaults to DISABLED at power up time i.e. variable speed is allowed
*/
// millis() / micros() B a s e d T I M E R S
//================================================^================================================
//
//These TIMER objects are non-blocking
class makeTIMER
{
#define MILLIS 0
#define MICROS 1
#define ENABLED true
#define DISABLED false
#define YES true
#define NO false
#define STILLtiming 0
#define EXPIRED 1
#define TIMERdisabled 2
private:
public:
static bool s_normalFlag; //when ENABLED, adjustable TIMERs run at normal speed
unsigned long Time; //when the TIMER started
//these "members" are needed to define a TIMER
byte TimerType; //what kind of TIMER is this? MILLIS/MICROS
unsigned long Interval; //delay time which we are looking for
bool TimerFlag; //is the TIMER enabled ? ENABLED/DISABLED
bool Restart; //do we restart this TIMER ? YES/NO
byte SpeedAdjustPin; //a potentiometer on this pin, A0-A5, adjusts TIMER speed
//================================================
//constructor with no parameters
makeTIMER()
{
TimerType = MILLIS;
Interval = 1000ul;
TimerFlag = ENABLED;
Restart = YES;
SpeedAdjustPin = 0;
Time = 0;
}
//================================================
//constructor with parameters
makeTIMER(byte _TimerType, unsigned long _Interval,
bool _TimerFlag, bool _Restart, byte _SpeedAdjustPin = 0)
{
TimerType = _TimerType;
Interval = _Interval;
TimerFlag = _TimerFlag;
Restart = _Restart;
SpeedAdjustPin = _SpeedAdjustPin;
Time = 0;
}
//================================================
//condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
//function to check the state of our TIMER ex: if(myTimer.checkTIMER() == EXPIRED);
byte checkTIMER()
{
//========================
//is this TIMER enabled ?
if (TimerFlag == ENABLED)
{
//============
//is this an adjustable TIMER OR is the "normalSpeed" switch closed ?
if (SpeedAdjustPin == 0 || s_normalFlag == ENABLED)
{
//============
//this TIMER "is not" speed adjustable,
//has this TIMER expired ?
if (getTime() - Time >= Interval)
{
//============
//should this TIMER restart again?
if (Restart == YES)
{
//restart this TIMER
Time = getTime();
}
//this TIMER has expired
return EXPIRED;
}
}
//============
//this TIMER is speed adjustable
else
{
//============
//for diagnostics, we use a potentiometer to adjust TIMER speed,
//has this TIMER expired ?
if (getTime() - Time >= Interval / adjustInterval())
{
//============
//should this TIMER restart again?
if (Restart == YES)
{
//restart this TIMER
Time = getTime();
}
//this TIMER has expired
return EXPIRED;
}
}
return STILLtiming;
} //END of if (TimerFlag == ENABLED)
//========================
else
{
//this TIMER is disabled
return TIMERdisabled;
}
} //END of checkTime()
//================================================
//function to enable and restart this TIMER ex: myTimer.enableRestartTIMER();
void enableRestartTIMER()
{
TimerFlag = ENABLED;
//restart this TIMER
Time = getTime();
} //END of enableRestartTIMER()
//================================================
//function to disable this TIMER ex: myTimer.disableTIMER();
void disableTIMER()
{
TimerFlag = DISABLED;
} //END of disableTIMER()
//================================================
//function to restart this TIMER ex: myTimer.restartTIMER();
void restartTIMER()
{
Time = getTime();
} //END of restartTIMER()
//================================================
//function to force this TIMER to expire ex: myTimer.expireTimer();
void expireTimer()
{
//force this TIMER to expire
Time = getTime() - Interval;
} //END of expireTimer()
//================================================
//function to set the Interval for this TIMER ex: myTimer.setInterval(100);
void setInterval(unsigned long value)
{
//set the Interval
Interval = value;
} //END of setInterval()
//================================================
//function to return the current time
unsigned long getTime()
{
//return the time i.e. millis() or micros()
//========================
if (TimerType == MILLIS)
{
return millis();
}
//========================
else
{
return micros();
}
} //END of getTime()
//================================================
//for diagnostics, a potentiometer on an analog pin is used to adjust TIMER speed, thanks alto777
unsigned int adjustInterval()
{
unsigned int Speed = analogRead(SpeedAdjustPin);
//using integer math to save on memory
Speed = 1 + (Speed * 14) / 1023; //Speed will have a range from 1 to 15
return Speed;
} //END of adjustInterval()
}; //END of class makeTIMER
//================================================
//initialize the static "s_normalFlag" variable,
//when ENABLED, adjustable TIMERs run at normal speed
bool makeTIMER::s_normalFlag = DISABLED;
// T I M E R D e f i n i t i o n s
//================================================^================================================
//
//========================
//example: uses default library values
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
// MILLIS, 1000ul, ENABLED, YES, 0
//makeTIMER testTIMER{};
//========================
makeTIMER heartbeatTIMER =
{
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
MILLIS, 500ul, ENABLED, YES, 0
};
//======================== (5ms * s_filter) i.e. 5ms * 10 = 50ms for checking a valid switch operation
makeTIMER switchesTIMER =
{
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
MILLIS, 5ul, ENABLED, YES, 0
};
//========================
makeTIMER machineTIMER =
{
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
MICROS, 1000ul, ENABLED, YES, 0
};
//========================
makeTIMER commonTIMER =
{
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
MILLIS, 1000ul, DISABLED, NO, 0
};
// c l a s s m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects, switches or sensors
//================================================
class makeInput
{
#define NOTvalidated 0
#define VALIDATED 1
#define NOchange 2
private:
public:
static byte s_filter;
//say the above validating "s_filter" variable is set to 10
//if we scan "inputs" every 5ms
//i.e. we sample our inputs every 5ms looking for a change in state.
//5ms * 10 = 50ms is needed to validate a switch change in state.
//i.e. a switch change in state is valid "only after" 10 identical changes are detected.
//This technique is used to filter out EMI (spikes), noise, etc.
//i.e. we ignore switch changes in state that are less than 50ms.
unsigned long switchTime; //the time the switch was closed
byte counter; //a counter used for validating a switch change in state
//these "members" are needed to define an "Input"
byte pin; //the digital input pin number
byte lastState; //the state the input was last in
//================================================
//constructor with parameters
makeInput(byte _pin, byte _lastState)
{
pin = _pin;
lastState = _lastState;
switchTime = 0;
counter = 0;
pinMode(pin, INPUT_PULLUP);
}
//================================================
//condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
//check to see if the input object has had a valid state change
byte validChange()
{
byte currentState = digitalRead(pin);
//===================================
//has there been an input change in state ?
if (lastState != currentState)
{
//we have had another similar change in state
counter++;
//is the "change in state" stable ?
if (counter >= s_filter)
{
//an input change has been validated
//get ready for the next scanning sequence
counter = 0;
//update to this new state
lastState = currentState;
if (currentState == CLOSED)
{
//capture the time when the switch closed
switchTime = millis();
}
return VALIDATED;
}
return NOTvalidated;
}
//===================================
//there has not been an input change in state
counter = 0;
return NOchange;
} //END of validChange()
}; //END of class makeInput
//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row are seen
byte makeInput::s_filter = 10;
// S t a t e M a c h i n e
//================================================^================================================
//
//the states in our State Machine
enum STATES : byte
{
STARTUP, STATE1, STATE2, STATE3, STATE4, FINISHED
};
STATES mState = STARTUP;
// G P I O s A n d V a r i a b l e s
//================================================^================================================
//
//Analogs
//================================================
//
//INPUTS
//================================================
//
//============ GPIO 2
makeInput mySwitch =
{
//.pin, .lastState
2, OPENED
};
//OUTPUTS
//================================================
//
const byte testLED = 12;
const byte heartbeatLED = 13;
//VARIABLES
//================================================
//
const unsigned long shortPushTime = 500ul;
const unsigned long longPushTime = 2000ul;
// s e t u p ( )
//================================================^================================================
//
void setup()
{
Serial.begin(115200);
digitalWrite(heartbeatLED, LEDoff);
pinMode(heartbeatLED, OUTPUT);
digitalWrite(testLED, LEDoff);
pinMode(testLED, OUTPUT);
//================================================
//LCD stuff
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// Skeleton
lcd.print(" Skeleton ");
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// Sketch
lcd.print(" Sketch ");
} //END of setup()
// l o o p ( )
//================================================^================================================
//
void loop()
{
//========================================================================
//Print the time it takes to return to this same spot.
//Comment the next 3 lines when no longer needed
//static unsigned long startTime;
//Serial.println(micros() - startTime);
//startTime = micros();
//======================================================================== T I M E R heartbeatLED
//condition returned: STILLtiming, EXPIRED or TIMERdisabled
//is it time to toggle the heartbeat LED ?
if (heartbeatTIMER.checkTIMER() == EXPIRED)
{
//toggle the heartbeat LED
digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
}
//======================================================================== T I M E R switches
//condition returned: STILLtiming, EXPIRED or TIMERdisabled
//is it time to check our switches ?
if (switchesTIMER.checkTIMER() == EXPIRED)
{
checkSwitches();
}
//======================================================================== T I M E R machine
//condition returned: STILLtiming, EXPIRED or TIMERdisabled
//is it time to service our State Machine ?
if (machineTIMER.checkTIMER() == EXPIRED)
{
checkMachine();
}
//================================================
// Other non blocking code goes here
//================================================
} //END of loop()
// c h e c k M a c h i n e ( )
//================================================^================================================
//
void checkMachine()
{
//================================================
//service the current "state"
switch (mState)
{
//========================
case STARTUP:
{
//do startup stuff
}
break;
//========================
case STATE1:
{
//condition returned: STILLtiming, EXPIRED or TIMERdisabled
if (commonTIMER.checkTIMER() == EXPIRED)
{
digitalWrite(testLED, LEDoff);
//we are finished with this TIMER
commonTIMER.disableTIMER();
//next state
mState = STARTUP;
}
}
break;
//========================
case STATE2:
{
//Do something
}
break;
//========================
case STATE3:
{
//Do something
}
break;
//========================
case STATE4:
{
//Do something
}
break;
//========================
case FINISHED:
{
//Do something
}
break;
} //END of switch/case
} //END of checkMachine()
// c h e c k S w i t c h e s ( )
//================================================^================================================
//
//we have access to:
//object.validChange() - checks to see if there was a valid state change
//object.pin - input hardware pin number
//object.lastState - the state the input was/is in
//object.switchTime - the millis() value when the switch closes
void checkSwitches()
{
//======================================================================== mySwitch
//was there a valid input change ?
//condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
if (mySwitch.validChange() == VALIDATED)
{
//========================
//was this switch closed ?
if (mySwitch.lastState == CLOSED)
{
digitalWrite(testLED, LEDon);
//make this TIMER 2 seconds
commonTIMER.setInterval(2000ul);
//start this TIMER
commonTIMER.enableRestartTIMER();
//next state
mState = STATE1;
}
//========================
//this switch was opened
else
{
Serial.print("The switch was closed for ");
Serial.print(millis() - mySwitch.switchTime);
Serial.println("ms.");
//================================================ Short Push
//was this a short push ?
if (millis() - mySwitch.switchTime <= shortPushTime)
{
//do something
Serial.println("Short Push");
}
//================================================ Long Push
//was this a long push ?
else if (millis() - mySwitch.switchTime >= longPushTime)
{
//do something
Serial.println("Long Push");
}
//================================================ Regular Push
//this was a regular push
else
{
//do something
Serial.println("Regular Push");
}
}
} //END of mySwitch
} //END of checkSwitches()
//
//================================================^================================================
//