Skip to content
Find a Rep Specifier Login EN  |  DE  |  ES

How to create a menu on a 0.96 inch 128x64 OLED?

adminSpecification Team
WallProtect

To create a menu on a 0.96 inch 128x64 OLED, you need to write firmware that uses a graphics library, like Adafruit_SSD1306 or u8g2, to draw text and shapes on the display, then handle user input from buttons to navigate through menu items. The 128x64 pixel resolution (128 columns, 64 rows) means you can display roughly 8 lines of 16-pixel font text (like 6x8 or 8x8) or 4 lines of a larger 16x32 font. A typical menu system involves storing menu items in an array, tracking a current index, and redrawing the display when a button is pressed. For hardware, you’ll need a microcontroller (like an Arduino, ESP32, or STM32) connected to the OLED via I2C or SPI. The 0.96 inch 128x64 spi i2c oled display is a common choice because it supports both interfaces, giving you flexibility in wiring and speed. I2C uses only two wires (SDA, SCL) and runs at 100kHz or 400kHz, while SPI uses four wires (MOSI, MISO, SCK, CS) and can reach 10MHz, making it faster for animations. The display’s controller, typically SSD1306, has 128x64 bits of internal RAM, so you write pixel data to a buffer and then send it via I2C or SPI commands. A menu system on this small screen requires careful layout planning because of the limited real estate.

Hardware Setup and Wiring Details
Start by connecting the OLED to your microcontroller. For I2C, the default address is 0x3C or 0x3D, check your module’s datasheet. Wire VCC to 3.3V or 5V (most modules have a voltage regulator), GND to ground, SDA to the microcontroller’s I2C data pin (like A4 on Arduino Uno), and SCL to the clock pin (A5). For SPI, use pins: CS (chip select), DC (data/command), RES (reset), MOSI, and SCK. On an Arduino Uno, typical SPI pins are: MOSI on pin 11, SCK on pin 13, and you can assign CS, DC, and RES to any digital pins (e.g., pins 10, 9, 8). The 0.96 inch 128x64 spi i2c oled display modules often come with both interfaces broken out, so you can choose based on your project’s pin availability. I2C is easier for beginners because it uses fewer wires, but SPI is faster for redrawing complex menus. For a menu system, you’ll also need at least three buttons: one for up, one for down, and one for select. More buttons (like back or cancel) improve usability. Connect each button to a digital input pin with a pull-up resistor (10kΩ) or use the microcontroller’s internal pull-up. Debounce the buttons in software with a 50ms delay to avoid multiple triggers.

Memory and Buffer Management
The SSD1306 controller has 128x64 bits of internal RAM, which equals 1024 bytes (128 * 64 / 8). When you use a library like Adafruit_SSD1306, it allocates a 1024-byte buffer in the microcontroller’s RAM. This buffer is where you draw text, lines, and shapes before sending it to the display via the display.display() function. On an Arduino Uno with 2KB of SRAM, that buffer uses half of your available RAM, so you must manage memory carefully. For a menu system, avoid storing large strings in RAM; instead, use PROGMEM to store menu text in flash memory. For example, store menu items as an array of pointers to flash strings: const char menu1[] PROGMEM = "Main Menu"; Then access them with strcpy_P(buffer, (char*)pgm_read_word(&menuItems[i]));. This reduces RAM usage from 20 bytes per string to just 2 bytes per pointer. On an ESP32 or STM32 with more RAM, you can be less strict, but on AVR-based microcontrollers, it’s critical. The 128x64 resolution means each character in a 6x8 font (like the default Adafruit font) takes 6 pixels wide and 8 pixels tall, so you can fit 21 characters per line (128/6 = 21.33) and 8 lines (64/8 = 8). For a 16x32 font, you get 8 characters per line and 2 lines. Plan your menu layout accordingly: a main menu with 4-5 items works well, each item using a 16-pixel tall font for readability.

Menu Data Structure and Navigation Logic
Define a menu as a struct or array of items. Each item can have a label, a function pointer, and a submenu pointer. For a simple linear menu, use an array of strings and an integer index. For example, in C++: const char* menuItems[] = {"Start Game", "Settings", "High Scores", "About"}; Then track the current index with int currentItem = 0;. When the up button is pressed, decrement the index (wrap around if needed); when down is pressed, increment it. On select, call a function based on the index. For a hierarchical menu, use a linked list or tree structure. Each node has a parent, children, and a label. When you enter a submenu, push the current menu onto a stack, then display the child menu. When you press back, pop the stack. This stack approach works well with limited memory; use a fixed-size array of 10 levels to avoid recursion overhead. The display update logic should redraw only when a button is pressed, not continuously, to save CPU cycles. For example, in the loop, check for button presses, update the index, call display.clearDisplay(), draw the menu items with highlighting, and call display.display(). Highlight the selected item by inverting its colors (draw a filled rectangle behind the text) or by using a larger font. On a 128x64 OLED, inverted text is very readable because the pixels are bright white or blue against a black background.

