conectar-arduino-a-una-pantalla-tft

Conectar Arduino a una pantalla TFT de 1.4" a 3.2"

¿Qué es un pantalla TFT?

Una pantalla TFT es uno de los tipos de pantallas que podemos emplear en proyectos de electrónica y Arduino. El tamaño de estas pantallas generalmente varía entre 1.44” a 3.2” y resoluciones de 128x128 a 240x320 pixeles.

Al igual que el resto de pantallas, un TFT necesitan un controlador que traduzca la información recibida en las señales necesarias de bajo nivel para controlar la electrónica del panel. Este controlador condicionará el esquema de conexión y el código que deberemos usar.

Dentro de la electrónica casera existen varios modelos de TFT y controladores disponible, siendo habituales el ST7735 para tamaños de hasta 120x160 pixels, y el ILI9341 para tamaños de hasta 240x320 pixels. Otros controladores son el ILI9163C, el HX8357 (320X480PX) y el Samsung S6D02A1 (QDTech).

En la mayoría de los casos la comunicación con la pantalla TFT se realiza a través del bus SPI debido al alto volumen de datos que requieren. Por tanto son sencillos de conectar y usar desde Arduino.

El tamaño y la resolución máxima de la pantalla que podremos controlar viene determinado por las características del modelo de procesador que estamos empleando. En general, manejar una pantalla TFT puede considerarse en el límite de las capacidades de Arduino ya que suponen una importante carga de procesador y de memoria (sólo el programa ocupará en torno a un 50% de la memoria disponible).

Con un Arduino UNO, Mini,y Pro no resulta aconsejable resoluciones mayores de 240x320 pixels (hasta 3.5” aproximadamente”). Para resoluciones superiores, como 320x480 pixeles, deberemos emplear un procesador superior el STM32 o el ESP8266. Para emplear pantallas más grandes, de 600x800 (7”) en adelante, deberías pensar en emplear otra opción como un Raspberry PI.

Muchos de los modelos disponibles incorporan un lector de tarjetas SD o micro SD, accesible a través del bus SPI, que puede ser empleado para almacenar imágenes que serán mostradas en el TFT. También existen modelos, con un precio superior, que incorporan un panel táctil. En el caso de paneles TFT táctiles, con mayor razón, deberíais plantearos emplear una opción más potente.

Pese a las limitaciones de potencia estas pequeñas pantallas son ampliamente empleadas en gran cantidad de proyectos ya que son una excelente forma de mostrar información al usuario. Por tanto, son una gran alternativa a otros tipos de display como los display LCD Hitachi HD44780 o el LCD Nokia 5510.

Precio

Los precios de estas pequeñas pantallas TFT varía mucho en función de su tamaño, que a su vez está relacionado con la resolución y el controlador.

Entre las más habituales, y por tanto más baratas, podemos encontrar pantallas TFT de 2.4” y 240x320 pixels con el controlador ILI9341 por unos 3.20€, buscando en vendedores internacionales de AliExpress.

Existen otros modelos con el ILI9341, con tamaños de 2.2” a 3.2” y resolución de 240x320 pixels, a un precio similar.

arduino-tft-.qdtech-componente

Con el controlador ST7735 podemos encontrar pantallas TFT de 1.8” con 128x160 pixels por 3.15€.

arduino_lcd_tft_ILI9341-componente

También con el mismo controlador ST7735 encontramos pantallas de 1.44” de 128x128 pixels con un precio de 2.6€.

arduino-tft-st7735-componente

Por supuesto también encontraremos otros tipos de pantallas con controladores QDTech, Samsung S6D02A1 o ILI9163C. En general, suelen ser más caras que las pantallas anteriores.

¿Cómo funciona un pantalla TFT?

Las pantallas TFT son una variación de las pantallas LCD (Liquid Crystal Display). A grandes rasgos, un LCD es un panel de cristal que se vuelve opaco y su opacidad se controla eléctricamente.

Los paneles LCD no generan su propia luz sino que necesitan una luz trasera (backlight) proporcionada por alguna fuente de luz (por ejemplo, fluorescencia o LED). El panel LCD tapa la luz emitida en ciertos puntos, lo que permite generar una imagen.

arduino_lcd_tft_display

