ILI9341 Arduino: conectar la pantalla TFT, táctil e imágenes SD
Cómo conectar una pantalla TFT ILI9341 a Arduino: nivel de 3,3 V en el Uno, Adafruit GFX, táctil XPT2046, BMP desde SD, ESP32 con TFT_eSPI y la pantalla blanca.
Conectar una ILI9341 a Arduino es la forma más barata y extendida de añadir a tu proyecto una pantalla de 240×320 píxeles con 65 mil colores. Estos módulos TFT de 2,4 y 2,8 pulgadas sobre placa roja se conectan por SPI con solo unos pocos cables, y las versiones táctiles llevan además un controlador táctil XPT2046 y una ranura para tarjeta microSD. Con un solo módulo puedes mostrar gráficos en color, botones táctiles e imágenes leídas de la tarjeta. Es ideal para menús, paneles de control, juegos y marcos de fotos donde una OLED o una LCD de caracteres ya no basta.
En esta guía aprenderás paso a paso cómo funciona el módulo, cómo conectar con seguridad las salidas de 5 V del Arduino Uno a las entradas de 3,3 V de la pantalla, cómo dibujar textos, formas y colores con las librerías Adafruit_ILI9341 y Adafruit GFX, cómo leer el táctil y cómo mostrar imágenes BMP desde una tarjeta SD. Después veremos el dibujo mucho más rápido en el ESP32 con TFT_eSPI, el uso de MicroPython en la Raspberry Pi Pico y todas las causas del problema de la "pantalla blanca", con el que casi todo el mundo se encuentra alguna vez.