Drawing Text and Graphics Efficiently
The Adafruit_SSD1306 library includes setTextSize(), setTextColor(), and setCursor() functions. For a menu, use setTextSize(1) for 6x8 font, which gives you 8 lines. To highlight an item, draw a filled rectangle at the item’s position: display.fillRect(0, currentItem * 8, 128, 8, WHITE); then draw the text in black on top: display.setTextColor(BLACK, WHITE);. This creates a white background with black text for the selected item. For the other items, use display.setTextColor(WHITE, BLACK);. If you want a scrollable menu with more than 8 items, implement a scroll offset. For example, if you have 20 items, only show 8 at a time, and when the cursor moves past the visible range, shift the offset. This is common in embedded systems. The scroll offset can be calculated as: int offset = max(0, currentItem - 3); so the selected item is always in the middle of the screen. Then draw items from offset to offset + 7. This requires careful boundary checking to avoid accessing out-of-bounds array elements. For a 128x64 display, the vertical resolution is 64 pixels, so with 8-pixel tall fonts, you have exactly 8 lines. If you use 10-pixel fonts (like some custom fonts), you get 6 lines, which is less but more readable. The u8g2 library supports many fonts, including proportional fonts, which can save horizontal space. For example, the u8g2_font_6x10_tf font is 6 pixels wide and 10 pixels tall, giving you 21 characters per line and 6 lines. This is a good compromise for menus.

Button Debouncing and Input Handling
Debouncing is essential because mechanical buttons bounce for 5-20ms. Use a state machine or a simple delay-based debounce. For example, read the button state, if it changes, wait 50ms, then read again. If the state is still the same, accept it as a valid press. A more efficient method is using a timestamp: if (digitalRead(buttonPin) == LOW && millis() - lastDebounceTime > debounceDelay). Store the last stable state and compare it to the current state. For a menu, you need to detect both short presses (for navigation) and long presses (for quick scrolling or back). Implement a timer that counts how long a button is held. If held for more than 500ms, treat it as a repeat press, incrementing the menu index every 200ms. This speeds up navigation through long lists. On an ESP32, you can use interrupts for buttons, but on Arduino Uno, polling in the loop is fine because the OLED update is fast (10-20ms for a full frame). The total loop time should be under 50ms to feel responsive. For a 128x64 OLED, the I2C bus speed at 400kHz can send a full frame in about 20ms (1024 bytes * 10 bits per byte / 400kbps = 25.6ms, plus overhead). SPI at 10MHz is faster: 1024 bytes * 8 bits / 10MHz = 0.82ms, plus command overhead. So SPI is better for menus with animations or fast scrolling.

Power Consumption and Optimization
The 0.96 inch 128x64 OLED consumes about 20mA when all pixels are on (white) and 0.5mA when off. For a battery-powered device, you can reduce power by turning off the display after a timeout, or by using partial display updates. The SSD1306 supports display on/off commands (0xAE and 0xAF). Also, you can reduce the contrast with command 0x81 followed by a value (0-255). Lower contrast (e.g., 50) reduces power slightly. For a menu system, you can also update only the changed portion of the screen. The SSD1306 supports page addressing mode, where you can set the column and page start/end addresses to update a specific rectangle. This is more complex but saves power and time. For example, if only the selected item changes, update just that 8-pixel tall strip. The library’s display.display() function sends the entire buffer, which is simpler but less efficient. To implement partial updates, you need to manually set the GDDRAM address and write only the bytes for the changed region. This reduces I2C traffic from 1024 bytes to, say, 128 bytes for a single line, saving power and time. On a 128x64 OLED, each page is 8 pixels tall, so there are 8 pages. You can update a single page by setting the page address (0xB0 to 0xB7) and column address (0x00 to 0x7F for lower nibble, 0x10 to 0x17 for upper nibble). This is advanced but useful for low-power projects.

