Skip to content
Engineering Notes

How to draw shapes on 2.8 inch TFT display with Arduino?

admin· ·OpenLib

How to draw shapes on 2.8 inch TFT display with Arduino

To draw shapes on a 2.8 inch TFT display with Arduino, you connect the display via SPI (Serial Peripheral Interface) using the Adafruit_GFX and Adafruit_ILI9341 libraries (or the MCUFriend variant for specific clones). The display typically uses the ILI9341 driver, which supports 240x320 pixel resolution and 262K colors. After wiring – SCK to pin 13, MOSI to pin 11, CS to pin 10, DC to pin 9, RST to pin 8, and VCC to 5V – you initialize the display with `tft.begin()` and call functions like `tft.drawCircle()`, `tft.fillRect()`, or `tft.drawTriangle()`. For example, drawing a filled red circle at center (120, 160) with radius 50 requires `tft.fillCircle(120, 160, 50, ILI9341_RED)`. This works because the library maps pixel coordinates to the display’s internal frame buffer, and the SPI clock speed is set to 16 MHz by default for reliable 5V operation. The display’s 5V logic level compatibility means you can power it directly from the Arduino’s 5V pin, drawing around 80-120 mA depending on brightness.

For a deeper dive into the hardware, the 2.8 inch TFT display module for Arduino (available at 2.8 inch tft display module for arduino) uses a 4-wire SPI interface, but the actual pinout includes MISO, MOSI, SCK, CS, DC, RST, and LED. The ILI9341 controller has a maximum SPI clock of 10 MHz for write operations, but many Arduino libraries push it to 16 MHz, which works on most boards like the Uno, Mega, or Due. The display’s flash memory holds a font table and initialization commands, but you can override these with software. The pixel format is 16-bit RGB565, where each pixel uses 2 bytes (5 bits for red, 6 for green, 5 for blue). This means the frame buffer is 240 * 320 * 2 = 153,600 bytes, but the Arduino’s RAM (2 KB on Uno) can’t hold it, so shapes are drawn directly to the display via SPI commands. The library handles this by sending pixel data in chunks, typically 320 pixels per row for horizontal lines.

When drawing shapes, the Adafruit_GFX library provides primitives like drawPixel, drawLine, drawRect, fillRect, drawCircle, fillCircle, drawTriangle, fillTriangle, drawRoundRect, and fillRoundRect. Each function takes integer coordinates (x, y) and a 16-bit color value. For example, tft.drawRect(10, 20, 100, 50, 0x07E0) draws a green rectangle outline from (10,20) to (110,70). The color 0x07E0 is pure green in RGB565 (bits: 00000 111111 00000). The library uses Bresenham’s algorithm for lines and circles, which is efficient for integer arithmetic. The fillCircle function uses a scanline fill algorithm, iterating over each row within the circle’s bounding box and drawing horizontal lines. This runs in O(n) time where n is the number of pixels, so a filled circle of radius 50 takes about 7,854 pixels, which at 16 MHz SPI takes roughly 2 milliseconds per 320-pixel row, totaling around 50 ms for the whole circle.

Performance varies by Arduino board. On an Uno (16 MHz, 8-bit), drawing a full-screen filled rectangle (240x320) takes about 240 * 320 / (16 MHz / 8 cycles per byte) ≈ 38 ms, but actual SPI overhead adds latency. The SPI.transfer() function sends 8 bits per clock cycle, so 320 pixels per row (640 bytes) at 16 MHz takes 40 µs, but the library also sends command bytes and waits for the display to process. The ILI9341 datasheet says the write cycle time (tWC) is 15 ns minimum, but the Arduino’s SPI clock is limited to 8 MHz on some boards due to prescaler settings. On a Mega (16 MHz), you can use SPI clock divider 2 for 8 MHz, which is safe. The library’s setAddrWindow command sets a rectangular region for bulk pixel writes, reducing SPI overhead. For example, to fill a 100x100 square, you send one command for the window, then 10,000 pixel bytes (20,000 bytes) in a single burst, which takes about 2.5 ms at 8 MHz.

Drawing complex shapes like triangles requires the library to compute the bounding box and then iterate over rows. The drawTriangle function uses three line segments, each drawn with Bresenham’s algorithm. The fillTriangle function uses a scanline algorithm that sorts the three vertices by y-coordinate, then fills between the two edges. This is computationally heavier because it calculates slopes for each row. For a triangle with a height of 100 pixels, it loops 100 times, each time drawing a horizontal line of varying length. The library’s code is optimized for AVR, but on an Uno, you might see a 10-20 ms delay for a large filled triangle. The drawRoundRect function draws a rectangle with rounded corners using four quarter-circles and four straight lines, while fillRoundRect fills the interior with horizontal lines, clipping at the corner arcs. The radius parameter (e.g., 10 pixels) determines the corner curvature, and the library uses a precomputed table for circle points to avoid floating-point math.

