Guia do ILI9341 com Arduino: ligações do display TFT, toque e imagens do SD
Como ligar um display TFT ILI9341 ao Arduino: conversão de nível para 3,3 V no Uno, Adafruit GFX, toque XPT2046, BMP do cartão SD, TFT_eSPI no ESP32 e solução para a tela branca.
Uma montagem com ILI9341 e Arduino é o jeito mais barato e popular de acrescentar ao seu projeto um display de 240×320 pixels com 65 mil cores. Esses módulos TFT de 2,4 e 2,8 polegadas, em placa vermelha, se ligam por SPI com poucos fios, e as versões com toque também trazem um controlador de toque XPT2046 e um slot para cartão microSD. Com um único módulo, você mostra gráficos coloridos, botões de toque e imagens lidas de um cartão. É ideal para menus, painéis, jogos e porta-retratos em que um OLED ou um LCD de caracteres já não basta.
Neste guia você vai aprender passo a passo como o módulo funciona, como ligar com segurança as saídas de 5 V do Arduino Uno às entradas de 3,3 V do display, como desenhar textos, formas e cores com as bibliotecas Adafruit_ILI9341 e Adafruit GFX, como ler a tela de toque e como mostrar imagens BMP de um cartão SD. Depois veremos o desenho muito mais rápido no ESP32 com a TFT_eSPI, o uso de MicroPython no Raspberry Pi Pico e todas as causas do problema da “tela branca” que quase todo mundo enfrenta pelo menos uma vez.

