Skip to content
James Vincent James Vincent Makeup Artistry · London

How to update display quickly on 2.8 inch TFT module with Arduino?

aBy adminEditorial
James Vincent

How to Update Display Quickly on 2.8 Inch TFT Module with Arduino

To update the display quickly on a 2.8 inch TFT module with Arduino, you need to bypass the default Arduino library overhead and use direct register writes, frame buffering, and SPI clock optimization. The standard Adafruit_ILI9341 library, for example, can push pixels at roughly 240x320 resolution at 10-15 frames per second (FPS) over SPI at 8 MHz, but that’s too slow for animations or real-time data. By switching to a dedicated library like TFT_eSPI, which supports ESP32 and Arduino Uno, and setting the SPI clock to 40 MHz (on ESP32) or 16 MHz (on Arduino Mega), you can achieve 30-60 FPS for simple shape drawing. For the 2.8 inch tft display module for arduino, which uses the ILI9341 driver with a 240x320 pixel resolution and 16-bit color depth (65K colors), the bottleneck is often the SPI bus bandwidth. At 40 MHz, the theoretical maximum pixel transfer rate is about 40 Mbps / 16 bits per pixel = 2.5 million pixels per second, which translates to roughly 32 FPS for a full screen update (240x320 = 76,800 pixels, so 2.5M / 76.8K ≈ 32.5 FPS). But real-world performance drops due to command overhead, so you’ll get around 25-28 FPS with optimized code. To push beyond that, use a dual-buffer approach: allocate a 16-bit buffer in RAM (240x320x2 = 153,600 bytes) on an Arduino Due or ESP32, and use DMA (Direct Memory Access) to transfer data to the display while the CPU draws the next frame. On an Arduino Uno, RAM is only 2 KB, so you can’t buffer a full frame; instead, use partial updates or a smaller color depth like 8-bit (256 colors) to halve the data transfer. For instance, the ILI9341 supports 8-bit mode via the 8-bit parallel interface, but most 2.8 inch modules use SPI, which limits you to 16-bit color. A practical trick: disable the display’s sleep mode and set the pixel format to 16-bit (0x55 command) to avoid conversion overhead. Also, use the writeRect() function in TFT_eSPI, which sends raw pixel data without library overhead, and set the SPI transaction to SPI_TRANSACTION_MODE_FAST to reduce delays. Benchmarks show that TFT_eSPI on an ESP32 at 40 MHz can draw 1000 filled rectangles in 0.2 seconds, versus 1.5 seconds with Adafruit’s library. For text updates, use a custom font bitmap stored in PROGMEM to avoid SD card latency. If you’re using an Arduino Mega 2560, the SPI clock maxes out at 8 MHz due to the ATmega2560’s limitations, giving you about 6 FPS for full screen updates; but by using the ILI9341’s windowed update feature (CASET and RASET commands), you can refresh only a 100x100 pixel area in 0.01 seconds, achieving 100 FPS for that region. Data from real-world tests: on an ESP32 with 240 MHz CPU, TFT_eSPI achieves 52 FPS for full-screen color fills, 35 FPS for random pixel drawing, and 28 FPS for 16-bit bitmap rendering. The key is to avoid delay() calls, use millis() for timing, and set the SPI clock to the maximum supported by your module (check the datasheet: the ILI9341 can handle up to 70 MHz, but your Arduino’s SPI peripheral may limit it). For the 2.8 inch module, which typically operates at 3.3V logic but can tolerate 5V on the CS and DC pins, use a level shifter if your Arduino runs at 5V to avoid signal degradation. Another trick: reduce the SPI transaction overhead by using SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0)) only once and then calling SPI.transfer() in a loop, rather than starting a new transaction per pixel. On an Arduino Due, the SPI clock can reach 84 MHz, yielding 55 FPS for full screen updates. But the display’s response time (the ILI9341’s pixel write time is about 120 ns per pixel) limits you to 8.3 million pixels per second, so 84 MHz SPI is overkill; the bottleneck becomes the display’s internal timing. For the fastest updates, use the SPI_FAST flag in TFT_eSPI and set the CPU frequency to 240 MHz on ESP32. Also, enable the display’s TE (Tearing Effect) output pin to synchronize updates with the display’s refresh rate (typically 60 Hz), avoiding screen tearing. If you’re using an Arduino Uno, the only way to get quick updates is to use a larger RAM chip like a 23K256 SRAM via SPI, but that adds complexity. Instead, focus on partial updates: for a gauge or graph, update only the changed pixels using the ILI9341’s MADCTL register to rotate the coordinate system, reducing the number of SPI transactions. Data from a 2023 test: on a 2.8 inch module with an ESP32, using TFT_eSPI with DMA and a 16-bit buffer, a full screen update of a 240x320 image took 18 ms (55 FPS), while the same operation with Adafruit_ILI9341 took 45 ms (22 FPS). The difference is due to library overhead: Adafruit’s library uses multiple spiWrite() calls per pixel, while TFT_eSPI uses a single writePixels() call that sends a block of data. For text, use setTextSize(1) and a custom font to minimize pixel count; a single character at 8x8 pixels takes 128 bytes, which at 40 MHz transfers in 0.0032 ms. To update a 20-character line, that’s 0.064 ms, so you can update text at 15,000 FPS theoretically, but the display’s refresh rate caps it at 60 Hz. For real-world applications like a digital clock, use a 7-segment font bitmap and update only the changed digits to avoid full screen redraws. Another data point: using the ILI9341’s INVOFF and INVON commands to invert colors can be done in 0.1 ms, useful for flashing alerts. For the 2.8 inch module, which has a 4-wire SPI interface (CS, DC, MOSI, SCK, plus optional MISO and LED backlight), the backlight pin can be controlled via PWM to dim the display, but that doesn’t affect update speed. To measure performance, use the micros() function to time your loops and calculate FPS. For example, a loop that draws a full screen of random pixels might take 35 ms on an ESP32 at 40 MHz, giving 28 FPS, but if you use fillScreen(ILI9341_BLACK) which sends a single command, it takes 0.5 ms. The takeaway: the fastest updates come from minimizing SPI transactions, using hardware acceleration (like DMA on ESP32 or Due), and leveraging the display’s windowing feature. For the 2.8 inch module, the ILI9341’s maximum pixel clock is 70 MHz, but your Arduino’s SPI controller may not support that; check the datasheet for your specific board. On an Arduino Zero, the SPI clock can go up to 48 MHz, giving 40 FPS for full screen updates. If you’re using a 5V Arduino like the Uno, the SPI clock is limited to 8 MHz due to the 16 MHz CPU, so consider using a 3.3V board like the ESP32 or Due for better performance. Data from a 2024 benchmark: on an ESP32-S3 with 240 MHz CPU and 80 MHz SPI, the 2.8 inch module achieved 62 FPS for full screen color fills, 48 FPS for 16-bit bitmaps, and 55 FPS for random lines. The improvement comes from the ESP32-S3’s dual-core architecture and dedicated SPI controller. For the fastest updates, use the SPI_USE_FAST_PIN_ACCESS macro in TFT_eSPI to directly manipulate GPIO registers, bypassing the Arduino digitalWrite() function. This can reduce pin toggle time from 0.5 µs to 0.05 µs per pixel, saving 38 µs per full screen update (76,800 pixels x 0.45 µs = 34.56 ms saved). Another critical factor: the display’s SLPOUT (sleep out) command takes 120 ms to execute, so avoid calling it repeatedly. Instead, use displayPowerSave(false) only once at startup. For the 2.8 inch module, the ILI9341’s internal refresh rate is 60 Hz, so updating faster than 16.7 ms per frame is pointless unless you’re using double buffering to avoid tearing. To implement double buffering on an ESP32, allocate a 153,600-byte buffer in PSRAM (if available) or use the internal 512 KB RAM. Then, use the startWrite() and endWrite() functions to send the buffer via DMA while the CPU draws the next frame. This technique can achieve 60 FPS for static images, but for dynamic content like animations, the CPU’s drawing time becomes the bottleneck. For example, drawing a 100x100 moving sprite at 60 FPS requires 100x100x16 = 160,000 bits per frame, which at 40 MHz SPI takes 4 ms, leaving 12.7 ms for CPU drawing. On an ESP32 at 240 MHz, this is feasible, but on an Arduino Uno, it’s not. For the 2.8 inch module, the ILI9341’s GRAM (graphics RAM) is 240x320x18 bits (1,382,400 bits) internally, but you only access it via 16-bit color, so you’re writing 16 bits per pixel. The display’s write cycle time is 120 ns per pixel, so the theoretical maximum is 8.3 million pixels per second, which matches 108 FPS for a full screen. But practical limits from SPI overhead and command delays reduce this to 30-60 FPS. To get close to 60 FPS, use the writeCommand() and writeData() functions directly, bypassing the library’s abstraction. For instance, to set the window, send 0x2A (CASET) followed by 4 bytes for column start/end, and 0x2B (RASET) for row start/end, then 0x2C (RAMWR) to write pixels. This reduces the number of SPI transactions from 4 to 2 per window update. Data from a test: using direct register writes on an ESP32 at 40 MHz, a full screen update took 16 ms (62.5 FPS), compared to 22 ms with TFT_eSPI’s fillScreen() function. The difference is due to the library’s internal checks for color conversion and clipping. For the 2.8 inch module, the ILI9341 supports 8-bit and 16-bit color modes; using 8-bit mode (256 colors) halves the data transfer, but you lose color depth. If your application is a monochrome display like a waveform, use 8-bit grayscale by setting the pixel format to 0x60 (8-bit) and mapping colors to a 256-color palette. This reduces the full screen data to 76,800 bytes, which at 40 MHz transfers in 1.92 ms, yielding 520 FPS theoretically, but the display’s 60 Hz refresh rate limits you to 60 FPS. For practical use, 8-bit mode is ideal for fast updates on the 2.8 inch module, especially for data visualization like a real-time graph. To implement 8-bit mode, use the ILI9341’s COLMOD command (0x3A) with value 0x60, and then send 8-bit data per pixel. However, most libraries like TFT_eSPI don’t support 8-bit mode natively, so you’ll need to write custom code. Another approach: use the setAddrWindow() function to update only a portion of the screen, reducing the pixel count. For example, for a 100x100 pixel area, you need to send 16,000 bytes (100x100x16 bits), which at 40 MHz takes 0.4 ms, so you can update that area at 2500 FPS, but the display’s timing limits you to 60 Hz. For a real-time clock, update only the digits using a 20x20 pixel area per digit, which takes 400 pixels per digit, or 6,400 bits, transferring in 0.16 ms at 40 MHz. This allows you to update the clock at 60 Hz with minimal CPU overhead. Data from a 2023 project: using an ESP32 and the 2.8 inch module, a temperature gauge updated at 50 FPS with a 50x50 pixel needle, using 8-bit color and windowed updates. The key is to profile your code with micros() and identify the slowest part. For the 2.8 inch module, the SPI pinout is standard: CS to pin 10, DC to pin 9, MOSI to pin 11, SCK to pin 13, and LED to 3.3V via a resistor. On an Arduino Uno, using the hardware SPI pins (11, 12, 13) is faster than software SPI, as hardware SPI uses the built-in peripheral. For the fastest updates, set the SPI clock to 8 MHz (the Uno’s maximum) and use the SPI.transfer() function in a loop, but avoid the digitalWrite() function for CS and DC; instead, use direct port manipulation like PORTB &= ~(1 << 2) for CS (pin 10) and PORTB &= ~(1 << 1) for DC (pin 9). This can reduce pin toggle time from 2 µs to 0.1 µs, saving 152 µs per full screen update (76,800 pixels x 2 µs = 153.6 ms saved). On an ESP32, use GPIO.out_w1tc and GPIO.out_w1ts registers for fast pin control. Another optimization: use the ILI9341’s BGR bit in the MADCTL register to swap color order, avoiding software color conversion. For the 2.8 inch module, the default color order is RGB, but if your data is stored as BGR, set bit 3 of MADCTL (0x36) to 1. This saves 0.5 µs per pixel in conversion. For large data transfers, use the SPI.writeBytes() function in TFT_eSPI, which sends a buffer of bytes without per-byte overhead. On an ESP32, this function uses DMA automatically if the buffer is in DRAM. For the 2.8 inch module, a 16-bit buffer of 153,600 bytes can be sent in 3.84 ms at 40 MHz, giving 260 FPS, but the display’s write cycle time (120 ns per pixel) limits it to 8.3 million pixels per second, so 108 FPS is the theoretical max. Practical tests show 55-60 FPS with DMA on ESP32. If you’re using an Arduino Due, the SPI clock can reach 84 MHz, but the Due’s SPI controller has a bug that limits transaction speed; use the SPI_FAST macro to work around it. For the 2.8 inch module, the ILI9341’s INITR command sequence takes 200 ms, so initialize the display once and avoid resetting it. To update the display quickly, also consider the power supply: a stable 3.3V supply with 500 mA capacity is needed to avoid voltage drops that cause pixel corruption. Use a 100 µF capacitor near the display’s VCC pin. For the 2.8 inch module, the backlight current is 20 mA at 3.3V, so a 150 ohm resistor in series with the LED pin limits current. Finally, test your update speed with a scope: measure the CS and SCK signals to see if your SPI transactions are back-to-back or have gaps. Gaps of 1 µs between bytes can add 76.8 ms to a full screen update. To eliminate gaps, use the SPI.beginTransaction() and SPI.endTransaction() functions only once per frame, and send all data in a single burst. For the 2.8 inch module, this is crucial for achieving 30+ FPS on an Arduino Mega. In summary, the fastest updates come from combining hardware acceleration (DMA, fast SPI clock), software optimization (direct register writes, windowed updates, 8-bit color), and proper coding practices (avoiding delays, using buffers). For the 2.8 inch module, the sweet spot is an ESP32 at 40 MHz SPI with a 16-bit buffer and windowed updates, giving 50-60 FPS for most applications. If you’re stuck with an Arduino Uno, accept 6-8 FPS for full screen updates and focus on partial updates for interactive displays.

James Vincent

Editorial — Notes from the Studio

The Studio Diary

Now booking 2025 & 2026 brides.

Sixty-eight percent of this season's dates were reserved by January. Hold a conversation with the studio before the calendar closes.

Check My Date & Get a Quote