¿Qué es la pantalla TFT ILI9341 y cómo funciona?
El ILI9341 es el chip controlador que hay detrás del cristal. Contiene unos 172 KB de memoria de imagen (GRAM) donde caben los 240×320 píxeles. El Arduino envía comandos y datos de color a esa memoria por SPI, y el chip refresca la imagen por sí solo decenas de veces por segundo. Por eso la imagen no tiene que guardarse en la RAM del Arduino: incluso un Uno con 2 KB de RAM puede manejar una pantalla grande a color. Para dibujar algo, primero se elige una ventana (x, y, ancho, alto) y luego se envían los píxeles de esa ventana uno tras otro. El pin DC indica si el byte enviado es un comando (LOW) o un dato (HIGH). Los colores suelen enviarse en formato RGB565, es decir, 16 bits por píxel: 5 bits de rojo, 6 de verde y 5 de azul. Cómo funciona el bus SPI lo tienes explicado en detalle en Qué es SPI.
| Característica | Valor |
|---|---|
| Chip controlador | ILI9341 |
| Resolución | 240×320 píxeles (320×240 en horizontal) |
| Color | 65.536 colores con RGB565 (el chip admite 262 mil) |
| Tamaños | 2,2", 2,4", 2,8" y 3,2" (todos 240×320) |
| Interfaz | SPI de 4 hilos: CS, DC, SCK, MOSI (MISO para leer) |
| Alimentación (VCC) | 3,3–5 V (la placa lleva un regulador de 3,3 V) |
| Nivel lógico | 3,3 V (no tolera 5 V) |
| Consumo | Unos 0,3–0,6 W, casi todo de la retroiluminación |
| Táctil | Resistivo, controlador XPT2046 (en los modelos táctiles) |
| Hardware extra | Ranura para tarjeta microSD (pines SPI propios) |
¿2,4 o 2,8 pulgadas? Módulo SPI frente a shield para Uno
| Característica | Módulo SPI 2,4" | Módulo SPI 2,8" | Shield 2,4" para Uno |
|---|---|---|---|
| Resolución | 240×320 | 240×320 | 240×320 |
| Conexión | SPI, con cables | SPI, con cables | Paralelo de 8 bits, se pincha encima del Uno |
| Pines usados | 5 para la pantalla, 8–9 con táctil y SD | 5 para la pantalla, 8–9 con táctil y SD | Casi todos (D2–D9, A0–A4, D10–D13 para la SD) |
| Controlador | ILI9341 | ILI9341 | Variable: ILI9341, ILI9325, ST7789 y otros |
| Librería | Adafruit_ILI9341, TFT_eSPI | Adafruit_ILI9341, TFT_eSPI | MCUFRIEND_kbv |
| Táctil | XPT2046 (SPI) | XPT2046 (SPI) | Analógico de 4 hilos (librería TouchScreen) |
| ESP32 y Pico | Compatible | Compatible | Pensado para Uno y Mega |
Este artículo trata del módulo SPI, porque también funciona con el ESP32 y la Pico y deja libres la mayoría de los pines del Uno. Los dos módulos SPI tienen exactamente los mismos pines y el mismo código; la pantalla de 2,8 pulgadas tiene píxeles algo más grandes, así que es más fácil acertar en los botones táctiles. Si tu pantalla es un shield que se pincha directamente sobre el Uno, las librerías SPI no pueden manejarla y el resultado suele ser una pantalla blanca. Para ese tipo de shield instala la librería MCUFRIEND_kbv, lee el identificador del controlador con tft.readID() (0x9341 en el ILI9341) e inicialízala con tft.begin(ID). Si buscas una alternativa más pequeña pero más nítida, mira Pantalla TFT ST7789, y para comparar todas las opciones, Tipos de pantallas para Arduino.
Pines del ILI9341 y tabla de conexiones
En un borde del módulo está el conector principal de 14 pines: los 9 primeros son para la pantalla y los 5 últimos, que empiezan por T_, para el táctil. Los 4 pines del borde opuesto (SD_CS, SD_MOSI, SD_MISO, SD_SCK) van a la ranura de la tarjeta. Los tres dispositivos pueden compartir el mismo bus SPI (SCK, MOSI, MISO); solo sus pines CS tienen que ser distintos. La tabla coincide con todos los códigos de este artículo; en la Pico, el táctil va a un bus SPI aparte por la diferencia de velocidad.
| Pin del módulo | Arduino Uno (con conversor de nivel) | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | 3V3 | 3V3(OUT) |
| GND | GND | GND | GND |
| CS | D10 | GPIO 15 | GP17 |
| RESET | D8 | GPIO 4 | GP21 |
| DC | D9 | GPIO 2 | GP20 |
| SDI (MOSI) | D11 | GPIO 23 | GP19 |
| SCK | D13 | GPIO 18 | GP18 |
| LED | 3.3V | 3V3 | 3V3(OUT) |
| SDO (MISO) | D12 (directo) | GPIO 19 | GP16 |
| T_CLK | D13 | GPIO 18 | GP10 |
| T_CS | D7 | GPIO 21 | GP13 |
| T_DIN | D11 | GPIO 23 | GP11 |
| T_DO | D12 (directo) | GPIO 19 | GP12 |
| T_IRQ | D2 (directo) | Libre | Libre |
| SD_CS | D4 | GPIO 5 | Libre |
| SD_MOSI / SD_MISO / SD_SCK | D11 / D12 / D13 | GPIO 23 / 19 / 18 | Libre |
Conversión de nivel a 3,3 V en el Arduino Uno
El ESP32 y la Raspberry Pi Pico ya trabajan a 3,3 V, así que puedes conectar la pantalla directamente. En placas de 5 V como el Uno, el Nano y el Mega, en cambio, cada línea que va del Arduino a la pantalla debe bajarse a 3,3 V: CS, DC, RESET, SCK, MOSI y, si usas el táctil y la SD, T_CLK, T_DIN, T_CS, SD_CS, SD_MOSI y SD_SCK. Las líneas SDO, T_DO, SD_MISO y T_IRQ, que van de la pantalla al Arduino, no necesitan conversor: el Uno lee como HIGH cualquier tensión por encima de 3 V, y una salida de 3,3 V lo cumple. En la práctica hay cuatro opciones:
- Buffer 74HC4050 (la solución más limpia): se alimenta a 3,3 V, admite 5 V en sus entradas y convierte 6 líneas en un solo chip. Es rápido y funciona sin problemas con SPI a 8 MHz. El antiguo CD4050 también sirve, pero es lento; si la imagen sale corrupta tendrás que bajar la velocidad del SPI. Para la pantalla sola bastan 5 líneas; con táctil y SD se suman T_CS y SD_CS, en total 7, así que usa un segundo chip o un divisor de tensión para la séptima.
- Divisores de tensión con resistencias: en cada línea pon 1 kΩ entre el pin del Arduino y el de la pantalla, y 2 kΩ entre el pin de la pantalla y GND; obtienes 5 V × 2 / 3 ≈ 3,3 V. Puedes calcular otras parejas de resistencias con la calculadora de divisor de tensión. Es barato, pero 10 resistencias para 5 líneas llenan la protoboard.
- Módulo conversor de nivel ya hecho: los módulos de 4 u 8 canales con MOSFET BSS138 se cablean fácilmente, pero sus resistencias pull-up suavizan los flancos de la señal a altas velocidades. Si la imagen sale corrupta, baja el SPI a 4 MHz con tft.begin(4000000). Tienes los métodos comparados en Conversor de nivel lógico.
- El método que debes evitar: el popular truco de "una resistencia de 10 kΩ en serie en cada línea" descarga el exceso de tensión en los diodos de protección internos del chip. Puede funcionar un tiempo, pero es inestable y a la larga puede dañar la pantalla.
Paso a paso: primera prueba con Adafruit_ILI9341
- Abre el Gestor de librerías del Arduino IDE, busca Adafruit ILI9341 e instálala. Cuando pregunte por las dependencias, elige Install all para que se instalen también Adafruit GFX y Adafruit BusIO. Si es tu primera librería, te ayudará Cómo añadir una librería en Arduino.
- Para el táctil, instala además la librería XPT2046_Touchscreen de Paul Stoffregen. La librería SD ya viene con el Arduino IDE.
- Conecta la pantalla según la tabla de arriba, a través de un conversor de nivel en el Uno. En la primera prueba puedes dejar libres los pines del táctil y de la SD.
- Carga el siguiente código y abre el Monitor Serie a 9600 baudios. En la pantalla deberían aparecer textos y formas, y en el Monitor Serie los códigos de diagnóstico de la pantalla.
- Prueba también los ejemplos incluidos: Archivo > Ejemplos > Adafruit ILI9341 > graphicstest mide cuántos microsegundos tarda cada operación de dibujo.
1#include <SPI.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_ILI9341.h>
4
5#define TFT_CS 10 // ekran seçme / chip select
6#define TFT_DC 9 // komut/veri seçimi / command/data select
7#define TFT_RST 8 // donanım reset / hardware reset
8
9Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST); // donanım SPI: D11 MOSI, D13 SCK / hardware SPI
10uint16_t orange; // kendi rengimiz / our own colour
11
12void setup() {
13 Serial.begin(9600);
14 tft.begin(); // Uno'da 8 MHz SPI / 8 MHz SPI on Uno
15
16 // Teşhis: çalışan ekranda 0x94 ve 0xC0 beklenir / Diagnostics: a working display reports 0x94 and 0xC0
17 Serial.print(F("Power Mode: 0x"));
18 Serial.println(tft.readcommand8(ILI9341_RDMODE), HEX);
19 Serial.print(F("Self Diagnostic: 0x"));
20 Serial.println(tft.readcommand8(ILI9341_RDSELFDIAG), HEX);
21
22 tft.setRotation(1); // yatay, 320x240 / landscape, 320x240
23 tft.fillScreen(ILI9341_BLACK);
24 orange = tft.color565(255, 128, 0); // RGB'den RGB565'e / RGB to RGB565
25
26 tft.setTextColor(ILI9341_WHITE);
27 tft.setTextSize(3); // 18x24 piksel karakter / 18x24 px characters
28 tft.setCursor(10, 10);
29 tft.print(F("BlueGrays"));
30 tft.setTextColor(ILI9341_CYAN);
31 tft.setTextSize(2);
32 tft.setCursor(10, 45);
33 tft.print(F("ILI9341 320x240"));
34
35 tft.fillRect(10, 75, 90, 60, ILI9341_RED); // dolu dikdörtgen / filled rectangle
36 tft.drawRect(115, 75, 90, 60, ILI9341_GREEN); // çerçeve / outline
37 tft.fillCircle(265, 105, 30, ILI9341_BLUE); // dolu daire / filled circle
38 tft.fillRoundRect(10, 150, 140, 50, 10, orange); // yuvarlak köşeli düğme / rounded button
39 tft.drawTriangle(180, 200, 240, 150, 300, 200, ILI9341_YELLOW);
40 tft.drawFastHLine(0, 220, 320, ILI9341_MAGENTA); // yatay çizgi / horizontal line
41}
42
43void loop() {
44 // Arka plan rengiyle yazınca eski rakam silinir, ekran titremez / writing with a background colour erases the old digits, no flicker
45 tft.setTextColor(ILI9341_WHITE, orange);
46 tft.setTextSize(2);
47 tft.setCursor(22, 167);
48 tft.print(millis() / 1000);
49 tft.print(F(" s"));
50 delay(200);
51}| Función | Qué hace |
|---|---|
| tft.begin(frecuencia) | Inicia la pantalla; sin frecuencia usa SPI a 8 MHz en el Uno y a 40 MHz en el ESP32 |
| tft.setRotation(0–3) | Elige la orientación: 0 y 2 vertical (240×320), 1 y 3 horizontal (320×240) |
| tft.fillScreen(color) | Pinta toda la pantalla de un color |
| tft.setTextColor(texto, fondo) | Fija el color del texto; con color de fondo sobrescribe limpiamente el texto anterior |
| tft.setTextSize(n) | Amplía el texto n veces; tamaño 1 = caracteres de 6×8 píxeles |
| tft.drawRect / fillRect(x, y, ancho, alto, color) | Rectángulo con borde o relleno |
| tft.fillRoundRect(x, y, ancho, alto, r, color) | Rectángulo relleno con esquinas redondeadas; ideal para botones |
| tft.drawCircle / fillCircle(x, y, r, color) | Círculo o círculo relleno |
| tft.color565(r, g, b) | Convierte valores RGB de 0–255 en un color de 16 bits |
| tft.readcommand8(comando) | Lee un registro de la pantalla (MISO debe estar conectado); para diagnosticar fallos |
La librería trae constantes de color listas para usar (19, como ILI9341_RED, ILI9341_NAVY e ILI9341_ORANGE); para un color propio usa color565(). Esta función coloca uno junto a otro los 5 bits altos del rojo, los 6 bits altos del verde y los 5 bits altos del azul: color = (R >> 3) << 11 | (G >> 2) << 5 | (B >> 3). Para el naranja (255, 165, 0), por ejemplo, 31 << 11 = 0xF800 y 41 << 5 = 0x0520 se combinan en 0xFD20, que es justo el valor de ILI9341_ORANGE en la librería. El verde recibe un bit más porque el ojo humano es más sensible al verde.
Leer la pantalla táctil XPT2046
En los módulos táctiles hay una fina capa resistiva sobre el cristal. Donde presiona tu dedo o un lápiz, dos capas conductoras se tocan; el chip XPT2046 mide la posición X e Y de ese punto con un ADC de 12 bits como valores brutos de 0 a 4095 y además da un valor de presión (Z). La librería considera que no hay toque si Z está por debajo de 400. El chip comparte el bus SPI con la pantalla, pero se comunica más despacio, a unos 2 MHz; la librería ajusta la velocidad por sí sola en cada lectura. El pin T_IRQ pasa a LOW al tocar y evita que el procesador haga lecturas inútiles. El siguiente código convierte la pantalla en una pequeña pizarra: eliges un color en las casillas de arriba y borras la pantalla con el botón "SIL" (borrar).
1#include <SPI.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_ILI9341.h>
4#include <XPT2046_Touchscreen.h>
5
6#define TFT_CS 10
7#define TFT_DC 9
8#define TFT_RST 8
9#define T_CS 7 // dokunmatik seçme / touch chip select
10#define T_IRQ 2 // dokununca LOW olur / goes LOW on touch
11#define SD_CS 4 // SD kullanılmıyor, HIGH tutulur / SD unused, kept HIGH
12
13// Kalibrasyon: Seri Monitör'deki ham değerlerle değiştir / Calibration: replace with the raw values from Serial Monitor
14#define TS_MINX 300
15#define TS_MAXX 3800
16#define TS_MINY 300
17#define TS_MAXY 3800
18
19Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
20XPT2046_Touchscreen ts(T_CS, T_IRQ);
21
22uint16_t brush = ILI9341_YELLOW; // fırça rengi / brush colour
23
24void drawPalette() {
25 tft.fillRect(0, 0, 40, 40, ILI9341_RED);
26 tft.fillRect(40, 0, 40, 40, ILI9341_GREEN);
27 tft.fillRect(80, 0, 40, 40, ILI9341_BLUE);
28 tft.fillRect(120, 0, 40, 40, ILI9341_YELLOW);
29 tft.drawRect(260, 0, 60, 40, ILI9341_WHITE); // silme düğmesi / clear button
30 tft.setTextColor(ILI9341_WHITE);
31 tft.setTextSize(2);
32 tft.setCursor(273, 13);
33 tft.print(F("SIL"));
34}
35
36void setup() {
37 Serial.begin(9600);
38 pinMode(SD_CS, OUTPUT);
39 digitalWrite(SD_CS, HIGH); // SD kart hattı meşgul etmesin / keep the SD card off the bus
40 tft.begin();
41 tft.setRotation(1); // ekran yatay / display landscape
42 ts.begin();
43 ts.setRotation(1); // dokunmatik de aynı yönde / touch in the same orientation
44 tft.fillScreen(ILI9341_BLACK);
45 drawPalette();
46}
47
48void loop() {
49 if (!ts.touched()) return; // dokunma yok / no touch
50 TS_Point p = ts.getPoint(); // ham x, y (0-4095) ve basınç z / raw x, y and pressure z
51 Serial.print(p.x); Serial.print(',');
52 Serial.print(p.y); Serial.print(',');
53 Serial.println(p.z);
54
55 int16_t x = constrain(map(p.x, TS_MINX, TS_MAXX, 0, 319), 0, 319); // ekran koordinatı / screen coordinate
56 int16_t y = constrain(map(p.y, TS_MINY, TS_MAXY, 0, 239), 0, 239);
57
58 if (y < 40) { // üst şerit: palet ve düğme / top strip: palette and button
59 if (x < 40) brush = ILI9341_RED;
60 else if (x < 80) brush = ILI9341_GREEN;
61 else if (x < 120) brush = ILI9341_BLUE;
62 else if (x < 160) brush = ILI9341_YELLOW;
63 else if (x >= 260) { tft.fillScreen(ILI9341_BLACK); drawPalette(); }
64 } else {
65 tft.fillCircle(x, y, 3, brush); // parmağın altına nokta / dot under the finger
66 }
67}Los valores brutos no son 0 y 4095 justo en las esquinas; en la mayoría de los módulos los bordes quedan aproximadamente entre 200–400 y 3700–3900. Para calibrar, abre el Monitor Serie, toca con un lápiz la esquina superior izquierda y la inferior derecha, y escribe los valores x e y que veas en las constantes TS_MINX, TS_MAXX, TS_MINY y TS_MAXY. Si el punto aparece reflejado respecto a tu dedo, intercambia el mínimo y el máximo de ese eje en map(); si X e Y están totalmente cambiados, prueba valores de 0 a 3 en ts.setRotation(). Un menú real funciona igual: dibujas botones y compruebas dentro de qué rectángulo cae el punto tocado. La capa resistiva responde también al dedo, pero para botones pequeños es más precisa una punta tipo lápiz.
Mostrar imágenes BMP desde la tarjeta SD
La ranura microSD de la parte trasera del módulo te permite guardar logotipos, fondos y fotos en una tarjeta y mostrarlos en la pantalla. Para preparar una imagen, reduce una foto a 320×240 píxeles y guárdala en Paint como Mapa de bits de 24 bits (.bmp). Como la librería SD de Arduino trabaja con nombres de archivo cortos, usa un nombre de 8 caracteres como máximo, por ejemplo RESIM.BMP, y formatea la tarjeta en FAT32. Los detalles de la tarjeta y la solución al error initialization failed están en Módulo de tarjeta SD para Arduino. Recuerda que esta ranura también es de 3,3 V: en el Uno, las líneas SD_CS, SD_MOSI y SD_SCK también deben pasar por el conversor de nivel.
1#include <SPI.h>
2#include <SD.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_ILI9341.h>
5
6#define TFT_CS 10
7#define TFT_DC 9
8#define TFT_RST 8
9#define SD_CS 4
10#define T_CS 7
11#define CHUNK 20 // tek okumada 20 piksel (60 bayt) / 20 pixels (60 bytes) per read
12
13Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
14
15uint16_t read16(File &f) { // BMP sayıları küçük uçlu / BMP numbers are little-endian
16 uint16_t lo = f.read();
17 uint16_t hi = f.read();
18 return lo | (hi << 8);
19}
20
21uint32_t read32(File &f) {
22 uint32_t lo = read16(f);
23 uint32_t hi = read16(f);
24 return lo | (hi << 16);
25}
26
27bool drawBmp(const char *name, int16_t x, int16_t y) {
28 File f = SD.open(name);
29 if (!f) return false; // dosya yok / file not found
30 if (read16(f) != 0x4D42) { f.close(); return false; } // "BM" imzası / "BM" signature
31 read32(f); // dosya boyutu / file size
32 read32(f); // ayrılmış alan / reserved
33 uint32_t offset = read32(f); // piksel verisinin başı / start of pixel data
34 read32(f); // başlık boyutu / header size
35 int32_t w = (int32_t)read32(f);
36 int32_t h = (int32_t)read32(f);
37 read16(f); // düzlem sayısı / colour planes
38 uint16_t depth = read16(f);
39 uint32_t comp = read32(f);
40 if (depth != 24 || comp != 0) { f.close(); return false; } // yalnız 24 bit, sıkıştırmasız / 24-bit uncompressed only
41
42 bool bottomUp = true; // BMP satırları alttan üste / rows are stored bottom-up
43 if (h < 0) { h = -h; bottomUp = false; }
44 uint32_t rowSize = (w * 3 + 3) & ~3UL; // satırlar 4 baytın katı / rows padded to 4 bytes
45 int32_t drawW = w, drawH = h;
46 if (x + drawW > tft.width()) drawW = tft.width() - x; // ekrana sığmayanı kırp / clip to the screen
47 if (y + drawH > tft.height()) drawH = tft.height() - y;
48
49 uint8_t sdBuf[CHUNK * 3];
50 uint16_t line[CHUNK];
51 for (int32_t row = 0; row < drawH; row++) {
52 uint32_t pos = offset + (bottomUp ? (h - 1 - row) : row) * rowSize;
53 f.seek(pos); // satırın başına git / jump to the start of the row
54 for (int32_t col = 0; col < drawW; col += CHUNK) {
55 int16_t n = drawW - col;
56 if (n > CHUNK) n = CHUNK;
57 f.read(sdBuf, n * 3);
58 for (int16_t i = 0; i < n; i++) { // BGR'den RGB565'e / BGR to RGB565
59 line[i] = tft.color565(sdBuf[i * 3 + 2], sdBuf[i * 3 + 1], sdBuf[i * 3]);
60 }
61 tft.drawRGBBitmap(x + col, y + row, line, n, 1); // parçayı ekrana bas / push the chunk to the screen
62 }
63 }
64 f.close();
65 return true;
66}
67
68void setup() {
69 Serial.begin(9600);
70 pinMode(T_CS, OUTPUT);
71 digitalWrite(T_CS, HIGH); // dokunmatik hattı meşgul etmesin / keep touch off the bus
72 tft.begin();
73 tft.setRotation(1); // 320x240 yatay / landscape
74 tft.fillScreen(ILI9341_BLACK);
75 tft.setTextSize(2);
76
77 if (!SD.begin(SD_CS)) { // kart başlatılamadı / card init failed
78 tft.setTextColor(ILI9341_RED);
79 tft.setCursor(10, 10);
80 tft.print(F("SD kart bulunamadi"));
81 return;
82 }
83 uint32_t t = millis();
84 if (!drawBmp("RESIM.BMP", 0, 0)) {
85 tft.setTextColor(ILI9341_YELLOW);
86 tft.setCursor(10, 10);
87 tft.print(F("RESIM.BMP okunamadi"));
88 }
89 Serial.print(F("Cizim suresi / Draw time (ms): "));
90 Serial.println(millis() - t);
91}
92
93void loop() {
94}Los primeros 54 bytes de un archivo BMP son la cabecera: ahí están la firma "BM", dónde empiezan los datos de píxeles, el ancho, el alto y la profundidad de color. Los píxeles se guardan en orden azul, verde, rojo (BGR) y normalmente de abajo arriba, y cada fila se rellena hasta un múltiplo de 4 bytes. Por eso el código salta a la posición correcta del archivo para cada fila de la pantalla, lee 20 píxeles (60 bytes) de una vez, los convierte a RGB565 y los dibuja con drawRGBBitmap(). Trabajar en trozos pequeños mantiene todo dentro de la RAM del Uno y permite que la tarjeta SD y la pantalla se turnen en el mismo bus SPI. El tiempo de dibujo se muestra en el Monitor Serie; en el Uno, una imagen a pantalla completa aparece fila a fila en unos pocos segundos. Si quieres mostrar PNG o JPEG y dibujar mucho más rápido, pasa a un ESP32 con las librerías TFT_eSPI y TJpg_Decoder.
Dibujo rápido en el ESP32 con TFT_eSPI