O que é o display TFT ILI9341 e como ele funciona?
O ILI9341 é o chip driver atrás do vidro. Ele tem cerca de 172 KB de memória de vídeo (GRAM), que guarda todos os 240×320 pixels. O Arduino manda comandos e dados de cor para essa memória por SPI, e o chip atualiza a imagem sozinho dezenas de vezes por segundo. Por isso a imagem não precisa ficar na RAM do Arduino: até um Uno com 2 KB de RAM consegue acionar uma tela colorida grande. Para desenhar algo, você primeiro seleciona uma janela (x, y, largura, altura) e depois manda os pixels dessa janela um atrás do outro. O pino DC diz ao chip se o byte enviado é um comando (LOW) ou um dado (HIGH). As cores costumam ser enviadas no formato RGB565, ou seja, 16 bits por pixel: 5 bits de vermelho, 6 de verde e 5 de azul. Você encontra em detalhes como o barramento SPI funciona em O que é SPI.
| Característica | Valor |
|---|---|
| Chip driver | ILI9341 |
| Resolução | 240×320 pixels (320×240 em paisagem) |
| Cor | 65.536 cores com RGB565 (o chip suporta 262 mil) |
| Tamanhos de tela | 2,2", 2,4", 2,8" e 3,2" (todos 240×320) |
| Interface | SPI de 4 fios: CS, DC, SCK, MOSI (MISO para leitura) |
| Alimentação (VCC) | 3,3–5 V (há um regulador de 3,3 V na placa) |
| Nível lógico | 3,3 V (não tolera 5 V) |
| Consumo | Cerca de 0,3–0,6 W, a maior parte na luz de fundo |
| Toque | Resistivo, controlador XPT2046 (nos modelos com toque) |
| Hardware extra | Slot para cartão microSD (pinos SPI separados) |
2,4 ou 2,8 polegadas? Módulo SPI ou shield para Uno
| Característica | Módulo SPI de 2,4" | Módulo SPI de 2,8" | Shield para Uno de 2,4" |
|---|---|---|---|
| Resolução | 240×320 | 240×320 | 240×320 |
| Ligação | SPI, com jumpers | SPI, com jumpers | Paralela de 8 bits, encaixa em cima do Uno |
| Pinos usados | 5 para o display, 8–9 com toque e SD | 5 para o display, 8–9 com toque e SD | Quase todos (D2–D9, A0–A4, D10–D13 para o SD) |
| Driver | ILI9341 | ILI9341 | Varia: ILI9341, ILI9325, ST7789 e outros |
| Biblioteca | Adafruit_ILI9341, TFT_eSPI | Adafruit_ILI9341, TFT_eSPI | MCUFRIEND_kbv |
| Toque | XPT2046 (SPI) | XPT2046 (SPI) | Analógico de 4 fios (biblioteca TouchScreen) |
| ESP32 e Pico | Adequado | Adequado | Feito para o Uno e o Mega |
Este artigo trata do módulo SPI, porque ele também funciona com o ESP32 e o Pico e deixa livre a maioria dos pinos do Uno. Os dois módulos SPI têm exatamente os mesmos pinos e o mesmo código; a tela de 2,8 polegadas tem pixels um pouco maiores, então fica mais fácil acertar os botões de toque. Se o seu display encaixa direto em cima do Uno como um shield, as bibliotecas SPI não conseguem acioná-lo, e o resultado costuma ser uma tela branca. Para um shield assim, instale a biblioteca MCUFRIEND_kbv, leia o ID do driver com tft.readID() (0x9341 para o ILI9341) e inicie com tft.begin(ID). Para uma alternativa menor, mas mais nítida, veja display TFT ST7789 e, para comparar todas as opções, leia tipos de display para Arduino.
Pinagem do ILI9341 e tabela de ligações
Uma borda do módulo tem o conector principal de 14 pinos: os 9 primeiros pinos são do display e os 5 últimos, que começam com T_, são do toque. Os 4 pinos da borda oposta (SD_CS, SD_MOSI, SD_MISO, SD_SCK) vão para o slot do cartão. Os três dispositivos podem dividir o mesmo barramento SPI (SCK, MOSI, MISO); só os pinos CS precisam ser separados. A tabela abaixo corresponde a todos os sketches deste artigo; no Pico, o toque vai em um barramento SPI separado por causa da diferença de velocidade.
| Pino do módulo | Arduino Uno (pelo conversor de nível) | 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 (direto) | GPIO 19 | GP16 |
| T_CLK | D13 | GPIO 18 | GP10 |
| T_CS | D7 | GPIO 21 | GP13 |
| T_DIN | D11 | GPIO 23 | GP11 |
| T_DO | D12 (direto) | GPIO 19 | GP12 |
| T_IRQ | D2 (direto) | Não ligado | Não ligado |
| SD_CS | D4 | GPIO 5 | Não ligado |
| SD_MOSI / SD_MISO / SD_SCK | D11 / D12 / D13 | GPIO 23 / 19 / 18 | Não ligado |
Conversão de nível para 3,3 V no Arduino Uno
O ESP32 e o Raspberry Pi Pico já funcionam com 3,3 V, então você pode ligar o display diretamente. Em placas de 5 V como o Uno, o Nano e o Mega, toda linha que vai do Arduino para o display precisa ser baixada para 3,3 V: CS, DC, RESET, SCK, MOSI e, se você usar toque e SD, T_CLK, T_DIN, T_CS, SD_CS, SD_MOSI e SD_SCK. As linhas SDO, T_DO, SD_MISO e T_IRQ, que vêm do display para o Arduino, não precisam de conversor: o Uno lê como HIGH qualquer coisa acima de 3 V, e uma saída de 3,3 V atende a isso. Na prática, há quatro jeitos de fazer:
- Chip buffer 74HC4050 (a solução mais limpa): ele funciona com 3,3 V, aceita 5 V nas entradas e converte 6 linhas em um só chip. É rápido e funciona sem problemas com SPI a 8 MHz. O CD4050, mais antigo, também funciona, mas é lento; se a imagem ficar corrompida, você vai precisar baixar a velocidade do SPI. O display sozinho precisa de 5 linhas; com toque e SD, você acrescenta T_CS e SD_CS e chega a 7 linhas, então use um segundo chip ou um divisor de tensão para a sétima.
- Divisores de tensão com resistores: para cada linha, ponha 1 kΩ entre o pino do Arduino e o pino do display e 2 kΩ entre o pino do display e o GND; isso dá 5 V × 2 / 3 ≈ 3,3 V. Você pode calcular outros pares de resistores com a calculadora de divisor de tensão. É barato, mas 10 resistores para 5 linhas lotam a protoboard.
- Módulo conversor de nível pronto: módulos de 4 ou 8 canais com MOSFETs BSS138 são fáceis de ligar, mas os resistores de pull-up deles amolecem as bordas do sinal em velocidades altas. Se a imagem ficar corrompida, baixe o SPI para 4 MHz com tft.begin(4000000). Os métodos são comparados em conversor de nível lógico.
- O método a evitar: o popular truque de “resistor de 10 kΩ em série em cada linha” joga a tensão excedente nos diodos de proteção internos do chip. Pode funcionar por um tempo, mas não é confiável e pode danificar o display a longo prazo.
Passo a passo: primeiro teste com a Adafruit_ILI9341
- Abra o Gerenciador de Bibliotecas no Arduino IDE, procure Adafruit ILI9341 e instale. Quando ele perguntar sobre as dependências, escolha Instalar tudo para que a Adafruit GFX e a Adafruit BusIO também sejam instaladas. Se esta for a sua primeira biblioteca, Como instalar uma biblioteca no Arduino vai ajudar.
- Para o toque, instale também a biblioteca XPT2046_Touchscreen, de Paul Stoffregen. A biblioteca SD vem com o Arduino IDE.
- Ligue o display de acordo com a tabela acima, pelo conversor de nível no Uno. No primeiro teste, você pode deixar os pinos de toque e de SD desligados.
- Carregue o sketch abaixo e abra o Monitor Serial em 9600 baud. Você deve ver textos e formas no display e os códigos de diagnóstico do display no Monitor Serial.
- Experimente também os exemplos que vêm com a biblioteca: Arquivo > Exemplos > Adafruit ILI9341 > graphicstest mede quantos microssegundos cada operação de desenho leva.
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}| Função | O que faz |
|---|---|
| tft.begin(frequência) | Inicia o display; sem frequência, usa SPI de 8 MHz no Uno e de 40 MHz no ESP32 |
| tft.setRotation(0–3) (orientação) | Escolhe a orientação: 0 e 2 retrato (240×320), 1 e 3 paisagem (320×240) |
| tft.fillScreen(cor) | Pinta a tela inteira de uma cor |
| tft.setTextColor(texto, fundo) | Define a cor do texto; com uma cor de fundo, sobrescreve o texto antigo de forma limpa |
| tft.setTextSize(n) (tamanho do texto) | Aumenta o texto n vezes; o tamanho 1 = caracteres de 6×8 pixels |
| tft.drawRect / fillRect(x, y, largura, altura, cor) | Contorno de retângulo ou retângulo preenchido |
| tft.fillRoundRect(x, y, largura, altura, r, cor) | Retângulo preenchido com cantos arredondados; ideal para botões |
| tft.drawCircle / fillCircle(x, y, r, cor) | Círculo ou círculo preenchido |
| tft.color565(r, g, b) (conversão de cor) | Converte valores RGB de 0–255 em uma cor de 16 bits |
| tft.readcommand8(comando) | Lê um registrador do display (o MISO precisa estar ligado); para diagnóstico |
Constantes de cor prontas (19 delas, como ILI9341_RED, ILI9341_NAVY e ILI9341_ORANGE) vêm com a biblioteca; para uma cor sua, use color565(). Ela junta lado a lado os 5 bits mais altos do vermelho, os 6 mais altos do verde e os 5 mais altos do azul: cor = (R >> 3) << 11 | (G >> 2) << 5 | (B >> 3). Para o laranja (255, 165, 0), por exemplo, 31 << 11 = 0xF800 e 41 << 5 = 0x0520 se combinam em 0xFD20, que é exatamente o valor ILI9341_ORANGE da biblioteca. O verde ganha um bit a mais porque o olho humano é mais sensível ao verde.
Lendo a tela de toque XPT2046
Nos módulos com toque, há uma fina camada resistiva sobre o vidro. Onde o dedo ou uma caneta pressiona, duas camadas condutoras se tocam; o chip XPT2046 mede a posição X e Y desse ponto com um ADC de 12 bits como valores brutos de 0 a 4095 e também informa um valor de pressão (Z). A biblioteca considera sem toque um valor de Z abaixo de 400. O chip fica no mesmo barramento SPI do display, mas conversa em uma velocidade menor, por volta de 2 MHz; a biblioteca ajusta a velocidade sozinha a cada leitura. O pino T_IRQ vai a LOW quando a tela é tocada, o que poupa o processador de leituras inúteis. O sketch abaixo transforma a tela em uma pequena lousa de desenho: você escolhe uma cor nas caixas do topo e apaga a tela com o botão “SIL” (apagar, em turco).
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}Os valores brutos não são 0 e 4095 bem nos cantos; na maioria dos módulos, as bordas ficam mais ou menos entre 200–400 e 3700–3900. Para calibrar, abra o Monitor Serial, toque com uma caneta os cantos superior esquerdo e inferior direito e ponha os valores de x e y que aparecerem nas constantes TS_MINX, TS_MAXX, TS_MINY e TS_MAXY. Se o ponto aparecer espelhado em relação ao dedo, troque no map() os valores mínimo e máximo daquele eixo; se X e Y estiverem totalmente trocados, experimente valores de ts.setRotation() de 0 a 3. Um menu de verdade funciona do mesmo jeito: você desenha botões e confere em qual retângulo cai o ponto tocado. A camada resistiva também funciona com o dedo, mas uma ponta parecida com uma caneta é mais precisa para botões pequenos.
Mostrando imagens BMP do cartão SD
O slot microSD atrás do módulo permite guardar logotipos, fundos e fotos em um cartão e mostrá-los na tela. Para preparar uma imagem, redimensione uma foto para 320×240 pixels e salve no Paint como Bitmap de 24 bits (.bmp). Como a biblioteca SD do Arduino trabalha com nomes de arquivo curtos, use um nome de até 8 caracteres, como RESIM.BMP, e formate o cartão em FAT32. Os detalhes do cartão em si e a solução para o erro initialization failed estão em módulo de cartão SD no Arduino. Lembre que esse slot também é de 3,3 V: no Uno, as linhas SD_CS, SD_MOSI e SD_SCK também precisam passar pelo conversor de nível.
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}Os primeiros 54 bytes de um arquivo BMP são o cabeçalho: ali ficam a assinatura “BM”, onde começam os dados dos pixels, a largura, a altura e a profundidade de cor. Os pixels ficam na ordem azul, verde, vermelho (BGR) e normalmente de baixo para cima, e cada linha é completada até um múltiplo de 4 bytes. Por isso o sketch salta para a posição certa do arquivo para cada linha da tela, lê 20 pixels (60 bytes) por vez, converte-os para RGB565 e os envia com drawRGBBitmap(). Trabalhar em pedaços pequenos mantém tudo dentro da RAM do Uno e deixa o cartão SD e o display se revezarem no mesmo barramento SPI. O tempo de desenho aparece no Monitor Serial; no Uno, uma imagem de tela inteira aparece linha por linha ao longo de alguns segundos. Se quiser mostrar arquivos PNG ou JPEG e desenhar muito mais rápido, passe para um ESP32 com as bibliotecas TFT_eSPI e TJpg_Decoder.
Desenho rápido no ESP32 com a TFT_eSPI

