Here is an example from a few weeks ago.
You will see a switch press looks at acceptable window values that we must fall within.
Scanning of the ladder is done every 100ms.
//
// https://forum.arduino.cc/t/using-millis-to-blink-two-leds-at-differant-durations/944151?u=larryd
//
// Version YY/MM/DD Comments
// ======= ======== =======================================================
// 1.00 22/01/08 Running code
// 1.02 22/01/08 using micros() for better pulse timing
// 1.03 22/01/16 Added LCD and increment and decrement switches to adjust timing of LEDs
// 1.04 22/01/17 Changed out individual switches for analog_switch_ladder
// 1.05 22/01/17 Added EEPROM stuff
// 1.06 22/01/17 Fixed a timing bug
// 1.07 22/01/18 Now using 'pulseHighCounter' to disable LCD updates while a pulse is HIGH.
// This prevents the possibility of adding LCD code execution time to our pulse.
// 1.08 22/01/18 Added "graemecns" numbers to reflect his resistor ladder.
// 1.09 22/01/19 Ignore all 'non-essential ' code execution 'while' we are timing a pulse
//
#include <EEPROM.h>
//******************************************************************************
//Switch operation:
//
//Left/Right switches change the menu
//Up/Down switches change the value of the variable
//Select switch updates the EEPROM with all new values
//******************************************************************************
//remove comment marks for an I2C LCD
#define serialLCDisBeingUsed //uncomment if you have a serial LCD <--------------<<<<<
//********************************************
#ifdef serialLCDisBeingUsed
#include <Wire.h>
//LarryD's ladder numbers
#define RIGHT_10BIT_ADC 0 //Right
#define UP_10BIT_ADC 145 //Up
#define DOWN_10BIT_ADC 329 //Down
#define LEFT_10BIT_ADC 505 //Left
#define SELECT_10BIT_ADC 741 //Select
//Use I2C library: https://github.com/duinoWitchery/hd44780
//LCD Reference: https://www.arduino.cc/en/Reference/LiquidCrystal
//this LCD is I2C wired
#include <hd44780.h> //main hd44780 header
//N O T E :
//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: I2CexpDiag.ino
//C:\Users\YourName\Documents\Arduino1.69\libraries\hd44780\examples\ioClass\hd44780_I2Cexp\I2CexpDiag
hd44780_I2Cexp lcd(0x3F);
//hd44780_I2Cexp lcd(0x27);
//********************************************
#else
//this LCD is parallel wired
#include <LiquidCrystal.h>
// LCD pin 4 6 11 12 13 14
// RS EN D4 D5 D6 D7
LiquidCrystal lcd( 8, 9, 4, 5, 6, 7);
//"graemecns" ladder numbers
#define RIGHT_10BIT_ADC 0 //Right
#define UP_10BIT_ADC 101 //Up
#define DOWN_10BIT_ADC 259 //Down
#define LEFT_10BIT_ADC 410 //Left
#define SELECT_10BIT_ADC 640 //Select
#endif
//******************************************************************************
#define LEDon HIGH
#define LEDoff LOW
#define ENABLED true
#define DISABLED false
#define OPENED HIGH
#define CLOSED LOW //+5V---[Internal 50k]---PIN---[switch]---GND
//For diagnostics only
//macro definition for a pulse on UNO pin 11 i.e. PINB3
//63ns
//pinMode(11,OUTPUT); // add to setup()
//#define PULSE62_11 cli(); PINB = bit(PINB3); PINB = bit(PINB3); sei()
//******************************************************************************
// https://www.freetronics.com.au/pages/16x2-lcd-shield-quickstart-guide#.YeUrY_7MLcd
//
//Analog switch ladder stuff
// A0
// |
//Resistor Ladder +5V---[ 2k ]-+-[330R]-.-[620R]-.-[ 1k ]-.-[3.3k]-.
// | | | | |
// Rsw Usw Dsw Lsw Ssw
// '--------'--------'--------'--------'--------GND
#define BUTTON_ADC_PIN A0 //A0 is connected to the ladder as seen above
//ADC readings expected for the 5 buttons on the ADC input
#define BUTTONHYSTERESIS 10 //hysteresis for valid button sensing window
//return values for checkSwitches()
#define BUTTON_NONE 0
#define BUTTON_RIGHT 1
#define BUTTON_UP 2
#define BUTTON_DOWN 3
#define BUTTON_LEFT 4
#define BUTTON_SELECT 5
byte buttonJustPressed = false; //true after a checkSwitches() call if triggered
byte buttonJustReleased = true; //true after a checkSwitches() call if triggered
byte buttonWas = BUTTON_NONE; //for detection of button events
//******************************************************************************
//LCD State Machine stuff
enum SM {Startup, Waiting, LED1onTime, LED2onTime, LEDsOffTime, LastState};
SM displayStateMachine = Startup;
//******************************************************************************
const byte LED1pin = 2;
const byte LED2pin = 3;
const byte heartbeatLED = 13;
bool LED1Flag = DISABLED;
bool LED2Flag = DISABLED;
bool newCycleFlag = ENABLED;
bool nextCycleFlag = DISABLED;
//For reference, an update to the LCD can take 10-20 ms,
//hence we can consider an LCD update results in short term blocking of our code.
//N O T E : when the pulseHighCounter is not equal to zero, we disable updates to the LCD.
//If we did not disable updates when a pulse was HIGH, and we "called" the update LCD routine,
//the update routine execution time is added (can be added) to the pulse time.
//This would cause a pulse width that is wider than we want.
//
byte pulseHighCounter = 0;
byte menuCounter = Waiting;
byte buttonSwitch = BUTTON_NONE;
const unsigned int incDecAmount = 100; //100 for both micro and milli seconds adjustments
//EEPROM stuff
//N O T E : if the EEPROM data is valid, EEPROM location 0 will be 0xAA
unsigned int LED1onDurationAddress = 10; //10 11 12 13
unsigned int LED2onDurationAddress = 14; //14 15 16 17
unsigned int LEDoffTimeAddress = 18; //18 19 20 21
//timing stuff
unsigned long LED1onDurationDefault = 8 * 1000UL; //ON time for LED1 in micro seconds
unsigned long LED1onDuration = LED1onDurationDefault;
unsigned long LED1onDurationNew;
unsigned long LED2onDurationDefault = 10 * 1000UL; //ON time for LED2 in micro seconds
unsigned long LED2onDuration = LED2onDurationDefault;
unsigned long LED2onDurationNew;
unsigned long LEDoffTimeDefault = 2 * 1000UL; //time between cycles in milliseconds
unsigned long LEDoffTime = LEDoffTimeDefault;
unsigned long LEDoffTimeNew;
unsigned long heartbeatMillis;
unsigned long switchMillis;
unsigned long displaySM_Millis;
unsigned long nextCycleMillis;
unsigned long LED1onMicros;
unsigned long LED2onMicros;
//******************************************************************************
void setup()
{
Serial.begin(115200);
pinMode(heartbeatLED, OUTPUT);
//pinMode(11, OUTPUT);
pinMode(LED1pin, OUTPUT);
digitalWrite(LED1pin, LEDoff);
pinMode(LED2pin, OUTPUT);
digitalWrite(LED2pin, LEDoff);
lcd.begin(16, 2);
} //END of setup()
//******************************************************************************
void loop()
{
//**************************************** c h e c k S w i t c h e s T I M E R
//is it time to check the switches ?
if (pulseHighCounter == 0 && millis() - switchMillis >= 100)
{
//restart this TIMER
switchMillis = millis();
buttonSwitch = checkSwitches();
}
//**************************************** L E D 1 O F F T I M E R
//time to turn OFF LED1 ?
if (LED1Flag == ENABLED && micros() - LED1onMicros >= LED1onDuration)
{
digitalWrite(LED1pin, LEDoff);
//DISABLE this TIMER
LED1Flag = DISABLED;
pulseHighCounter--;
}
//**************************************** L E D 2 O F F T I M E R
//time to turn OFF LED2 ?
if (LED2Flag == ENABLED && micros() - LED2onMicros >= LED2onDuration)
{
digitalWrite(LED2pin, LEDoff);
//DISABLE this TIMER
LED2Flag = DISABLED;
pulseHighCounter--;
}
//=============================================================
//non critical events are only allowed when we 'are not' waiting for a pulse to finish
if (pulseHighCounter == 0)
{
//**************************************** h e a r t b e a t L E D T I M E R
//is it time to toggle the heartbeatLED ?
if (pulseHighCounter == 0 && millis() - heartbeatMillis >= 500)
{
//restart this TIMER
heartbeatMillis = millis();
//Toggle the heartbeatLED
digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
}
//**************************************** n e x t C y c l e T I M E R
//time to start a new cycle
if (nextCycleFlag == ENABLED && millis() - nextCycleMillis >= LEDoffTime)
{
//DISABLE this TIMER
nextCycleFlag = DISABLED;
//allow another cycle to start
newCycleFlag = ENABLED;
}
//**************************************** d i s p l a y u p d a t e T I M E R
//is it time to check the display State Machine ?
if (millis() - displaySM_Millis >= 500)
{
//restart this TIMER
displaySM_Millis = millis();
//N O T E : any 'Machine State' that updates the LCD takes ~ 20ms to complete
//hence we can consider an LCD update results in short term blocking of our code
displayMachine();
}
//**************************************** L E D s O N ?
//should we turn on the LEDs ?
if (newCycleFlag == ENABLED)
{
newCycleFlag = DISABLED;
//enable the LED1 OFF TIMER
LED1Flag = ENABLED;
//start the TIMER
LED1onMicros = micros();
digitalWrite(LED1pin, LEDon);
//this disables slow LCD updates while any LED/pulse is HIGH
pulseHighCounter++;
//enable the LED2 OFF TIMER
LED2Flag = ENABLED;
//start the TIMER
LED2onMicros = micros();
digitalWrite(LED2pin, LEDon);
//this disables slow LCD updates while any LED/pulse is HIGH
pulseHighCounter++;
//enable the nextCycle TIMER
nextCycleFlag = ENABLED;
//start the TIMER
nextCycleMillis = millis();
}
} //END of if(pulseHighCounter == 0)
//****************************************
//Other non blocking code goes here
//****************************************
} //END of loop()
//******************************************************************************
void displayMachine()
{
switch (displayStateMachine)
{
//****************************************
case Startup:
{
//return the variables to their last values
//is the EEPROM data valid ?
if (EEPROM_ReadByte(0) == 0xAA)
{
//get the values from EEPROM
LED1onDuration = EEPROM_ReadLong(LED1onDurationAddress);
LED2onDuration = EEPROM_ReadLong(LED2onDurationAddress);
LEDoffTime = EEPROM_ReadLong(LEDoffTimeAddress);
}
//we have to update to the default times
else
{
//mark the EEPROM as valid data
EEPROM_WriteByte(0, 0xAA);
//save the default times
EEPROM_WriteLong(LED1onDurationAddress, LED1onDurationDefault);
EEPROM_WriteLong(LED2onDurationAddress, LED2onDurationDefault);
EEPROM_WriteLong(LEDoffTimeAddress, LEDoffTimeDefault);
}
//update the "New" variables
LED1onDurationNew = LED1onDuration;
LED2onDurationNew = LED2onDuration;
LEDoffTimeNew = LEDoffTime;
//next state
displayStateMachine = Waiting;
}
break;
//****************************************
case Waiting:
{
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// 1=XXXXX 2=YYYYY
lcd.print("1= 2= ");
lcd.setCursor(2, 0);
lcd.print(LED1onDuration);
lcd.setCursor(11, 0);
// 111111
// 0123456789012345
// 1=XXXXX 2=YYYYY
lcd.print(LED2onDuration);
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// Off = ZZZZZ ms
lcd.print("Off = ms ");
lcd.setCursor(6, 1);
// 111111
// 0123456789012345
// Off = ZZZZZ ms
lcd.print(LEDoffTime);
}
break;
//****************************************
case LED1onTime:
{
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// LED1 ON = XXXXX
lcd.print("LED1 ON = ");
lcd.setCursor(10, 0);
lcd.print(LED1onDuration);
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// New = XXXXX
lcd.print("New = ");
lcd.setCursor(10, 1);
lcd.print(LED1onDurationNew);
}
break;
//****************************************
case LED2onTime:
{
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// LED2 ON = XXXXX
lcd.print("LED2 ON = ");
lcd.setCursor(10, 0);
lcd.print(LED2onDuration);
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// New = XXXXX
lcd.print("New = ");
lcd.setCursor(10, 1);
lcd.print(LED2onDurationNew);
}
break;
//****************************************
case LEDsOffTime:
{
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// LED1 ON = XXXXX
lcd.print("LED OFF = ");
lcd.setCursor(10, 0);
lcd.print(LEDoffTime);
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// New = XXXXX
lcd.print("New = ");
lcd.setCursor(10, 1);
lcd.print(LEDoffTimeNew);
}
break;
} //END of switch...Case
} //END of displayMachine()
//******************************************************************************
byte checkSwitches()
{
unsigned int buttonVoltage;
//start out assuming no button is pressed
byte button = BUTTON_NONE;
//read the switch ladder voltage
buttonVoltage = analogRead(BUTTON_ADC_PIN);
//****************************** i n c r e m e n t m e n u
//is the voltage within valid voltage windows
if (buttonVoltage < (RIGHT_10BIT_ADC + BUTTONHYSTERESIS))
{
button = BUTTON_RIGHT;
//is this a new button press ?
if (buttonJustReleased == true)
{
buttonJustReleased = false;
//increment the menu
menuCounter++;
//have we reached the end of the menus ?
if (menuCounter == LastState)
{
menuCounter = Waiting;
}
displayStateMachine = menuCounter;
}
}
//****************************** i n c r e m e n t t i m e
else if (buttonVoltage >= (UP_10BIT_ADC - BUTTONHYSTERESIS)
&& buttonVoltage <= (UP_10BIT_ADC + BUTTONHYSTERESIS))
{
button = BUTTON_UP;
//is this a new button press ?
if (buttonJustReleased == true)
{
buttonJustReleased = false;
switch (displayStateMachine)
{
//*****************
case LED1onTime:
{
//microsecond adjustment
LED1onDurationNew = LED1onDurationNew + incDecAmount;
}
break;
//*****************
case LED2onTime:
{
//microsecond adjustment
LED2onDurationNew = LED2onDurationNew + incDecAmount;
}
break;
//*****************
case LEDsOffTime:
{
//millisecond adjustment
LEDoffTimeNew = LEDoffTimeNew + incDecAmount;
}
break;
} //END of switch...case
}
}
//****************************** d e c r e m e n t t i m e
else if (buttonVoltage >= (DOWN_10BIT_ADC - BUTTONHYSTERESIS)
&& buttonVoltage <= (DOWN_10BIT_ADC + BUTTONHYSTERESIS))
{
button = BUTTON_DOWN;
//is this a new button press ?
if (buttonJustReleased == true)
{
buttonJustReleased = false;
switch (displayStateMachine)
{
//*****************
case LED1onTime:
{
//microsecond adjustment
LED1onDurationNew = LED1onDurationNew - incDecAmount;
//don't go too low
if (LED1onDurationNew < incDecAmount)
{
LED1onDurationNew = incDecAmount;
}
}
break;
//*****************
case LED2onTime:
{
//microsecond adjustment
LED2onDurationNew = LED2onDurationNew - incDecAmount;
//don't go too low
if (LED2onDurationNew < incDecAmount)
{
LED2onDurationNew = incDecAmount;
}
}
break;
//*****************
case LEDsOffTime:
{
//millisecond adjustment
LEDoffTimeNew = LEDoffTimeNew - incDecAmount;
//don't go too low
if (LEDoffTimeNew < incDecAmount)
{
LEDoffTimeNew = incDecAmount;
}
}
break;
} //END of switch...case
}
}
//****************************** d e c r e m e n t m e n u
else if (buttonVoltage >= (LEFT_10BIT_ADC - BUTTONHYSTERESIS)
&& buttonVoltage <= (LEFT_10BIT_ADC + BUTTONHYSTERESIS))
{
button = BUTTON_LEFT;
//is this a new button press ?
if (buttonJustReleased == true)
{
buttonJustReleased = false;
//decrement the menu
menuCounter--;
//have we reached the end of the menus ?
if (menuCounter == Startup)
{
menuCounter = Waiting;
}
displayStateMachine = menuCounter;
}
}
//****************************** s t o r e n e w t i m e s
else if (buttonVoltage >= (SELECT_10BIT_ADC - BUTTONHYSTERESIS)
&& buttonVoltage <= (SELECT_10BIT_ADC + BUTTONHYSTERESIS))
{
button = BUTTON_SELECT;
//is this a new button press ?
if (buttonJustReleased == true)
{
buttonJustReleased = false;
//update the EEPROM to the new values, if they have changed
//*****************
if (LED1onDuration != LED1onDurationNew)
{
LED1onDuration = LED1onDurationNew;
EEPROM_WriteLong(LED1onDurationAddress, LED1onDurationNew);
}
//*****************
if (LED2onDuration != LED2onDurationNew)
{
LED2onDuration = LED2onDurationNew;
EEPROM_WriteLong(LED2onDurationAddress, LED2onDurationNew);
}
//*****************
if (LEDoffTime != LEDoffTimeNew)
{
LEDoffTime = LEDoffTimeNew;
EEPROM_WriteLong(LEDoffTimeAddress, LEDoffTimeNew);
}
}
//need to adjust our menu counter to the new state
menuCounter = Waiting;
displayStateMachine = Waiting;
}
//******************************
//handle button flags for just pressed and just released events
if ((buttonWas == BUTTON_NONE) && (button != BUTTON_NONE))
{
//the button was just pressed, set buttonJustPressed, this can optionally be used to
//trigger a once-off action for a button press event
//it's the duty of the receiver to clear these flags if it wants to detect
//a new button change event
buttonJustPressed = true;
buttonJustReleased = false;
}
//******************************
if ((buttonWas != BUTTON_NONE) && (button == BUTTON_NONE))
{
buttonJustPressed = false;
buttonJustReleased = true;
}
//save the latest button value, for change event detection next time round
buttonWas = button;
//******************************
return (button);
} //END of checkSwitches()
//******************************************************************************
//This function will write a 4 byte (32bit) long to the EEPROM at
//the specified address to address + 3.
void EEPROM_WriteLong(int address, long value)
{
//Decomposition from a long to 4 bytes by using bit shift.
//One = Most significant -> Four = Least significant byte
byte four = (value & 0xFF);
byte three = ((value >> 8) & 0xFF);
byte two = ((value >> 16) & 0xFF);
byte one = ((value >> 24) & 0xFF);
//Write the 4 bytes into the eeprom memory.
EEPROM.write(address, four);
EEPROM.write(address + 1, three);
EEPROM.write(address + 2, two);
EEPROM.write(address + 3, one);
} //END of EEPROM_WriteLong()
//******************************************************************************
//This function will write a byte (8 bits) to the EEPROM at
//the specified address.
void EEPROM_WriteByte(int address, byte value)
{
//Decomposition from a long to 1 byte by using bit shift.
byte one = (value);
//Write the 1 bytes into the eeprom memory.
EEPROM.write(address, one);
} //END of EEPROM_WriteByte()
//******************************************************************************
long EEPROM_ReadLong(int address)
{
//Read the 4 bytes from the EEPROM memory.
long four = EEPROM.read(address);
long three = EEPROM.read(address + 1);
long two = EEPROM.read(address + 2);
long one = EEPROM.read(address + 3);
long r;
r = (one << 24);
r = r + (two << 16);
r = r + (three << 8);
r = r + four;
return r;
} //END of EEPROM_ReadLong()
//******************************************************************************
byte EEPROM_ReadByte(int address)
{
//Read the 1 byte from the EEPROM memory.
byte one = EEPROM.read(address);
//Return the Byte
return one;
} //END of EEPROM_ReadByte()
//******************************************************************************
// E N D O F C O D E
//******************************************************************************