Para conseguir tapar la luz emitida esta polarizada y la última capa de la pantalla presenta un cristal polarizado. Las moléculas del LCD pueden ser alineadas eléctricamente para modificar la polarización de la luz que la atraviesa, “girándola” a medida que pasa a través del cristal. De esta forma, se puede controlar que el grado de opacidad del panel LCD.

En una pantalla TFT RGB cada pixel tiene tres pequeñas celdas LCD con filtros de color que tapan uno de los componentes de la luz trasera, lo que genera la imagen formada por subpixels de color.

La tecnología TFT (Thin Transistor Layer) hace referencia a la electrónica necesaria para conmutar el LCD. Los subpixels se distribuyen en matriz, de forma que cada uno se controla la encendiendo la fila y columna correspondientes. Para evitar las caídas de tensión producidas al alimentar varios pixels en serie, cada uno dispone de un pequeño transistor en una capa delgada, que da origen al nombre de esta tecnología.

Esquema de montaje

La conexión de estos paneles es sencilla, simplemente alimentamos el módulo desde Arduino mediante GND y 5V y conectamos los pines de datos del TFT con Arduino como vimos en la entrada sobre el bus SPI.

Como vimos en esta entrada, la mayor dificultad es que cada fabricante emplea denominaciones ligeramente diferentes para los pines que intervienen en el bus SPI.

Basándonos en la tabla que vimos en la entrada sobre el bus SPI con designaciones habituales para los pines en distintos dispositivos, la siguiente tabla muestra los distintos pines que encontraremos en los distintos modelos de TFT, con la equivalencia en colores y pines de Arduino que emplearemos en esta entrada.

NombreAliasPin (en Arduino Uno o Nano)Descripcion
VCC+3.3 … 5 Volt
GNDGroundGround
SCLKCLK/SCK/SCLKD13 (hardware)Clock (SPI)
MISOMISO/SDO/DOUTD12 (hardware)Master In Slave Out (SPI)
MOSIMOSI/SDI/DIND11 (hardware)Master Out Slave In (SPI)
SSSS/CS/SDAD10 (hardware, solo en esclavo)Slave/Chip Select (SPI)
RESRST/RES/RESTD9 (variable, se fija por software)Controller Reset
RSRS/DCD8 (variable, se fija por software)Mode: Command/Data

La conexión de cada pantalla TFT dependerá tanto del controlador que emplee como de los detalles del módulo en el que va montada. A modo de ejemplo, el siguiente esquema muestra la conexión de una TFT 2.2” 240x320 pixels con controlador ILI9341.

arduino_lcd_tft_ili9341-conexion

El siguiente esquema muestra la conexión de un módulo con pantalla TFT 1.8” 128x160 pixels con controlador ST7735.

arduino-tft-.qdtech-conexion

Mientras que en el último ejemplo tenemos un módulo TFT 1.4” 128x128 pixels también con controlador ST7735.

arduino-tft-st7735-esquema

En todos los casos la conexión vista desde Arduino sería la misma, que se muestra en la siguiente imagen.

arduino-tft-conexion

Los pines de SPI que figuran son válidos para los modelos En Arduino Uno, Nano y Mini Pro. Para otros modelos de Arduino consultar el esquema patillaje correspondiente.

Verificar que vuestra placa es compatible con 5V antes de conectarla a Arduino. Si no, tendréis que usar un adaptador de nivel lógico, o colocar resistencias de 1K que limiten la corriente que llega a la pantalla TFT.

Ejemplos de códigos

Para manejar la pantalla TFT necesitaremos una librería apropiada para el tipo de controlador que emplea. La siguiente lista contiene enlaces a librerías para los principales controladores de TFT.

A su vez todas estas librerías emplean la Adafruit GFX para realizar gráficos de forma sencilla.

Cada librería proporciona ejemplos de código, que resulta aconsejable revisar. Todos los códigos son muy similares, ya que emplean la librería Adafrut GFX para los gráficos.

No tendría mucho sentido incorporar en la entrada todos. A modo de ejemplo, el siguiente código muestra una demo para una pantalla TFT de 2.2”/2.4” con controlador ILI9341 y una resolución de 240x340 pixels.

/***************************************************
This is our GFX example for the Adafruit ILI9341 Breakout and Shield
----> http://www.adafruit.com/products/1651

Check out the links above for our tutorials and wiring diagrams
These displays use SPI to communicate, 4 or 5 pins are required to
interface (RST is optional)
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada for Adafruit Industries.
MIT license, all text above must be included in any redistribution
****************************************************/

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"