O ESP32 funciona com 3,3 V e consegue acionar o SPI a até 40 MHz, então você não precisa de conversor de nível e preencher a tela inteira cai, na teoria, para uns 31 milissegundos. Para usar de verdade essa velocidade, a biblioteca TFT_eSPI, de Bodmer, é a escolha habitual. Ela acessa diretamente o hardware SPI do ESP32 e suporta fontes suavizadas (anti-aliased) e, principalmente, buffers fora da tela chamados sprites. Você desenha à vontade em um sprite na RAM e manda o resultado para a tela de uma vez, então nem gráficos que mudam rápido piscam. O que torna a TFT_eSPI diferente é que os pinos não são definidos no sketch, e sim no arquivo User_Setup.h dentro da pasta da biblioteca. Abra-o, comente as linhas de pinos do ESP8266 (NodeMCU), que vêm ativas por padrão, e ative 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}O sketch desenha o valor de um potenciômetro no GPIO 34 como um gráfico rolando e mostra os quadros por segundo (FPS) no canto do sprite. Um sprite de 300×100 pixels com cor de 16 bits ocupa 60 KB de RAM; isso é cerca de 30 vezes toda a memória do Uno, mas não é problema para o ESP32. Para o toque, os pinos T_CLK, T_DIN e T_DO se juntam às linhas SCK, MOSI e MISO do display, e só o T_CS (GPIO 21) fica separado. Para obter os seus próprios valores de calibração, rode uma vez o exemplo Generic > Touch_calibrate da TFT_eSPI, toque nas setas que aparecem nos cantos e copie para o array calData os 5 números mostrados no Monitor Serial. A linha pronta Setup42_ILI9341_ESP32.h em User_Setup_Select.h escolhe os mesmos pinos do display, mas você precisa acrescentar a linha TOUCH_CS a ela você mesmo. Se você é novo no ESP32, leia antes O que é o ESP32.
ILI9341 com Raspberry Pi Pico e MicroPython
O Pico também é uma placa de 3,3 V, então o display se liga diretamente. O driver de MicroPython mais usado é o par ili9341.py e xpt2046.py do repositório micropython-ili9341, de rdagger; basta copiar os dois arquivos para o Pico com o Thonny. No código abaixo, o display fica no SPI0 a 40 MHz e o controlador de toque em um barramento SPI1 separado a 1 MHz, então a diferença de velocidade entre os dois chips não é problema. O código desenha textos e formas e depois põe pontos amarelos onde você tocar, formando uma lousa de desenho simples. Se é a primeira vez que você usa o Pico e o Thonny, comece por O que é o 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)No modo retrato (rotation=0), a largura é 240 e a altura 320; para usar a tela em paisagem, passe width=320 e height=240 junto com rotation=90. A calibração padrão do driver de toque (x_min=100, x_max=1962) pode não servir para todo módulo; se o ponto sair deslocado, mostre os valores de raw_touch() e passe ao Touch() os seus próprios x_min, x_max, y_min e y_max. Para mostrar imagens, a função draw_image() do driver lê arquivos .raw convertidos para RGB565; o script utils/img2rgb565.py do repositório faz essa conversão no computador.
O problema da tela branca e como resolver
O problema mais famoso do ILI9341 é uma tela que fica totalmente branca com a luz de fundo acesa. A tela branca é na verdade uma pista: a alimentação e a luz de fundo estão funcionando, mas o chip driver nunca foi inicializado. Quando o painel não recebe comandos, os pixels deixam a luz passar e a tela parece branca. Então o problema é quase sempre de comunicação. Siga a tabela abaixo de cima para baixo:
| Sintoma | Causa provável | Solução |
|---|---|---|
| Tela branca, ligado direto ao Uno | Sinais de 5 V, sem conversor de nível | Use um 74HC4050 ou divisores de tensão; troque o módulo se o chip estiver danificado |
| Tela branca, fios ligados | CS e DC trocados, ou os números dos pinos do código não batem com as ligações | Compare uma por uma as definições TFT_CS, TFT_DC e TFT_RST com a tabela |
| Tela branca, RESET solto | RESET desligado ou no pino errado | Ligue o RESET ao pino do código ou a 3,3 V; nunca o deixe solto |
| Tela branca no ESP32 (TFT_eSPI) | User_Setup.h não editado ou apagado por uma atualização | Desative as linhas do ESP8266 e escreva de novo os pinos do ESP32 e o ILI9341_DRIVER |
| Tela branca de vez em quando, imagem deslocada ou embaralhada | Jumpers longos, SPI rápido demais, contatos frouxos na protoboard | Mantenha os fios com menos de 10–15 cm; baixe o SPI para 20–27 MHz, ou 4 MHz no Uno |
| Tela branca só com o toque ou o SD ligados | O pino CS do outro dispositivo está solto e bloqueia o barramento | Configure todo pino CS sem uso como OUTPUT e escreva HIGH nele no código |
| Não funciona com nenhum código | O display não é um ILI9341 (ST7789, ILI9488) ou é um shield paralelo | Leia a etiqueta atrás; uma tela de 3,5 polegadas e 320×480 é um ILI9488, e um shield precisa da MCUFRIEND_kbv |
| Tela branca, luz de fundo piscando | Alimentação insuficiente | Alimente o VCC com 5 V e teste outro cabo USB e outra fonte |
Outros erros comuns
- Cores erradas: se o vermelho aparece como azul, o painel usa a ordem BGR; mude a configuração TFT_RGB_ORDER na TFT_eSPI. Se as cores parecerem um negativo, experimente tft.invertDisplay(true).
- Imagem de cabeça para baixo ou espelhada: mude o valor de setRotation() entre 0 e 3 e ajuste os eixos do toque de acordo.
- O display funciona, mas o toque ou o SD não: confira se T_CS e SD_CS batem com os pinos do código. Em alguns módulos, a saída SDO do display não solta o barramento; nesse caso, experimente desligar o pino SDO do display, e só a leitura de diagnóstico vai parar de funcionar.
- O BMP não aparece: o cartão não está em FAT32, o nome do arquivo tem mais de 8 caracteres ou a imagem não é de 24 bits. Não esqueça de escolher 24 bits no Paint; BMPs de 16 e 32 bits não são desenhados por este sketch.
- Faltam caracteres especiais: a fonte GFX padrão não tem letras acentuadas como ç, ã ou é. Na TFT_eSPI, você pode criar a sua própria fonte suavizada, ou usar uma fonte GFX que inclua os caracteres de que precisa.
- O display está lento: uma atualização de tela inteira no Uno leva pelo menos 0,15 segundo. Para animações e jogos, passe para um ESP32 ou um Pico e redesenhe só a área que muda.
- O módulo esquenta: pode haver 5 V no VCC com o jumper J1 fechado, ou o VCC e o GND podem estar invertidos; desligue a alimentação na hora.
Ideias de projetos com o ILI9341
- Painel de casa inteligente com toque: controle luzes e relés com botões grandes na tela; para ideias, veja o projeto de casa inteligente com Arduino.
- Display colorido do tempo: mostre com ícones e números grandes a previsão do tempo que você pega na internet com um ESP32 (estação meteorológica com ESP32).
- Painel de sensores: desenhe a temperatura, a umidade e a pressão de um BME280 como mostradores, com um gráfico das últimas 24 horas.
- Console de jogos retrô: jogue Snake, Pong ou Tetris em cores; acrescente um módulo joystick para o controle e veja projetos de jogos com Arduino para ideias.
- Porta-retratos digital: passe pelas imagens BMP do cartão SD a cada poucos segundos.
- Miniosciloscópio: desenhe os valores do analogRead como uma forma de onda suave com um sprite no ESP32.
- Fechadura com teclado de toque: desenhe um teclado numérico na tela e abra um relé ou um servo quando o código certo for digitado.
O ILI9341 é o jeito mais acessível de dar aos seus projetos uma interface de toque colorida: comece com o Uno para aprender as funções básicas de desenho e passe para o ESP32 com a TFT_eSPI quando precisar de velocidade e de animação suave. Se preferir começar com uma tela menor, o display OLED (SSD1306) é um bom ponto de partida. No app BlueGrays você encontra os projetos de display do tempo com ESP32, registrador de temperatura, sensor de estacionamento e jogos com display, com esquemas de circuito e código passo a passo.
Perguntas frequentes
O display ILI9341 funciona com 5 V?
O pino VCC do módulo aceita 3,3–5 V porque há um regulador de 3,3 V na placa. Os pinos de sinal, como CS, DC, SCK e MOSI, porém, são só de 3,3 V. Ao usá-lo com um Arduino Uno, ponha nessas linhas um conversor de nível como o 74HC4050 ou divisores de tensão.
Por que o meu ILI9341 mostra só uma tela branca?
Uma tela branca significa que a luz de fundo está acesa, mas o chip driver nunca foi inicializado. As causas mais comuns são ligações erradas de CS, DC ou RESET, falta de conversor de nível no Uno, um User_Setup.h não editado na TFT_eSPI e um display que na verdade não é um ILI9341.
Qual biblioteca devo usar para o ILI9341?
Para o Arduino Uno, o Nano e o Mega, a Adafruit_ILI9341 com a Adafruit GFX é a escolha mais fácil. Em placas ESP32, ESP8266 e RP2040, prefere-se a TFT_eSPI, muito mais rápida. Shields paralelos de 2,4 polegadas que encaixam no Uno precisam, no lugar, da MCUFRIEND_kbv.
Como calibro a tela de toque do ILI9341?
O controlador de toque XPT2046 retorna valores brutos de 0 a 4095. Anote os menores e os maiores valores que aparecem no Monitor Serial quando você toca os cantos e converta-os em coordenadas da tela com map(). Se você usa a TFT_eSPI, o exemplo Touch_calibrate faz isso por você e mostra um array de calibração pronto.
Qual é a diferença entre o ILI9341 de 2,4 e o de 2,8 polegadas?
Os dois têm resolução de 240×320 pixels e usam os mesmos pinos e o mesmo código. A tela de 2,8 polegadas tem pixels um pouco maiores, então o texto fica mais fácil de ler e os botões de toque mais fáceis de apertar. O módulo de 2,4 polegadas cabe em gabinetes menores.
Posso usar o ILI9341 com um Arduino Nano?
Sim. Os pinos SPI do Nano são os mesmos do Uno: D13 SCK, D11 MOSI e D12 MISO. O Nano também usa lógica de 5 V, então você precisa de um conversor de nível. Os 2 KB de RAM dele bastam para acionar o display, mas não para guardar imagens grandes, então você precisa ler as imagens do cartão SD em pedaços pequenos.
Projetos para montar depois desta leitura
Ver todos os projetos
ESP32 HTTPClient + API de clima (com display OLED SSD1306)
Neste projeto, usamos o ESP32 para fazer uma chamada REST à API de clima wttr.in pela internet com HTTPClient.h. Diferentemente da versão anterior com LCD I2C, aqui usamos um display OLED SSD1306 (128×64), que permite usar fontes grandes, ícones vetoriais e uma apresentação visual rica em uma única tela. Sete ícones vetoriais de condições do tempo (ensolarado, parcialmente nublado, nublado, chuvoso, com neve, com tempestade e com neblina) são desenhados durante a execução, sem arquivos bitmap.