Real-World Example: Temperature Monitor Menu
Let’s say you want a menu that shows temperature, humidity, and settings. The main menu items are: “Temperature”, “Humidity”, “Settings”, “About”. When you select “Temperature”, it shows the current reading in large font. Use a 16x32 font for the value (e.g., “25.4°C”) and a 6x8 font for the label. The 128x64 display can show 8 characters of 16x32 font per line, so “25.4°C” fits (6 characters). For the settings submenu, items like “Unit: C/F”, “Alarm On/Off”, “Backlight”. Each setting item can be toggled by pressing select. Implement a state machine: MAIN_MENU, SUBMENU, VALUE_DISPLAY. Each state has its own draw function and button handler. For example, in MAIN_MENU, up/down changes the index, select enters the submenu. In SUBMENU, up/down changes the setting, select toggles the value, back returns to main menu. The back button can be a dedicated button or a long press of the select button. Store the current state and menu stack in global variables. The total code size for such a menu on an Arduino Uno is about 10-15KB of flash, leaving room for sensor libraries. The RAM usage is about 1.2KB (1024 bytes for buffer, 200 bytes for variables). This is tight but workable. On an ESP32, you have more resources, so you can add animations like fading or scrolling text.

Common Pitfalls and Debugging Tips
One common issue is the OLED not displaying anything. Check the I2C address with an I2C scanner sketch. For SPI, ensure the CS, DC, and RES pins are correctly assigned and that the RES pin is pulled high after power-up. Another issue is flickering when updating the menu. This happens because the display is cleared and redrawn every loop. To fix it, only update the display when a button is pressed, not continuously. Use a flag like bool menuChanged = true; and set it to true on button press, then in the loop, if the flag is true, redraw and set it to false. Also, avoid using delay() in the loop because it blocks button reading. Instead, use non-blocking timing with millis(). For example, for button debounce, use if (millis() - lastDebounceTime > debounceDelay). For a menu with many items, the text might overflow the screen. Use display.getCursorX() and display.getCursorY() to check positions, or use a library that supports text wrapping. The u8g2 library has setFontPosTop() and setFontDirection() for more control. Another pitfall is the menu index going out of bounds. Always check the array size: if (currentItem < 0) currentItem = numItems - 1; and if (currentItem >= numItems) currentItem = 0;. For submenus, ensure the parent pointer is valid to avoid crashes. Test with a serial monitor to print the current state and index for debugging.

Advanced Features: Icons and Graphics
You can add small icons to the menu to make it more intuitive. For a 128x64 display, a 16x16 pixel icon fits well next to the text. Create a bitmap array in PROGMEM: const unsigned char icon[] PROGMEM = {0x00, 0x00, ...}; with 32 bytes (16x16/8). Use display.drawBitmap(x, y, icon, 16, 16, WHITE); to draw it. For a menu item, place the icon at x=0, y=itemY, and the text at x=18, y=itemY+4. This uses 18 pixels of horizontal space, leaving 110 pixels for text (about 18 characters of 6x8 font). For a settings menu, you can use a slider or checkbox graphic. For example, a checkbox can be a 8x8 pixel square, filled if enabled. Draw it with display.drawRect() and display.fillRect(). For a progress bar, use a filled rectangle that scales with the value. The 128x64 OLED’s pixel density is about 128 PPI (pixels per inch) for a 0.96 inch diagonal, so details are sharp. You can also use anti-aliased fonts from the u8g2 library, but they require more flash memory. For example, u8g2_font_helvR08_tf is a proportional font that looks clean. However, proportional fonts make alignment harder because character widths vary. Use a monospaced font for simpler menu alignment. The 128x64 resolution is also suitable for a 4x4 grid of icons (32x32 pixels each), which can be used for a grid-based menu, like a phone app launcher. This requires more complex input handling, like a joystick or 4-directional buttons.

Performance Benchmarks and Data
On an Arduino Uno at 16MHz, using I2C at 400kHz, a full screen update takes about 25ms. With SPI at 4MHz, it takes about 3ms. For a menu with 8 items, redrawing only the changed line (one page) takes 3ms on I2C and 0.4ms on SPI. This means you can update the menu at 30-300 frames per second, but human perception is fine at 10-15 FPS. The SSD1306’s internal oscillator is about 500kHz, and the display refresh rate is 100Hz, so the pixel persistence is not an issue. The maximum number of menu items you can store in flash on an Arduino Uno (32KB flash) is about 500 strings of 20 characters each (10KB), leaving room for code. But with 2KB RAM, you can only have a few active variables. On an ESP32 with 4MB flash and 520KB RAM, you can store hundreds of menu items with icons and animations. For a practical project, a menu with 20-30 items is common. The 128x64 OLED’s viewing angle is 160 degrees, so it’s readable from the side. The contrast ratio is high (2000:

About the author

admin

Part of the WallProtect specification team supporting architects, interior designers, and facility managers with technical submittals across healthcare, education, and commercial interiors.

Specify WallProtect for your next project

Request a free sample kit, Revit families, and ISO/ASTM documentation — delivered within 5 working days for stocked colors.

Request a Free Sample Kit