#define TFT_CS 10

#define TFT_DC 8

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 ILI9341 = Adafruit_ILI9341(TFT_CS, TFT_DC);

void setup() {
  Serial.begin(9600);
  Serial.println("ILI9341 Test!");

  ILI9341.begin();

  // read diagnostics (optional but can help debug problems)
  uint8_t x = ILI9341.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = ILI9341.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = ILI9341.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = ILI9341.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = ILI9341.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(500);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(3000);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(500);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(500);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(500);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(500);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(500);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(500);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(500);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(500);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(500);

  Serial.println(F("Done!"));

}

void loop(void) {
  for (uint8_t rotation = 0; rotation<4; rotation++) {
    ILI9341.setRotation(rotation);
    testText();
    delay(1000);
  }
}

unsigned long testFillScreen() {
  unsigned long start = micros();
  ILI9341.fillScreen(ILI9341_BLACK);
  ILI9341.fillScreen(ILI9341_RED);
  ILI9341.fillScreen(ILI9341_GREEN);
  ILI9341.fillScreen(ILI9341_BLUE);
  ILI9341.fillScreen(ILI9341_BLACK);
  return micros() - start;
}

unsigned long testText() {
  ILI9341.fillScreen(ILI9341_BLACK);
  unsigned long start = micros();
  ILI9341.setCursor(0, 0);
  ILI9341.setTextColor(ILI9341_WHITE);  ILI9341.setTextSize(1);
  ILI9341.println("Hello World!");
  ILI9341.setTextColor(ILI9341_YELLOW); ILI9341.setTextSize(2);
  ILI9341.println(1234.56);
  ILI9341.setTextColor(ILI9341_RED);    ILI9341.setTextSize(3);
  ILI9341.println(0xDEADBEEF, HEX);
  ILI9341.println();
  ILI9341.setTextColor(ILI9341_GREEN);
  ILI9341.setTextSize(5);
  ILI9341.println("Groop");
  ILI9341.setTextSize(2);
  ILI9341.println("I implore thee,");
  ILI9341.setTextSize(1);
  ILI9341.println("my foonting turlingdromes.");
  ILI9341.println("And hooptiously drangle me");
  ILI9341.println("with crinkly bindlewurdles,");
  ILI9341.println("Or I will rend thee");
  ILI9341.println("in the gobberwarts");
  ILI9341.println("with my blurglecruncheon,");
  ILI9341.println("see if I don't!");
  return micros() - start;
}

