ILI9341 Arduino: collegare il display TFT, touch e immagini da SD
Collegare un display TFT ILI9341 ad Arduino: livelli 3,3 V sull'Uno, Adafruit GFX, touch XPT2046, BMP da SD, ESP32 con TFT_eSPI e soluzione allo schermo bianco.
Collegare un ILI9341 ad Arduino è il modo più economico e diffuso per aggiungere al tuo progetto un display da 240×320 pixel con 65 mila colori. Questi moduli TFT da 2,4 e 2,8 pollici su scheda rossa si collegano in SPI con pochi fili, e le versioni touch hanno anche un controller touch XPT2046 e uno slot per schede microSD. Con un solo modulo puoi mostrare grafica a colori, pulsanti touch e immagini lette dalla scheda. È ideale per menu, cruscotti, giochi e cornici digitali, dove un OLED o un LCD a caratteri non bastano più.
In questa guida imparerai passo dopo passo come funziona il modulo, come collegare in sicurezza le uscite a 5 V dell'Arduino Uno agli ingressi a 3,3 V del display, come disegnare testi, forme e colori con le librerie Adafruit_ILI9341 e Adafruit GFX, come leggere il touch e come mostrare immagini BMP da una scheda SD. Poi vedremo il disegno molto più veloce sull'ESP32 con TFT_eSPI, l'uso di MicroPython sul Raspberry Pi Pico e tutte le cause dello "schermo bianco", un problema che quasi tutti incontrano almeno una volta.