El ESP32 funciona a 3,3 V y puede llevar el SPI hasta 40 MHz, así que no necesitas conversor de nivel y rellenar toda la pantalla baja en teoría a unos 31 milisegundos. Para aprovechar de verdad esa velocidad se suele usar la librería TFT_eSPI de Bodmer. Accede directamente al hardware SPI del ESP32, admite fuentes suavizadas y, sobre todo, búferes fuera de pantalla llamados sprites. Dibujas todo lo que quieras en un sprite en la RAM y envías el resultado a la pantalla de una vez, así que incluso los gráficos que cambian rápido no parpadean. Lo que distingue a TFT_eSPI es que los pines no se configuran en el código, sino en el archivo User_Setup.h de la carpeta de la librería. Ábrelo, comenta las líneas de pines del ESP8266 (NodeMCU) que vienen activas por defecto y activa estas:
1// TFT_eSPI/User_Setup.h içinde yalnız bu satırlar açık kalsın / keep only these lines active in TFT_eSPI/User_Setup.h
2#define ILI9341_DRIVER
3
4#define TFT_MISO 19
5#define TFT_MOSI 23
6#define TFT_SCLK 18
7#define TFT_CS 15 // ekran seçme / chip select
8#define TFT_DC 2 // komut/veri / data/command
9#define TFT_RST 4 // sıfırlama / reset
10#define TOUCH_CS 21 // dokunmatik seçme (T_CS) / touch chip select (T_CS)
11
12#define LOAD_GLCD // 1: 8 piksel yazı tipi / 8 px font
13#define LOAD_FONT2 // 2: 16 piksel / 16 px
14#define LOAD_FONT4 // 4: 26 piksel / 26 px
15#define LOAD_FONT7 // 7: 7 segment rakamlar / 7-segment digits
16#define LOAD_GFXFF // Adafruit FreeFont'lar / Adafruit FreeFonts
17#define SMOOTH_FONT
18
19#define SPI_FREQUENCY 40000000 // kablolar uzunsa 27000000 / 27 MHz with long wires
20#define SPI_READ_FREQUENCY 20000000
21#define SPI_TOUCH_FREQUENCY 25000001#include <SPI.h>
2#include <TFT_eSPI.h> // pinler User_Setup.h içinde / pins are set in User_Setup.h
3
4TFT_eSPI tft = TFT_eSPI();
5TFT_eSprite graph = TFT_eSprite(&tft); // ekran dışı çizim alanı / off-screen canvas
6
7// Touch_calibrate örneğinin verdiği 5 sayıyla değiştir / replace with the 5 numbers from the Touch_calibrate example
8uint16_t calData[5] = {286, 3534, 283, 3600, 6};
9
10const int GW = 300, GH = 100; // grafik boyutu / graph size
11uint8_t samples[GW]; // son 300 ölçüm / last 300 readings
12uint32_t frames = 0, fps = 0, lastFps = 0;
13
14void setup() {
15 tft.init();
16 tft.setRotation(1); // yatay, 320x240 / landscape, 320x240
17 tft.setTouch(calData);
18 tft.fillScreen(TFT_BLACK);
19 tft.setTextColor(TFT_WHITE, TFT_BLACK);
20 tft.drawString("ESP32 + TFT_eSPI", 10, 8, 4); // 4 numaralı yazı tipi, 26 px / font 4, 26 px
21 graph.createSprite(GW, GH); // 300x100x2 bayt = 60 KB RAM / 60 KB of RAM
22}
23
24void loop() {
25 memmove(samples, samples + 1, GW - 1); // grafiği sola kaydır / scroll the graph left
26 samples[GW - 1] = map(analogRead(34), 0, 4095, 0, GH - 1); // GPIO 34'teki potansiyometre / pot on GPIO 34
27
28 graph.fillSprite(TFT_BLACK);
29 for (int y = 0; y < GH; y += 25) graph.drawFastHLine(0, y, GW, TFT_DARKGREY); // ızgara / grid
30 for (int x = 1; x < GW; x++) {
31 graph.drawLine(x - 1, GH - 1 - samples[x - 1], x, GH - 1 - samples[x], TFT_GREEN);
32 }
33 graph.setTextColor(TFT_YELLOW);
34 graph.drawString(String(fps) + " FPS", 4, 4, 2);
35 graph.pushSprite(10, 50); // tek seferde ekrana gönder, titreme yok / push in one go, no flicker
36
37 uint16_t tx, ty;
38 if (tft.getTouch(&tx, &ty)) { // dokunulduysa / if touched
39 tft.fillRect(10, 170, 300, 30, TFT_BLACK);
40 tft.setTextColor(TFT_CYAN, TFT_BLACK);
41 tft.drawString("Dokunma / Touch: " + String(tx) + ", " + String(ty), 10, 175, 2);
42 }
43
44 frames++;
45 if (millis() - lastFps >= 1000) { // saniyede bir FPS hesapla / compute FPS once per second
46 fps = frames;
47 frames = 0;
48 lastFps = millis();
49 }
50}El código dibuja el valor de un potenciómetro conectado al GPIO 34 como una gráfica que se desplaza y muestra los fotogramas por segundo (FPS) en la esquina del sprite. Un sprite de 300×100 píxeles con color de 16 bits ocupa 60 KB de RAM; es unas 30 veces toda la memoria del Uno, pero no supone ningún problema para el ESP32. En el táctil, los pines T_CLK, T_DIN y T_DO se unen a las líneas SCK, MOSI y MISO de la pantalla, y solo T_CS (GPIO 21) va aparte. Para obtener tus propios valores de calibración, ejecuta una vez el ejemplo Generic > Touch_calibrate de TFT_eSPI, toca las flechas de las esquinas y copia en el array calData los 5 números que aparecen en el Monitor Serie. La línea ya preparada Setup42_ILI9341_ESP32.h de User_Setup_Select.h elige los mismos pines de pantalla, pero tendrás que añadirle tú la línea TOUCH_CS. Si empiezas con el ESP32, lee antes Qué es el ESP32.
ILI9341 con la Raspberry Pi Pico y MicroPython
La Pico también es una placa de 3,3 V, así que la pantalla se conecta directamente. El controlador de MicroPython más usado son los archivos ili9341.py y xpt2046.py del repositorio micropython-ili9341 de rdagger; basta con copiar ambos a la Pico con Thonny. En el siguiente código, la pantalla está en el SPI0 a 40 MHz y el táctil en un bus SPI1 aparte a 1 MHz, así que la diferencia de velocidad entre los dos chips no da problemas. El código dibuja textos y formas y luego pone puntos amarillos donde toques, creando una pizarra sencilla. Si usas la Pico y Thonny por primera vez, empieza por Qué es la Raspberry Pi Pico.
1from machine import Pin, SPI
2from time import sleep_ms
3from ili9341 import Display, color565 # ili9341.py Pico'da olmalı / must be on the Pico
4from xpt2046 import Touch # xpt2046.py Pico'da olmalı / must be on the Pico
5
6# Ekran: SPI0, 40 MHz / Display: SPI0, 40 MHz
7spi_tft = SPI(0, baudrate=40000000, sck=Pin(18), mosi=Pin(19), miso=Pin(16))
8tft = Display(spi_tft, cs=Pin(17), dc=Pin(20), rst=Pin(21), width=240, height=320, rotation=0)
9
10# Dokunmatik: ayrı SPI1, 1 MHz / Touch: separate SPI1, 1 MHz
11spi_touch = SPI(1, baudrate=1000000, sck=Pin(10), mosi=Pin(11), miso=Pin(12))
12touch = Touch(spi_touch, cs=Pin(13), width=240, height=320)
13
14WHITE = color565(255, 255, 255)
15YELLOW = color565(255, 255, 0)
16
17tft.clear(color565(0, 0, 0)) # ekranı temizle / clear the screen
18tft.draw_text8x8(10, 10, "BlueGrays + Pico", WHITE)
19tft.fill_rectangle(10, 30, 100, 50, color565(255, 0, 0)) # dolu dikdörtgen / filled rectangle
20tft.draw_rectangle(130, 30, 100, 50, color565(0, 255, 0)) # çerçeve / outline
21tft.fill_circle(60, 130, 30, color565(0, 128, 255)) # dolu daire / filled circle
22tft.draw_line(0, 175, 239, 175, color565(255, 0, 255)) # çizgi / line
23tft.draw_text8x8(10, 185, "Ekrana dokun / Touch me", YELLOW)
24
25while True:
26 raw = touch.raw_touch() # dokunma yoksa None / None when not touched
27 if raw is not None:
28 x, y = touch.normalize(*raw) # ham değeri piksele çevir / raw value to pixels
29 y = 319 - y # eksen ters ise / if the axis is inverted
30 x = min(max(x, 3), 236) # ekran dışına taşma / keep inside the screen
31 y = min(max(y, 3), 316)
32 tft.fill_circle(x, y, 3, YELLOW)
33 sleep_ms(20)En vertical (rotation=0) el ancho es 240 y el alto 320; para usar la pantalla en horizontal, pasa width=320 y height=240 junto con rotation=90. La calibración por defecto del táctil (x_min=100, x_max=1962) puede no encajar con todos los módulos; si el punto se desvía, imprime los valores de raw_touch() y pasa a Touch() tus propios x_min, x_max, y_min e y_max. Para mostrar imágenes, la función draw_image() del controlador lee archivos .raw convertidos a RGB565; el script utils/img2rgb565.py del repositorio hace esa conversión en tu ordenador.
Pantalla blanca: causas y solución
El problema más famoso del ILI9341 es una pantalla completamente blanca con la retroiluminación encendida. En realidad, la pantalla blanca es una pista: la alimentación y la retroiluminación funcionan, pero el chip controlador nunca llegó a inicializarse. Cuando el panel no recibe comandos, los píxeles dejan pasar la luz y la pantalla se ve blanca. Así que el problema casi siempre está en la comunicación. Revisa la tabla de arriba abajo:
| Síntoma | Causa probable | Solución |
|---|---|---|
| Pantalla blanca, conectada directamente al Uno | Señales de 5 V sin conversor de nivel | Usa un 74HC4050 o divisores de tensión; si el chip se dañó, cambia el módulo |
| Pantalla blanca con los cables conectados | CS y DC intercambiados, o los pines del código no coinciden con el cableado | Compara una a una las definiciones TFT_CS, TFT_DC y TFT_RST con la tabla |
| Pantalla blanca, RESET al aire | RESET sin conectar o en el pin equivocado | Conecta RESET al pin del código o a 3,3 V; nunca lo dejes al aire |
| Pantalla blanca en el ESP32 (TFT_eSPI) | User_Setup.h sin editar o borrado por una actualización | Desactiva las líneas del ESP8266 y vuelve a escribir los pines del ESP32 y ILI9341_DRIVER |
| Blanca a ratos, imagen desplazada o corrupta | Cables jumper largos, SPI demasiado rápido, contactos flojos en la protoboard | Usa cables de menos de 10–15 cm; baja el SPI a 20–27 MHz, o a 4 MHz en el Uno |
| Blanca solo con el táctil o la SD conectados | El CS del otro dispositivo está al aire y bloquea el bus | Pon en el código cada CS sin usar como OUTPUT y en HIGH |
| No funciona con ningún código | La pantalla no es ILI9341 (ST7789, ILI9488) o es un shield paralelo | Lee la serigrafía trasera; una de 3,5 pulgadas y 320×480 es ILI9488, un shield necesita MCUFRIEND_kbv |
| Pantalla blanca y retroiluminación que parpadea | Alimentación insuficiente | Alimenta VCC desde 5 V y prueba otro cable USB y otra fuente |
Otros errores frecuentes
- Colores incorrectos: si el rojo sale azul, el panel usa orden BGR; cambia el ajuste TFT_RGB_ORDER en TFT_eSPI. Si los colores parecen un negativo, prueba tft.invertDisplay(true).
- Imagen al revés o reflejada: cambia el valor de setRotation() entre 0 y 3 y ajusta los ejes del táctil en consecuencia.
- La pantalla funciona, pero el táctil o la SD no: comprueba que T_CS y SD_CS coinciden con los pines del código. En algunos módulos, la salida SDO de la pantalla no libera el bus; en ese caso prueba a desconectar el pin SDO de la pantalla, y solo dejará de funcionar la lectura de diagnóstico.
- El BMP no aparece: la tarjeta no está en FAT32, el nombre del archivo tiene más de 8 caracteres o la imagen no es de 24 bits. No olvides elegir 24 bits en Paint; este código no dibuja BMP de 16 ni de 32 bits.
- No salen las tildes ni la ñ: la fuente por defecto de GFX no incluye caracteres como á, é o ñ. En TFT_eSPI puedes crear tu propia fuente suavizada o usar una fuente GFX que incluya esos caracteres.
- La pantalla va lenta: en el Uno, refrescar la pantalla completa tarda al menos 0,15 segundos. Para animaciones y juegos pasa a un ESP32 o una Pico y redibuja solo la zona que cambia.
- El módulo se calienta: puede que haya 5 V en VCC con el puente J1 cerrado o que VCC y GND estén invertidos; desconecta la alimentación de inmediato.
Ideas de proyectos con el ILI9341
- Panel táctil de casa inteligente: controla luces y relés con botones grandes en la pantalla; tienes ideas en Proyecto de casa inteligente con Arduino.
- Pantalla del tiempo a color: muestra el tiempo que el ESP32 obtiene de internet con iconos y números grandes (Estación meteorológica con ESP32).
- Panel de sensores: dibuja la temperatura, la humedad y la presión de un BME280 como indicadores de aguja con una gráfica de las últimas 24 horas.
- Consola de juegos retro: juega a la serpiente, al Pong o al Tetris en color; añade un módulo joystick para controlarlos y busca ideas en Proyectos de juegos con Arduino.
- Marco de fotos digital: cambia cada pocos segundos las imágenes BMP de la tarjeta SD.
- Mini osciloscopio: dibuja los valores de analogRead como una onda fluida con un sprite en el ESP32.
- Cerradura táctil con clave: dibuja un teclado numérico en la pantalla y abre un relé o un servo cuando se introduzca el código correcto.
El ILI9341 es la forma más económica de dar a tus proyectos una interfaz táctil a color: empieza con el Uno para aprender las funciones básicas de dibujo y pasa al ESP32 con TFT_eSPI cuando necesites velocidad y animaciones fluidas. Si prefieres empezar con una pantalla más pequeña, Pantalla OLED (SSD1306) es un buen punto de partida. En la app BlueGrays encontrarás la pantalla del tiempo con ESP32, el registrador de temperatura, el sensor de aparcamiento y proyectos de juegos con pantalla, con esquemas de circuito y código paso a paso.
Preguntas frecuentes
¿La pantalla ILI9341 funciona con 5 V?
El pin VCC del módulo admite 3,3–5 V porque la placa lleva un regulador de 3,3 V. Sin embargo, los pines de señal como CS, DC, SCK y MOSI son solo de 3,3 V. Con un Arduino Uno debes poner en esas líneas un conversor de nivel, como un 74HC4050 o divisores de tensión.
¿Por qué mi ILI9341 se queda en blanco?
La pantalla blanca indica que la retroiluminación está encendida pero el chip controlador no se ha inicializado. Las causas más frecuentes son un cableado incorrecto de CS, DC o RESET, no usar conversor de nivel en el Uno, no editar User_Setup.h en TFT_eSPI y usar una pantalla que en realidad no es ILI9341.
¿Qué librería se usa para el ILI9341?
Para Arduino Uno, Nano y Mega, lo más sencillo es Adafruit_ILI9341 con Adafruit GFX. En placas ESP32, ESP8266 y RP2040 se prefiere TFT_eSPI, que es mucho más rápida. Los shields paralelos de 2,4 pulgadas que se pinchan sobre el Uno necesitan MCUFRIEND_kbv.
¿Cómo se calibra el táctil del ILI9341?
El controlador táctil XPT2046 devuelve valores brutos de 0 a 4095. Anota los valores mínimo y máximo que ves en el Monitor Serie al tocar las esquinas y conviértelos a coordenadas de pantalla con map(). Si usas TFT_eSPI, el ejemplo Touch_calibrate lo hace por ti y te da un array de calibración listo.
¿Qué diferencia hay entre el ILI9341 de 2,4 y el de 2,8 pulgadas?
Los dos tienen una resolución de 240×320 píxeles y usan los mismos pines y el mismo código. La de 2,8 pulgadas tiene píxeles algo más grandes, así que el texto se lee mejor y es más fácil pulsar los botones táctiles. El módulo de 2,4 pulgadas cabe en cajas más pequeñas.
¿Se puede usar el ILI9341 con un Arduino Nano?
Sí. Los pines SPI del Nano son los mismos que los del Uno: D13 SCK, D11 MOSI y D12 MISO. El Nano también trabaja con lógica de 5 V, así que necesitas un conversor de nivel. Sus 2 KB de RAM bastan para manejar la pantalla, pero no para guardar imágenes grandes, que tendrás que leer de la tarjeta SD por partes.
Proyectos para hacer después de leer esto
Ver todos los proyectos
ESP32 HTTPClient + API del tiempo (con pantalla OLED SSD1306)
En este proyecto, utilizamos el ESP32 para realizar una llamada a la API REST de la información meteorológica (wttr.in) a través de Internet, utilizando HTTPClient.h. A diferencia de la versión anterior con pantalla LCD I2C, aquí utilizamos una pantalla OLED SSD1306 (128×64), lo que permite utilizar fuentes grandes, iconos vectoriales y una presentación visual muy rica en una sola pantalla. Para indicar el tiempo, se dibujan en tiempo de ejecución 7 iconos vectoriales diferentes (soleado, parc

Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
¡Construye un registrador de temperatura con marca de tiempo con ESP32! Lee temperatura/humedad del DHT22 cada minuto, obtén la hora real del DS1307 RTC y registra en tarjeta microSD como CSV. El OLED muestra en vivo la temperatura (fuente grande), porcentaje de humedad, marca de tiempo y contador de registros. El archivo /temp_log.csv en la tarjeta SD se abre en Excel, Google Sheets o Python pandas para análisis — perfecto para monitoreo de invernadero, seguimiento de temperatura del hog

Lámpara con mezclador de colores ESP32 (3 potenciómetros + LED RGB + OLED)
¡Una lámpara mezcladora de colores inteligente que combina el color que quieras girando tres potenciómetros con el ESP32! A medida que giras los potenciómetros de rojo, verde y azul, el LED RGB de cátodo común cambia de color al instante. En la pantalla OLED SSD1306 se muestran en tiempo real los valores de cada color en un rango de 0 a 255, barras de relleno visuales y el código de color HEX para la web (como #FF8040). Un miniestudio de color profesional: enseña la teoría del color RGB, la lect

Sensor de aparcamiento ESP32 (HC-SR04 + barra de 5 LED + zumbador + OLED)
¡La versión ESP32 del sensor de aparcamiento para coches! Medimos la distancia al objeto que hay delante mediante el sensor ultrasónico HC-SR04. A medida que la distancia disminuye, la barra de 5 LED se va llenando (verde → amarillo → rojo) y la frecuencia de los pitidos del zumbador se acorta. Cuando se acerca demasiado (menos de 10 cm), suena una alarma continua y se encienden todos los LED. En la pantalla OLED SSD1306 se muestra, en letra grande, el valor actual en centímetros, un mensaje de

Juego de Raquetas - SSD1306
Una pelota, dos palas y tú: juegas al tenis de mesa, uno de los primeros videojuegos de la historia, en una pequeña pantalla OLED. Con dos botones mueves tu pala de la derecha arriba y abajo, mientras

OLED T-Rex Run: Juego del dinosaurio con Arduino
El juego del dinosaurio que aparece cuando se va internet, ahora en tu mesa: el dinosaurio corre por la pequeña pantalla OLED, los cactus se le echan encima y tú saltas con un solo botón. Tu puntuació

Pantalla LCD I2C para ESP32 (16x2)
En este proyecto, mostramos texto conectando una pantalla LCD de 16x2 caracteres al ESP32 a través del protocolo I2C. Gracias al I2C, podemos controlar la pantalla con solo 2 pines de datos (SDA y SCL); así se elimina el desperdicio de pines que supone la conexión paralela clásica, que requiere más de 6 pines. El código imprime un mensaje fijo en la primera línea y un contador que aumenta cada segundo en la segunda línea. Además, al inicio ejecutamos el escáner I2C para mostrar la dirección de l

Juego Arduino LCD Runner
Juego endless runner de desplazamiento lateral que se ejecuta en una pantalla LCD de 16x2 caracteres. El héroe corre automáticamente, el jugador salta los obstáculos pulsando el botón. Aparecen obstáculos aleatorios en las filas superior e inferior. El zumbador pasivo reproduce la melodía de apertura, el sonido de salto, el sonido de celebración de 50 puntos y la melodía de fin de partida. La pantalla de inicio "BlueGrays - Arduino Projects" se muestra al arrancar.
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

