// 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
#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 = 8; // number of items in the list
const int MAX_ITEM_LENGTH = 20; // maximum characters for the item name
char menu_items [NUM_ITEMS] [MAX_ITEM_LENGTH] = { // array with item names
{ "3D Cube" },
{ "Battery" },
{ "Dashboard" },
{ "Fireworks" },
{ "GPS Speed" },
{ "Big Knob" },
{ "Park Sensor" },
{ "Turbo Gauge" }
};
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
oled.begin(128, 64, sizeof(tiny4koled_init_128x64), tiny4koled_init_128x64); // 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.on(); // turn on the display
}
void fill_rest_of_line() {
// this function is used to fill everyhing 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 = 110 - stored_cursor_x; // calculate how much pixels we need to fill
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 (item_selected_displayed != item_selected) { // only redraw the screen if the selected item is different from the last time
// 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); // regular font
oled.setCursor(25, 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(); // 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(FONTPIXELOP16B); // bold font
oled.setCursor(25, 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); // regular font
oled.setCursor(25, 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<8; i++) { // for all 8 pages
if (i == item_selected) {
// draw scrollbar handle = scrollbar_filled
oled.bitmap(125, i, 125+3, i+1, scrollbar_filled);
} else {
// draw scrollbar background = scrollbar_empty
oled.bitmap(125, i, 125+3, i+1, scrollbar_empty);
}
}
item_selected_displayed = item_selected; // update which item is currently rendered on the display
}
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
}
}