// 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" }, // menu item = 0
{ "DEF" }, // menu item = 1
{ "ACC" }, // menu item = 2
{ "DEX" }, // menu item = 3
{ "MOV" }, // menu item = 4
{ "ATK" }, // menu item = 5
{ "DAM" }, // menu item = 6
};
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
int8_t HP_stat = 16; // initial value for the HP stat
int8_t DEF_stat = 17; // initial value for the DEF stat
int8_t ACC_stat = 18; // initial value for the ACC stat
int8_t DEX_stat = 19; // initial value for the DEX stat
int8_t MOV_stat = 15; // initial value for the MOV stat
int8_t ATK_stat = 14; // initial value for the ATK stat
int8_t DAM_stat = 13; // initial value for the DAM stat
const int NUM_OF_STATS = 7; //8; // number of items in the list
const int MAX_STAT_LENGTH = 20; // maximum characters for the item name
int8_t stat_value[NUM_OF_STATS][MAX_STAT_LENGTH] = { HP_stat, DEF_stat, ACC_stat, DEX_stat, MOV_stat, ATK_stat, DAM_stat};
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
screen_refresh();
}
int stat_display_value (int value) {
if(value == 0) {
return HP_stat;
}
if(value == 1) {
return DEF_stat;
}
if(value == 2) {
return ACC_stat;
}
if(value == 3) {
return DEX_stat;
}
if(value == 4) {
return MOV_stat;
}
if(value == 5) {
return ATK_stat;
}
if(value == 6) {
return DAM_stat;
}
}
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 = 58 - stored_cursor_x;//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 screen_refresh() {
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(stat_display_value(item_sel_previous));
//oled.print(HP_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.setCursor(45, 3); // set cursor, X is in pixels, Y is in pages
//String value = String(stat_value[item_selected]);
oled.print(stat_display_value(item_selected));
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.setCursor(45, 6); // set cursor, X is in pixels, Y is in pages
oled.print(stat_display_value(item_sel_next));
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);
}
}
}
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 screen --> stat screen
else {current_screen = 0;} // stat screen --> menu screen
screen_refresh();
}
if ((digitalRead(BUTTON_ENTER_PIN) == HIGH) && (button_ENTER_clicked == 1)) { // unclick
button_ENTER_clicked = 0;
}
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
screen_refresh();
}
if (digitalRead(BUTTON_DOWN_PIN) == LOW) { // button DOWN is pressed
item_selected = (item_selected + 1) % NUM_ITEMS; // switch to the next item
screen_refresh();
}
}
if(current_screen==1){
if (digitalRead(BUTTON_UP_PIN) == LOW) { // button UP is pressed
if(item_selected == 0 && HP_stat < 20) {
HP_stat = HP_stat + 1;
}
if(item_selected == 1) {
DEF_stat = DEF_stat + 1;
}
if(item_selected == 2) {
ACC_stat = ACC_stat + 1;
}
if(item_selected == 3) {
DEX_stat = DEX_stat + 1;
}
if(item_selected == 4) {
MOV_stat = MOV_stat + 1;
}
if(item_selected == 5) {
ATK_stat = ATK_stat + 1;
}
if(item_selected == 6) {
DAM_stat = DAM_stat + 1;
}
//item_selected = (item_selected - 1 + NUM_ITEMS) % NUM_ITEMS; // switch to the previous item
screen_refresh();
}
if (digitalRead(BUTTON_DOWN_PIN) == LOW) { // button DOWN is pressed
if(item_selected == 0 && HP_stat > 0) {
HP_stat = HP_stat - 1;
}
if(item_selected == 1) {
DEF_stat = DEF_stat - 1;
}
if(item_selected == 2) {
ACC_stat = ACC_stat - 1;
}
if(item_selected == 3) {
DEX_stat = DEX_stat - 1;
}
if(item_selected == 4) {
MOV_stat = MOV_stat - 1;
}
if(item_selected == 5) {
ATK_stat = ATK_stat - 1;
}
if(item_selected == 6) {
DAM_stat = DAM_stat - 1;
}
screen_refresh();
}
}
}