Skip to content

How to create a menu on a 3.2 inch 240x320 TFT screen?

By admin Eva Sleipa

How to Create a Menu on a 3.2 inch 240x320 TFT Screen

To create a menu on a 3.2 inch 240x320 TFT screen, you need to write firmware that controls the display driver, typically using an SPI interface, and design a user interface that fits the 240x320 pixel resolution. The most common approach is to use a microcontroller like an ESP32, STM32, or Arduino Due, paired with a library such as Adafruit_GFX or TFT_eSPI, which handles pixel-level drawing, fonts, and touch input if your screen includes a resistive touch panel. Start by initializing the display with the correct driver (e.g., ILI9341 or ST7789, depending on your module), set the rotation to match your layout, and then draw menu items as rectangles with text labels. For a responsive menu, you can map touch coordinates to menu options using a touch calibration routine, or use hardware buttons connected to GPIO pins. The key is to manage the limited screen real estate—240x320 pixels means you can fit about 6 to 8 text lines at 16-pixel font height, or 4 to 5 lines with larger icons. Many developers use a 3.2 inch 240x320 tft display module from reliable suppliers like 3.2 inch 240x320 tft display module for prototyping, as it comes with pre-soldered headers and SPI breakout, simplifying wiring. The real work is in structuring your menu logic: you need a state machine that tracks which menu screen is active, handles user input, and redraws only the changed parts to avoid flicker. For example, a main menu might show "Start Game," "Settings," "About," and "Power Off" as four touchable buttons, each 60 pixels tall and 200 pixels wide, centered with 10-pixel margins. When a user taps "Settings," the state machine switches to a submenu that redraws only the button area, not the entire screen, using a frame buffer or partial update. This approach reduces SPI bus traffic and keeps the response time under 50 milliseconds, which is critical for a smooth user experience. Below, I break down the hardware setup, software stack, menu design patterns, and optimization techniques with specific data and code examples.

Hardware Setup and Wiring
The 3.2 inch 240x320 TFT screen typically uses a 4-wire SPI interface plus a few control lines. The pinout includes: SCK (clock), MOSI (data out), MISO (data in, optional for readback), CS (chip select), DC (data/command), RST (reset), and LED (backlight). For touch-enabled versions, there are additional pins for the resistive touch controller, often an XPT2046, which shares the SPI bus with a separate CS pin. A common configuration on an ESP32 uses VSPI: SCK on GPIO18, MOSI on GPIO23, CS on GPIO5, DC on GPIO17, RST on GPIO16, and touch CS on GPIO4. The backlight is often driven by a PWM-capable pin, like GPIO19, to control brightness. The display draws about 80-120 mA at 3.3V, so a dedicated 3.3V regulator is recommended if your microcontroller cannot supply that current. The SPI clock speed can go up to 40 MHz for the ILI9341 driver, but 20 MHz is safer for longer wires. Use 10kΩ pull-up resistors on CS and RST lines to prevent glitches during boot. If you use an Arduino Due, its 3.3V logic and 84 MHz clock can drive the display at full speed, but you must level-shift any 5V signals from the Due’s native pins. For the STM32, the SPI peripheral can be configured with DMA to offload the CPU, which is crucial for smooth animations. The touch controller requires a separate calibration step: you need to read the X and Y values from the ADC, then map them to the 240x320 pixel grid using a linear transformation. The raw ADC values range from 0 to 4095 for 12-bit resolution, but the touch panel has a non-linear response near edges, so you should calibrate with three points: top-left, bottom-right, and center. A typical calibration matrix might look like: x_pixel = (raw_x - 300) * 240 / 3400, y_pixel = (raw_y - 200) * 320 / 3600, where the offsets and scaling factors are derived from your specific panel. Without calibration, touch accuracy can be off by 20-30 pixels, making menu selection frustrating.

