#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SSD1306_NO_SPLASH
// ESP32
#define encoder0PinA 2
#define encoder0PinB 4
#define encoder0Press 5
// OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
// Misc
#define BUTTONPRESSEDSTATE 0
#define DEBOUNCEDELAY 20
const int menuTimeout = 10;
const bool menuLargeText = 0;
const int maxmenuItems = 12;
const int itemTrigger = 1;
const int topLine = 18;
const byte lineSpace1 = 9;
const byte lineSpace2 = 17;
const int displayMaxLines = 5;
const int MaxmenuTitleLength = 10;
void doEncoder();
void demoMenu();
void menuActions();
void value1();
void menuValues();
void reUpdateButton();
void serviceMenu();
int serviceValue(bool _blocking);
void createList(String _title, int _noOfElements, String *_list);
void displayMessage(String _title, String _message);
void resetMenu();
enum menuModes {
off,
menu,
value,
message,
};
menuModes menuMode = off;
struct oledMenus {
// menu
String menuTitle = "";
int noOfmenuItems = 0;
int selectedMenuItem = 0;
int highlightedMenuItem = 0;
String menuItems[maxmenuItems + 1];
uint32_t lastMenuActivity = 0;
int mValueEntered = 0;
int mValueLow = 0;
int mValueHigh = 0;
int mValueStep = 0;
};
oledMenus oledMenu;
struct rotaryEncoders {
volatile int encoder0Pos = 0;
volatile bool encoderPrevA;
volatile bool encoderPrevB;
uint8_t encoderPrevState;
uint32_t reLastButtonChange = 0;
bool encoderPrevButton = 0;
int reButtonDebounced = 0;
const bool reButtonPressedState = BUTTONPRESSEDSTATE;
const uint32_t reDebounceDelay = DEBOUNCEDELAY;
bool reButtonPressed = 0;
};
rotaryEncoders rotaryEncoder;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void demoMenu() {
resetMenu();
menuMode = menu;
oledMenu.noOfmenuItems = 4;
oledMenu.menuTitle = "menu";
oledMenu.menuItems[1] = "Start";
oledMenu.menuItems[2] = "Stop";
oledMenu.menuItems[3] = "Pos Down";
oledMenu.menuItems[4] = "Pos Up";
}
void menuActions() {
if (oledMenu.menuTitle == "menu") {
if (oledMenu.selectedMenuItem == 1) {
Serial.println("START");
}
if (oledMenu.selectedMenuItem == 2) {
Serial.println("STOP");
}
if (oledMenu.selectedMenuItem == 3) {
resetMenu();
menuMode = value;
oledMenu.menuTitle = "Up";
oledMenu.mValueLow = 0;
oledMenu.mValueHigh = 20000;
oledMenu.mValueStep = 100;
oledMenu.mValueEntered = 0;
}
if (oledMenu.selectedMenuItem == 4) {
resetMenu();
menuMode = value;
oledMenu.menuTitle = "Down";
oledMenu.mValueLow = 0;
oledMenu.mValueHigh = 20000;
oledMenu.mValueStep = 100;
oledMenu.mValueEntered = 0;
}
oledMenu.selectedMenuItem = 0;
}
}
void menuValues() {
if (oledMenu.menuTitle == "demo_value") {
String tString = String(oledMenu.mValueEntered);
displayMessage("ENTERED", "\nYou entered\nthe value\n " + tString);
}
if (oledMenu.menuTitle == "Up") {
demoMenu();
}
if (oledMenu.menuTitle == "Down") {
demoMenu();
}
}
void reUpdateButton() {
bool tReading = digitalRead(encoder0Press);
if (tReading != rotaryEncoder.encoderPrevButton) rotaryEncoder.reLastButtonChange = millis();
if ( (unsigned long)(millis() - rotaryEncoder.reLastButtonChange) > rotaryEncoder.reDebounceDelay ) {
if (rotaryEncoder.encoderPrevButton == rotaryEncoder.reButtonPressedState) {
if (rotaryEncoder.reButtonDebounced == 0) {
rotaryEncoder.reButtonPressed = 1;
if (menuMode == off) demoMenu();
}
rotaryEncoder.reButtonDebounced = 1;
} else {
rotaryEncoder.reButtonDebounced = 0;
}
}
rotaryEncoder.encoderPrevButton = tReading;
}
void menuUpdate() {
if (menuMode == off) return;
if ( (unsigned long)(millis() - oledMenu.lastMenuActivity) > (menuTimeout * 1000) ) {
resetMenu();
return;
}
switch (menuMode) {
case menu:
serviceMenu();
menuActions();
break;
case value:
serviceValue(0);
if (rotaryEncoder.reButtonPressed) {
menuValues();
break;
}
}
}
void serviceMenu() {
if (rotaryEncoder.encoder0Pos >= itemTrigger) {
rotaryEncoder.encoder0Pos -= itemTrigger;
oledMenu.highlightedMenuItem++;
oledMenu.lastMenuActivity = millis();
}
if (rotaryEncoder.encoder0Pos <= -itemTrigger) {
rotaryEncoder.encoder0Pos += itemTrigger;
oledMenu.highlightedMenuItem--;
oledMenu.lastMenuActivity = millis();
}
if (rotaryEncoder.reButtonPressed == 1) {
oledMenu.selectedMenuItem = oledMenu.highlightedMenuItem;
oledMenu.lastMenuActivity = millis();
}
const int _centreLine = displayMaxLines / 2 + 1;
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);
// 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(" ");
}
display.display();
}
int serviceValue(bool _blocking) {
const int _valueSpacingX = 30;
const int _valueSpacingY = 5;
uint32_t tTime;
do {
if (rotaryEncoder.encoder0Pos >= itemTrigger) {
rotaryEncoder.encoder0Pos -= itemTrigger;
oledMenu.mValueEntered -= oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis();
}
if (rotaryEncoder.encoder0Pos <= -itemTrigger) {
rotaryEncoder.encoder0Pos += itemTrigger;
oledMenu.mValueEntered += oledMenu.mValueStep;
oledMenu.lastMenuActivity = millis();
}
if (oledMenu.mValueEntered < oledMenu.mValueLow) {
oledMenu.mValueEntered = oledMenu.mValueLow;
oledMenu.lastMenuActivity = millis();
}
if (oledMenu.mValueEntered > oledMenu.mValueHigh) {
oledMenu.mValueEntered = oledMenu.mValueHigh;
oledMenu.lastMenuActivity = millis();
}
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);
// 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();
reUpdateButton();
tTime = (unsigned long)(millis() - oledMenu.lastMenuActivity);
} while (_blocking && rotaryEncoder.reButtonPressed == 0 && tTime < (menuTimeout * 1000));
if (_blocking) menuMode = off;
return oledMenu.mValueEntered;
}
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();
}
void resetMenu() {
menuMode = off;
oledMenu.selectedMenuItem = 0;
rotaryEncoder.encoder0Pos = 0;
oledMenu.noOfmenuItems = 0;
oledMenu.menuTitle = "";
oledMenu.highlightedMenuItem = 0;
oledMenu.mValueEntered = 0;
rotaryEncoder.reButtonPressed = 0;
oledMenu.lastMenuActivity = millis();
display.clearDisplay();
display.display();
}
void IRAM_ATTR doEncoder() {
static const int8_t lookupTable[] = {
0, 1, -1, 2,
-1, 0, 2, 1,
1, 2, 0, -1,
2, -1, 1, 0
};
static volatile bool pinA = false;
static volatile bool pinB = false;
pinA = digitalRead(encoder0PinA);
pinB = digitalRead(encoder0PinB);
rotaryEncoder.encoderPrevA = pinA;
rotaryEncoder.encoderPrevB = pinB;
int8_t position = (rotaryEncoder.encoderPrevA << 1) | (rotaryEncoder.encoderPrevB << 2) | (pinA << 1) | pinB;
rotaryEncoder.encoder0Pos -= lookupTable[position];
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(encoder0Press, INPUT_PULLUP);
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
rotaryEncoder.encoder0Pos = 0;
attachInterrupt(digitalPinToInterrupt(encoder0PinA), doEncoder, CHANGE);
demoMenu();
}
void loop() {
reUpdateButton();
menuUpdate();
}