Registrador de temperatura com ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Monte um registrador de temperatura com data e hora usando ESP32! Leia a temperatura e a umidade do DHT22 a cada minuto, obtenha a hora real do DS1307 RTC e grave os dados em CSV no cartão microSD. O OLED exibe a temperatura atual em letras grandes, a porcentagem de umidade, a data e a hora e o contador de registros. O arquivo /temp_log.csv no cartão SD pode ser aberto no Excel, Google Sheets ou pandas do Python para análise — ideal para monitorar estufas, acompanhar a temperatura de casa ou…

Luminária com mistura de cores e ESP32 (3 potenciômetros + LED RGB + OLED)
Uma luminária inteligente que permite misturar a cor que você quiser girando três potenciômetros com o ESP32! Ao girar os potenciômetros de vermelho, verde e azul, o LED RGB de cátodo comum muda de cor em tempo real. O display OLED SSD1306 mostra os valores de cada cor na faixa de 0–255, barras de preenchimento e o código HEX da cor para a web, como #FF8040.

Sensor de estacionamento com ESP32 (HC-SR04 + barra de 5 LEDs + buzzer + OLED)
A versão com ESP32 do sensor de estacionamento de carro! Usamos o sensor ultrassônico HC-SR04 para medir a distância até o objeto à sua frente. Conforme a distância diminui, a barra de 5 LEDs vai se preenchendo (verde → amarelo → vermelho) e o intervalo entre os bipes do buzzer diminui. Quando o objeto chega muito perto (menos de 10 cm), o alarme soa continuamente e todos os LEDs acendem.