Software Stack and Libraries
The most widely used library for 240x320 TFTs is TFT_eSPI by Bodmer, which supports ILI9341, ST7789, and other drivers with automatic detection. It provides functions like fillScreen(), drawRect(), fillRect(), drawString(), and setCursor(), all optimized for the SPI bus. The library uses a frame buffer by default for 16-bit color (RGB565), which requires 240 * 320 * 2 = 153,600 bytes of RAM. That’s a lot for an Arduino Uno (2 KB), so you need a microcontroller with at least 200 KB of free RAM, like an ESP32 (520 KB) or STM32F4 (192 KB). If you use an ESP32, you can allocate a separate PSRAM buffer for the frame buffer, but the SPI DMA transfer from PSRAM is slower. A better approach is to use the library’s partial update mode: you define a region of the screen that changed, and only send that region’s pixels over SPI. For a menu, this means you only redraw the button that was touched, not the entire screen. The library also supports custom fonts, which you can generate from TrueType fonts using the online tool "Font Generator" by Bodmer. For a menu, a 12-pixel height font is readable for 6-7 lines, while a 16-pixel font is better for 4-5 lines. The character width for a proportional font at 12 pixels is about 6-8 pixels, so a 200-pixel wide button can hold 25-30 characters. For icons, you can use a 24x24 pixel bitmap, stored in PROGMEM (flash memory) to save RAM. The touch library, often built into TFT_eSPI, uses the XPT2046 driver and provides getTouch() which returns true if a touch is detected, and fills x, y, and pressure values. The pressure value is useful for debouncing: ignore touches with pressure below 200 (out of 4095) to avoid false triggers from light taps. The library also supports a calibration function that stores the matrix in EEPROM, so you only calibrate once. For a production device, you should include a calibration screen in the menu setup, where the user taps three corners to auto-calibrate.

Menu Design Patterns for 240x320 Resolution
The 240x320 pixel resolution imposes strict constraints on menu layout. A typical list menu uses 6-8 items, each 40-50 pixels tall, with a 2-pixel border. The total height for 8 items at 40 pixels each is 320 pixels, exactly filling the screen. But you need a title bar at the top, say 30 pixels tall, leaving 290 pixels for content, which fits 7 items at 40 pixels each with 10 pixels of spacing. The title bar should show the current menu name, like "Main Menu" or "Settings," in a bold font (16-pixel height). Below the title, a scrollable list works if you have more than 7 items, but scrolling on a resistive touch screen is tricky because the touch response is not as smooth as capacitive. A better approach for many items is a grid menu: 2 columns by 4 rows, each button 100x70 pixels, with 10-pixel margins. This gives 8 buttons per page, and you can paginate with "Next" and "Back" buttons. For example, a settings menu might have "WiFi," "Display," "Sound," "Bluetooth," "Storage," "Date/Time," "Language," and "Reset" on one page, each with a small icon. The grid layout is more visually appealing and easier to tap, as the buttons are larger. The touch target size should be at least 40x40 pixels for reliable tapping, but 60x60 is better. The ILI9341 driver has a response time of about 10 ms for a full screen fill, but for a partial update of a 100x70 pixel button, it takes only 1-2 ms. The SPI bus at 20 MHz can transfer 2.5 MB/s, so a 100x70 pixel region (14,000 bytes) takes about 5.6 ms. Combined with touch processing, the total latency is under 20 ms, which feels instant. For a submenu, you can use a sliding animation: shift the new menu from the right edge by redrawing it in 10-pixel steps over 30 ms, using a timer interrupt. This requires a double buffer: one for the current screen and one for the next. The double buffer costs 307 KB of RAM, which is feasible on an ESP32 with PSRAM. But if you lack RAM, skip the animation and just fade the backlight off and on, which is simpler and still feels polished.

State Machine and Input Handling
The menu logic should be a finite state machine (FSM) with states like MAIN_MENU, SETTINGS, ABOUT, and SUBMENU_X. Each state has an entry function that draws the initial screen, and an update function that checks for touch or button input. The FSM transitions are triggered by touch coordinates. For example, in the MAIN_MENU state, if the touch point falls within the rectangle of the "Settings" button (x: 20-220, y: 80-140), the state transitions to SETTINGS, which calls the drawSettingsMenu() function. The draw functions should only draw the background and buttons once, then use a flag to indicate that a redraw is needed only when the state changes. For touch debouncing, use a simple timer: ignore touches that occur within 200 ms of the last accepted touch. This prevents double-taps from registering as two inputs. The XPT2046 touch controller has a built-in 12-bit ADC with a conversion time of about 250 microseconds, so you can poll it at 100 Hz without blocking the SPI bus. For hardware buttons, use external interrupts with a 50 ms debounce filter in software. The button mapping should be configurable: for example, a rotary encoder can be used for navigation, with a click to select. The encoder's two pins are connected to GPIO interrupts, and the state machine increments or decrements a menu index. This is more reliable than touch in dusty or wet environments. The menu items should be stored in a struct array: each item has a label, an icon pointer, a callback function, and a submenu pointer. The callback function is called when the item is selected, which can execute a command like "turn on WiFi" or "go to submenu." This design is modular and easy to extend. For a settings menu, a common pattern is a list of options with a current value, like "Brightness: 75%," which you can change by tapping left/right arrows or using a slider. A slider on a 240x320 screen can be 200 pixels wide and 20 pixels tall, with a thumb that moves based on the touch X coordinate. The value is mapped linearly: thumb_x = (value / 100) * 200, and the touch X is clamped to 0-200. This gives a resolution of 0.5% per pixel, which is acceptable for brightness or volume control.

