Skip to content
BreakingRaccoon City Files Ep. 312 drops Friday — RE9 codename dossier inside

How to display a menu on a 3.18 inch 128x64 COG LCD?

To display a menu on a 3.18 inch 128x64 COG LCD, you need to drive the display via SPI or I2C, manage a framebuffer in memory, and implement a state machine or a simple menu tree structure. The 128x64 pixel resolution is not high, but it’s enough for a text-based menu with 4-6 lines of text at 8x8 font, or 2-3 lines with a larger 16x16 font. The key is to use a microcontroller like an STM32, ESP32, or Arduino, and write efficient code that updates only changed parts of the screen to avoid flicker and save power. The display module itself, like the 3.18 inch 128x64 cog lcd display, uses a COG (chip-on-glass) design, which means the driver IC is bonded directly to the glass, reducing thickness and improving reliability. The typical controller for this size is the ST7565 or similar, which supports 4-wire SPI, 3-wire SPI, and parallel interface. You’ll need to set up the SPI bus at 2-8 MHz, depending on your MCU, and send commands to initialize the display: set bias ratio (1/9 for 128x64), set V0 voltage regulator, turn on the booster, and set the display start line. The initialization sequence is usually around 20-30 commands, and you can find it in the datasheet. Once initialized, you write pixel data to the 128x64 framebuffer, which is 1024 bytes (128 columns * 64 rows / 8 bits per byte). For a menu, you’ll create a struct that holds menu items, each with a label, a function pointer, and optional submenu pointers. For example, a simple menu might have 4 items: “Start”, “Settings”, “Info”, “Exit”. Each item is displayed as a string, and you highlight the selected item by inverting its background or drawing a rectangle around it. You track the current selection with an index, and when the user presses a button (like up/down/enter), you update the index and redraw only the changed lines. The refresh rate should be at least 30 Hz for smooth interaction, but since the ST7565 has a write time of about 1-2 ms per byte, a full screen update takes about 1-2 ms * 1024 = 1-2 seconds if you write every byte sequentially. That’s too slow. Instead, use partial updates: only write the bytes that changed. For a menu, you typically change only the highlight line, so you update 2-3 lines (16-24 bytes) per button press, which takes less than 50 µs. The display’s contrast is adjustable via the V0 voltage, which is set by a potentiometer or a PWM pin. The typical operating voltage for the LCD is 3.3V, but the backlight (if present) may need 5V and 20-40 mA. The COG module has a 20-pin FPC connector, with pins like CS, DC, RST, SCK, MOSI, VDD, VSS, LED+, LED-. You’ll need to connect these to your MCU. The SPI mode is mode 3 (CPOL=1, CPHA=1) for most ST7565 variants. The data transfer is MSB first, and each byte represents 8 vertical pixels in a column. So column 0, page 0 corresponds to pixels (0,0) to (0,7). The menu layout depends on your font. For a 8x8 font, you can fit 16 characters per line (128/8) and 8 lines (64/8). But with a 3.18 inch diagonal, the pixel pitch is about 0.56 mm, so characters are readable from 30 cm. For a more practical menu, use a 12x16 font: 10 characters per line, 4 lines. That gives you enough space for a title and 3 menu items. The font data is stored in flash as a bitmap array. For a 12x16 font, each character uses 24 bytes (12 columns * 16 rows / 8). You can generate this with a font tool like TheDotFactory. The menu state machine can be implemented as a switch-case or a function pointer table. For example, you have a state variable that points to the current menu array. Each menu item has a label, a function to call when selected, and a pointer to the next menu. When the user presses “Enter”, you call the function, which might change the state to a submenu. The submenu is just another array of items. This is called a tree menu, and it’s common in embedded systems. The depth can be 3-4 levels without issue. The memory usage for the menu structure is small: each item might be 8 bytes for the label pointer, 4 bytes for the function pointer, and 4 bytes for the submenu pointer, so 16 bytes per item. A 10-item menu uses 160 bytes of RAM. The framebuffer is 1024 bytes, which is fine for an MCU with 8 KB or more. But if you’re using an Arduino Uno with only 2 KB RAM, you need to be careful. You can reduce the framebuffer to 512 bytes by using a 128x32 region, but that cuts the display in half. Alternatively, you can write directly to the display without a framebuffer, but that complicates partial updates. The COG LCD’s driver IC has internal RAM that matches the display resolution, so you can read back pixel data, but the read operation is slow and not supported in all SPI modes. Most implementations use a local framebuffer and write to the display only when needed. The refresh rate for static menus is not critical, but when scrolling, you need to update the entire screen at 10-15 Hz to avoid tearing. The ST7565 supports hardware scrolling via the “Display Start Line” register, which lets you shift the displayed area without rewriting data. This is useful for a scrolling menu: you can set the start line to 0, then increment it to scroll the entire display up. But for a menu with selection, you’ll still need to update the highlight. The power consumption of the COG LCD is about 0.5-1 mA without backlight, and 20-30 mA with backlight. If you’re battery-powered, you can turn off the backlight after a timeout and use a low-power sleep mode for the MCU. The display itself can be put into sleep mode with a command, which reduces current to <10 µA. The wake-up time is about 1 ms. For user input, you can use 3 buttons: up, down, enter. The buttons are debounced with a 50 ms timer. The menu logic is simple: on up, decrement index; on down, increment index; on enter, call the function. The index wraps around or stops at the ends. The menu items are displayed as strings, and you can use a small font for the title and a larger font for the items. The title is centered at the top, and the items are listed below. The selected item is highlighted by drawing a filled rectangle behind the text. The rectangle color is white (pixels on) and the text color is black (pixels off) for inverted mode. The contrast is set to a value that makes the pixels visible but not too dark. The typical contrast register value is 0x20 to 0x3F, but you need to adjust it for your specific module. The COG LCD’s viewing angle is 6 o’clock, meaning the best view is from the bottom. The display is transmissive, so it needs a backlight, but some versions are reflective and use ambient light. The 3.18 inch size is common for industrial control panels, test equipment, and medical devices. The operating temperature range is -20°C to +70°C, which is suitable for most environments. The SPI interface is robust, but you need to keep the wires short (<10 cm) to avoid signal degradation. The clock speed should be limited to 2 MHz for long wires. The initialization sequence is critical: you must set the bias ratio to 1/9 for 128x64, set the V0 voltage regulator to internal, set the booster to 3x, and set the display start line to 0. The sequence is: reset the display (hold RST low for 1 ms, then high), send command 0xAE (display off), 0xA2 (bias 1/9), 0xA0 (segment direction normal), 0xC8 (common direction reverse), 0x22 (voltage regulator), 0x2F (booster on), 0x40 (display start line 0), 0x81 (contrast set), 0x20 (contrast value), 0xAF (display on). After that, you can write data. The data is written in pages, where each page is 8 rows. There are 8 pages (64/8). You set the column address (0-127) and page address (0-7) before writing data. The column address is set with command 0x10 (high nibble) and 0x00 (low nibble) for column 0. The page address is set with command 0xB0 for page 0. Then you write the data bytes. For a menu, you’ll write the font data for each character. The font data is stored as a byte array, where each byte represents 8 vertical pixels. For a 12x16 font, you have 12 bytes per row, and 2 rows per character (16 rows / 8). The menu item string is drawn by iterating through the characters and copying the font data to the framebuffer. The framebuffer is a 2D array: uint8_t fb[8][128]. You update the framebuffer, then write the changed pages to the display. The write function is: void lcd_write_page(uint8_t page, uint8_t *data, uint8_t len). It sets the page and column, then sends len bytes. For a menu update, you might write 2 pages (16 rows) for the highlighted line. The total time for a menu update is about 100 µs, which is imperceptible. The button debouncing is done with a timer interrupt that samples the buttons every 10 ms. If the button state is stable for 3 samples (30 ms), it’s considered pressed. The menu logic runs in the main loop, checking for button events. The state machine is: while (1) { if (button_up) { if (current_index > 0) current_index--; else current_index = item_count - 1; draw_menu(); } if (button_down) { if (current_index < item_count - 1) current_index++; else current_index = 0; draw_menu(); } if (button_enter) { menu_items[current_index].function(); } } The draw_menu() function clears the menu area, draws the title, and draws each item. The highlight is drawn by inverting the pixels of the selected item. The inversion is done by XORing the framebuffer bytes with 0xFF for that line. The menu items are stored in flash to save RAM. The label is a const char* pointer. The function pointer is a void (*)(void). The submenu pointer is a const struct MenuItem*. The menu structure is defined as: struct MenuItem { const char *label; void (*function)(void); const struct MenuItem *submenu; }; The main menu is an array of MenuItem. The submenus are separate arrays. The depth is limited by the call stack, but you can implement a stack-based navigation instead of recursion. The stack holds the previous menu and index. When the user enters a submenu, you push the current menu and index onto the stack, then set the current menu to the submenu. When the user presses back, you pop the stack. This avoids recursion and uses only a few bytes of RAM. The stack depth is typically 3-4 levels. The display’s backlight is controlled by a PWM pin. You can dim the backlight to save power. The PWM frequency should be above 100 Hz to avoid flicker. The backlight current is 20-30 mA at full brightness. You can reduce it to 5 mA for low-light conditions. The contrast is also adjustable via a potentiometer or a PWM pin. The contrast voltage V0 is typically 8-10V, generated by the internal booster. The booster efficiency is about 80%. The display’s response time is 100-200 ms, which is fine for static menus. The COG LCD’s lifetime is 50,000 hours at 25°C. The storage temperature is -30°C to +80°C. The module weighs about 10 grams. The FPC connector is 0.5 mm pitch, 20 pins. You can solder it to a PCB or use a FPC socket. The pinout is: 1: VSS (GND), 2: VDD (3.3V), 3: NC, 4: CS, 5: RST, 6: DC, 7: SCK, 8: MOSI, 9: LED+, 10: LED-, 11-20: NC. The SPI interface uses 4 wires: CS, DC, SCK, MOSI. The DC pin is used to select command (low) or data (high). The RST pin is active low. The CS pin is active low. The typical SPI transaction is: set CS low, set DC low, send command byte, set DC high, send data bytes, set CS high. The command and data bytes are sent with the same SPI transfer. The display’s internal RAM is organized as 128 columns by 8 pages. Each page is 8 rows. So column 0, page 0 is the top-left corner. The column address is set with two commands: 0x10 (high nibble) and 0x00 (low nibble) for column 0. The page address is set with 0xB0 for page 0. The data bytes are written sequentially, and the column address increments automatically. After writing 128 bytes, the column address wraps to 0, and the page address increments. This allows you to write a full screen with 8 writes of 128 bytes each. For a menu, you only write the pages that changed. The font data is stored in a 2D array: const uint8_t font[][24] = { ... }; Each character is 12 columns wide and 16 rows high. The 24 bytes are: first 12 bytes for the top 8 rows, next 12 bytes for the bottom 8 rows. When drawing a character, you copy the 12 bytes to the framebuffer at the appropriate column and page. The character’s position is given by (x, y) where x is the column (0-127) and y is the row (0-63). The page is y/8, and the offset within the page is y%8. But since the font is 16 rows high, it spans two pages. So you write the top 12 bytes to page y/8, and the bottom 12 bytes to page y/8+1. The framebuffer update is done with memcpy. The menu’s title is drawn at the top, centered. The items are drawn below, with a fixed spacing. The highlight is drawn by XORing the framebuffer bytes for the selected item. The XOR operation is done before writing to the display. The display’s contrast is set to a value that makes the background dark and the pixels bright. The typical contrast register value is 0x20 for a 3.3V supply. You can adjust it in the menu as a settings option. The settings menu might have items like “Brightness”, “Contrast”, “Reset”. The brightness is controlled by PWM, and the contrast by writing to the display’s register. The reset function clears the framebuffer and reinitializes the display. The menu’s navigation is intuitive: up/down to select, enter to confirm, and a long press on up to go back. The long press is detected by a timer that counts the button press duration. If the button is held for 1 second, it’s a long press. This is implemented with a state machine that tracks the button state and the press time. The debounce is still 30 ms, but the long press is detected after 1 second of continuous press. The menu’s response is immediate, and the user feels no lag. The display’s refresh rate is 60 Hz, but the menu updates are triggered only by button presses. The power consumption is low, so the system can run on a coin cell for months if the backlight is off. The COG LCD’s module has a built-in voltage regulator, so you don’t need an external boost converter. The input voltage is 3.3V, but the module can accept 2.7V to 5.5V. The current consumption is 0.5 mA without backlight, and 20 mA with backlight. The backlight is an LED array with a forward voltage of 3.0V at 20 mA. You can drive it with a transistor or a PWM pin. The display’s viewing angle is 6 o’clock, so you should mount it with the FPC at the bottom. The module’s thickness is 2.5 mm, which is thin enough for portable devices. The 128x64 resolution is standard for many graphic LCDs, and the 3.18 inch size gives a good balance between readability and compactness. The menu implementation is straightforward, and you can add features like icons, scrolling text, or animation. The icons are 16x16 pixels, stored as 32 bytes each. You can draw them next to the menu items. The scrolling text is useful for long strings that don’t fit in 16 characters. You scroll the text by shifting the column address or by moving the text in the framebuffer. The animation is done by updating the framebuffer at a fixed rate. The COG LCD’s driver IC supports hardware acceleration for some operations, but it’s not commonly used. The SPI speed is the bottleneck, but 2 MHz is enough for most applications. The menu’s code size is about 2-4 KB, depending on the number of items and fonts. The font data for a 12x16 font with 95 characters is about 2.3 KB. The total program size is under 10 KB, which fits in most MCUs. The RAM usage is 1024 bytes for the framebuffer, plus 100-200 bytes for the menu structure and variables. The system is reliable and can run for years without issues. The display’s lifetime is 50