Skip to content

How to display a menu list on a 1.54 inch 128x64 OLED?

admin Pillar Café

How to display a menu list on a 1.54 inch 128x64 OLED

To display a menu list on a 1.54 inch 128x64 oled display, you need to combine hardware interfacing with a structured software approach that leverages the display’s limited resolution and monochrome nature. The 128x64 pixel grid means you have 128 columns and 64 rows, which translates to roughly 8 lines of text if you use a standard 8x8 pixel font, or about 4 lines with a larger 16x16 font for readability. The key is to design a menu system that scrolls or pages through options, using button inputs for navigation, all while managing the SPI or I2C communication protocol efficiently. This display, often based on the SSD1306 or SH1106 driver, supports both graphic and text modes, but for a menu, you’ll typically use the graphic mode to draw custom elements like arrows, borders, and highlighted selections. The physical connection involves four pins for SPI (CS, DC, MOSI, SCK) plus power and ground, running at 3.3V or 5V logic depending on your microcontroller. For a robust implementation, you’ll want to precompute the menu layout in memory, update only changed regions to avoid flicker, and handle debouncing for button inputs. Let’s break down the specifics with data-driven details.

Hardware setup and pixel constraints
The 1.54 inch 128x64 oled display typically uses a resolution of 128x64 pixels, with a physical active area of about 35.0mm x 17.5mm, giving a pixel pitch of roughly 0.27mm. For a menu list, each character in a 8x8 font occupies 8 pixels wide and 8 pixels tall, so you can fit 16 characters per line (128/8) and 8 lines total (64/8). But if you use a 6x8 font (common for compact displays), you get 21 characters per line and still 8 lines. However, for a menu with readable titles, you’ll likely want to reserve the top line for a header (e.g., “Menu Options”) and the bottom line for a status bar or scroll indicator, leaving 6 lines for actual menu items. Each menu item can be a short string like “1. Settings” or “2. Data Log”, which at 8x8 font takes about 10-12 pixels width, leaving room for a selection arrow or icon. The display’s contrast ratio is typically 2000:1, and the viewing angle is 160 degrees, which is fine for most embedded applications. The SSD1306 driver supports a frame buffer of 1024 bytes (128x64/8), and you need to refresh the entire display at a rate of at least 10 Hz to avoid visible flicker, though 30 Hz is better for smooth scrolling. Using SPI at 10 MHz, you can transfer the full frame buffer in about 0.8 ms, leaving plenty of CPU time for menu logic.

Menu data structure and memory management
A menu list on this OLED requires a data structure that holds strings, function pointers, and navigation states. For a typical 8-item menu, you’ll define an array of structs, each containing a label (up to 20 characters), a unique ID, and a callback function. The frame buffer is 1024 bytes, but you can optimize by only updating the region that changes. For example, when the user scrolls from item 3 to item 4, you only redraw the two affected lines, saving about 256 bytes of data transfer per update. The microcontroller’s RAM is often limited (e.g., 2KB on an ATmega328P), so you should store menu strings in flash memory using PROGMEM in Arduino or similar. A typical menu with 8 items, each with a 20-character label, takes 160 bytes in flash, plus 8 bytes for pointers and 8 bytes for IDs, totaling 176 bytes. The frame buffer itself takes 1024 bytes, so you need at least 1.2KB of RAM, which is fine for most MCUs with 2KB+ RAM. For scrolling, you can implement a circular buffer that holds the current top index and visible items. If you have 8 items but only 6 visible lines, you need to track the offset. For example, with items 0-7, if offset=2, you display items 2,3,4,5,6,7. The user presses down to increment offset, and up to decrement, with wrap-around at the ends. This requires just 2 bytes of state (offset and selected index).

Font rendering and text layout
For the 128x64 OLED, you have several font options. A 5x7 font (5 pixels wide, 7 pixels tall) gives 25 characters per line and 9 lines, but the 7-pixel height leaves gaps. A 8x8 font is more common for menu systems because it aligns with the byte boundaries of the frame buffer. The SSD1306 driver uses pages of 8 pixels tall, so 8x8 fonts fit exactly one page per line. Each character glyph is stored as 8 bytes (one per column), and a full ASCII set of 95 characters takes 760 bytes in flash. For a menu, you might only need uppercase letters, numbers, and a few symbols, reducing the font table to 300 bytes. When drawing text, you calculate the x,y position based on the character size. For a menu item at line 2 (y=16 pixels), you start at x=0 for the item number, then x=8 for the label. If you want a selection highlight, you invert the pixels of that line by XORing the frame buffer bytes for that page. For example, to highlight line 2, you take the 128 bytes for page 2 (y=16-23) and XOR each byte with 0xFF. This creates a solid black background with white text, which is very readable. The contrast can be adjusted via the SSD1306’s contrast register (0x81), with values from 0 to 255, where 128 is typical for indoor use.