Color depth is a key factor. The ILI9341 supports 262K colors, but the library uses 16-bit RGB565, which gives 65,536 colors. You can define colors using hex values like 0xF800 (red), 0x07E0 (green), 0x001F (blue), or use the ILI9341_ constants. The library also includes a color565 function to convert RGB 8-bit values to 16-bit (e.g., tft.color565(255, 0, 0) gives red). The display’s gamma correction is handled internally, but you can adjust the contrast via the writeCommand function. For example, sending 0xC0 (power control 1) with a value of 0x23 sets the default voltage level. The datasheet lists 16 commands for power, gamma, and timing, but you rarely need them for basic shape drawing.

Memory management is critical. The Uno’s 2 KB SRAM is barely enough for the library’s variables (about 1.2 KB for the GFX canvas and SPI buffer). The Adafruit_ILI9341 library uses a 512-byte SPI transmit buffer, but you can reduce it by defining SPI_BUFFER_SIZE in the header. For drawing shapes, the library allocates temporary arrays for line segments, but these are small (e.g., 320 bytes for a row buffer). If you run out of RAM, the sketch will crash or behave erratically. The Mega (8 KB SRAM) or Due (96 KB) handle larger shapes better. The MCUFriend_kbv library is an alternative that supports many clones and includes a fillScreen function that uses a 320-byte buffer for faster fills. Benchmarks show that fillScreen on a 2.8-inch display takes 70 ms on Uno versus 35 ms on Mega due to the 8-bit vs 32-bit architecture.

Drawing shapes with touch input adds another layer. The display module often includes a resistive touch panel (XPT2046 controller) connected via SPI on separate pins (e.g., T_IRQ, T_DO, T_DIN, T_CS). You can use the TouchScreen library to read coordinates and then draw shapes at the touch position. For example, tft.fillCircle(touchX, touchY, 10, ILI9341_BLUE) draws a blue dot where you press. The touch resolution is 4096x4096, but you map it to 240x320 using map(). The touch panel draws about 10 mA, so total current consumption is around 130 mA. The display’s backlight LED (pin 15) can be controlled via PWM on pin 3 to adjust brightness, drawing 20-100 mA depending on duty cycle. A typical PWM frequency of 500 Hz with a 10-bit resolution gives 1024 brightness levels.

Advanced shape drawing includes using the drawBitmap function for pre-defined images. You can store a 16-bit bitmap in PROGMEM (flash) and draw it with tft.drawBitmap(x, y, bitmap, width, height, color). For example, a 50x50 pixel icon uses 5,000 bytes of flash. The library also supports drawXBitmap for 1-bit images (monochrome). The setRotation function (0-3) rotates the coordinate system, affecting all shapes. Rotation 1 swaps x and y, so (0,0) becomes (0,240) in portrait mode. The display’s orientation is controlled by the MADCTL register (0x36), where you can set bits for row/column exchange and BGR order. The default is RGB, but some clones use BGR, so you might need to adjust the color mapping.

For real-world applications, drawing shapes on a 2.8-inch TFT is used in data loggers, game consoles, and weather stations. The response time for a single pixel is about 1 µs at 8 MHz SPI, but the library’s overhead adds 10-20 µs per function call. For a fast animation, you can use tft.startWrite() and tft.endWrite() to batch multiple shape commands, reducing SPI transaction overhead. The ILI9341’s write cycle time is 15 ns, but the Arduino’s SPI clock limits throughput. A 240x320 pixel fill at 16 MHz takes 15.36 ms (240 * 320 * 2 bytes / 16 MHz), but the library’s row-by-row approach adds 320 * 2 µs = 640 µs for command overhead, totaling 16 ms. The display’s internal frame buffer is 172,800 bytes (240x320x1.5 for 18-bit mode), but the library uses 16-bit mode, so it only sends 2 bytes per pixel.

Common issues include incorrect pin mapping, especially with the LED pin (backlight). If you leave it floating, the display stays dark. Connect it to 3.3V or 5V through a 100-ohm resistor to limit current to 20 mA. The RST pin can be tied to the Arduino’s reset pin or a digital output for software reset. The DC pin (data/command) must be toggled correctly; the library sets it low for commands and high for data. The CS pin is active low, and you must enable it before each SPI transaction. The MISO pin is optional for reading the display’s ID, but the library doesn’t use it for writes. Some clones require a specific initialization sequence; the MCUFriend_kbv library auto-detects the driver by reading the ID register (0xD3) and sends the correct init commands. For example, the ILI9341 returns 0x9341, while the ILI9340 returns 0x9340.

