How to program a 3.2 inch 240x320 TFT display with STM32?
How to program a 3.2 inch 240x320 TFT display with STM32
To program a 3.2 inch 240x320 TFT display with an STM32 microcontroller, you need to connect the display to the STM32 via SPI or parallel interface, initialize the display driver (typically ILI9341 or similar), and send pixel data using a framebuffer or direct drawing commands. The most common approach is using SPI, which requires only a few pins: SCK, MOSI, MISO, CS, DC, and RST. For a 3.2 inch 240x320 tft display module, the ILI9341 driver is widely used, and you can find pre-built libraries like Adafruit_ILI9341 or u8g2 that support STM32 via the Arduino core or STM32 HAL libraries. The key steps are: configure the SPI peripheral with a clock speed up to 36 MHz (depending on your STM32 model), set up the GPIO pins for control signals, initialize the display with the correct commands (e.g., software reset, sleep out, display on), and then write pixel data in RGB565 format. For performance, use DMA to transfer framebuffer data to the display, which can achieve refresh rates over 30 fps for static images. Below, I break down the hardware connections, software setup, and optimization techniques with specific data and code examples.
Hardware connections are critical for reliable operation. The 3.2 inch 240x320 TFT display typically uses a 16-bit parallel interface, but most hobbyist projects use the 4-wire SPI variant. For SPI, connect the following pins on the STM32 to the display module: SCK (SPI clock) to pin 18, MOSI (Master Out Slave In) to pin 19, MISO (Master In Slave Out) to pin 20 (optional, for reading display memory), CS (Chip Select) to any GPIO (e.g., PA4), DC (Data/Command) to any GPIO (e.g., PA5), and RST (Reset) to any GPIO (e.g., PA6). The display also requires a 3.3V power supply (up to 200 mA) and a backlight LED that can be controlled via PWM. For the STM32F103C8T6 (Blue Pill), the SPI1 peripheral is on PA5 (SCK), PA7 (MOSI), and PA6 (MISO). The maximum SPI clock speed for the ILI9341 is 40 MHz, but the STM32F103 can only go up to 36 MHz in SPI mode. Use a logic level converter if the STM32 is 3.3V and the display expects 5V—though most 3.2-inch modules are 3.3V tolerant. Check the datasheet of your specific 3.2 inch 240x320 tft display module for the exact pinout, as some modules have a built-in SD card slot that shares SPI lines.
Software initialization sequence is the core of the programming. The ILI9341 driver requires a specific command sequence to wake up and configure the display. Here is a typical initialization sequence used in many libraries, with the exact hex values from the ILI9341 datasheet (version 1.0):
1. Send command 0x01 (Software Reset) and wait 120 ms.
2. Send command 0x11 (Sleep Out) and wait 150 ms.
3. Send command 0xCF (Power Control B) with 3 data bytes: 0x00, 0xC1, 0x30.
4. Send command 0xED (Power On Sequence) with 4 data bytes: 0x64, 0x03, 0x12, 0x81.
5. Send command 0xE8 (Driver Timing Control A) with 3 data bytes: 0x85, 0x00, 0x78.
6. Send command 0xCB (Power Control A) with 5 data bytes: 0x39, 0x2C, 0x00, 0x34, 0x02.
7. Send command 0xF7 (Pump Ratio Control) with 1 data byte: 0x20.
8. Send command 0xEA (Driver Timing Control B) with 2 data bytes: 0x00, 0x00.
9. Send command 0xC0 (Power Control 1) with 1 data byte: 0x23 (VRH[5:0] = 0x23).
10. Send command 0xC1 (Power Control 2) with 1 data byte: 0x10 (SAP[2:0] = 0x10).
11. Send command 0xC5 (VCOM Control 1) with 2 data bytes: 0x3E, 0x28.
12. Send command 0xC7 (VCOM Control 2) with 1 data byte: 0x86.
13. Send command 0x36 (Memory Access Control) with 1 data byte: 0x48 (for RGB orientation, adjust for your layout).
14. Send command 0x3A (Pixel Format Set) with 1 data byte: 0x55 (16-bit RGB565).
15. Send command 0xB1 (Frame Rate Control) with 2 data bytes: 0x00, 0x18 (60 Hz refresh).
16. Send command 0xB6 (Display Function Control) with 3 data bytes: 0x08, 0x82, 0x27.
17. Send command 0xF2 (Enable 3G) with 1 data byte: 0x00.
18. Send command 0x26 (Gamma Set) with 1 data byte: 0x01.
19. Send command 0xE0 (Positive Gamma Correction) with 15 data bytes: 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00.
20. Send command 0xE1 (Negative Gamma Correction) with 15 data bytes: 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F.
21. Send command 0x29 (Display On) and wait 100 ms.
This sequence is derived from the ILI9341 datasheet (Section 9.3, Initial Sequence) and is used by the Adafruit library. For STM32, you can implement this in a function that toggles the DC pin low for commands and high for data. Use the HAL_SPI_Transmit function for each byte. For example, in STM32CubeIDE, your code would look like:
void ILI9341_WriteCommand(uint8_t cmd) {
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
}
void ILI9341_WriteData(uint8_t data) {
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
}
After initialization, you can set the window address using commands 0x2A (Column Address Set) and 0x2B (Page Address Set), then write pixels using command 0x2C (Memory Write). For a 240x320 display, you need to send 240*320 = 76,800 pixels, each 2 bytes (RGB565), totaling 153,600 bytes. At 36 MHz SPI clock, the theoretical transfer time is 153,600 * 8 / 36e6 = 34.1 ms, but with overhead, you get around 40-50 ms per frame, giving 20-25 fps. Using DMA can reduce CPU load but not the transfer time.
Framebuffer management is essential for complex graphics. For the STM32F103, which has only 20 KB of SRAM, you cannot store a full framebuffer (153,600 bytes) in RAM. Instead, use a partial framebuffer or draw directly to the display. For example, you can split the screen into 16-row bands, each requiring 240*16*2 = 7,680 bytes, which fits in SRAM. Or use a double-buffer in external SRAM if your board has it (e.g., STM32F4 Discovery has 512 KB). For the STM32F103, a common technique is to use a line buffer of 240*2 = 480 bytes and draw line by line. This limits you to simple shapes and text, but for images, you need to read from flash memory (e.g., SPI flash or SD card) and write directly to the display. The Adafruit library uses a 16-line buffer for the STM32, but you can optimize by using the STM32’s FSMC peripheral if you use a parallel interface—FSMC can write 16-bit data in one cycle, achieving 10+ MB/s transfer rates, which is 10x faster than SPI.
Performance optimization involves several techniques. First, use the highest SPI clock speed that your display supports. The ILI9341 datasheet specifies a maximum SPI clock of 40 MHz, but the STM32F103’s SPI can only go up to 36 MHz (APB2 clock / 2). Set the SPI prescaler to 2 (SPI_BAUDRATEPRESCALER_2) for 36 MHz. Second, use DMA for SPI transfers to avoid blocking the CPU. In STM32CubeIDE, enable DMA for SPI TX and use HAL_SPI_Transmit_DMA. Third, minimize command overhead by using the window address only once per frame and sending all pixel data in one burst. For example, after setting the window, you can send the entire framebuffer in one DMA transfer. Fourth, use the ILI9341’s “write continue” command (0x3C) to send data without resetting the window, but this is rarely needed. Fifth, consider using a parallel interface (8-bit or 16-bit) if your STM32 has FSMC. For the STM32F407, which has FSMC, you can connect the display’s data pins to the FSMC data bus and control pins to the FSMC address lines, achieving 10-20 MB/s write speeds. This is especially useful for video playback. For the 3.2 inch 240x320 tft display module, the 16-bit parallel interface is standard, but you need to check if your STM32 has enough pins (at least 18 data pins plus control).
Software libraries available for STM32 include:
- Adafruit_ILI9341: Originally for Arduino, but ported to STM32 via the STM32duino core or directly using HAL. It supports all basic drawing functions (lines, circles, rectangles, text) and uses a 16-bit framebuffer. The library is well-documented and has examples for the STM32F103.
- u8g2: A monochrome library that also supports color displays via the ILI9341 driver. It is optimized for small microcontrollers and uses a partial framebuffer. It supports many fonts and is great for text-heavy applications.
- LVGL: A full-featured GUI library that supports STM32 and ILI9341. It requires a framebuffer of at least 1/4 of the screen size (38,400 bytes) and runs well on STM32F4 with external RAM. LVGL provides widgets like buttons, sliders, and charts, and uses DMA for rendering.
- STM32CubeMX generated code: You can use the STM32 HAL library directly with the ILI9341 driver code from the STM32CubeExpansion package. This is the most flexible but requires more manual coding.
For a beginner, I recommend using the Adafruit library with the STM32duino core (Arduino IDE for STM32). Install the core, select your board (e.g., “Generic STM32F103C series”), and install the Adafruit ILI9341 library via the library manager. Then, modify the pins in the example code to match your wiring. The example code includes a test pattern and touch screen support (if your module has a touch controller like XPT2046).
Touch screen integration is common on these modules. The 3.2 inch display often includes a resistive touch screen with a 4-wire interface connected to an XPT2046 ADC. The touch controller uses SPI as well, but you need to share the SPI bus with the display. Use a separate CS pin for the touch controller (e.g., PA7). The XPT2046 returns 12-bit X and Y coordinates. Calibration is required to map the raw ADC values to pixel coordinates. The Adafruit library includes a touch screen class that handles this. For STM32, you can read the touch data by sending the command byte 0x90 (for X) and 0xD0 (for Y) and reading 2 bytes of data. The conversion time is about 1 ms per read. For a responsive UI, poll the touch controller at 100 Hz (10 ms interval).
Power consumption is a consideration for battery-powered projects. The ILI9341 consumes about 20 mA in normal operation, plus 20-30 mA for the backlight (depending on brightness). The STM32F103 consumes about 30 mA at 72 MHz. Total system power is around 70-100 mA. To reduce power, you can put the display into sleep mode (command 0x10) and turn off the backlight via PWM. The STM32 can also enter low-power modes (e.g., Stop mode) while retaining the display state. For example, in a weather station, you can update the display every 10 seconds and sleep the MCU in between, reducing average power to 10 mA.
Common issues and debugging include:
- No display: Check power supply (3.3V, 200 mA), check that RST pin is pulled high (or toggled low then high), and verify SPI clock polarity and phase (mode 0: CPOL=0, CPHA=0).
- Wrong colors: Check pixel format (0x55 for RGB565) and byte order (MSB first). Some displays expect BGR order, so you may need to swap bytes.
- Flickering: Use a higher refresh rate (set frame rate control to 0x00, 0x1F for 70 Hz) or use double buffering.
- Slow updates: Increase SPI clock, use DMA, or switch to parallel interface.
- Touch not working: Check that the touch controller is not interfering with the display SPI bus (use separate CS lines).
For a 3.2 inch 240x320 tft display module, the datasheet provides the exact pinout and initialization commands. Always verify the driver IC (ILI9341, HX8357, or ST7789) by reading the module’s label or the datasheet. Some modules use a different driver, like the ST7789, which has a different initialization sequence but similar SPI commands. For example, the ST7789 requires command 0x36 (Memory Data Access Control) with a different byte value for landscape orientation. If your module is from a generic supplier, try the ILI9341 initialization first, as it is the most common.
Advanced techniques include using the STM32’s Chrom-ART accelerator (DMA2D) on STM32F4/F7 series to fill rectangles or blend images without CPU load. For example, on the STM32F407, you can use DMA2D to fill a framebuffer with a solid color in 1-2 ms, then transfer to the display via FSMC. This allows 60 fps animation. For the STM32F103, you can use the hardware SPI with DMA and a double-buffer in external SRAM (e.g., 23LC1024 SPI SRAM) to achieve 30 fps for simple graphics. Another technique is to use the display’s “window address” to update only a small region (e.g., a button) instead of the whole screen, reducing data transfer by 10x. For example, to update a 50x20 pixel button, you only need to send 50*20*2 = 2,000 bytes, which takes 0.5 ms at 36 MHz.
Testing and validation should include a simple test pattern: fill the screen with red (0xF800), green (0x07E0), blue (0x001F), and white (0xFFFF) to verify color accuracy. Then, draw a grid of lines to check for dead pixels. For touch, draw a crosshair at the touch point to verify calibration. Use the STM32’s serial port to print raw touch coordinates for debugging. The ILI9341 also supports a “read pixel” command (0x2E) to verify that the display is writing correctly, but this requires MISO connection
Keep reading
Three more stories from the desk, picked by the editors.
The Rent vs. Regret calculator, refreshed for 2025
240,000 readers have planned a cross-state move with it. Here's what's new.
CultureWhy every city feels the same now
A field report from 14 downtowns in 14 weeks, plus the data behind the sameness.
MoneyThe quietly brutal math of a $1,800 raise
What moves when the number moves — and why most planners get the ordering wrong.