Skip to content

How to draw shapes on a 2.4 inch 240x320 display?

aadmin· · By the NDAGLinks editors

How to Draw Shapes on a 2.4 inch 240x320 Display

To draw shapes on a 2.4 inch 240x320 ips display, you need to interface it with a microcontroller like an ESP32 or STM32 using SPI or parallel RGB protocols, then leverage a graphics library such as Adafruit_GFX or LVGL to render primitives like lines, rectangles, circles, and triangles. The display itself has a pixel resolution of 240 columns by 320 rows, with a typical active area of 36.72mm x 48.96mm, a pixel pitch of 0.153mm, and a color depth of 16-bit (65,536 colors) via RGB565 format. For example, using an ESP32 with SPI at 40 MHz, you can achieve a frame rate of about 30-60 fps for simple shapes, depending on the buffer size and drawing method. The controller IC, often ILI9341 or ST7789, handles the pixel addressing, so you send commands like 0x2C for memory write after setting the window with 0x2A (column) and 0x2B (row). Data is transmitted in 2-byte chunks per pixel, so a full screen fill requires 240 * 320 * 2 = 153,600 bytes. For shapes, you calculate the bounding box and only update those pixels, reducing data transfer. For instance, a filled rectangle of 50x50 pixels takes 50 * 50 * 2 = 5,000 bytes, which at 40 MHz SPI (about 5 MB/s theoretical) takes roughly 1 ms, though overhead from library calls adds 5-10 ms. The 2.4 inch 240x320 ips display supports both SPI and RGB interfaces; SPI uses 4 wires (SCK, MOSI, MISO, CS, DC, RST) and is simpler for low-pin-count MCUs, while RGB parallel requires 16-18 data lines plus control signals for higher refresh rates up to 60 fps. For drawing shapes, you must initialize the display with specific timing parameters: for ILI9341, the reset pulse should be at least 10 µs, followed by a 120 ms delay, then send commands like 0x11 (sleep out) with 150 ms wait, and 0x29 (display on). The color format is set by 0x3A with value 0x55 for 16-bit. Below is a typical initialization sequence for an STM32F103C8T6 using SPI at 18 MHz:

CommandData BytesDescriptionDelay (ms)
0x01NoneSoftware reset120
0x11NoneSleep out150
0x3A0x55Set pixel format to 16-bit10
0x360x48Memory access control (orientation)10
0x29NoneDisplay on50