Button input handling and debouncing
Navigation typically uses three buttons: up, down, and select. You can also add a back button for nested menus. For a 1.54 inch OLED, the buttons are usually connected to GPIO pins with pull-up resistors. The debounce time for mechanical buttons is about 10-20 ms, so you should implement a software debounce with a timer interrupt or a polling loop that checks the state every 5 ms. A simple state machine can track button press, release, and long press. For example, if the button is pressed for 50 ms, it’s a valid click; if held for 500 ms, it’s a long press for fast scrolling. The MCU’s clock speed (e.g., 16 MHz on an Arduino Uno) gives you 62.5 ns per instruction, so a debounce check every 5 ms costs 0.5% CPU time. The menu logic should run in the main loop, checking for button events and updating the display only when a change occurs. This reduces power consumption, as the OLED itself draws about 20 mA when active, but you can put it in sleep mode (drawing 0.1 mA) between menu interactions. Typical use cases like a data logger or control panel benefit from this efficiency.

Scrolling and animation techniques
When the menu has more items than visible lines, scrolling is essential. There are two common methods: page scrolling and line-by-line scrolling. Page scrolling jumps to the next set of items, which is simpler but less smooth. Line-by-line scrolling shifts the display content by one pixel or one line. For a 128x64 OLED, line-by-line scrolling at 8-pixel increments (one page) is easy: you just adjust the offset and redraw the new line. However, smooth scrolling at 1-pixel increments requires shifting the entire frame buffer, which is computationally expensive. For example, to scroll up by 1 pixel, you need to shift all 1024 bytes by one bit, which takes about 1000 shift operations. On a 16 MHz MCU, this takes about 1 ms, which is acceptable for a 30 Hz refresh. But for a menu, smooth scrolling is often overkill; page scrolling with a small animation (like a fade or slide) is more practical. You can implement a slide effect by gradually increasing the y-offset of the new items over 10 frames, each frame taking 1 ms to render. This gives a 100 ms animation, which feels responsive. The SSD1306 supports hardware scrolling via commands (0x26 for horizontal, 0x27 for vertical), but these are limited to full-screen scrolling and don’t work well with partial updates. So software scrolling is preferred for menu lists.

Power optimization and refresh rates
The 1.54 inch OLED consumes 20-30 mA at full brightness, but you can reduce this by lowering the contrast or using partial display updates. The SSD1306 has a charge pump that generates the 7-15V needed for OLED pixels, and you can disable it via command 0xAE to save power. For a menu that updates infrequently, you can set the display to sleep mode between interactions, drawing only 0.1 mA. The refresh rate of the display itself is 100 Hz internally, but you only need to send data when the menu changes. A typical menu interaction (press a button, update display) takes 5 ms, so the display is active for 0.5% of the time if the user interacts once per second. This extends battery life in portable devices. The SPI bus speed can be set to 1 MHz to reduce EMI, but 10 MHz is fine for short traces. The display’s response time is about 10 microseconds, so no visible lag.

Real-world implementation example with data
Let’s say you’re building a menu for a temperature controller with 6 items: “Set Temp”, “View History”, “Alarm Config”, “System Info”, “Calibrate”, and “Exit”. Using a 8x8 font, you have 6 visible lines, so no scrolling is needed. Each item is displayed as “1: Set Temp” (12 characters, 96 pixels wide). The selected item is highlighted with inverted pixels. The header “Main Menu” is at line 0, and the footer “Press Select” is at line 7. The frame buffer for this static menu is 1024 bytes, but you only update the changed line when the selection moves. The MCU polls buttons every 10 ms, and the display refresh rate is 50 Hz. The total code size is about 4KB, with 1KB for the font table, 1KB for the menu strings, and 2KB for the logic. The RAM usage is 1.2KB for the frame buffer plus 0.5KB for variables. This runs on an ATmega328P with 32KB flash and 2KB RAM, leaving plenty of room for other features. The SPI communication uses 4 pins, and the buttons use 3 pins, totaling 7 GPIOs. The entire system draws 25 mA at 5V, or 125 mW.

Common pitfalls and how to avoid them
One frequent issue is ghosting or flickering when updating the display. This happens if you clear the entire frame buffer before redrawing, causing a blank screen for a few milliseconds. To avoid this, use a double buffer: keep a copy of the current frame buffer in RAM, and only write the differences. For a menu, you can precompute the entire menu layout in a separate buffer, then copy it to the display buffer when a change occurs. Another issue is the display’s limited viewing angle for text—if you use a 5x7 font, the characters are thin and hard to read from a distance. Stick to 8x8 or 8x16 fonts for better legibility. Also, the SSD1306’s internal oscillator can drift, so you should set the display clock divide ratio (command 0xD5) to a value like 0x80 for stable operation. The contrast setting (0x81) should be adjusted based on ambient light; for bright environments, use 0xCF (207), and for dim, use 0x7F (127).

