#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Function prototyping
uint8_t readButtons();
void mnHandleMenu();
void doLED();
bool WipeMenu();
uint8_t mnMainPage(uint8_t ctlButton);
uint8_t mnSetNumBlinks(uint8_t ctlButton);
uint8_t mnEnableBlinks(uint8_t ctlButton);
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2); //main
// LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x20, 16, 2); // for proteus
// give the menu page states names
enum menuStates
{
ST_INIT = 0,
ST_MAIN,
ST_WIPE
};
// possible return values from menu pages
enum menuReturns
{
MN_CONTINUE = 0,
MN_DONE
};
// up/dn/sel switch returns (includes no switch)
enum swReturns_e
{
NO_SW = 0,
UP,
DN,
SEL
};
// fixed values for LED toggle and inter-frame delays
const uint32_t BLNK_LEDDLY = 250ul;
const uint32_t BLNK_BIGDLY = 2000ul;
const uint8_t
grSwReturns[] = {NO_SW, UP, DN, SEL};
const uint8_t
grControlButtons[3] = {2, 3, 4};
const uint8_t pinLED = LED_BUILTIN;
bool bBlinkEnable = false;
uint8_t grPinLasts[3];
uint8_t numBlinks = 10;
// pointer to a function that returns a u8 and which expects a u8
typedef uint8_t (*pFunc_t)(uint8_t);
// activeFunc is a pointer to the currently-active menu page
pFunc_t activeFunc;
void setup()
{
// set up the up/dn/sel pins
for (uint8_t i = 0; i < 3; i++)
{
pinMode(grControlButtons[i], INPUT_PULLUP);
grPinLasts[i] = digitalRead(grControlButtons[i]);
}
// init the LCD
lcd.init();
lcd.backlight();
// and set the LED to blink
pinMode(pinLED, OUTPUT);
// init the menu function pointer to the main menu page
activeFunc = &mnMainPage;
} // setup
void loop()
{
// blink the LED
doLED();
// handle the menu
mnHandleMenu();
}
uint8_t readButtons(void)
{
static uint8_t
idx = 0;
static uint32_t
tRead = 0ul;
uint32_t
tNow = millis();
uint8_t
retval,
swNow;
// read the switches, one every 10mS
retval = NO_SW;
if (tNow - tRead >= 1ul)
{
tRead = tNow;
swNow = digitalRead(grControlButtons[idx]);
if (swNow != grPinLasts[idx])
{
grPinLasts[idx] = swNow;
if (swNow == LOW)
retval = grSwReturns[idx + 1];
}
idx++;
if (idx == 3)
idx = 0;
}
return retval;
} // readButtons
void mnHandleMenu(void)
{
// call the active menu page and send it the latest up/dn/sel
// states
activeFunc(readButtons());
} // mnHandleMenu
void doLED(void)
{
static bool
lastEnable = bBlinkEnable;
static uint8_t
nBlinks = 10,
bLEDState = false;
static uint32_t
tBlink = 0ul,
tDelay = BLNK_BIGDLY;
uint32_t
tNow = millis();
// if not time for a blink or the blink is disabled, just leave
if ((tNow - tBlink) < tDelay || bBlinkEnable == false)
return;
// has bBlinkEnable changed to "true"?
if (lastEnable != bBlinkEnable)
{
lastEnable = bBlinkEnable;
if (bBlinkEnable == true)
// yes; set its blink count to the global count
nBlinks = numBlinks;
}
// blink the LED
tBlink = tNow;
bLEDState ^= true;
digitalWrite(pinLED, bLEDState ? HIGH : LOW);
tDelay = BLNK_LEDDLY;
if (bLEDState == false)
{
// if the LED has gone off, decrement the local blink count
nBlinks--;
// if it has reached zero...
if (nBlinks == 0)
{
tDelay = BLNK_BIGDLY;
nBlinks = numBlinks;
}
}
} // doLED
uint8_t mnMainPage(uint8_t ctlButton)
{
static int8_t
highlight = 0;
static uint8_t
state = ST_INIT;
switch (state)
{
case ST_INIT:
// initialize this menu page
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(F("Set #LEDblnk"));
lcd.setCursor(1, 1);
lcd.print(F("Tgl LEDblnk"));
highlight = 0;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
state = ST_MAIN;
break;
case ST_MAIN:
// we stay in the main page until we hit SEL
switch (ctlButton)
{
case UP:
lcd.setCursor(0, highlight);
lcd.print(F(" "));
highlight--;
if (highlight < 0)
highlight = 1;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
break;
case DN:
lcd.setCursor(0, highlight);
lcd.print(F(" "));
highlight++;
if (highlight > 1)
highlight = 0;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
break;
case SEL:
// user hit select
// move to the "wipe" state to "animate" clearing the menu...
state = ST_WIPE;
break;
}
break;
case ST_WIPE:
// when WipeMenu returns true, the old menu is erased so...
if (WipeMenu() == true)
{
// set the activeFunc to the menu item the user selected
activeFunc = (highlight == 0) ? &mnSetNumBlinks : &mnEnableBlinks;
// and set the local state here to INIT again so when
// we get back to this menu the screen is properly initialized
state = ST_INIT;
}
break;
}
return MN_CONTINUE;
} // mnMainPage
uint8_t mnSetNumBlinks(uint8_t ctlButton)
{
char
szStr[10];
static int8_t
highlight = 0;
static uint8_t
state = ST_INIT;
// these states are similar to the main page but are specific to
// setting the number of blinks
switch (state)
{
case ST_INIT:
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(F("Set#Blinks"));
lcd.setCursor(4, 1);
sprintf(szStr, "%3d", numBlinks);
lcd.print(szStr);
state = ST_MAIN;
break;
case ST_MAIN:
switch (ctlButton)
{
case UP:
if (numBlinks < 255)
numBlinks++;
lcd.setCursor(4, 1);
sprintf(szStr, "%3d", numBlinks);
lcd.print(szStr);
break;
case DN:
if (numBlinks > 10)
numBlinks--;
lcd.setCursor(4, 1);
sprintf(szStr, "%3d", numBlinks);
lcd.print(szStr);
break;
case SEL:
state = ST_WIPE;
break;
}
break;
case ST_WIPE:
if (WipeMenu() == true)
{
activeFunc = &mnMainPage;
state = ST_INIT;
}
break;
}
return MN_CONTINUE;
} // mnSetNumBlinks
uint8_t mnEnableBlinks(uint8_t ctlButton)
{
char
szStr[10];
static int8_t
highlight = 0;
static uint8_t
state = ST_INIT;
// these states are similar to the main page but are specific to
// enabling or disabling the blinking
switch (state)
{
case ST_INIT:
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(F("Enable"));
lcd.setCursor(1, 1);
lcd.print(F("Disable"));
highlight = 0;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
state = ST_MAIN;
break;
case ST_MAIN:
switch (ctlButton)
{
case UP:
lcd.setCursor(0, highlight);
lcd.print(F(" "));
highlight--;
if (highlight < 0)
highlight = 1;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
break;
case DN:
lcd.setCursor(0, highlight);
lcd.print(F(" "));
highlight++;
if (highlight > 1)
highlight = 0;
lcd.setCursor(0, highlight);
lcd.print(F(">"));
break;
case SEL:
state = ST_WIPE;
break;
}
break;
case ST_WIPE:
if (WipeMenu() == true)
{
activeFunc = &mnMainPage;
bBlinkEnable = (highlight == 0) ? true : false;
state = ST_INIT;
}
break;
}
return MN_CONTINUE;
} // mnEnableBlinks
bool WipeMenu(void)
{
static uint8_t
col = 0;
static uint32_t
tChar = 0ul;
uint32_t
tNow = micros();
if (tNow - tChar >= 15000ul)
{
tChar = tNow;
for (uint8_t i = 0; i < 2; i++)
{
lcd.setCursor(col, i);
lcd.print(F(" "));
}
col++;
if (col == 16)
{
col = 0;
return true;
}
}
return false;
} // WipeMenu