// ATtiny85 with 128x64px SSD1306 IIC OLED Display to create a simple menu
// created by upir, 2023
// youtube channel: https://www.youtube.com/upir_upir
// YOUTUBE VIDEO: https://youtu.be/ksHLeNBC0IQ
// SOURCE FILES: https://github.com/upiir/attiny85_oled_menu
// Links from the video:
// ATtiny85 chip: https://s.click.aliexpress.com/e/_DeT7tBh
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Arduino prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// USB to 5V power cable with crocodile clips: https://s.click.aliexpress.com/e/_DcjKaRH
// Image2cpp (convert array to image): https://javl.github.io/image2cpp/
// Photopea (online graphics editor like Photoshop): https://www.photopea.com
// Transparent OLED display: https://s.click.aliexpress.com/e/_Dns6eLz
// 128x64 SSD1306 OLED Display 1.54": https://s.click.aliexpress.com/e/_DCYdWXb
// 128x64 SSD1306 OLED Display 0.96": https://s.click.aliexpress.com/e/_DCKdvnh
// 128x64 SSD1306 OLED Display 2.42": https://s.click.aliexpress.com/e/_DFdMoTh
// Additional boards URL for attiny85: https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json
// Related videos from the video:
// Videos using ATtiny85 chip: https://www.youtube.com/playlist?list=PLjQRaMdk7pBbt-is-fkRmUoRcV4dzraYH
// Arduino + OLED displays: https://www.youtube.com/playlist?list=PLjQRaMdk7pBZ1UV3IL5ol8Qc7R9k-kwXA
// Arduino UNO menu with u8g: https://youtu.be/HVHVkKt-ldc
// Arduino UNO menu with u8g2: https://youtu.be/K5e0lFRvZ2E
// Xmas PCB badge: https://youtu.be/Zqb2OhphVPo
#include <TinyWireM.h> // To use the Adafruit's TinyWireM library:
#include <Tiny4kOLED.h> // we are using the Tiny4kOLED library to draw on the display
#define BUTTON_UP_PIN PB4 // pin for UP button
#define BUTTON_DOWN_PIN PB3 // pin for DOWN button
#define BUTTON_ENTER_PIN PB5 // pin for ENTER button
#include "fontpixelop16b.h" // font pixel operator bold
#include "fontpixelop16.h" // font pixel operator regular
#include "bg.h" // 128x64px fullscreen background image
#include "icons.h" // 16x16px icons
const unsigned char scrollbar_empty [] PROGMEM = { B00000000, B01010101, B00000000 }; // image for scrollbar background 3x8px
const unsigned char scrollbar_filled [] PROGMEM = { B11111110, B11111111, B11111110 }; // image for scrollbar handle 3x8px
const int NUM_ITEMS = 7; //8; // number of items in the list
const int MAX_ITEM_LENGTH = 6; // maximum characters for the item name
char menu_items [NUM_ITEMS] [MAX_ITEM_LENGTH] = { // array with item names
{ "HP" },
{ "DEF" },
{ "ACC" },
{ "DEX" },
{ "MOV" },
{ "ATK" },
{ "DAM" },
//{ "Trbo" }
};
int current_screen = 0; // 0 = menu, 1 = Stat value,
int button_UP_clicked = 0; // only perform action when button is clicked, and wait until another press
int button_ENTER_clicked = 0; // same as above
int button_DOWN_clicked = 0; // same as above
int Health_stat = 20; // initial value for the health stat
int Accuracy_stat = 1; // initial value for the health stat
int Dexterity_stat = 2; // initial value for the health stat
char Stat_values [21] [2] = { // array with item names
{ "20" },
{ "19" },
{ "18" },
{ "17" },
{ "16" },
{ "15" },
{ "14" },
{ "13" },
{ "12" },
{ "11" },
{ "10" },
{ "9" },
{ "8" },
{ "7" },
{ "6" },
{ "5" },
{ "4" },
{ "3" },
{ "2" },
{ "1" },
{ "0" }
};
int item_selected = 0; // which item in the menu is selected
int item_selected_displayed = 10; // currently displayed menu item on the OLED display, so we don´t need to redraw the screen all the time
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
void setup() {
pinMode(BUTTON_UP_PIN, INPUT_PULLUP); // up button, set to INPUT_PULLUP = when not pressed, pin is HIGH
pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); // down button, set to INPUT_PULLUP = when not pressed, pin is HIGH
pinMode(BUTTON_ENTER_PIN, INPUT_PULLUP); // enter button, set to INPUT_PULLUP = when not pressed, pin is HIGH
//oled.begin(128, 64, sizeof(tiny4koled_init_128x64), tiny4koled_init_128x64); // display initialization
oled.begin(64, 48, sizeof(tiny4koled_init_64x48), tiny4koled_init_64x48); // display initialization
oled.enableChargePump(); // The default is off, but most boards need this.
oled.setRotation(1); // The default orientation is not the most commonly used.
oled.clear(); // clear the screen
//oled.bitmap(0, 0, 128, 8, epd_bitmap_bg_no_labels); // draw 128x64px background image
//oled.bitmap(0, 0, 64, 6, epd_bitmap_bg_no_labels); // draw 128x64px background image
oled.on(); // turn on the display
}
void fill_rest_of_line() {
// this function is used to fill everything from the current X position to the 110px position with black color
// this is required to draw over the previous string and cover any leftovers
int stored_cursor_x = oled.getCursorX(); // get cursor X position in pixels
int stored_cursor_y = oled.getCursorY(); // get cursor Y position in pages
int clear_width = 54 - stored_cursor_x; // calculate how much pixels we need to fill (old value at 110)
oled.fillLength(/*B10101010*/ 0, clear_width); // fill specified number of columns with black color
oled.setCursor(stored_cursor_x, stored_cursor_y + 1); // jump to the next page
oled.fillLength(/*B10101010*/ 0, clear_width); // fill specified number of columns with black color
}
void loop() { // main loop
if ((digitalRead(BUTTON_ENTER_PIN) == LOW) && (button_ENTER_clicked == 0)) { // select button clicked, jump between screens
button_ENTER_clicked = 1; // set button to clicked to only perform the action once
if (current_screen == 0) {current_screen = 1;} // menu items screen --> screenshots screen
else {current_screen = 0;} // qr codes screen --> menu items screen
}
if ((digitalRead(BUTTON_ENTER_PIN) == HIGH) && (button_ENTER_clicked == 1)) { // unclick
button_ENTER_clicked = 0;
}
if (item_selected_displayed != item_selected) { // only redraw the screen if the selected item is different from the last time
if(current_screen == 0){
oled.clear();
oled.bitmap(0,1,64,6,epd_bitmap_BG_3);
}
if(current_screen == 1){
oled.clear();
oled.bitmap(0,1,64,6,epd_bitmap_BG_4);
}
// 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
// draw top item
oled.setFont(FONTPIXELOP16); //(FONT6X8); //(FONTPIXELOP16); // regular font
oled.setCursor(22, 0); // set cursor, X is in pixels, Y is in pages
oled.print(menu_items[item_sel_previous]); // draw menu item label
fill_rest_of_line();
oled.setCursor(45, 0); // set cursor, X is in pixels, Y is in pages
//oled.print(menu_items[item_sel_previous]);
oled.print(Health_stat);
//fill_rest_of_line(); // fill rest of the line with black color
oled.bitmap(4, 0, 4+16, 2, bitmap_icons[item_sel_previous]); // draw icons
// draw middle item
oled.setFont(FONTPIXELOP16); //(FONT6X8P); //(FONTPIXELOP16B); // bold font
oled.setCursor(22, 3); // set cursor, X is in pixels, Y is in pages
oled.print(menu_items[item_selected]); // draw menu item label
fill_rest_of_line(); // fill rest of the line with black color
oled.bitmap(4, 3, 4+16, 5, bitmap_icons[item_selected]); // draw icons
// draw bottom item
oled.setFont(FONTPIXELOP16); //(FONT6X8); //(FONTPIXELOP16); // regular font
oled.setCursor(22, 6); // set cursor, X is in pixels, Y is in pages
oled.print(menu_items[item_sel_next]); // draw menu item label
fill_rest_of_line(); // fill rest of the line with black color
oled.bitmap(4, 6, 4+16, 8, bitmap_icons[item_sel_next]); // draw icons
// draw scrollbar
for (byte i=0; i<7; i++) { // for all 8 pages (byte i=0; i<8; i++)
if (i == item_selected) {
// draw scrollbar handle = scrollbar_filled
oled.bitmap(61, i, 61+3, i+1, scrollbar_filled);
} else {
// draw scrollbar background = scrollbar_empty
oled.bitmap(61, i, 61+3, i+1, scrollbar_empty);
}
}
item_selected_displayed = item_selected; // update which item is currently rendered on the display
}
if(current_screen==0){
if (digitalRead(BUTTON_UP_PIN) == LOW) { // button UP is pressed
item_selected = (item_selected - 1 + NUM_ITEMS) % NUM_ITEMS; // switch to the previous item
}
if (digitalRead(BUTTON_DOWN_PIN) == LOW) { // button DOWN is pressed
item_selected = (item_selected + 1) % NUM_ITEMS; // switch to the next item
}
}
if(current_screen==1){
if (digitalRead(BUTTON_UP_PIN) == LOW) { // button UP is pressed
//item_selected = (item_selected - 1 + NUM_ITEMS) % NUM_ITEMS; // switch to the previous item
}
if (digitalRead(BUTTON_DOWN_PIN) == LOW) { // button DOWN is pressed
// item_selected = (item_selected + 1) % NUM_ITEMS; // switch to the next item
}
}
}