// simple project using Arduino UNO and 128x64 OLED Display to display a menu
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// YOUTUBE VIDEO: https://youtu.be/HVHVkKt-ldc
// links from the video:
// Flipper Zero menu - https://docs.flipperzero.one/basics/control#M5BZO
// WOKWI start project progress bar - https://wokwi.com/projects/300867986768527882
// image2cpp - https://javl.github.io/image2cpp/
// 128x64 SSD1306 OLED Display: https://s.click.aliexpress.com/e/_DCKdvnh
// Transparent OLED display: https://s.click.aliexpress.com/e/_Dns6eLz
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Arduino UNO MINI: https://store.arduino.cc/products/uno-mini-le
// Big OLED Display: https://s.click.aliexpress.com/e/_ADL0T9
// Arduino breadboard prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// u8g fonts (fonts available for u8g library): https://nodemcu-build.com/u8g-fonts.php
// u8g documentation: https://github.com/olikraus/u8glib/wiki/userreference
// Photopea (online Photoshop-like tool): https://www.photopea.com/
// image2cpp (convert images into C code): https://javl.github.io/image2cpp/
// Push buttons - https://s.click.aliexpress.com/e/_DmXS8B9
// Related videos:
// Arduino Parking Sensor - https://youtu.be/sEWw087KOj0
// Turbo pressure gauge with Arduino and OLED display - https://youtu.be/JXmw1xOlBdk
// Arduino Car Cluster with OLED Display - https://youtu.be/El5SJelwV_0
// Knob over OLED Display - https://youtu.be/SmbcNx7tbX8
// Arduino + OLED = 3D ? - https://youtu.be/kBAcaA7NAlA
// Arduino OLED Gauge - https://youtu.be/xI6dXTA02UQ
// Smaller & Faster Arduino - https://youtu.be/4GfPQoIRqW8
#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI
// U8GLIB_SSD1306_128X64 u8g(13, 11, 8, 9, 10); // SPI connection
// for SPI connection, use this wiring:
// GND > GND
// VCC > 5V
// SCL > 13
// SDA > 11
// RES > 10
// DC > 9
// CS > 8
// all the arrays below are generated from images using image2cpp website
// scroll down to see the actual code
const unsigned char upir_logo [] PROGMEM = { // this is another way how to define images, using binary notation
B00010101, B11010111,
B00010101, B01000101,
B00010101, B10010110,
B00011001, B00010101
};
// 'icon_3dcube', 16x16px
const unsigned char bitmap_icon_3dcube [] PROGMEM = {
0x00, 0x00, 0x01, 0x80, 0x07, 0x60, 0x19, 0x18, 0x61, 0x06, 0x51, 0x0a, 0x45, 0xa2, 0x41, 0x02,
0x45, 0x22, 0x41, 0x02, 0x45, 0xa2, 0x51, 0x0a, 0x61, 0x06, 0x19, 0x18, 0x07, 0x60, 0x01, 0x80
};
// 'icon_battery', 16x16px
const unsigned char bitmap_icon_battery [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x40, 0x04, 0x5b, 0x66, 0x5b, 0x66,
0x5b, 0x66, 0x40, 0x04, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'turbo', 64x64px
const unsigned char epd_bitmap_turbo [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf7,
0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x73,
0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x33,
0x00, 0x1e, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x33, 0x00, 0x3c, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x33,
0x00, 0x70, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0xe1, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x33,
0x01, 0xc7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33,
0x07, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x06, 0x38, 0x00, 0xff, 0x00, 0x00, 0x00, 0x33,
0x0e, 0x70, 0x07, 0xff, 0xc0, 0x00, 0x00, 0x33, 0x1c, 0xe0, 0x1f, 0x81, 0xf0, 0x00, 0x00, 0x33,
0x18, 0xc0, 0x3c, 0x00, 0x3c, 0x00, 0x00, 0x33, 0x39, 0xc0, 0xf0, 0x00, 0x1e, 0x00, 0x00, 0x33,
0x31, 0x80, 0xe0, 0x3c, 0x07, 0xff, 0xff, 0xf3, 0x33, 0x81, 0xc1, 0xff, 0x83, 0xff, 0xff, 0xf3,
0x73, 0x03, 0x87, 0xff, 0xc1, 0xc0, 0x0e, 0x7f, 0x67, 0x07, 0x0f, 0x1c, 0xe1, 0xc0, 0x0c, 0x3e,
0x66, 0x06, 0x1c, 0x0c, 0x70, 0xc0, 0x0e, 0x08, 0xe6, 0x06, 0x18, 0x0c, 0x38, 0xe3, 0xc6, 0x00,
0xc6, 0x0e, 0x38, 0x1c, 0x18, 0x67, 0xe6, 0x00, 0xce, 0x0c, 0x3e, 0x7c, 0x1c, 0x66, 0x66, 0x00,
0xcc, 0x0c, 0x7f, 0xfe, 0x1c, 0x66, 0x66, 0x00, 0xcc, 0x0c, 0x61, 0xc7, 0xfc, 0x77, 0xe6, 0x00,
0xcc, 0x0c, 0x61, 0xc3, 0xfc, 0x73, 0xc6, 0x00, 0xcc, 0x0c, 0x61, 0xc3, 0x0c, 0x70, 0x06, 0x00,
0xc0, 0x0c, 0x60, 0xc7, 0x0c, 0x70, 0x06, 0x00, 0xc0, 0x0c, 0x70, 0xfe, 0x0c, 0x60, 0x06, 0x00,
0xc0, 0x0c, 0x31, 0xfe, 0x1c, 0x60, 0x06, 0x00, 0xc0, 0x0e, 0x33, 0x86, 0x18, 0x60, 0x06, 0x00,
0xe0, 0x0e, 0x1f, 0x07, 0x38, 0xe0, 0x06, 0x00, 0x60, 0x06, 0x1e, 0x03, 0xf0, 0xc0, 0x0e, 0x00,
0x60, 0x07, 0x0f, 0x01, 0xe1, 0xc0, 0x0c, 0x00, 0x70, 0x03, 0x87, 0xff, 0xc1, 0x80, 0x0c, 0x00,
0x30, 0x01, 0xc1, 0xff, 0x83, 0x80, 0x1c, 0x00, 0x30, 0x01, 0xe0, 0x38, 0x07, 0x00, 0x18, 0x00,
0x38, 0x00, 0xf0, 0x00, 0x1e, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7c, 0x00, 0x3c, 0x06, 0x30, 0x00,
0x1c, 0x00, 0x1f, 0x81, 0xf0, 0x0e, 0x70, 0x00, 0x0e, 0x00, 0x07, 0xff, 0xe0, 0x1c, 0x60, 0x00,
0x06, 0x00, 0x01, 0xff, 0x00, 0x38, 0xe0, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x71, 0xc0, 0x00,
0x03, 0x80, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x01, 0xc7, 0x00, 0x00,
0x00, 0xe0, 0x00, 0x00, 0x03, 0x8e, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00,
0x00, 0x3c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x07, 0xc0, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x1f, 0x80, 0x00, 0x00,
0x00, 0x00, 0x7f, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 384)
const unsigned char* bitmap_icons[3] = {
bitmap_icon_3dcube,
bitmap_icon_battery,
epd_bitmap_turbo,
};
// ------------------ end generated bitmaps from image2cpp ---------------------------------
const int NUM_ITEMS = 3; // number of items in the list and also the number of screenshots and screenshots with QR codes (other screens)
const int MAX_ITEM_LENGTH = 20; // maximum characters for the item name
char menu_items [NUM_ITEMS] [MAX_ITEM_LENGTH] = { // array with item names
{ "Fuel Info" },
{ "Trip Info" },
{ "Personalization" },
};
// note - when changing the order of items above, make sure the other arrays referencing bitmaps
// also have the same order, for example array "bitmap_icons" for icons, and other arrays for screenshots and QR codes
#define OUTPUTA 4 // pin for UP button
#define BUTTON_SELECT_PIN 8 // pin for SELECT button
#define OUTPUTB 5 // pin for DOWN button
#define FUEL_INFO 7
#define TRIP_INFO 10
#define PERSONALIZATION 11
#define SELECT 13
int button_up_clicked = 0; // only perform action when button is clicked, and wait until another press
int button_select_clicked = 0; // same as above
int button_down_clicked = 0; // same as above
int item_selected = 0; // which item in the menu is selected
int item_sel_previous; // previous item - used in the menu screen to draw the item before the selected one
int item_sel_next; // next item - used in the menu screen to draw next item after the selected one
int current_screen = 0; // 0 = menu, 1 = screenshot, 2 = qr
int aState;
int aLastState;
void setup() {
Serial.begin(9600);
u8g.setColorIndex(1); // set the color to white
// define pins for buttons
// INPUT_PULLUP means the button is HIGH when not pressed, and LOW when pressed
// since it´s connected between some pin and GND
pinMode(OUTPUTA, INPUT); // up button
pinMode(BUTTON_SELECT_PIN, INPUT); // select button
pinMode(OUTPUTB, INPUT); // down button
aLastState = digitalRead(OUTPUTA);
pinMode(FUEL_INFO, OUTPUT);
pinMode(TRIP_INFO, OUTPUT);
pinMode(PERSONALIZATION, OUTPUT);
pinMode(SELECT, OUTPUT);
}
void loop() {
aState = digitalRead(OUTPUTA);
if (current_screen == 0) { // MENU SCREEN
if (aState != aLastState) {
if (digitalRead(OUTPUTB) != aLastState) {
// up and down buttons only work for the menu screen
//if ((digitalRead(BUTTON_UP_PIN) == LOW) && (button_up_clicked == 0)) { // up button clicked - jump to previous menu item
item_selected = item_selected - 1; // select previous item
button_up_clicked = 1; // set button to clicked to only perform the action once
if (item_selected < 0) { // if first item was selected, jump to last item
item_selected = NUM_ITEMS - 1;
}
} else {
//else if ((digitalRead(BUTTON_DOWN_PIN) == LOW) && (button_down_clicked == 0)) { // down button clicked - jump to next menu item
item_selected = item_selected + 1; // select next item
button_down_clicked = 1; // set button to clicked to only perform the action once
if (item_selected >= NUM_ITEMS) { // last item was selected, jump to first menu item
item_selected = 0;
}
}
aLastState = aState;
}
// if ((digitalRead(BUTTON_UP_PIN) == HIGH) && (button_up_clicked == 1)) { // unclick
// button_up_clicked = 0;
// }
// if ((digitalRead(BUTTON_DOWN_PIN) == HIGH) && (button_down_clicked == 1)) { // unclick
// button_down_clicked = 0;
// }
}
if ((digitalRead(BUTTON_SELECT_PIN) == HIGH) && (button_select_clicked == 0)) { // select button clicked, jump between screens
button_select_clicked = 1; // set button to clicked to only perform the action once
//if (current_screen == 0) {
executeChoice(item_selected);
//}
if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_select_clicked == 1)) { // unclick
button_select_clicked = 0;
}
}
// set correct values for the previous and next items
item_sel_previous = item_selected - 1;
if (item_sel_previous < 0) {
item_sel_previous = NUM_ITEMS - 1; // previous item would be below first = make it the last
}
item_sel_next = item_selected + 1;
if (item_sel_next >= NUM_ITEMS) {
item_sel_next = 0; // next item would be after last = make it the first
}
u8g.firstPage(); // required for page drawing mode for u8g library
do {
if (current_screen == 0) { // MENU SCREEN
// selected item background
//u8g.drawBitmapP(0, 22, 128 / 8, 21, bitmap_item_sel_outline);
// draw previous item as icon + label
//u8g.setFont(u8g_font_7x14);
//u8g.drawStr(25, 15, menu_items[item_sel_previous]);
//u8g.drawBitmapP( 4, 2, 16 / 8, 16, bitmap_icons[item_sel_previous]);
// draw selected item as icon + label in bold font
u8g.setFont(u8g_font_7x14B);
//u8g.drawStr(25, 15 + 20 + 2, menu_items[item_selected]);
u8g.drawBitmapP( 46, 22, 16 / 8, 16, bitmap_icons[item_selected]);
// draw next item as icon + label
u8g.setFont(u8g_font_7x14);
//u8g.drawStr(25, 15 + 20 + 20 + 2 + 2, menu_items[item_sel_next]);
//u8g.drawBitmapP( 4, 46, 16 / 8, 16, bitmap_icons[item_sel_next]);
// draw upir logo
u8g.drawBitmapP(128 - 16 - 4, 64 - 4, 16 / 8, 4, upir_logo);
}
} while ( u8g.nextPage() ); // required for page drawing mode with u8g library
}
void executeChoice(int item_selected) {
switch (item_selected) {
case 0 :
Serial.print("Execute choice "); Serial.print(menu_items[item_selected]); Serial.print(" - "); Serial.println(menu_items[item_selected]);
digitalWrite(FUEL_INFO, HIGH);
delay(100);
digitalWrite(FUEL_INFO, LOW);
break;
case 1 :
Serial.print("Execute choice "); Serial.print(menu_items[item_selected]); Serial.print(" - "); Serial.println(menu_items[item_selected]);
digitalWrite(TRIP_INFO, HIGH);
delay(100);
digitalWrite(TRIP_INFO, LOW);
break;
case 2 :
Serial.print("Execute choice "); Serial.print(menu_items[item_selected]); Serial.print(" - "); Serial.println(menu_items[item_selected]);
digitalWrite(PERSONALIZATION, HIGH);
delay(100);
digitalWrite(PERSONALIZATION, LOW);
break;
// case 3 :
// Serial.print("Execute choice "); Serial.print(menu_items[item_selected]); Serial.print(" - "); Serial.println(menu_items[item_selected]);
// digitalWrite(SELECT, HIGH);
// delay(100);
// digitalWrite(SELECT, LOW);
// break;
default :
Serial.print("Execute choice "); Serial.print(menu_items[item_selected]); Serial.print(" - "); Serial.println(menu_items[item_selected]);
break;
}
}