Jogo de bola e raquetes - SSD1306
Uma bola, duas raquetes e você: jogue tênis de mesa, um dos primeiros videogames, em um pequeno display OLED. Dois botões movem sua raquete à direita para cima e para baixo, enquanto o computador cont

Corrida do T-Rex no OLED: jogo do dinossauro com Arduino
O jogo do dinossauro que aparece quando a internet cai, agora na sua mesa: o dinossauro corre pelo pequeno display OLED, os cactos vêm na sua direção e você pula com um único botão. Sua pontuação aume

Display LCD I2C com ESP32 (16x2)
Neste projeto, exibimos texto conectando um display LCD de 16x2 caracteres ao ESP32 pelo protocolo I2C. Com o I2C, controlamos o display usando apenas dois pinos de dados (SDA e SCL), dispensando o uso de seis ou mais pinos da conexão paralela tradicional. O código mostra uma mensagem fixa na primeira linha e um contador que aumenta a cada segundo na segunda linha. Na inicialização, também executamos um scanner I2C para mostrar o endereço do display no Monitor Serial (geralmente 0x27 ou 0x3F).

Jogo de corrida no LCD com Arduino
Jogo de corrida infinita com rolagem lateral em um display LCD de 16x2 caracteres. O herói corre automaticamente, e o jogador pressiona o botão para pular os obstáculos. Obstáculos aleatórios aparecem nas linhas superior e inferior. O buzzer passivo toca a melodia de abertura, o som de pulo, o som de comemoração a cada 50 pontos e a melodia de fim de jogo. A tela de abertura "BlueGrays - Arduino Projects" aparece ao iniciar. A pontuação é atualizada em tempo real no canto superior direito.
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