Che cos'è il display TFT ILI9341 e come funziona?
L'ILI9341 è il chip driver che si trova dietro al vetro. Contiene circa 172 KB di memoria video (GRAM) che conserva tutti i 240×320 pixel. Arduino invia comandi e dati di colore a questa memoria tramite SPI, e il chip aggiorna l'immagine da solo decine di volte al secondo. Per questo l'immagine non deve stare nella RAM di Arduino: anche un Uno con 2 KB di RAM può pilotare un grande display a colori. Per disegnare qualcosa si sceglie prima una finestra (x, y, larghezza, altezza) e poi si inviano i pixel di quella finestra uno dopo l'altro. Il pin DC indica se il byte inviato è un comando (LOW) o un dato (HIGH). I colori di solito si inviano nel formato RGB565, cioè 16 bit per pixel: 5 bit di rosso, 6 di verde e 5 di blu. Il funzionamento del bus SPI è spiegato in dettaglio in Che cos'è l'SPI.
| Caratteristica | Valore |
|---|---|
| Chip driver | ILI9341 |
| Risoluzione | 240×320 pixel (320×240 in orizzontale) |
| Colori | 65.536 colori con RGB565 (il chip ne gestisce 262 mila) |
| Dimensioni | 2,2", 2,4", 2,8" e 3,2" (tutti 240×320) |
| Interfaccia | SPI a 4 fili: CS, DC, SCK, MOSI (MISO per la lettura) |
| Alimentazione (VCC) | 3,3–5 V (regolatore da 3,3 V sulla scheda) |
| Livello logico | 3,3 V (non tollera i 5 V) |
| Consumo | Circa 0,3–0,6 W, quasi tutto per la retroilluminazione |
| Touch | Resistivo, controller XPT2046 (sui modelli touch) |
| Hardware extra | Slot per scheda microSD (pin SPI separati) |
2,4 o 2,8 pollici? Modulo SPI o shield per Uno
| Caratteristica | Modulo SPI 2,4" | Modulo SPI 2,8" | Shield 2,4" per Uno |
|---|---|---|---|
| Risoluzione | 240×320 | 240×320 | 240×320 |
| Collegamento | SPI, con cavetti | SPI, con cavetti | Parallelo a 8 bit, si innesta sopra l'Uno |
| Pin usati | 5 per il display, 8–9 con touch e SD | 5 per il display, 8–9 con touch e SD | Quasi tutti (D2–D9, A0–A4, D10–D13 per la SD) |
| Driver | ILI9341 | ILI9341 | Variabile: ILI9341, ILI9325, ST7789 e altri |
| Libreria | Adafruit_ILI9341, TFT_eSPI | Adafruit_ILI9341, TFT_eSPI | MCUFRIEND_kbv |
| Touch | XPT2046 (SPI) | XPT2046 (SPI) | Analogico a 4 fili (libreria TouchScreen) |
| ESP32 e Pico | Adatto | Adatto | Pensato per Uno e Mega |
Questo articolo parla del modulo SPI, perché funziona anche con ESP32 e Pico e lascia liberi la maggior parte dei pin dell'Uno. I due moduli SPI hanno esattamente gli stessi pin e lo stesso codice; il display da 2,8 pollici ha pixel un po' più grandi, quindi è più facile centrare i pulsanti touch. Se il tuo display è uno shield che si innesta direttamente sull'Uno, le librerie SPI non possono pilotarlo e il risultato di solito è uno schermo bianco. Per uno shield del genere installa la libreria MCUFRIEND_kbv, leggi l'ID del driver con tft.readID() (0x9341 per l'ILI9341) e avvialo con tft.begin(ID). Per un'alternativa più piccola ma più nitida guarda Display TFT ST7789, e per confrontare tutte le opzioni leggi Tipi di display per Arduino.
Pinout dell'ILI9341 e tabella dei collegamenti
Su un lato del modulo c'è il connettore principale a 14 pin: i primi 9 servono al display, gli ultimi 5, che iniziano con T_, al touch. I 4 pin sul lato opposto (SD_CS, SD_MOSI, SD_MISO, SD_SCK) vanno allo slot della scheda. I tre dispositivi possono condividere lo stesso bus SPI (SCK, MOSI, MISO); solo i loro pin CS devono essere separati. La tabella corrisponde a tutti gli sketch di questo articolo; sul Pico il touch è collegato a un bus SPI separato per via della differenza di velocità.
| Pin del modulo | Arduino Uno (tramite convertitore di livello) | 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 (diretto) | GPIO 19 | GP16 |
| T_CLK | D13 | GPIO 18 | GP10 |
| T_CS | D7 | GPIO 21 | GP13 |
| T_DIN | D11 | GPIO 23 | GP11 |
| T_DO | D12 (diretto) | GPIO 19 | GP12 |
| T_IRQ | D2 (diretto) | Libero | Libero |
| SD_CS | D4 | GPIO 5 | Libero |
| SD_MOSI / SD_MISO / SD_SCK | D11 / D12 / D13 | GPIO 23 / 19 / 18 | Libero |
Conversione di livello a 3,3 V sull'Arduino Uno
ESP32 e Raspberry Pi Pico lavorano già a 3,3 V, quindi puoi collegare il display direttamente. Sulle schede a 5 V come Uno, Nano e Mega, invece, ogni linea che va da Arduino al display deve essere portata a 3,3 V: CS, DC, RESET, SCK, MOSI e, se usi touch e SD, T_CLK, T_DIN, T_CS, SD_CS, SD_MOSI e SD_SCK. Le linee SDO, T_DO, SD_MISO e T_IRQ, che vanno dal display ad Arduino, non hanno bisogno di convertitore: l'Uno legge come HIGH qualsiasi tensione sopra i 3 V, e un'uscita a 3,3 V la supera. In pratica ci sono quattro strade:
- Buffer 74HC4050 (la soluzione più pulita): si alimenta a 3,3 V, accetta 5 V sugli ingressi e converte 6 linee in un solo chip. È veloce e funziona senza problemi con l'SPI a 8 MHz. Anche il vecchio CD4050 va bene, ma è lento; se l'immagine si rovina dovrai abbassare la velocità dell'SPI. Per il solo display bastano 5 linee; con touch e SD si aggiungono T_CS e SD_CS, quindi 7 in tutto: per la settima usa un secondo chip o un partitore di tensione.
- Partitori di tensione con resistenze: per ogni linea metti 1 kΩ tra il pin di Arduino e quello del display, e 2 kΩ tra il pin del display e GND; ottieni 5 V × 2 / 3 ≈ 3,3 V. Puoi calcolare altre coppie di resistenze con il calcolatore di partitore di tensione. È economico, ma 10 resistenze per 5 linee affollano la breadboard.
- Modulo convertitore di livello già pronto: i moduli a 4 o 8 canali con MOSFET BSS138 si collegano facilmente, ma le loro resistenze di pull-up ammorbidiscono i fronti del segnale alle alte velocità. Se l'immagine si rovina, porta l'SPI a 4 MHz con tft.begin(4000000). Trovi il confronto tra i metodi in Convertitore di livello logico.
- Il metodo da evitare: il trucco diffuso della "resistenza da 10 kΩ in serie su ogni linea" scarica la tensione in eccesso sui diodi di protezione interni del chip. Può funzionare per un po', ma è instabile e alla lunga può danneggiare il display.
Passo dopo passo: primo test con Adafruit_ILI9341
- Nell'Arduino IDE apri il Gestore librerie, cerca Adafruit ILI9341 e installala. Quando chiede delle dipendenze scegli Install all, così vengono installate anche Adafruit GFX e Adafruit BusIO. Se è la tua prima libreria, ti aiuterà Come aggiungere una libreria Arduino.
- Per il touch installa anche la libreria XPT2046_Touchscreen di Paul Stoffregen. La libreria SD è già inclusa nell'Arduino IDE.
- Collega il display seguendo la tabella qui sopra, sull'Uno tramite un convertitore di livello. Per il primo test puoi lasciare liberi i pin del touch e della SD.
- Carica lo sketch seguente e apri il Monitor seriale a 9600 baud. Sul display dovresti vedere testi e forme, nel Monitor seriale i codici di diagnosi del display.
- Prova anche gli esempi inclusi: File > Esempi > Adafruit ILI9341 > graphicstest misura quanti microsecondi richiede ogni operazione di disegno.
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}| Funzione | Cosa fa |
|---|---|
| tft.begin(frequenza) | Avvia il display; senza frequenza usa l'SPI a 8 MHz sull'Uno e a 40 MHz sull'ESP32 |
| tft.setRotation(0–3) | Sceglie l'orientamento: 0 e 2 verticale (240×320), 1 e 3 orizzontale (320×240) |
| tft.fillScreen(colore) | Colora tutto lo schermo con un solo colore |
| tft.setTextColor(testo, sfondo) | Imposta il colore del testo; con il colore di sfondo sovrascrive in modo pulito il testo precedente |
| tft.setTextSize(n) | Ingrandisce il testo n volte; dimensione 1 = caratteri da 6×8 pixel |
| tft.drawRect / fillRect(x, y, larghezza, altezza, colore) | Contorno di rettangolo o rettangolo pieno |
| tft.fillRoundRect(x, y, larghezza, altezza, r, colore) | Rettangolo pieno con angoli arrotondati; ideale per i pulsanti |
| tft.drawCircle / fillCircle(x, y, r, colore) | Cerchio o cerchio pieno |
| tft.color565(r, g, b) | Converte valori RGB da 0–255 in un colore a 16 bit |
| tft.readcommand8(comando) | Legge un registro del display (MISO deve essere collegato); per la diagnosi |
La libreria include costanti di colore già pronte (19, come ILI9341_RED, ILI9341_NAVY e ILI9341_ORANGE); per un colore tuo usa color565(). Questa funzione affianca i 5 bit più alti del rosso, i 6 bit più alti del verde e i 5 bit più alti del blu: colore = (R >> 3) << 11 | (G >> 2) << 5 | (B >> 3). Per l'arancione (255, 165, 0), ad esempio, 31 << 11 = 0xF800 e 41 << 5 = 0x0520 si combinano in 0xFD20, esattamente il valore di ILI9341_ORANGE nella libreria. Il verde riceve un bit in più perché l'occhio umano è più sensibile al verde.
Leggere il touch screen XPT2046
Nei moduli touch c'è un sottile strato resistivo sopra il vetro. Dove premono il dito o un pennino, due strati conduttivi si toccano; il chip XPT2046 misura la posizione X e Y di quel punto con un ADC a 12 bit come valori grezzi da 0 a 4095 e fornisce anche un valore di pressione (Z). La libreria considera assente il tocco se Z è sotto 400. Il chip sta sullo stesso bus SPI del display, ma comunica più lentamente, intorno ai 2 MHz; la libreria regola da sola la velocità a ogni lettura. Il pin T_IRQ va a LOW quando tocchi lo schermo ed evita al processore letture inutili. Lo sketch seguente trasforma lo schermo in una piccola lavagna: scegli un colore dai riquadri in alto e cancelli lo schermo con il pulsante "SIL" (cancella).
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}I valori grezzi non sono 0 e 4095 proprio negli angoli; nella maggior parte dei moduli i bordi stanno più o meno tra 200–400 e 3700–3900. Per calibrare apri il Monitor seriale, tocca con un pennino l'angolo in alto a sinistra e quello in basso a destra e scrivi i valori x e y che vedi nelle costanti TS_MINX, TS_MAXX, TS_MINY e TS_MAXY. Se il punto appare specchiato rispetto al dito, scambia minimo e massimo di quell'asse in map(); se X e Y sono del tutto invertiti, prova i valori da 0 a 3 in ts.setRotation(). Un vero menu funziona allo stesso modo: disegni dei pulsanti e controlli in quale rettangolo cade il punto toccato. Lo strato resistivo risponde anche al dito, ma per i pulsanti piccoli una punta tipo pennino è più precisa.
Mostrare immagini BMP dalla scheda SD
Lo slot microSD sul retro del modulo ti permette di tenere loghi, sfondi e foto su una scheda e mostrarli sul display. Per preparare un'immagine riduci una foto a 320×240 pixel e salvala con Paint come Bitmap a 24 bit (.bmp). Poiché la libreria SD di Arduino lavora con nomi di file corti, usa un nome di massimo 8 caratteri come RESIM.BMP e formatta la scheda in FAT32. I dettagli sulla scheda e la soluzione all'errore initialization failed sono in Modulo scheda SD per Arduino. Ricorda che anche questo slot è a 3,3 V: sull'Uno anche le linee SD_CS, SD_MOSI e SD_SCK devono passare dal convertitore di livello.
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}I primi 54 byte di un file BMP sono l'intestazione: lì si trovano la firma "BM", il punto in cui iniziano i dati dei pixel, la larghezza, l'altezza e la profondità di colore. I pixel sono salvati nell'ordine blu, verde, rosso (BGR) e normalmente dal basso verso l'alto, e ogni riga è completata fino a un multiplo di 4 byte. Per questo lo sketch salta nel punto giusto del file per ogni riga dello schermo, legge 20 pixel (60 byte) alla volta, li converte in RGB565 e li disegna con drawRGBBitmap(). Lavorare a piccoli pezzi tiene tutto nella RAM dell'Uno e permette alla scheda SD e al display di usare a turno lo stesso bus SPI. Il tempo di disegno viene scritto nel Monitor seriale; sull'Uno un'immagine a tutto schermo compare riga per riga in pochi secondi. Se vuoi mostrare PNG o JPEG e disegnare molto più velocemente, passa a un ESP32 con le librerie TFT_eSPI e TJpg_Decoder.
Disegno veloce sull'ESP32 con TFT_eSPI