Drawing shapes with anti-aliasing is not supported by the library, but you can simulate it by drawing multiple pixels with varying alpha. For example, a circle with a soft edge uses a gradient of colors from transparent to opaque, but this requires a custom function that calculates pixel intensity based on distance from the center. The library’s drawPixel function is fast enough for this, but you need to manage the color gradient manually. The display’s 262K colors allow smooth gradients, but the 16-bit mode limits to 65K colors, which is still sufficient for most applications. The gamma correction on the ILI9341 is set to 2.2 by default, which matches human perception for linear brightness.

Power consumption is a consideration for battery-powered projects. The display draws 80 mA with backlight on, 50 mA with backlight off, and 20 mA in sleep mode. You can enter sleep mode with tft.writeCommand(0x10) (sleep in) and wake with 0x11 (sleep out). The setRotation function also affects power because the display’s row driver must refresh the panel at 60 Hz. The ILI9341’s refresh rate is fixed at 60 Hz, but you can reduce it by sending a custom command for the frame rate register (0xB1). The default is 60 Hz, but you can set it to 30 Hz to save power, though this may cause flicker. The display’s internal oscillator runs at 1.5 MHz, and the SPI clock is independent.

For troubleshooting, use a logic analyzer to check SPI signals. The SCK line should have a clean square wave at 8 MHz, and the MOSI line should show data bytes after the CS goes low. The DC line toggles before each byte. The display’s response time for a command is 100 ns, but the Arduino’s delayMicroseconds(1) is often enough. If shapes appear garbled, check the initialization sequence. The ILI9341 requires a 5 ms delay after reset, then commands for pixel format (0x3A), memory access control (0x36), and display on (0x29). The library does this automatically, but if you use a custom init, you must send at least 20 commands. The datasheet lists 34 registers for configuration, including gamma correction (0xE0-0xEF) and power control (0xC0-0xC5).

In summary, drawing shapes on a 2.8-inch TFT with Arduino is straightforward with the right libraries and wiring, but performance, memory, and power constraints require careful planning. The ILI9341’s 240x320 resolution and 16-bit color give you a rich canvas for graphics, and the Adafruit_GFX library provides a robust set of primitives. The key is to match the shape complexity to the board’s capabilities, using batch writes and minimal SPI transactions for speed. The 2.8 inch tft display module for arduino is a reliable choice for prototyping, with 5V logic and a 4-wire SPI that simplifies wiring. Always verify the pinout with your specific module, as some clones swap the MOSI and MISO pins or use a different CS pin. The library’s documentation includes examples for drawing shapes, but you can also adapt the code for custom patterns like polygons, arcs, or bezier curves by combining the primitives. The display’s 60 Hz refresh rate ensures smooth animations, and the touch panel adds interactivity for drawing apps. For high-speed applications, consider using a 32-bit board like the Teensy or ESP32, which can push the SPI clock to 40 MHz, reducing shape drawing time by a factor of 5. The ILI9341’s maximum SPI clock is 10 MHz for writes, but many boards exceed this safely, though you might see occasional data corruption at higher speeds. Always test with a simple shape like a rectangle before moving to complex graphics. The library’s setClipRect function can limit drawing to a region, improving performance for partial updates. For example, updating a 50x50 area takes 1/48th of the full screen time, which is useful for UI elements like buttons or sliders. The display’s internal memory is not accessible for buffering, so all drawing must be done in real-time, but the library’s efficient algorithms make this feasible for most Arduino projects. The key takeaway is that the combination of the ILI9341 driver, SPI interface, and Adafruit_GFX library provides a flexible platform for shape drawing, with trade-offs in speed, memory, and power that you can optimize for your specific application. The 2.8-inch form factor is popular for its balance of size and resolution, fitting in handheld devices while offering enough detail for text and graphics. The module’s 5V compatibility eliminates the need for level shifters, and the included SD card slot (if present) allows for storing bitmaps or fonts, expanding the drawing capabilities beyond primitives. The SD card uses a separate SPI bus (CS pin 4), and you can read images from it using the SD library, then draw them with drawBitmap. This opens up possibilities for full-color images, though the 16-bit color depth means some compression is needed for large files. The display’s viewing angle is 120 degrees horizontal and 100 degrees vertical, which is adequate for most uses. The backlight is an LED with a typical lifespan of 20,000 hours, so you can leave it on for extended periods. For outdoor use, the display’s brightness (250 cd/m²) is sufficient in shade but may be washed out in direct sunlight. The touch panel’s resistive technology works with any stylus or finger

Build your own curated indexFree for public libraries. Pro adds private mirroring and CVE alerts.
Create your free library