/**************************************************************************************************
*
* OLED display simple none blocking menu System - i2c version SSD1306 - 30mar22
*
* This sketch is on Github: https://github.com/alanesq/BasicOLEDMenu
*
**************************************************************************************************
The sketch displays a menu on the oled and when an item is selected it sets a
flag and waits until the event is acted upon. Max menu items on a 128x64 oled
is four.
Notes: text size 1 = 21 x 8 characters on the larger oLED display
text size 2 = 10 x 4
For more oled info see: https://randomnerdtutorials.com/guide-for-oled-display-with-arduino/
See the "menus below here" section for examples of how to use the menus
**************************************************************************************************/
#include <Arduino.h> // required by platformIO
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SSD1306_NO_SPLASH
// ----------------------------------------------------------------
// S E T T I N G S
// ----------------------------------------------------------------
// OLED(SSD1306)のI2Cアドレスや解像度定義
#define OLED_ADDR 0x3C // OLED i2c address
#define SCREEN_WIDTH 128 // OLED display width, in pixels (usually 128)
#define SCREEN_HEIGHT 64 // OLED display height, in pixels (64 for larger oLEDs)
#define OLED_RESET -1 // Reset pin gpio (or -1 if sharing Arduino reset pin)
//各ボタンのGPIPピンを定義
#define BUTTON_UP_PIN 8 // pin for UP button
#define BUTTON_DOWN_PIN 9 // pin for DOWN button
#define BUTTON_OK_PIN 10 // pin for Select button
#define BUTTON_CANCEL_PIN 11 // pin for CANCEL button
#define BUTTON_MENU_PIN 12 // pin for MENU button
int button_up_clicked = 0; // only perform action when button is clicked, and wait until another press
int button_down_clicked = 0; // same as above
int button_ok_clicked = 0; // same as above
int button_ok_pressed = 0;
int button_cancel_clicked = 0; //
int button_cancel_pressed = 0; //
int button_menu_clicked = 0; //
int button_updown_value = 0;
const int serialDebug = 0;
const int menuTimeout = 20; // 無操作時に消灯するまでのタイムアウト (秒指定)
const bool menuLargeText = 0; // show larger text when possible (if struggling to read the small text)
const int maxMenuItems = 12; // max number of items used in any of the menus (keep as low as possible to save memory)
const int topLine = 18; // y position of lower area of the display (18 with two colour displays)
const byte lineSpace1 = 9; // line spacing for textsize 1 (small text)
const byte lineSpace2 = 17; // line spacing for textsize 2 (large text)
const int displayMaxLines = 5; // max lines that can be displayed in lower section of display in textsize1 (5 on larger oLeds)
const int MaxmenuTitleLength = 10; // max characters per line when using text size 2 (usually 10)
const String menuTitle_LMV = "LMV Menu";
// -------------------------------------------------------------------------------------------------
// forward declarations
void menuActions();
void value1();
void menuValues();
void serviceMenu();
int serviceValue(bool _blocking);
void createList(String _title, int _noOfElements, String *_list);
void displayMessage(String _title, String _message);
void resetMenu();
// menus
// modes that the menu system can be in
enum menuModes
{
off, // display is off
menu, // a menu is active
value, // 'enter a value' none blocking is active
message, // displaying a message
blocking, // a blocking procedure is in progress (see enter value)
valIp
};
menuModes menuMode = off; // default mode at startup is off
struct oledMenus
{
// menu
String menuTitle = ""; // the title of active mode
int noOfmenuItems = 0; // number if menu items in the active menu
int selectedMenuItem = 0; // when a menu item is selected it is flagged here until actioned and cleared
int highlightedMenuItem = 0; // which item is curently highlighted in the menu
String menuItems[maxMenuItems + 1]; // store for the menu item titles
uint32_t lastMenuActivity = 0; // time the menu last saw any activity (used for timeout)
// 'enter a value'
int mValueEntered = 0; // store for number entered by value entry menu
int mValueLow = 0; // lowest allowed value
int mValueHigh = 0; // highest allowed value
int mValueStep = 0; // step size when encoder is turned
};
oledMenus oledMenu;
// -------------------------------------------------------------------------------------------------
// The custom menus go below here
// -------------------------------------------------------------------------------------------------
// Start the default menu
void defaultMenu()
{
resetMenu(); // clear any previous menu
menuMode = menu; // enable menu mode
oledMenu.noOfmenuItems = 8; // set the number of items in this menu
oledMenu.menuTitle = menuTitle_LMV; // menus title (used to identify it)
oledMenu.menuItems[1] = "SHOW IP ADDR";
oledMenu.menuItems[2] = "SET IP ADDR";
oledMenu.menuItems[3] = "STATUS";
oledMenu.menuItems[4] = "On or Off";
oledMenu.menuItems[5] = "Enter value";
oledMenu.menuItems[6] = "Enter value-blocking";
oledMenu.menuItems[7] = "Message";
oledMenu.menuItems[8] = "Menus Off";
}
// -----------------------------------------------
// Actions for menu selections are put in here
// note: it would probably be better to put these in their own seperate procedures but I have kept
// them all in this one place to make it easier to follow
void menuActions()
{
if (oledMenu.menuTitle == menuTitle_LMV)
{ // actions when an item is selected in "demo_menu"
if (oledMenu.selectedMenuItem == 1)
{
displayMessage("IPv4", "192.168.10.128\n10.50.20.64\n");
}
if (oledMenu.selectedMenuItem == 2)
{
//IP変更
resetMenu();
value_ip(); // enter a value IP
}
// demonstrate quickly create a menu from a list
if (oledMenu.menuTitle == menuTitle_LMV && oledMenu.selectedMenuItem == 3)
{
//各種ステータス表示
if (serialDebug)
Serial.println("LMV menu: create menu from a list");
String tList[] = {"capture_rock:OK", "control_ronin:OK", "control_sony:OK", "CPU:30%", "MEM:3.5GB", "Tempeture:54C"};
createList("status", 6, &tList[0]);
}
// demonstrate selecting between 2 options only
if (oledMenu.selectedMenuItem == 4)
{
resetMenu();
menuMode = value;
oledMenu.menuTitle = "on or off";
oledMenu.mValueLow = 0;
oledMenu.mValueHigh = 1;
oledMenu.mValueStep = 1;
oledMenu.mValueEntered = 0; // set parameters
}
// demonstrate usage of 'enter a value' (none blocking)
if (oledMenu.selectedMenuItem == 5)
{
if (serialDebug)
Serial.println("LMV_menu: none blocking enter value");
resetMenu();
value1(); // enter a value
}
// demonstrate usage of 'enter a value' (blocking) which is quick and easy but stops all other tasks until the value is entered
if (oledMenu.selectedMenuItem == 6)
{
if (serialDebug)
Serial.println("demo_menu: blocking enter a value");
// set perameters
resetMenu();
menuMode = value;
oledMenu.menuTitle = "blocking";
oledMenu.mValueLow = 0;
oledMenu.mValueHigh = 50;
oledMenu.mValueStep = 1;
oledMenu.mValueEntered = 5;
int tEntered = serviceValue(1); // request value
Serial.println("The value entered was " + String(tEntered));
defaultMenu();
}
// demonstrate usage of message
if (oledMenu.selectedMenuItem == 7)
{
if (serialDebug)
{
Serial.println("demo_menu: message");
}
displayMessage("Message", "Hello\nThis is a demo\nmessage."); // 21 chars per line, "\n" = next line
}
// turn menu/oLED off
else if (oledMenu.selectedMenuItem == 8)
{
if (serialDebug)
Serial.println("demo_menu: menu off");
resetMenu(); // turn menus off
}
oledMenu.selectedMenuItem = 0; // clear menu item selected flag as it has been actioned
}
// actions when an item is selected in the demo_list menu
if (oledMenu.menuTitle == "demo_list")
{
// back to main menu
if (oledMenu.selectedMenuItem == 1)
{
if (serialDebug)
Serial.println("demo_list: back to main menu");
defaultMenu();
}
oledMenu.selectedMenuItem = 0; // clear menu item selected flag as it has been actioned
}
} // menuActions
// -----------------------------------------------
// demonstration of how to enter a value
void value1()
{
resetMenu(); // clear any previous menu
menuMode = value; // enable value entry
oledMenu.menuTitle = "demo_value"; // title (used to identify which number was entered)
oledMenu.mValueLow = 0; // minimum value allowed
oledMenu.mValueHigh = 100; // maximum value allowed
oledMenu.mValueStep = 1; // step size
oledMenu.mValueEntered = 50; // starting value
}
void value_ip()
{
resetMenu(); // clear any previous menu
menuMode = valIp; // enable value entry
oledMenu.menuTitle = "Set IP Addr"; // title (used to identify which number was entered)
oledMenu.mValueLow = 0; // minimum value allowed
oledMenu.mValueHigh = 100; // maximum value allowed
oledMenu.mValueStep = 1; // step size
oledMenu.mValueEntered = 50; // starting value
}
// actions for value entered put in here
void menuValues()
{
// action for "demo_value"
if (oledMenu.menuTitle == "demo_value")
{
String tString = String(oledMenu.mValueEntered);
if (serialDebug)
Serial.println("demo_value: The value entered was " + tString);
displayMessage("ENTERED", "\nYou entered\nthe value\n " + tString);
// alternatively use 'resetMenu()' here to turn menus off after value entered - or use 'defaultMenu()' to re-start the default menu
}
// action for "on or off"
if (oledMenu.menuTitle == "on or off")
{
if (serialDebug)
Serial.println("demo_menu: on off selection was " + String(oledMenu.mValueEntered));
defaultMenu();
}
}
// -------------------------------------------------------------------------------------------------
// custom menus go above here
// -------------------------------------------------------------------------------------------------
// oled SSD1306 display connected to I2C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup()
{
Serial.begin(115200);
while (!Serial)
;
delay(50); // start serial comms
Serial.println("\nStarting LMV Menu\n");
pinMode(BUTTON_UP_PIN, INPUT_PULLUP); // up button
pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); // down button
pinMode(BUTTON_OK_PIN, INPUT_PULLUP); // OK button
pinMode(BUTTON_CANCEL_PIN, INPUT_PULLUP); // Cancel button
pinMode(BUTTON_MENU_PIN, INPUT_PULLUP); // Menu button
// initialise the oled display
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR))
{
if (serialDebug)
Serial.println(("\nError initialising the oled display"));
}
Wire.setClock(100000);
defaultMenu(); // start the default menu
}
// ----------------------------------------------------------------
// -loop
// ----------------------------------------------------------------
// called from main loop
void loop()
{
updateOkButton(); //
menuUpdate(); // update or action the oled menu
// ボタンを押していない状態→ボタン押下の判定
if ((digitalRead(BUTTON_UP_PIN) == LOW) && (button_up_clicked == 0))
{ // up button clicked - jump to previous menu item
Serial.println("Btn Up Click");
button_up_clicked = 1;
button_ok_pressed = 0;
button_cancel_pressed = 0;
}
else if ((digitalRead(BUTTON_DOWN_PIN) == LOW) && (button_down_clicked == 0))
{ // down button clicked - jump to next menu item
Serial.println("Btn Down Click");
button_down_clicked = 1;
button_ok_pressed = 0;
button_cancel_pressed = 0;
}
else if ((digitalRead(BUTTON_MENU_PIN) == LOW) && (button_menu_clicked == 0))
{
Serial.println("Btn Menu Click");
button_menu_clicked = 1;
button_ok_pressed = 0;
button_cancel_pressed = 0;
}
// ボタン押下→離した場合の判定
if ((digitalRead(BUTTON_UP_PIN) == HIGH) && (button_up_clicked == 1))
{ // unclick
Serial.println("Btn Up UnClick");
button_up_clicked = 0;
button_updown_value = -1;
}
if ((digitalRead(BUTTON_DOWN_PIN) == HIGH) && (button_down_clicked == 1))
{ // unclick
Serial.println("Btn Down UnClick");
button_down_clicked = 0;
button_updown_value = 1;
}
if ((digitalRead(BUTTON_MENU_PIN) == HIGH) && (button_menu_clicked == 1))
{ // unclick
Serial.println("Btn Menu UnClick");
button_menu_clicked = 0;
defaultMenu();
}
} // oledLoop
// OKボタンの押下状況
void updateOkButton()
{
if ((digitalRead(BUTTON_OK_PIN) == LOW) && (button_ok_clicked == 0))
{
Serial.println("Btn OK Click");
button_ok_clicked = 1;
}
if ((digitalRead(BUTTON_OK_PIN) == HIGH) && (button_ok_clicked == 1))
{ // unclick
Serial.println("Btn OK UnClick");
button_ok_clicked = 0;
button_ok_pressed = 1;
}
if ((digitalRead(BUTTON_CANCEL_PIN) == LOW) && (button_cancel_clicked == 0))
{
Serial.println("Btn Cancel Click");
button_cancel_clicked = 1;
}
if ((digitalRead(BUTTON_CANCEL_PIN) == HIGH) && (button_cancel_clicked == 1))
{ // unclick
Serial.println("Btn Cancel UnClick");
button_cancel_clicked = 0;
button_cancel_pressed = 1;
defaultMenu();
}
}
// ----------------------------------------------------------------
// -update the active menu
// ----------------------------------------------------------------
void menuUpdate()
{
if (menuMode == off)
{
return; // if menu system is turned off do nothing more
}
// if no recent activity then turn oled off
//指定時間経過による消灯
if ((unsigned long)(millis() - oledMenu.lastMenuActivity) > (menuTimeout * 1000))
{
resetMenu();
return;
}
switch (menuMode)
{
// if there is an active menu
case menu:
serviceMenu();
menuActions();
break;
// if there is an active none blocking 'enter value'
case value:
serviceValue(0);
if (button_ok_pressed == 1)
{ // if the button has been pressed
menuValues(); // a value has been entered so action it
break;
}
case valIp:
serviceValueIp(0);
if (button_ok_pressed == 1)
{ // if the button has been pressed
menuValues(); // a value has been entered so action it
break;
}
// if a message is being displayed
case message:
if (button_ok_pressed == 1)
{
defaultMenu(); // if button has been pressed return to default menu
}
break;
}
}
// ----------------------------------------------------------------
// -service active menu
// ----------------------------------------------------------------
void serviceMenu()
{
if (button_updown_value == 1)
{
//下方向に進む
oledMenu.highlightedMenuItem++;
if(oledMenu.highlightedMenuItem > oledMenu.noOfmenuItems)
{
oledMenu.highlightedMenuItem = oledMenu.noOfmenuItems;
}
Serial.println("current item '" + oledMenu.menuItems[oledMenu.highlightedMenuItem] + "'");
button_updown_value = 0;
oledMenu.lastMenuActivity = millis(); // log time
}
if (button_updown_value == -1)
{
//上方向に進む
oledMenu.highlightedMenuItem--;
if(oledMenu.highlightedMenuItem < 1)
{
oledMenu.highlightedMenuItem = 1;
}
Serial.println("current item '" + oledMenu.menuItems[oledMenu.highlightedMenuItem] + "'");
button_updown_value = 0;
oledMenu.lastMenuActivity = millis(); // log time
}
if (button_ok_pressed == 1)
{
oledMenu.selectedMenuItem = oledMenu.highlightedMenuItem; // flag that the item has been selected
oledMenu.lastMenuActivity = millis(); // log time
if (serialDebug)
Serial.println("menu '" + oledMenu.menuTitle + "' item '" + oledMenu.menuItems[oledMenu.highlightedMenuItem] + "' selected");
}
const int _centreLine = displayMaxLines / 2 + 1; // mid list point
display.clearDisplay();
display.setTextColor(WHITE);
// verify valid highlighted item
if (oledMenu.highlightedMenuItem > oledMenu.noOfmenuItems)
oledMenu.highlightedMenuItem = oledMenu.noOfmenuItems;
if (oledMenu.highlightedMenuItem < 1)
oledMenu.highlightedMenuItem = 1;
// title
display.setCursor(0, 0);
if (menuLargeText)
{
display.setTextSize(2);
display.println(oledMenu.menuItems[oledMenu.highlightedMenuItem].substring(0, MaxmenuTitleLength));
}
else
{
if (oledMenu.menuTitle.length() > MaxmenuTitleLength)
display.setTextSize(1);
else
display.setTextSize(2);
display.println(oledMenu.menuTitle);
}
display.drawLine(0, topLine - 1, display.width(), topLine - 1, WHITE); // draw horizontal line under title
// menu
display.setTextSize(1);
display.setCursor(0, topLine);
for (int i = 1; i <= displayMaxLines; i++)
{
int item = oledMenu.highlightedMenuItem - _centreLine + i;
if (item == oledMenu.highlightedMenuItem)
display.setTextColor(BLACK, WHITE);
else
display.setTextColor(WHITE);
if (item > 0 && item <= oledMenu.noOfmenuItems)
display.println(oledMenu.menuItems[item]);
else
display.println(" ");
}
//// how to display some updating info. on the menu screen
// display.setCursor(80, 25);
// display.println(millis());
display.display();
}
// ----------------------------------------------------------------
// -service value entry
// ----------------------------------------------------------------
// if _blocking set to 1 then all other tasks are stopped until a value is entered
int serviceValue(bool _blocking)
{
const int _valueSpacingX = 30; // spacing for the displayed value y position
const int _valueSpacingY = 5; // spacing for the displayed value y position
if (_blocking)
{
menuMode = blocking;
oledMenu.lastMenuActivity = millis(); // log time of last activity (for timeout)
}
uint32_t tTime;
do
{
if (button_updown_value == 1)
{
button_updown_value = 0;
oledMenu.mValueEntered -= oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis(); // log time
}
if (button_updown_value == -1)
{
button_updown_value = 0;
oledMenu.mValueEntered += oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis(); // log time
}
if (oledMenu.mValueEntered < oledMenu.mValueLow)
{
oledMenu.mValueEntered = oledMenu.mValueLow;
oledMenu.lastMenuActivity = millis(); // log time
}
if (oledMenu.mValueEntered > oledMenu.mValueHigh)
{
oledMenu.mValueEntered = oledMenu.mValueHigh;
oledMenu.lastMenuActivity = millis(); // log time
}
display.clearDisplay();
display.setTextColor(WHITE);
// title
display.setCursor(0, 0);
if (oledMenu.menuTitle.length() > MaxmenuTitleLength)
display.setTextSize(1);
else
display.setTextSize(2);
display.println(oledMenu.menuTitle);
display.drawLine(0, topLine - 1, display.width(), topLine - 1, WHITE); // draw horizontal line under title
// value selected
display.setCursor(_valueSpacingX, topLine + _valueSpacingY);
display.setTextSize(3);
display.println(oledMenu.mValueEntered);
// range
display.setCursor(0, display.height() - lineSpace1 - 1); // bottom of display
display.setTextSize(1);
display.println(String(oledMenu.mValueLow) + " to " + String(oledMenu.mValueHigh));
// bar
int Tlinelength = map(oledMenu.mValueEntered, oledMenu.mValueLow, oledMenu.mValueHigh, 0, display.width());
display.drawLine(0, display.height() - 1, Tlinelength, display.height() - 1, WHITE);
display.display();
updateOkButton(); // check status of button
tTime = (unsigned long)(millis() - oledMenu.lastMenuActivity); // time since last activity
} while (_blocking && button_ok_pressed == 0 && tTime < (menuTimeout * 1000)); // if in blocking mode repeat until button is pressed or timeout
if (_blocking)
{
menuMode = off;
}
return oledMenu.mValueEntered; // used when in blocking mode
}
int serviceValueIp(bool _blocking)
{
const int _valueSpacingX = 30; // spacing for the displayed value y position
const int _valueSpacingY = 5; // spacing for the displayed value y position
if (_blocking)
{
menuMode = blocking;
oledMenu.lastMenuActivity = millis(); // log time of last activity (for timeout)
}
uint32_t tTime;
do
{
if (button_updown_value == 1)
{
button_updown_value = 0;
oledMenu.mValueEntered -= oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis(); // log time
}
if (button_updown_value == -1)
{
button_updown_value = 0;
oledMenu.mValueEntered += oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis(); // log time
}
if (oledMenu.mValueEntered < oledMenu.mValueLow)
{
oledMenu.mValueEntered = oledMenu.mValueLow;
oledMenu.lastMenuActivity = millis(); // log time
}
if (oledMenu.mValueEntered > oledMenu.mValueHigh)
{
oledMenu.mValueEntered = oledMenu.mValueHigh;
oledMenu.lastMenuActivity = millis(); // log time
}
display.clearDisplay();
display.setTextColor(WHITE);
// title
display.setCursor(0, 0);
if (oledMenu.menuTitle.length() > MaxmenuTitleLength)
display.setTextSize(1);
else
display.setTextSize(2);
display.println(oledMenu.menuTitle);
display.drawLine(0, topLine - 1, display.width(), topLine - 1, WHITE); // draw horizontal line under title
// value selected
display.setCursor(0, topLine + _valueSpacingY);
display.setTextSize(1);
display.println("192.168.10.128");
display.display();
updateOkButton(); // check status of button
tTime = (unsigned long)(millis() - oledMenu.lastMenuActivity); // time since last activity
} while (_blocking && button_ok_pressed == 0 && tTime < (menuTimeout * 1000)); // if in blocking mode repeat until button is pressed or timeout
if (_blocking)
{
menuMode = off;
}
return oledMenu.mValueEntered; // used when in blocking mode
}
// ----------------------------------------------------------------
// -list create
// ----------------------------------------------------------------
// create a menu from a list
// e.g. String tList[]={"main menu", "2", "3", "4", "5", "6"};
// createList("demo_list", 6, &tList[0]);
void createList(String _title, int _noOfElements, String *_list)
{
resetMenu(); // clear any previous menu
menuMode = menu; // enable menu mode
oledMenu.noOfmenuItems = _noOfElements; // set the number of items in this menu
oledMenu.menuTitle = _title; // menus title (used to identify it)
for (int i = 1; i <= _noOfElements; i++)
{
oledMenu.menuItems[i] = _list[i - 1]; // set the menu items
}
}
// ----------------------------------------------------------------
// -message display
// ----------------------------------------------------------------
// 21 characters per line, use "\n" for next line
// assistant: < line 1 >< line 2 >< line 3 >< line 4 >
void displayMessage(String _title, String _message)
{
resetMenu();
menuMode = message;
display.clearDisplay();
display.setTextColor(WHITE);
// title
display.setCursor(0, 0);
if (menuLargeText)
{
display.setTextSize(2);
display.println(_title.substring(0, MaxmenuTitleLength));
}
else
{
if (_title.length() > MaxmenuTitleLength)
display.setTextSize(1);
else
display.setTextSize(2);
display.println(_title);
}
// message
display.setCursor(0, topLine);
display.setTextSize(1);
display.println(_message);
display.display();
}
// ----------------------------------------------------------------
// -reset menu system
// ----------------------------------------------------------------
void resetMenu()
{
// reset all menu variables / flags
menuMode = off;
oledMenu.selectedMenuItem = 0;
button_updown_value = 0;
oledMenu.noOfmenuItems = 0;
oledMenu.menuTitle = "";
oledMenu.highlightedMenuItem = 0;
oledMenu.mValueEntered = 0;
button_ok_pressed = 0;
button_cancel_pressed = 0;
oledMenu.lastMenuActivity = millis(); // log time
// clear oled display
display.clearDisplay();
display.display();
}
// ---------------------------------------------- end ----------------------------------------------