L'ESP32 lavora a 3,3 V e può portare l'SPI fino a 40 MHz: non serve un convertitore di livello e riempire tutto lo schermo scende in teoria a circa 31 millisecondi. Per sfruttare davvero questa velocità si usa di solito la libreria TFT_eSPI di Bodmer. Accede direttamente all'hardware SPI dell'ESP32, supporta font con bordi smussati e soprattutto buffer fuori schermo chiamati sprite. Disegni quanto vuoi in uno sprite nella RAM e invii il risultato allo schermo in un colpo solo, così nemmeno la grafica che cambia velocemente sfarfalla. La particolarità di TFT_eSPI è che i pin non si impostano nello sketch, ma nel file User_Setup.h dentro la cartella della libreria. Aprilo, commenta le righe dei pin ESP8266 (NodeMCU) attive di default e attiva queste:
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}Lo sketch disegna il valore di un potenziometro collegato al GPIO 34 come un grafico che scorre e mostra i fotogrammi al secondo (FPS) nell'angolo dello sprite. Uno sprite da 300×100 pixel a 16 bit occupa 60 KB di RAM; è circa 30 volte tutta la memoria dell'Uno, ma per l'ESP32 non è un problema. Nel touch, i pin T_CLK, T_DIN e T_DO si uniscono alle linee SCK, MOSI e MISO del display e solo T_CS (GPIO 21) resta separato. Per ottenere i tuoi valori di calibrazione, esegui una volta l'esempio Generic > Touch_calibrate di TFT_eSPI, tocca le frecce che compaiono negli angoli e copia nell'array calData i 5 numeri stampati nel Monitor seriale. La riga già pronta Setup42_ILI9341_ESP32.h in User_Setup_Select.h seleziona gli stessi pin del display, ma la riga TOUCH_CS devi aggiungerla tu. Se sei alle prime armi con l'ESP32, leggi prima Che cos'è l'ESP32.
ILI9341 con Raspberry Pi Pico e MicroPython
Anche il Pico è una scheda a 3,3 V, quindi il display si collega direttamente. Il driver MicroPython più usato è la coppia di file ili9341.py e xpt2046.py del repository micropython-ili9341 di rdagger; basta copiarli entrambi sul Pico con Thonny. Nel codice seguente il display è sull'SPI0 a 40 MHz e il touch su un bus SPI1 separato a 1 MHz, così la differenza di velocità tra i due chip non crea problemi. Il codice disegna testi e forme e poi mette punti gialli dove tocchi, creando una lavagna semplice. Se usi Pico e Thonny per la prima volta, parti da Che cos'è il 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)In verticale (rotation=0) la larghezza è 240 e l'altezza 320; per usare lo schermo in orizzontale passa width=320 e height=240 insieme a rotation=90. La calibrazione predefinita del touch (x_min=100, x_max=1962) potrebbe non andare bene per ogni modulo; se il punto è spostato, stampa i valori di raw_touch() e passa a Touch() i tuoi x_min, x_max, y_min e y_max. Per le immagini, la funzione draw_image() del driver legge file .raw convertiti in RGB565; lo script utils/img2rgb565.py del repository fa questa conversione sul tuo computer.
Schermo bianco: cause e soluzione
Il problema più famoso dell'ILI9341 è uno schermo completamente bianco con la retroilluminazione accesa. Lo schermo bianco in realtà è un indizio: alimentazione e retroilluminazione funzionano, ma il chip driver non è mai stato inizializzato. Quando il pannello non riceve comandi, i pixel lasciano passare la luce e lo schermo appare bianco. Quindi il problema è quasi sempre nella comunicazione. Controlla la tabella dall'alto verso il basso:
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Schermo bianco, collegato direttamente all'Uno | Segnali a 5 V senza convertitore di livello | Usa un 74HC4050 o dei partitori di tensione; se il chip è danneggiato sostituisci il modulo |
| Schermo bianco con i cavi collegati | CS e DC scambiati, o i pin nel codice non corrispondono al cablaggio | Confronta una per una le definizioni TFT_CS, TFT_DC e TFT_RST con la tabella |
| Schermo bianco, RESET scollegato | RESET non collegato o sul pin sbagliato | Collega RESET al pin del codice o a 3,3 V; non lasciarlo mai flottante |
| Schermo bianco sull'ESP32 (TFT_eSPI) | User_Setup.h non modificato o cancellato da un aggiornamento | Disattiva le righe ESP8266 e riscrivi i pin dell'ESP32 e ILI9341_DRIVER |
| Bianco a tratti, immagine spostata o disturbata | Cavetti lunghi, SPI troppo veloce, contatti allentati sulla breadboard | Tieni i cavi sotto i 10–15 cm; abbassa l'SPI a 20–27 MHz, o a 4 MHz sull'Uno |
| Bianco solo con touch o SD collegati | Il CS dell'altro dispositivo è flottante e blocca il bus | Nel codice imposta ogni CS non usato come OUTPUT e mettilo a HIGH |
| Non funziona con nessun codice | Il display non è un ILI9341 (ST7789, ILI9488) oppure è uno shield parallelo | Leggi la serigrafia sul retro; un 3,5 pollici da 320×480 è un ILI9488, uno shield richiede MCUFRIEND_kbv |
| Schermo bianco, retroilluminazione che sfarfalla | Alimentazione insufficiente | Alimenta VCC dai 5 V, prova un altro cavo USB e un altro alimentatore |
Altri errori frequenti
- Colori sbagliati: se il rosso appare blu, il pannello usa l'ordine BGR; cambia l'impostazione TFT_RGB_ORDER in TFT_eSPI. Se i colori sembrano un negativo, prova tft.invertDisplay(true).
- Immagine capovolta o specchiata: cambia il valore di setRotation() tra 0 e 3 e regola di conseguenza gli assi del touch.
- Il display funziona, ma touch o SD no: verifica che T_CS e SD_CS corrispondano ai pin del codice. Su alcuni moduli l'uscita SDO del display non libera il bus; in quel caso prova a scollegare il pin SDO del display, smetterà di funzionare solo la lettura di diagnosi.
- Il BMP non compare: la scheda non è in FAT32, il nome del file supera gli 8 caratteri o l'immagine non è a 24 bit. Ricorda di scegliere 24 bit in Paint; questo sketch non disegna BMP a 16 e 32 bit.
- Le lettere accentate non compaiono: il font predefinito di GFX non ha caratteri come à, è o ù. In TFT_eSPI puoi creare un tuo font smussato oppure usare un font GFX che contenga quei caratteri.
- Il display è lento: sull'Uno un aggiornamento a tutto schermo richiede almeno 0,15 secondi. Per animazioni e giochi passa a un ESP32 o a un Pico e ridisegna solo la zona che cambia.
- Il modulo scalda: forse ci sono 5 V su VCC con il ponticello J1 chiuso, oppure VCC e GND sono invertiti; togli subito l'alimentazione.
Idee di progetti con l'ILI9341
- Pannello touch per la casa intelligente: controlla luci e relè con grandi pulsanti sullo schermo; trovi spunti in Progetto di casa intelligente con Arduino.
- Display meteo a colori: mostra il meteo che l'ESP32 scarica da internet con icone e cifre grandi (Stazione meteo con ESP32).
- Cruscotto dei sensori: disegna temperatura, umidità e pressione di un BME280 come strumenti a lancetta con il grafico delle ultime 24 ore.
- Console di giochi retrò: gioca a Snake, Pong o Tetris a colori; aggiungi un modulo joystick per i comandi e cerca idee in Progetti di giochi con Arduino.
- Cornice digitale: fai scorrere ogni pochi secondi le immagini BMP della scheda SD.
- Mini oscilloscopio: disegna i valori di analogRead come un'onda fluida con uno sprite sull'ESP32.
- Serratura touch con codice: disegna un tastierino numerico sullo schermo e apri un relè o un servo quando viene inserito il codice giusto.
L'ILI9341 è il modo più economico per dare ai tuoi progetti un'interfaccia touch a colori: parti con l'Uno per imparare le funzioni di disegno di base e passa all'ESP32 con TFT_eSPI quando ti servono velocità e animazioni fluide. Se preferisci iniziare con un display più piccolo, Display OLED (SSD1306) è un buon punto di partenza. Nell'app BlueGrays trovi il display meteo con ESP32, il registratore di temperatura, il sensore di parcheggio e progetti di giochi con display, con schemi elettrici e codice passo dopo passo.
Domande frequenti
Il display ILI9341 funziona a 5 V?
Il pin VCC del modulo accetta 3,3–5 V perché sulla scheda c'è un regolatore da 3,3 V. I pin di segnale come CS, DC, SCK e MOSI, però, sono solo a 3,3 V. Con un Arduino Uno devi mettere su queste linee un convertitore di livello, come un 74HC4050 o dei partitori di tensione.
Perché il mio ILI9341 resta bianco?
Lo schermo bianco indica che la retroilluminazione è accesa ma il chip driver non è stato inizializzato. Le cause più comuni sono un cablaggio sbagliato di CS, DC o RESET, l'assenza del convertitore di livello sull'Uno, un User_Setup.h non modificato in TFT_eSPI e un display che in realtà non è un ILI9341.
Quale libreria si usa per l'ILI9341?
Per Arduino Uno, Nano e Mega la scelta più semplice è Adafruit_ILI9341 con Adafruit GFX. Sulle schede ESP32, ESP8266 e RP2040 si preferisce TFT_eSPI, molto più veloce. Gli shield paralleli da 2,4 pollici che si innestano sull'Uno richiedono invece MCUFRIEND_kbv.
Come si calibra il touch dell'ILI9341?
Il controller touch XPT2046 restituisce valori grezzi da 0 a 4095. Annota i valori minimi e massimi che vedi nel Monitor seriale toccando gli angoli e convertili in coordinate dello schermo con map(). Se usi TFT_eSPI, l'esempio Touch_calibrate fa tutto da solo e ti dà un array di calibrazione pronto.
Che differenza c'è tra l'ILI9341 da 2,4 e da 2,8 pollici?
Entrambi hanno una risoluzione di 240×320 pixel e usano gli stessi pin e lo stesso codice. Il display da 2,8 pollici ha pixel un po' più grandi, quindi il testo si legge meglio ed è più facile premere i pulsanti touch. Il modulo da 2,4 pollici entra in contenitori più piccoli.
Si può usare l'ILI9341 con un Arduino Nano?
Sì. I pin SPI del Nano sono gli stessi dell'Uno: D13 SCK, D11 MOSI e D12 MISO. Anche il Nano lavora con logica a 5 V, quindi serve un convertitore di livello. I suoi 2 KB di RAM bastano per pilotare il display, ma non per tenere immagini grandi, che dovrai leggere a pezzi dalla scheda SD.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
ESP32 HTTPClient + API meteo (con display OLED SSD1306)
In questo progetto, utilizzando l'ESP32, effettuiamo una chiamata REST all'API meteo (wttr.in) tramite Internet utilizzando HTTPClient.h. A differenza della precedente versione con LCD I2C, qui utilizziamo un display OLED SSD1306 (128×64), che consente di utilizzare caratteri di grandi dimensioni, icone vettoriali e una ricca presentazione visiva su un unico schermo. Per le condizioni meteorologiche, 7 diverse icone vettoriali (soleggiato, parzialmente nuvoloso, nuvoloso, piovoso, nevoso, temp

Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Costruisci un data logger di temperatura con timestamp usando ESP32! Leggi temperatura/umidità dal DHT22 ogni minuto, ottieni l'ora reale dal DS1307 RTC e registra su scheda microSD in formato CSV. L'OLED mostra dal vivo temperatura (font grande), percentuale di umidità, timestamp e contatore di log. Il file /temp_log.csv sulla scheda SD si apre in Excel, Google Sheets o Python pandas per l'analisi — perfetto per il monitoraggio di serre, tracciamento della temperatura domestica, raccolta

Lampada con miscelatore di colori ESP32 (3 potenziometri + LED RGB + OLED)
Una lampada mista colori intelligente che, ruotando i 3 potenziometri con l'ESP32, permette di miscelare il colore desiderato! Man mano che si ruotano i potenziometri rosso, verde e blu, il LED RGB a catodo comune cambia colore istantaneamente. Sul display OLED SSD1306 vengono visualizzati in tempo reale i valori compresi tra 0 e 255 per ogni colore, le barre di riempimento visive e il codice colore HEX (#FF8040, ad esempio). Un mini studio di colore professionale: insegna la teoria dei colori R

Sensore di parcheggio ESP32 (HC-SR04 + barra a 5 LED + cicalino + OLED)
La versione ESP32 del sensore di parcheggio per auto! Grazie al sensore a ultrasuoni HC-SR04 misuriamo la distanza dall'oggetto davanti al veicolo. Man mano che la distanza diminuisce, la barra composta da 5 LED si riempie (da verde → giallo → rosso) e l'intervallo tra i bip del cicalino si accorcia. Quando ci si avvicina troppo (meno di 10 cm), l'allarme suona ininterrottamente e tutti i LED si accendono. Sul display OLED SSD1306 vengono visualizzati in caratteri grandi il valore istantaneo in

Gioco delle Racchette - SSD1306
Una pallina, due racchette e tu: giochi a ping pong, uno dei primissimi videogiochi, su un piccolo display OLED. Con due pulsanti muovi su e giù la tua racchetta a destra, mentre il computer guida que

OLED T-Rex Run: Gioco del dinosauro con Arduino
Il gioco del dinosauro che compare quando salta internet, ora sulla tua scrivania: il dinosauro corre sul piccolo display OLED, i cactus gli vengono incontro e tu salti con un solo pulsante. Il punteg

Schermo LCD I2C per ESP32 (16x2)
In questo progetto visualizziamo del testo collegando un display LCD a 16x2 caratteri all'ESP32 tramite il protocollo I2C. Grazie all'I2C possiamo controllare il display utilizzando solo 2 pin di dati (SDA e SCL); si elimina così lo spreco dei 6+ pin tipici del collegamento parallelo classico. Il codice stampa un messaggio fisso sulla prima riga e un contatore che aumenta ogni secondo sulla seconda riga. Inoltre, all'inizio, eseguendo lo scanner I2C, stampiamo l'indirizzo del display sul Serial

Gioco runner su LCD con Arduino
Gioco endless runner a scorrimento laterale su schermo LCD a 16x2 caratteri. L'eroe corre automaticamente, il giocatore salta gli ostacoli premendo il pulsante. Sulla riga superiore e inferiore compaiono ostacoli casuali. Il cicalino passivo riproduce la melodia di apertura, il suono di salto, il suono di celebrazione dei 50 punti e la melodia di fine gioco. All'avvio viene visualizzata la schermata iniziale "BlueGrays - Arduino Projects".
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 200 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