Advanced features: nested menus and icons
For a more complex menu, you can add submenus by storing a tree structure in flash. Each node has a parent pointer and a list of child items. When the user selects a menu item, you push the current menu onto a stack and load the new menu. The stack depth is typically 3-4 levels, using 12 bytes per level. You can also draw simple icons using bitmaps. For example, a gear icon for settings is 16x16 pixels, taking 32 bytes. With 128x64 resolution, you can fit 8 icons per row, but for a menu, you’d place one icon per line, 16 pixels wide, leaving 112 pixels for text. This reduces the number of characters per line to 14 (112/8), but it’s more visually appealing. The bitmap data can be stored in flash as a byte array, and you draw it by copying the bytes to the frame buffer. For a 16x16 icon, you need to write 32 bytes per icon, which takes 0.3 ms at 10 MHz SPI. This is negligible for a menu update.

Testing and debugging methods
To verify the menu display, use a logic analyzer to capture the SPI signals. The expected sequence is: CS low, send command byte (0x00 for command, 0x40 for data), then the data bytes. For a 128x64 display, each page (8 rows) requires 128 bytes. You can check the timing: at 10 MHz, 128 bytes take 102.4 microseconds. The total refresh for 8 pages is 819.2 microseconds. If the display flickers, check the refresh rate—it should be above 20 Hz. Also, measure the current draw with a multimeter; if it’s above 30 mA, the contrast may be too high. For debugging menu logic, use serial output to print the current state, offset, and selected index. This helps catch off-by-one errors in the scrolling algorithm. Another trick is to draw a grid pattern on the display to verify pixel alignment: draw horizontal lines at every 8 pixels and vertical lines at every 8 pixels. This ensures your font rendering is aligned with the page boundaries.

Performance benchmarks and comparisons
Compared to a 0.96 inch OLED (128x64), the 1.54 inch version has the same resolution but larger pixels, making text easier to read. The 1.54 inch OLED has a pixel area of 0.27mm² vs 0.15mm² for the 0.96 inch, so characters are 80% larger. For a menu, this means you can use a 8x8 font without strain, whereas the 0.96 inch might require a 6x8 font. The power consumption is similar (20-25 mA), but the larger display has a higher capacitance, so the charge pump takes longer to stabilize—about 100 ms after power-on. The SPI interface on the 1.54 inch version supports up to 20 MHz, but most MCUs are limited to 10 MHz. The frame buffer update time is the same: 1024 bytes at 10 MHz takes 0.82 ms. The menu rendering time for a full screen redraw is about 2 ms (including font lookup and pixel manipulation), so you can achieve 500 Hz updates if needed, but 30 Hz is more than enough for human interaction. The button response time is limited by the user’s reaction time (200 ms), so the menu system can be optimized for low power rather than speed.

Integration with microcontrollers and libraries
Most libraries like Adafruit_SSD1306 or U8g2 support the 1.54 inch 128x64 OLED with SPI. For a menu, you can use the U8g2 library’s built-in menu system, which handles scrolling, selection, and callbacks. However, for full control, writing your own menu code is better for performance. The U8g2 library uses a frame buffer of 1024 bytes and supports hardware acceleration for fonts. The library’s memory footprint is about 8KB for the full version, but you can trim it to 4KB by disabling unused features. The SPI interface is initialized with a clock divider of 2 (8 MHz on a 16 MHz MCU) for reliable communication. The library also supports I2C, but SPI is faster for full-screen updates. For a menu, I2C at 400 kHz takes 20 ms for a full refresh, which is too slow for smooth scrolling, so SPI is recommended. The display’s CS pin can be tied to ground if it’s the only SPI device, but using a separate pin allows multiple displays. The DC pin distinguishes between command (low) and data (high) bytes. The RESET pin can be connected to the MCU’s reset line or a GPIO for software reset.

Real-world data on menu item density
If you have a menu with 20 items, you need to scroll through 14 items beyond the visible 6. Each press of the down button moves the selection by one, and the display updates in 1 ms. The user can scroll through all 20 items in 20 button presses, taking about 2 seconds if they press quickly. The scroll offset wraps around, so item 20 is followed by item 1. The total flash storage for 20 items, each with a 20-character label, is 400 bytes plus 20 bytes for pointers, totaling 420 bytes. The frame buffer remains 1024 bytes. The menu logic uses a circular buffer to track the current offset and selected index, which is 2 bytes. The button debounce uses a 10 ms timer, so the CPU is idle for 99% of the time. This is efficient for battery-powered devices like a handheld data logger or a smart thermostat. The display’s lifetime is about 50,000 hours at 50% brightness, which is 5.7 years of continuous use, but for a menu that’s only active during user interaction, it lasts much longer.