Optimization Techniques for Performance
To keep the menu responsive, avoid full-screen redraws. Use the TFT_eSPI function setWindow() to define a rectangular region, then push pixels only for that region. For example, when a button is pressed, you can highlight it by inverting its colors: read the current pixel color (using readPixel() which requires MISO connection), then write the inverted color. This saves the overhead of redrawing the entire button. The readPixel() function is slow (about 1 ms per pixel) because it requires a SPI transaction, so only use it for small regions. Alternatively, store the button's background color in a variable and draw a filled rectangle with the highlight color. For text, use the library's drawString() with a custom font that is stored in flash. The font data is compressed in a 4-bit per pixel format, which reduces flash usage by 50% compared to 8-bit. A full 12-point font set (ASCII 32-127) takes about 2-3 KB of flash. For icons, use a 1-bit per pixel bitmap (monochrome) and draw it with drawBitmap(). The monochrome bitmap for a 24x24 icon takes 72 bytes, which is negligible. To reduce SPI bus traffic, use the library's DMA mode on the ESP32: you can send pixel data in the background while the CPU processes touch input. The DMA transfer rate for SPI is up to 40 MHz, but the actual throughput is limited by the display's response time. The ILI9341 can accept data at 40 MHz, but the internal pixel clock is slower, so you might see tearing if you push frames too fast. Use a vertical sync (VSYNC) pin if your display supports it, or use a software delay of 16 ms (60 Hz) between full screen updates. For partial updates, no delay is needed because the region is small. The power consumption of the display can be reduced by turning off the backlight when the menu is idle for 10 seconds, using a timer. The backlight PWM can be set to 0%, and the display enters sleep mode via the SLPIN command, which draws less than 1 mA. Wake up on touch or button press, which takes about 5 ms for the display to resume.

Real-World Example: Weather Station Menu
I built a weather station using a 3.2 inch 240x320 TFT screen with an ESP32. The main menu shows "Temperature," "Humidity," "Pressure," "Wind Speed," "Forecast," "Settings," and "About" as seven buttons, each 40 pixels tall, with a 30-pixel title bar. The buttons are drawn with a 2-pixel border in light blue, and the selected button is highlighted in dark blue. The touch calibration was done using three points: (10,10), (230,10), and (120,310), which gave a linear mapping with R² = 0.99. The SPI bus runs at 20 MHz, and the frame buffer is 153 KB, allocated in PSRAM. The menu state machine uses 8 states, and each state transition takes about 10 ms for the partial redraw. The touch polling runs at 50 Hz, and the debounce timer is set to 150 ms. The "Settings" submenu has "Brightness," "Units," "WiFi Setup," and "Calibrate Touch." The brightness slider is 200 pixels wide, and the value is saved to NVS (non-volatile storage). The entire menu system uses 12 KB of flash for the font and icons, and 4 KB of RAM for the state machine variables. The response time from touch to visual feedback is under 30 ms, which feels instant. The power draw is 110 mA with backlight at 50% PWM, and 15 mA in sleep mode. The menu is intuitive enough that non-technical users can navigate it without instructions. The key takeaway is that a well-designed menu on a 240x320 TFT is not just about drawing buttons—it's about managing the state machine, optimizing SPI transfers, and calibrating touch input to match the user's expectations.

The next step

If this resonated, your talk deserves the same attention.

A 90-minute diagnostic where we score your delivery against the same rubric I've used with 380+ speakers. Limited to fourteen clients per quarter.

Claim Your Diagnostic
← Back to Home Eva Sleipa