Once initialized, you draw shapes by setting a window and writing pixel data. For a line, use Bresenham’s algorithm to calculate intermediate points, then write each pixel. For a circle, the midpoint circle algorithm reduces computations by using symmetry. The Adafruit_GFX library, for example, implements these with integer math, so a circle of radius 20 takes about 125 pixels, requiring 250 bytes of SPI data. The library’s drawLine() function uses a fixed-point approach, while fillRect() loops through rows and columns. For performance, use DMA (Direct Memory Access) on the MCU to send SPI data without CPU intervention. On an ESP32, the SPI driver can handle 64-byte transfers in about 3 µs, so a 100-pixel line takes 0.6 ms. For complex shapes like polygons, you can use the drawPolygon() function in LVGL, which supports up to 10 vertices with anti-aliasing. The display’s IPS technology provides a viewing angle of 160 degrees horizontally and vertically, with a contrast ratio of 1000:1 and brightness of 300 cd/m², so colors remain consistent even when viewed from 45 degrees off-axis. The response time is 25 ms, so fast-moving shapes may show slight ghosting, but for static drawings, it’s negligible. The power consumption is about 200 mA at 3.3V when the backlight is on, so consider a PWM pin to control brightness via a MOSFET. For drawing shapes programmatically, you need to manage the frame buffer. On a resource-constrained MCU like an Arduino Uno (2 KB SRAM), you cannot store a full 153,600-byte buffer, so you draw directly to the display using windowed updates. For example, to draw a triangle, you calculate the three vertices, then use a scanline fill algorithm: for each y from min to max, find the left and right x intersections, then draw a horizontal line. This requires only a few hundred bytes of temporary storage. On an ESP32 with 520 KB SRAM, you can allocate a partial buffer of 240 * 40 = 9,600 bytes (40 rows) to speed up batch drawing. The SPI clock speed affects drawing speed: at 20 MHz, a pixel transfer takes 0.1 µs per byte, so a 50-pixel line takes 10 µs for data, but with command overhead, it’s about 50 µs. At 40 MHz, it halves to 25 µs. The display’s controller IC has a write cycle time of 66 ns, so the SPI speed is the bottleneck. For parallel RGB, the 16-bit bus can transfer a pixel in one clock cycle, so at 10 MHz, you get 10 million pixels per second, or about 65 fps for full screen, but it uses more GPIO pins (18 for data, plus control). The trade-off is pin count versus speed. For drawing shapes, you can also use hardware acceleration if the MCU has a parallel memory interface, like the FSMC on STM32F4, which maps the display to memory addresses, allowing direct writes with *(uint16_t*)0x60000000 = color. This reduces overhead to a single instruction per pixel, achieving 30-40 fps for complex shapes. The display’s pixel layout is RGB stripe, so each pixel is a combination of red (5 bits), green (6 bits), and blue (5 bits). To draw a red circle, send 0xF800 (RGB565: R=31, G=0, B=0). For a green line, use 0x07E0. For a blue rectangle, 0x001F. The gamma correction is set by the controller, but you can adjust it via 0xE0 to 0xE7 commands for positive and negative gamma, with 15 values each. For example, the default gamma for ILI9341 is: 0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0x87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00. Changing these affects shade uniformity. For drawing shapes with anti-aliasing, you need a frame buffer and sub-pixel rendering, which is computationally heavy. LVGL supports this with a software renderer, but on an ESP32, it reduces frame rate to 10-15 fps for complex scenes. For simple shapes, avoid anti-aliasing to keep speed. The display’s backlight is typically driven by a boost converter from 3.3V to 10-12V for the LED string, with a current of 20-30 mA per LED. The total backlight current is about 80-120 mA, so you can use a transistor to PWM it. For drawing shapes in a loop, use a timer interrupt to update the display at a fixed rate, like 30 Hz, and use double buffering if memory allows. On an ESP32, you can use the esp_timer API to create a 33 ms callback. The display’s touch interface, if present, is a resistive touch panel with 4 wires, requiring an ADC to read X and Y coordinates. For drawing shapes, you can use touch input to draw freehand, but that’s beyond primitive shapes. The SPI bus can be shared with other devices, but use separate chip selects. The display’s CS pin must be low during data transfer, and the DC pin selects command (low) or data (high). The RST pin is active low, and a 10 µF capacitor on the power line stabilizes voltage. For drawing a grid of shapes, like 10x10 rectangles, you calculate the positions: each rectangle is 24x32 pixels, with a 1-pixel gap, so total width = 10*24 + 9 = 249, which exceeds 240, so you must scale down to 23x31 pixels. The display’s aspect ratio is 3:4, so shapes should be proportioned accordingly. For a circle, the diameter should be less than 240 to fit horizontally. The library functions like drawCircle() take x, y, radius, and color. For a filled circle, use fillCircle(), which uses a similar algorithm but draws horizontal lines. The memory usage for a circle of radius 50 is about 7,854 pixels, or 15,708 bytes, which takes about 3 ms at 40 MHz SPI. For a triangle, use drawTriangle() with three points, and fillTriangle() for filled. The algorithm uses a scanline, so it’s efficient. The display’s orientation can be changed by setting the MADCTL register (0x36) with bits: MY (row order), MX (column order), MV (row/column exchange), ML (vertical refresh order), BGR (color order), and MH (horizontal refresh order). For portrait mode, use 0x48 (MY=0, MX=0, MV=0, ML=0, BGR=1, MH=0). For landscape, use 0x28 (MV=1). This affects how shapes are drawn: in landscape, the width becomes 320 and height 240, so you must adjust coordinates. For example, a rectangle of 100x50 in portrait becomes 50x100 in landscape. The library handles this if you set the width and height correctly. The display’s pixel clock for SPI is typically 40 MHz max, but some controllers like ST7789 support up to 62.5 MHz. On a Raspberry Pi Pico, you can use PIO to generate SPI at 50 MHz, achieving 8 MB/s, so a 100-pixel line takes 25 µs. The backlight can be controlled with a 1 kHz PWM frequency to avoid flicker. The display’s operating temperature is -20°C to +70°C, so it’s suitable for indoor use. For drawing shapes in a real-time system, prioritize the SPI transfer using interrupts. On an STM32, use the SPI interrupt with a circular buffer to send data without blocking. The display’s driver IC has a 240x320 pixel RAM, so you can write to any pixel without reading back. This simplifies shape drawing because you don’t need to read-modify-write. For overlapping shapes, just write the new color over the old one. The library’s fillScreen() function writes all 153,600 pixels, which takes about 30 ms at 40 MHz. For a game-like application, draw shapes in a background buffer and then copy to the display. On an ESP32 with PSRAM, you can allocate a full frame buffer of 153,600 bytes, update it in RAM, then send via DMA in one go. This reduces flicker and improves speed. The DMA transfer for a full screen at 40 MHz takes about 15 ms, leaving 33 ms for other tasks at 30 fps. The display’s color depth is 16-bit, so you can use gradients by interpolating between two colors. For a gradient rectangle, calculate the color for each pixel based on its position. For example, from red to blue: for x from 0 to 239, red = 31 - (31 * x / 239), blue = 31 * x / 239, then combine as RGB565. This requires 240*320 = 76,800 calculations, which on an ESP32 at 240 MHz takes about 10 ms. For a circle gradient, use distance from center. The display’s gamma can be set to linearize the response, but for most shapes, the default is fine. The SPI interface uses 4 wires: SCK (clock), MOSI (data), CS (chip select), DC (data/command), and RST (reset). The typical wiring: connect SCK to GPIO 18, MOSI to GPIO 23, CS to GPIO 5, DC to GPIO 2, RST to GPIO 4, and backlight to GPIO 16 via a 100 ohm resistor. The power supply should be 3.3V at 500 mA minimum. For drawing shapes, you can use the tft.drawPixel() function for individual pixels, but it’s slow for many pixels. Instead, use tft.startWrite() to set the window, then tft.writeColor() for each pixel, and tft.endWrite() to finish. This reduces overhead. For example, to draw a line from (10,10) to (200,100), use Bresenham’s algorithm and write each pixel. The library’s drawLine() does this automatically. The performance of drawLine() on an ESP32 at 40 MHz is about 0.5 µs per pixel, so a 200-pixel line takes 100 µs. For a filled rectangle, use fillRect(), which writes in a loop. For a 100x100 square, it writes 10,000 pixels, taking 5 ms. The display’s refresh rate is 60 Hz, but the SPI speed limits the update rate. For complex shapes, consider using the parallel RGB interface, which can update the entire screen at 60 fps. The parallel interface uses 16 data lines (D0-D15) plus WR, RD, CS, RS, RST, and backlight. The WR strobe is active low, and data is latched on the rising edge. The timing: WR cycle time is 100 ns, so a pixel write takes 100 ns, giving 10 million pixels per second, or 65 fps for full screen. This is ideal for real-time shape drawing. The trade-off is that you need a microcontroller with at least 18 free GPIO pins, like an STM32F429 with 144 pins. The display’s RGB interface can also be used with a DMA controller, like the STM32’s LTDC (LCD-TFT Display Controller), which handles the timing automatically. For example, with LTDC, you set up a frame buffer in SRAM, and the hardware sends data to the display at 60 fps without CPU intervention. This allows you to draw shapes in the buffer using software, and the display updates seamlessly. The buffer size is 240 * 320 * 2 = 153,600 bytes, which fits in the STM32F429’s 256 KB SRAM. The LTDC uses a pixel clock of 6.5 MHz for 60 fps, with horizontal back porch of 10 pixels, front porch of 10, sync width of 10, and vertical back porch of 10 lines, front porch of 10, sync width of 2. The total horizontal period is 240 + 10 + 10 + 10 = 270 pixels, and vertical is 320 + 10 + 10 + 2 = 342 lines, so the pixel clock is 270 * 342 * 60 = 5.54 MHz, rounded to 6.5 MHz. This is set via the LTDC registers. For drawing shapes, you can use the drawLine() function from a library like STemWin, which is optimized for the LTDC. The library uses hardware acceleration for bit-block transfers (bit blit), so a filled rectangle can be done in a single DMA2D operation. The DMA2D can copy a block of memory from a source to a destination, with color conversion if needed. For example, to fill a rectangle with red, you set the source to a 1-pixel buffer with 0xF800, and the destination to the rectangle area, and the DMA2D copies it in hardware. This takes about 0.5 ms for a 100x100 rectangle, compared to 5 ms with software. The display’s IPS technology ensures that shapes look the same from any angle, with a typical contrast ratio of 1000:1. The brightness is 300 cd/m², so you can see shapes in indoor light. The backlight can be dimmed to 10% for low-power use. The display’s driver IC also supports partial display mode, where you can update only a portion of the screen. For example, with 0x30 and 0x31 commands, you set the partial area, and then write data only to that region. This is useful for drawing shapes that change frequently, like a moving circle. The partial mode reduces SPI data transfer, so a 50x50 moving circle uses only 5,000 bytes per frame instead of 153,600.