// 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
// YOUTUBE VIDEO u8g2 version: https://youtu.be/K5e0lFRvZ2E
// 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
// LCD displays: https://s.click.aliexpress.com/e/_DBgR45P
// u8g2 documentation: https://github.com/olikraus/u8g2/wiki/u8gvsu8g2
// Image Magick: https://imagemagick.org/index.php
// LCD Image converter: https://lcd-image-converter.riuson.com/en/about/
// 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 "U8g2lib.h"
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); // [full framebuffer, size = 1024 bytes]
// all the arrays below are generated from images using Image Magick
// scroll down to see the actual code
const unsigned char upir_logo [] PROGMEM = {
0xEA, 0x3A, 0xAA, 0x28, 0x6A, 0x1A, 0x26, 0x2A, };
// 'lock', 16x16px
const unsigned char epd_bitmap_lock [] PROGMEM = {
0x80, 0x01, 0xc0, 0x03, 0x20, 0x04, 0x10, 0x08, 0x08, 0x10, 0x04, 0x20, 0x02, 0x40, 0xff, 0xff,
0x01, 0x80, 0x01, 0x80, 0xc9, 0x93, 0x55, 0xaa, 0x49, 0x93, 0x41, 0x82, 0x41, 0x82, 0xff, 0xff
};
// 'setting', 16x16px
const unsigned char epd_bitmap_setting [] PROGMEM = {
0xe0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x0a, 0x50, 0x12, 0x48, 0xc1, 0x83, 0x21, 0x84, 0x3f, 0xfc,
0x21, 0x84, 0x21, 0x84, 0xc1, 0x83, 0x12, 0x48, 0x0a, 0x50, 0x04, 0x20, 0x18, 0x18, 0xe0, 0x07
};
// 'pass', 16x16px
const unsigned char epd_bitmap_pass [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x1c, 0x10, 0x2a, 0x10, 0x49, 0x10, 0x08, 0x10,
0x08, 0x10, 0x08, 0x92, 0x08, 0x54, 0x08, 0x38, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const unsigned char epd_bitmap_SE1 [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xbf, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0xff, 0x0f, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xfe, 0xff, 0x1f, 0x1c, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc0, 0xff, 0xff, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x0f,
0xf8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x07, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x7f, 0x0e, 0xe0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xe0, 0xff,
0xff, 0x03, 0x00, 0x00, 0x80, 0xff, 0xff, 0x1f, 0xc0, 0xff, 0xff, 0xff, 0x00, 0x00, 0xc0, 0xff,
0xff, 0x3f, 0x00, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xc0, 0xff, 0xff, 0x63, 0x00, 0xfe, 0xff, 0xff,
0xff, 0x07, 0xc0, 0xff, 0xff, 0x01, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x7f, 0x80, 0xff, 0xff, 0x00,
0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x03, 0xfe, 0xff, 0x01, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff,
0x1f, 0xfc, 0xff, 0x01, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0x03, 0x00, 0x04,
0x00, 0xfc, 0xff, 0xff, 0xff, 0xc3, 0xff, 0x03, 0x00, 0x78, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x0f,
0xff, 0x01, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xfe, 0x00, 0x00, 0xe0, 0xff, 0x0f,
0x00, 0xe0, 0xff, 0x3f, 0xfc, 0x01, 0x00, 0x80, 0xff, 0xff, 0x0f, 0x00, 0xfe, 0xff, 0xf8, 0x01,
0x00, 0x00, 0xff, 0xff, 0xff, 0x03, 0xf0, 0xff, 0xf1, 0x03, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x3f,
0x80, 0xff, 0xe3, 0x03, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x03, 0xfe, 0xe3, 0x03, 0x00, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x1f, 0xf8, 0xc7, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x7f, 0xe0,
0xcf, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0xf8, 0xff, 0xc1, 0x8f, 0x01, 0x00, 0x00, 0xc0, 0x3f,
0x00, 0x00, 0xfc, 0x07, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x80, 0x1f, 0x1e, 0x00,
0x00, 0x00, 0x00, 0xf8, 0xff, 0x7f, 0x00, 0x3c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff,
0x0f, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfe, 0xff, 0x07, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0x20, 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
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 608)
const int epd_bitmap_allArray_LEN = 1;
const unsigned char* epd_bitmap_allArray[1] = {
epd_bitmap_SE1
};
// 'add', 16x16px
const unsigned char epd_bitmap_add [] PROGMEM = {
0x00, 0x00, 0xf0, 0x00, 0x08, 0x01, 0x08, 0x01, 0x08, 0x01, 0x08, 0x01, 0xf0, 0x00, 0x0c, 0x7f,
0x84, 0x80, 0x82, 0x88, 0x81, 0x88, 0x81, 0xbe, 0x80, 0x88, 0x80, 0x88, 0x80, 0x80, 0x00, 0x7f
};
// 'delete', 16x16px
const unsigned char epd_bitmap_delete [] PROGMEM = {
0x00, 0x00, 0xf0, 0x00, 0x08, 0x01, 0x08, 0x01, 0x08, 0x01, 0x08, 0x01, 0xf0, 0x00, 0x0c, 0x7f,
0x84, 0x80, 0x82, 0x80, 0x81, 0x80, 0x81, 0xbe, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x7f
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 384)
const unsigned char* bitmap_icons[2] = {
epd_bitmap_lock,
epd_bitmap_setting,
};
const unsigned char* bitmap_icons1[3] = {
epd_bitmap_pass,
epd_bitmap_add,
epd_bitmap_delete
};
// 'screenshot_3dcube', 128x64px
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8320)
// 'qr_3dcube', 128x64px
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8320)
// 'item_sel_outline', 128x21px
const unsigned char bitmap_item_sel_outline [] PROGMEM = {
0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xF8, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
};
// ------------------ 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 = 10; // maximum characters for the item name
const int NUM_ITEMS1 = 3; // number of items in the list and also the number of screenshots and screenshots with QR codes (other screens)
const int MAX_ITEM_LENGTH1 = 10; // maximum characters for the item name
char menu_items [NUM_ITEMS] [MAX_ITEM_LENGTH] = { // array with item names
{ "Unlock" },
{ "Setting" },
};
char menu_items1 [NUM_ITEMS1] [MAX_ITEM_LENGTH1] = { // array with item names
{ "Change PW" },
{ "Add ID" },
{ "Delete ID" },
};
// 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 BUTTON_UP_PIN 12 // pin for UP button
#define BUTTON_SELECT_PIN 8 // pin for SELECT button
#define BUTTON_DOWN_PIN 4 // pin for DOWN button
#define DEMO_PIN 13 // pin for demo mode, use switch or wire to enable or disable demo mode, see more details below
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 demo_mode = 0; // when demo mode is set to 1, it automatically goes over all the screens, 0 = control menu with buttons
int demo_mode_state = 0; // demo mode state = which screen and menu item to display
int demo_mode_delay = 0; // demo mode delay = used to slow down the screen switching
int x = 0;
void setup() {
u8g2.setColorIndex(1); // set the color to white
u8g2.begin();
u8g2.setBitmapMode(1);
// 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(BUTTON_UP_PIN, INPUT_PULLUP); // up button
pinMode(BUTTON_SELECT_PIN, INPUT_PULLUP); // select button
pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); // down button
pinMode(DEMO_PIN, INPUT_PULLUP);
}
void loop() {
if (digitalRead(DEMO_PIN) == LOW) {
demo_mode = 1; // enable demo mode
}
else {
demo_mode = 0; // disable demo mode
}
if (demo_mode == 1) { // when demo mode is active, automatically switch between all the screens and menu items
demo_mode_delay++; // increase demo mode delay
if (demo_mode_delay > 15) { // after some time, switch to another screen - change this value to make it slower/faster
demo_mode_delay = 0;
demo_mode_state++; // increase counter
if (demo_mode_state >= NUM_ITEMS*3) {demo_mode_state=0;} // jump back to the first screen
}
if (demo_mode_state % 3 == 0) {current_screen = 0; item_selected = demo_mode_state/3; } // menu screen
else if (demo_mode_state % 3 == 1) {current_screen = 1; item_selected = demo_mode_state/3;} // screenshots screen
else if (demo_mode_state % 3 == 2) {current_screen = 2; item_selected = demo_mode_state/3;} // qr codes screen
} // end demo mode section
if (current_screen == 0) { // MENU SCREEN
// 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 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;
}
}
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 (current_screen == 2) { // MENU SCREEN
// 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 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;
}
}
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) == LOW) && (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) && (item_selected == 0)) {current_screen = 1; } // menu items screen --> screenshots screen
else if ((current_screen == 0) && (item_selected == 1)) {current_screen = 2; }
else if ((current_screen == 2) && (item_selected == 0)) {current_screen = 3; } // menu items screen --> screenshots screen
else if ((current_screen == 2) && (item_selected == 1)) {current_screen = 4; }
else if ((current_screen == 2) && (item_selected == 2)) {current_screen = 5; }
else {current_screen = 0;} // screenshots screen --> qr codes screen
}
if ((digitalRead(BUTTON_SELECT_PIN) == HIGH) && (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
u8g2.clearBuffer(); // clear buffer for storing display content in RAM
if (current_screen == 0) { // MENU SCREEN
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(4,15,"Success unlock");
u8g2.drawXBMP( 24, 20, 80, 40, epd_bitmap_SE1);
}
else if ((current_screen == 2)) { // SCREENSHOTS SCREEN
// selected item background
u8g2.drawXBMP(0, 22, 128, 21, bitmap_item_sel_outline); // draw screenshot
// draw previous item as icon + label
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(25, 15, menu_items1[item_sel_previous]);
u8g2.drawXBMP( 4, 2, 16, 16, bitmap_icons1[item_sel_previous]);
// draw selected item as icon + label in bold font
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(25, 15+20+2, menu_items1[item_selected]);
u8g2.drawXBMP( 4, 24, 16, 16, bitmap_icons1[item_selected]);
// draw next item as icon + label
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(25, 15+20+20+2+2, menu_items1[item_sel_next]);
u8g2.drawXBMP( 4, 46, 16, 16, bitmap_icons1[item_sel_next]);
}
else if ((current_screen == 1)){
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(1, 20,"Please scan card ");
u8g2.drawStr(1, 45,"or enter password. ");
}
else if ((current_screen == 3)){
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(1, 15,"NEW PASS: ");
}
else if ((current_screen == 4)){
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(1, 15,"SCAN NEW ID: ");
}
else if ((current_screen == 5)){
u8g2.setFont(u8g_font_7x14B);
u8g2.drawStr(1, 15,"SCAN ID: ");
}
u8g2.sendBuffer(); // send buffer from RAM to display controller
}