This STM32 project demonstrates how to interface the STM32 Blue Pill board with an ILI9341 TFT display module featuring a 320×240 pixel resolution.
The project illustrates how to:
- Initialize and configure the ILI9341 display using the SPI protocol
- Display text using various colors and sizes
- Draw basic geometric shapes including: rectangles, circles, and lines with different colors.
This serves as a foundational example for building more advanced GUI projects with STM32 and color graphical displays.
About the STM32 Blue Pill Development Board:
The STM32 Blue Pill is a compact and low-cost development board based on the STM32F103C8T6 microcontroller from STMicroelectronics. This chip features an ARM Cortex-M3 core running at a maximum clock speed of 72 MHz. Its affordability, performance, and form factor have made it a favorite among hobbyists, students, and embedded systems developers.
Programming the STM32 Blue Pill:
To upload code to the Blue Pill board, one of the following tools can be used:
- ST-Link Programmer: This is the recommended method. The ST-Link allows fast and reliable flashing and debugging via the SWD (Serial Wire Debug) interface.
- USB-to-Serial Converter: Alternatively, you can use a USB-to-Serial converter to upload code through the UART bootloader. A popular example is the FT232RL USB-to-TTL module.
Note: If using the UART method, the BOOT0 pin must be set to 1 (HIGH) before powering on or resetting the board to enter bootloader mode.
Abbreviations:
TFT: Thin Film Transistor.
LCD: Liquid Crystal Display.
I2C: Inter-Integrated Circuit.
SPI: Serial Peripheral Interface.
IoT: Internet of things.
About the ILI9341 TFT Display Module:
The ILI9341 is a widely used TFT LCD controller found in small to medium-sized displays, particularly suited for embedded systems, DIY electronics, and microcontroller-based projects. It is favored for its rich feature set, ease of use, and compatibility with many platforms such as Arduino, STM32, ESP32, and Raspberry Pi.
Key Features and Specifications:
- Resolution: 240 × 320 pixels (portrait orientation by default)
- Screen Size: Typically ranges from 2.2” to 3.2” diagonal
- Color Depth: 16-bit (65K colors) to 18-bit (262K colors).
- Interface: SPI (Serial Peripheral Interface) — 4-wire or 5-wire variants. Some modules also support parallel interfaces, though less common in hobby use
- Backlight: Integrated LED backlight
- Touchscreen Capability: Most ILI9341 displays come with a resistive touchscreen with an integrated touch screen controller (XPT2046)
SD Card Slot: Some ILI9341 modules feature a built-in SD card slot, eliminating the need for a separate SD card module
The image shows the ILI9341 TFT module with integrated component details:
The ILI9341 display module shown above includes an onboard 5 V to 3.3 V LDO regulator (U2), the part number of this regulator is: XC6206P332MR (662K).
A J1 solder jumper lets you select the module’s supply voltage:
- Open J1 to power the module from 5 V (through the LDO).
- Close J1 to bypass the LDO and run the module directly from 3.3 V.
This module also integrates the XPT2046 resistive touchscreen controller (–datasheet–), providing full touchscreen functionality. Finally, a small transistor (Q1) on the board is used to drive and control the LED backlight.
ILI9341 TFT Display Module Pinout:
The pinout of an ILI9341 TFT display module can vary slightly depending on the specific board version, but for the module shown above, the pin functions are categorized as follows:
Display Pins (Main TFT Interface):
- VCC: Power supply input (typically 3.3 V or 5 V, depending on module design).
- GND: Ground.
- CS: Chip Select (active LOW).
- RESET: Display reset pin (active LOW).
- D/C (or DC): Data/Command control pin, with low for data mode and high for command mode.
- SDI (MOSI): SPI data input (Master Out, Slave In).
- SCK: SPI clock input.
- LED: Backlight LED control pin (usually tied to VCC via resistor for constant backlight).
- SDO (MISO): SPI data output (Master In, Slave Out).
Touch Screen Controller Pins (XPT2046 – SPI Interface):
- T_CLK: Touch controller SPI clock input.
- T_CS: Touch controller chip select (active LOW).
- T_DIN: Touch controller SPI data input (MOSI).
- T_DO: Touch controller SPI data output (MISO).
- T_IRQ: Touch interrupt output (active LOW when screen is touched).
SD Card Slot Pins (SPI Interface):
- SD_CS: SD card chip select (active LOW).
- SD_MOSI: SPI data input to the SD card.
- SD_MISO: SPI data output from the SD card.
- SD_SCK: SPI clock input for the SD card.
Interfacing STM32 Blue Pill Board with ILI9341 TFT Display:
The image below shows the circuit schematic for interfacing the STM32 Blue Pill board with the ILI9341 TFT display module operating in SPI mode.
Hardware Required:
This is a summary of the components needed to build this project:
- STM32 Blue Pill board – STM32F103C8T6, 32-bit Arm Cortex-M3 MCU (– datasheet –)
- ILI9341 TFT display module
- Breadboard and jumper wires
- STM32 microcontroller programmer (e.g., ST-Link, USB-to-Serial converter, etc.)
The ILI9341 TFT display board shown in the circuit diagram above has 14 pins: the first 9 pins are for the display, and the remaining 5 are for the touch module.
The display-side pins, numbered from 1 to 9 (from left to right), are as follows:
VCC (3.3V), GND (Ground), CS (Chip Select), RST (Reset), DC (or D/C: Data/Command), MOSI (or SDI), SCK (Clock), BL (Backlight LED), and MISO (or SDO). Where:
MOSI: Master-Out Slave-In
SDI: Serial Data In
MISO: Master-In Slave-Out
SDO: Serial Data Out
The ILI9341 display module is powered with 3.3V from the STM32 Blue Pill board. Jumper J1 (located on the back of the module) should be closed to ensure proper operation of the module at 3.3V.
The GND pin of the ILI9341 TFT display module is connected to the GND pin of the STM32 board, and the VCC pin of the display is connected to the 3.3V pin on the STM32 board.
The remaining signal pins of the ILI9341 display module are connected to the STM32 Blue Pill board as follows:
- CS —> STM32 Blue Pill pin PA4.
- RST —> STM32 Blue Pill pin RST.
- DC —> STM32 Blue Pill pin PA3.
- MOSI —> STM32 Blue Pill pin PA7.
- SCK —> STM32 Blue Pill pin PA5.
- BL —> STM32 Blue Pill pin 3V3.
- MISO pin is not connected.
Note: Pins PA4, PA5 and PA7 are hardware SPI1 module pins of the STM32F103C8T6 microcontroller respectively for NSS (Slave Select), SCK (serial clock) and MOSI (master-out slave-in).
Interfacing STM32 Blue Pill Board with ILI9341 TFT Display – Arduino Code:
The Arduino IDE (Integrated Development Environment) is used to write the project code. Before compiling, support for the STM32 Blue Pill board must be added to the IDE.
You can install the STM32 Blue Pill board using the Boards Manager in the Arduino IDE. The official GitHub repository of the Arduino core for STM32 microcontrollers (STM32duino) is available at:
https://github.com/stm32duino/Arduino_Core_STM32
Note: This project was tested using version 2.10.1 of the STM32 Arduino core.
To program the STM32F103C8T6 microcontroller, you can use an FT232RL USB-to-Serial UART converter. Alternatively, the ST-LINK V2 programmer can also be used and is supported by the Arduino IDE.
To compile and run the Arduino code for this project, you’ll need to install two libraries from Adafruit Industries:
Adafruit ILI9341 TFT Display Library: This is the driver library for the ILI9341 LCD. You can install it using the Arduino IDE Library Manager:
Navigate to Sketch → Include Library → Manage Libraries…
Search for “ILI9341” and install the library published by Adafruit.
Adafruit GFX Library – This library provides graphics primitives (text, shapes, etc.). It is also available through the Library Manager:
In the same Library Manager, search for “Adafruit GFX” and install the version published by Adafruit.
Note: During the installation of the Adafruit ILI9341 driver library, the Arduino IDE may prompt you to install additional dependencies, such as Adafruit BusIO. Make sure to accept and install all required libraries.
Tested Library Versions
The following library versions were used to develop and successfully test this project:
Adafruit GFX Library: Version 1.12.1
Adafruit ILI9341 TFT Display Library: Version 1.6.2
Adafruit BusIO: Version 1.17.2
Programming Hints:
The required libraries are included in the Arduino code as shown below:
1 2 3 | #include "SPI.h" // SPI communication library #include "Adafruit_GFX.h" // Adafruit Graphics library #include "Adafruit_ILI9341.h" // Adafruit ILI9341 display driver |
The hardware SPI1 module of the STM32F103C8T6 microcontroller is used to communicate with the ILI9341 TFT display. It is initialized in the Arduino code as follows:
1 2 3 | // Initialize STM32 SPI1 interface with the following pin configuration: // MOSI = PA7, MISO = PA6, SCK = PA5, SS (Chip Select) = PA4 SPIClass SPI_1(PA7, PA6, PA5, PA4); |
The rest signal pins of the ILI9341 TFT is initialized as shown below:
1 2 3 4 | // ILI9341 TFT display signal pin connections #define TFT_CS -1 // Chip Select (-1 if hardware SPI SS pin is used) #define TFT_DC PA3 // Data/Command #define TFT_RST -1 // Reset pin (-1 if connected to MCU reset pin) |
The ILI9341 TFT display can be initialized using the following line, which ties together the SPI bus and the control pins previously defined:
1 2 | // Initialize ILI9341 display with custom SPI and defined control pins Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI_1, TFT_DC, TFT_CS, TFT_RST); |
Before using any display functions (e.g., print(), drawLine(), etc.), the ILI9341 display must be properly initialized. If the initialization fails, the screen will remain blank.
Use the begin() function to initialize the display, as shown below:
1 | tft.begin(); // Initialize the ILI9341 TFT |
Full Arduino Code for STM32:
Note: The Arduino code provided below is adapted from an example sketch included with the Adafruit ILI9341 library. Minor modifications have been made to suit the hardware setup used in this project, specifically, the STM32 Blue Pill board.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | /****************************************************************************** * Project: Interfacing STM32 Blue Pill Board with ILI9341 TFT Display * * Display Resolution: 240x320 pixels * Communication Interface: SPI * Display Type: 65K color TFT (RGB565 format) * * This is free software distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * For more details and tutorials, visit: https://simple-circuit.com/ /*******************************************************************************/ #include "SPI.h" // SPI communication library #include "Adafruit_GFX.h" // Adafruit Graphics library #include "Adafruit_ILI9341.h" // Adafruit ILI9341 display driver // Initialize STM32 SPI1 interface with the following pin configuration: // MOSI = PA7, MISO = PA6, SCK = PA5, SS (Chip Select) = PA4 SPIClass SPI_1(PA7, PA6, PA5, PA4); // ILI9341 TFT display signal pin connections #define TFT_CS -1 // Chip Select (-1 if hardware SPI SS pin is used) #define TFT_DC PA3 // Data/Command #define TFT_RST -1 // Reset pin (-1 if connected to MCU reset pin) // Initialize ILI9341 display with custom SPI and defined control pins Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI_1, TFT_DC, TFT_CS, TFT_RST); void setup() { delay(200); Serial1.begin(9600); Serial1.println("ILI9341 Test!"); delay(500); tft.begin(); // Initialize the ILI9341 TFT // read diagnostics (optional but can help debug problems) uint8_t x = tft.readcommand8(ILI9341_RDMODE); Serial1.print("Display Power Mode: 0x"); Serial1.println(x, HEX); x = tft.readcommand8(ILI9341_RDMADCTL); Serial1.print("MADCTL Mode: 0x"); Serial1.println(x, HEX); x = tft.readcommand8(ILI9341_RDPIXFMT); Serial1.print("Pixel Format: 0x"); Serial1.println(x, HEX); x = tft.readcommand8(ILI9341_RDIMGFMT); Serial1.print("Image Format: 0x"); Serial1.println(x, HEX); x = tft.readcommand8(ILI9341_RDSELFDIAG); Serial1.print("Self Diagnostic: 0x"); Serial1.println(x, HEX); Serial1.println(F("Benchmark Time (microseconds)")); delay(10); Serial1.print(F("Screen fill ")); Serial1.println(testFillScreen()); delay(500); Serial1.print(F("Text ")); Serial1.println(testText()); delay(3000); Serial1.print(F("Lines ")); Serial1.println(testLines(ILI9341_CYAN)); delay(500); Serial1.print(F("Horiz/Vert Lines ")); Serial1.println(testFastLines(ILI9341_RED, ILI9341_BLUE)); delay(500); Serial1.print(F("Rectangles (outline) ")); Serial1.println(testRects(ILI9341_GREEN)); delay(500); Serial1.print(F("Rectangles (filled) ")); Serial1.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA)); delay(500); Serial1.print(F("Circles (filled) ")); Serial1.println(testFilledCircles(10, ILI9341_MAGENTA)); Serial1.print(F("Circles (outline) ")); Serial1.println(testCircles(10, ILI9341_WHITE)); delay(500); Serial1.print(F("Triangles (outline) ")); Serial1.println(testTriangles()); delay(500); Serial1.print(F("Triangles (filled) ")); Serial1.println(testFilledTriangles()); delay(500); Serial1.print(F("Rounded rects (outline) ")); Serial1.println(testRoundRects()); delay(500); Serial1.print(F("Rounded rects (filled) ")); Serial1.println(testFilledRoundRects()); delay(500); Serial1.println(F("Done!")); } void loop(void) { for(uint8_t rotation=0; rotation<4; rotation++) { tft.setRotation(rotation); testText(); delay(1000); } } unsigned long testFillScreen() { unsigned long start = micros(); tft.fillScreen(ILI9341_BLACK); yield(); tft.fillScreen(ILI9341_RED); yield(); tft.fillScreen(ILI9341_GREEN); yield(); tft.fillScreen(ILI9341_BLUE); yield(); tft.fillScreen(ILI9341_BLACK); yield(); return micros() - start; } unsigned long testText() { tft.fillScreen(ILI9341_BLACK); unsigned long start = micros(); tft.setCursor(0, 0); tft.setTextColor(ILI9341_WHITE); tft.setTextSize(1); tft.println("Hello World!"); tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2); tft.println(1234.56); tft.setTextColor(ILI9341_RED); tft.setTextSize(3); tft.println(0xDEADBEEF, HEX); tft.println(); tft.setTextColor(ILI9341_GREEN); tft.setTextSize(5); tft.println("Groop"); tft.setTextSize(2); tft.println("I implore thee,"); tft.setTextSize(1); tft.println("my foonting turlingdromes."); tft.println("And hooptiously drangle me"); tft.println("with crinkly bindlewurdles,"); tft.println("Or I will rend thee"); tft.println("in the gobberwarts"); tft.println("with my blurglecruncheon,"); tft.println("see if I don't!"); return micros() - start; } unsigned long testLines(uint16_t color) { unsigned long start, t; int x1, y1, x2, y2, w = tft.width(), h = tft.height(); tft.fillScreen(ILI9341_BLACK); yield(); x1 = y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t = micros() - start; // fillScreen doesn't count against timing yield(); tft.fillScreen(ILI9341_BLACK); yield(); x1 = w - 1; y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; yield(); tft.fillScreen(ILI9341_BLACK); yield(); x1 = 0; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; yield(); tft.fillScreen(ILI9341_BLACK); yield(); x1 = w - 1; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); yield(); return micros() - start; } unsigned long testFastLines(uint16_t color1, uint16_t color2) { unsigned long start; int x, y, w = tft.width(), h = tft.height(); tft.fillScreen(ILI9341_BLACK); start = micros(); for(y=0; y<h; y+=5) tft.drawFastHLine(0, y, w, color1); for(x=0; x<w; x+=5) tft.drawFastVLine(x, 0, h, color2); return micros() - start; } unsigned long testRects(uint16_t color) { unsigned long start; int n, i, i2, cx = tft.width() / 2, cy = tft.height() / 2; tft.fillScreen(ILI9341_BLACK); n = min(tft.width(), tft.height()); start = micros(); for(i=2; i<n; i+=6) { i2 = i / 2; tft.drawRect(cx-i2, cy-i2, i, i, color); } return micros() - start; } unsigned long testFilledRects(uint16_t color1, uint16_t color2) { unsigned long start, t = 0; int n, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(ILI9341_BLACK); n = min(tft.width(), tft.height()); for(i=n; i>0; i-=6) { i2 = i / 2; start = micros(); tft.fillRect(cx-i2, cy-i2, i, i, color1); t += micros() - start; // Outlines are not included in timing results tft.drawRect(cx-i2, cy-i2, i, i, color2); yield(); } return t; } unsigned long testFilledCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2; tft.fillScreen(ILI9341_BLACK); start = micros(); for(x=radius; x<w; x+=r2) { for(y=radius; y<h; y+=r2) { tft.fillCircle(x, y, radius, color); } } return micros() - start; } unsigned long testCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, r2 = radius * 2, w = tft.width() + radius, h = tft.height() + radius; // Screen is not cleared for this one -- this is // intentional and does not affect the reported time. start = micros(); for(x=0; x<w; x+=r2) { for(y=0; y<h; y+=r2) { tft.drawCircle(x, y, radius, color); } } return micros() - start; } unsigned long testTriangles() { unsigned long start; int n, i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(ILI9341_BLACK); n = min(cx, cy); start = micros(); for(i=0; i<n; i+=5) { tft.drawTriangle( cx , cy - i, // peak cx - i, cy + i, // bottom left cx + i, cy + i, // bottom right tft.color565(i, i, i)); } return micros() - start; } unsigned long testFilledTriangles() { unsigned long start, t = 0; int i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(ILI9341_BLACK); start = micros(); for(i=min(cx,cy); i>10; i-=5) { start = micros(); tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(0, i*10, i*10)); t += micros() - start; tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(i*10, i*10, 0)); yield(); } return t; } unsigned long testRoundRects() { unsigned long start; int w, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(ILI9341_BLACK); w = min(tft.width(), tft.height()); start = micros(); for(i=0; i<w; i+=6) { i2 = i / 2; tft.drawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 0, 0)); } return micros() - start; } unsigned long testFilledRoundRects() { unsigned long start; int i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(ILI9341_BLACK); start = micros(); for(i=min(tft.width(), tft.height()); i>20; i-=6) { i2 = i / 2; tft.fillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0)); yield(); } return micros() - start; } // End of code. // https://simple-circuit.com/ /*************************************************** This is our GFX example for the Adafruit ILI9341 Breakout and Shield ----> http://www.adafruit.com/products/1651 Check out the links above for our tutorials and wiring diagrams These displays use SPI to communicate, 4 or 5 pins are required to interface (RST is optional) Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! Written by Limor Fried/Ladyada for Adafruit Industries. MIT license, all text above must be included in any redistribution ****************************************************/ |
Interfacing STM32 Blue Pill Board with ILI9341 TFT Display Video:
The video below demonstrates the DIY hardware setup for interfacing an STM32 Blue Pill board with an ILI9341 color TFT display.
STM32 Blue Pill with ILI9341 Display – Proteus Simulation Video:
The video below demonstrates a Proteus simulation of the STM32 Blue Pill board interfaced with an ILI9341 TFT display.
Please note that the simulation circuit in Proteus differs from the actual hardware circuit. The real hardware schematic is shown above.
Proteus Simulation File Download:
You can download the Proteus simulation file from the link below.
Please note: Proteus version 8.15 or higher is required to open the project.
STM32 Blue Pill with ILI9341 – Proteus Simulation
Related Projects:
Arduino Interface with ST7789 Color TFT Display
Interfacing STM32 Blue Pill Board with ST7789 TFT Display
Interfacing STM32 Blue Pill with ST7735 Color TFT Display
Discover more from Simple Circuit
Subscribe to get the latest posts sent to your email.