unsigned long testLines(uint16_t color) {
  unsigned long start, t;
  int           x1, y1, x2, y2,
    w = ILI9341.width(),
    h = ILI9341.height();

  ILI9341.fillScreen(ILI9341_BLACK);

  x1 = y1 = 0;
  y2 = h - 1;
  start = micros();
  for (x2 = 0; x2<w; x2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  x2 = w - 1;
  for (y2 = 0; y2<h; y2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  t = micros() - start; // fillScreen doesn't count against timing

  ILI9341.fillScreen(ILI9341_BLACK);

  x1 = w - 1;
  y1 = 0;
  y2 = h - 1;
  start = micros();
  for (x2 = 0; x2<w; x2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  x2 = 0;
  for (y2 = 0; y2<h; y2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  t += micros() - start;

  ILI9341.fillScreen(ILI9341_BLACK);

  x1 = 0;
  y1 = h - 1;
  y2 = 0;
  start = micros();
  for (x2 = 0; x2<w; x2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  x2 = w - 1;
  for (y2 = 0; y2<h; y2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  t += micros() - start;

  ILI9341.fillScreen(ILI9341_BLACK);

  x1 = w - 1;
  y1 = h - 1;
  y2 = 0;
  start = micros();
  for (x2 = 0; x2<w; x2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);
  x2 = 0;
  for (y2 = 0; y2<h; y2 += 6) ILI9341.drawLine(x1, y1, x2, y2, color);

  return micros() - start;
}

unsigned long testFastLines(uint16_t color1, uint16_t color2) {
  unsigned long start;
  int           x, y, w = ILI9341.width(), h = ILI9341.height();

  ILI9341.fillScreen(ILI9341_BLACK);
  start = micros();
  for (y = 0; y<h; y += 5) ILI9341.drawFastHLine(0, y, w, color1);
  for (x = 0; x<w; x += 5) ILI9341.drawFastVLine(x, 0, h, color2);

  return micros() - start;
}

unsigned long testRects(uint16_t color) {
  unsigned long start;
  int           n, i, i2,
    cx = ILI9341.width() / 2,
    cy = ILI9341.height() / 2;

  ILI9341.fillScreen(ILI9341_BLACK);
  n = min(ILI9341.width(), ILI9341.height());
  start = micros();
  for (i = 2; i<n; i += 6) {
    i2 = i / 2;
    ILI9341.drawRect(cx - i2, cy - i2, i, i, color);
  }

  return micros() - start;
}

unsigned long testFilledRects(uint16_t color1, uint16_t color2) {
  unsigned long start, t = 0;
  int           n, i, i2,
    cx = ILI9341.width() / 2 - 1,
    cy = ILI9341.height() / 2 - 1;

  ILI9341.fillScreen(ILI9341_BLACK);
  n = min(ILI9341.width(), ILI9341.height());
  for (i = n; i>0; i -= 6) {
    i2 = i / 2;
    start = micros();
    ILI9341.fillRect(cx - i2, cy - i2, i, i, color1);
    t += micros() - start;
    // Outlines are not included in timing results
    ILI9341.drawRect(cx - i2, cy - i2, i, i, color2);
  }

  return t;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int x, y, w = ILI9341.width(), h = ILI9341.height(), r2 = radius * 2;

  ILI9341.fillScreen(ILI9341_BLACK);
  start = micros();
  for (x = radius; x<w; x += r2) {
    for (y = radius; y<h; y += r2) {
      ILI9341.fillCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int           x, y, r2 = radius * 2,
    w = ILI9341.width() + radius,
    h = ILI9341.height() + radius;

  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  start = micros();
  for (x = 0; x<w; x += r2) {
    for (y = 0; y<h; y += r2) {
      ILI9341.drawCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testTriangles() {
  unsigned long start;
  int           n, i, cx = ILI9341.width() / 2 - 1,
    cy = ILI9341.height() / 2 - 1;

  ILI9341.fillScreen(ILI9341_BLACK);
  n = min(cx, cy);
  start = micros();
  for (i = 0; i<n; i += 5) {
    ILI9341.drawTriangle(
      cx, cy - i, // peak
      cx - i, cy + i, // bottom left
      cx + i, cy + i, // bottom right
      ILI9341.color565(0, 0, i));
  }

  return micros() - start;
}

unsigned long testFilledTriangles() {
  unsigned long start, t = 0;
  int           i, cx = ILI9341.width() / 2 - 1,
    cy = ILI9341.height() / 2 - 1;

  ILI9341.fillScreen(ILI9341_BLACK);
  start = micros();
  for (i = min(cx, cy); i>10; i -= 5) {
    start = micros();
    ILI9341.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      ILI9341.color565(0, i, i));
    t += micros() - start;
    ILI9341.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      ILI9341.color565(i, i, 0));
  }

  return t;
}

unsigned long testRoundRects() {
  unsigned long start;
  int           w, i, i2,
    cx = ILI9341.width() / 2 - 1,
    cy = ILI9341.height() / 2 - 1;

  ILI9341.fillScreen(ILI9341_BLACK);
  w = min(ILI9341.width(), ILI9341.height());
  start = micros();
  for (i = 0; i<w; i += 6) {
    i2 = i / 2;
    ILI9341.drawRoundRect(cx - i2, cy - i2, i, i, i / 8, ILI9341.color565(i, 0, 0));
  }

  return micros() - start;
}

unsigned long testFilledRoundRects() {
  unsigned long start;
  int           i, i2,
    cx = ILI9341.width() / 2 - 1,
    cy = ILI9341.height() / 2 - 1;

  ILI9341.fillScreen(ILI9341_BLACK);
  start = micros();
  for (i = min(ILI9341.width(), ILI9341.height()); i>20; i -= 6) {
    i2 = i / 2;
    ILI9341.fillRoundRect(cx - i2, cy - i2, i, i, i / 8, ILI9341.color565(0, i, 0));
  }

  return micros() - start;
}

Resultado

Aquí tenéis un vídeo mostrando el resultado de ejecutar alguno de los ejemplos.