Ejemplo pantalla LCD táctil ILI9341 con Arduino

Autor: Oscar Gonzalez

Ejemplo pantalla LCD táctil ILI9341 con Arduino

Tiempo de lectura: 10 minutos

En éste tutorial aprenderás cómo conectar una pantalla LCD táctil ILI9341 con Arduino paso a paso

Ejemplo pantalla LCD táctil ILI9341 con Arduino

  • 0

0 Principiante

Ejemplo con Arduino

Instalación de librerías Arduino

No explicaré la instalación de las librerías ya que se escapa del propósito de ésta guía. Si no tienes claro cómo se instalan nuevas librerías en Arduino, puedes visitar nuestro tutorial Cómo instalar nuevas librerías en Arduino IDE

Ahora veremos el código fuente para hacer funcionar la pantalla ILI9341 en dos partes. Primero debemos ser capaces de dibujar en la pantalla diferentes figuras en el color que queramos. Para eso, usaremos la librería Adafruit_GFX que se encargará de toda la parte gráfica.

Lo segundo que vamos a hacer es controlar a pantalla táctil. Eso nos dará la posibilidad de hacer entornos gráficos mucho más interesantes. Esto lo haremos con la librería XPT2046 que se encargará de recuperar la información táctil de la pantalla.

El código es bastante fácil de seguir y lo único que necesitas es tener bien configurados los pines de conexión a la pantalla. Para eso, hay unos #defines al inicio del programa donde se indican los pines de cada cosa.

Este es un sencillo ejemplo muy básico de cómo dibujar diferentes figuras geométricas así como texto.

Cómo dibujar gráficos en pantalla ILI9341 con Arduino

/***************************************************  
  Ejemplo básico pantalla LCD táctil ILI9341   XPT2046
  Basado en el GFX example ILI9341 de Adafruit / Limor Fried/Ladyada for Adafruit Industries.  
  Oscar Gonzalez - BricoGeek.com 
  https://tienda.bricogeek.com
 ****************************************************/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <XPT2046_Touchscreen.h>

// Configuración de pines (Hardrware SPI)
#define TFT_DC 9
#define TFT_CS 5
#define TOUCH_CS 10     //touch screen chip select
#define TOUCH_IRQ 2     //touch screen interrupt

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);

void setup() {
  Serial.begin(9600);
  while (!Serial && (millis() <= 1000));
  
  Serial.println("ILI9341 Test!"); 

  // Init screen
  tft.begin();
  tft.setRotation(2);

  // Init touch
  ts.begin();
  ts.setRotation(0);

  Serial.print("width: "); 
  Serial.print(tft.width()); 
  Serial.print("Height: "); 
  Serial.println(tft.height());

  dibujaGraficos();
}

void dibujaGraficos()
{
  tft.fillScreen(ILI9341_BLACK);
  yield();

  // Titulo
  tft.fillRect(0, 0, 240, 30, ILI9341_BLUE);
  tft.drawRect(0, 0, 240, 30, ILI9341_YELLOW);  

  tft.setCursor(4, 7);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.println("BricoGeek.com");    

  // Bat level
  char texto[15];  
  sprintf(texto, "ILI9341 test");
  tft.setCursor(238-(strlen(texto)*6), 5);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);  
  tft.println(texto);

  // Separdor horizontal
  tft.drawLine(0, 90, 240, 90, ILI9341_WHITE); 

  // ---------------
  // Botón
  tft.fillRect(50, 160, 100, 50, ILI9341_RED);   // P
  
  tft.setCursor(75, 175);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.println("BOTON");    

}

unsigned long lastFrame = millis();
int state = 0;

void loop(void) {

  // Limita la frecuencia de actualización
  while((millis() - lastFrame) < 300);
  lastFrame = millis();  

  while (!ts.touched()) { } // Esperamos a tocar la pantalla  
      
  TS_Point p = ts.getPoint();

  // Mostramos las coodernadas en el monitor serial
  Serial.print("p.x: "); Serial.print(p.x); Serial.print(" p.y: "); Serial.println(p.y);

  // Botón
  if ( (p.x >= 50) && (p.x < 150) && (p.y >= 160) && (p.y < 210) ) // Coordenadas del botón
  {
    if (state == 1) { state = 0; }
    else { state = 1; } 
  }  

  if (state == 0)
  {
      // Verde
      tft.fillRect(50, 160, 100, 50, ILI9341_GREEN);
      tft.setCursor(75, 175);
      tft.setTextColor(ILI9341_BLACK);  tft.setTextSize(2);
      tft.println("BOTON");        
  }
  else
  {
      // Rojo
      tft.fillRect(50, 160, 100, 50, ILI9341_RED);
      tft.setCursor(75, 175);
      tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
      tft.println("BOTON");              
  }
}

Como calibrar la pantalla táctil ILI9341 con XPT2046

Si has cargado el ejemplo de código verás que hay algo raro. Podrás ver que la pantalla táctil no coincide con lo que hay dibujado en pantalla. Es decir, el punto X0, Y0 de la pantalla no corresponde con el mismo punto que tocas.

Esto es debido a que generalmente la membrana táctil no está alineada con la pantalla LCD, o bien no tiene las mismas dimensiones que la pantalla o incluso ambas cosas. Pero tranquilo, eso es normal y le ocurre a casi todas las pantallas táctiles.

Lo que hay que hacer es calibrar la membrana táctil en nuestro código para que esté todo alineado. Aunque no lo parece, es un tema bastante complejo ya que a parte de la alineación, hay que contar con la rotación de la membrana táctil con respecto a la pantalla LCD y nos podemos meter en un caos matemático bastante chungo.

Si quieres saber más sobre la teoría que hay detrás de la calibración de pantallas táctiles, puedes leer el paper de Wendy Fang y Tony Chang de Texas Instruments.

Nosotros aquí nos vamos a limitar a ver un ejemplo de la excelente librería XPT2046_Touchscreen. Pero ésta librería solo se encarga de recoger las coordenadas de la pantalla táctil y no hace ningún tipo de calibración. Aquí abajo puedes ver un ejemplo, aunque te recomiendo que descargues la última versión actualizada desde el gestor de librerías del IDE de Arduino.

El código de calibración está inspirado en el trabajo de Bob de bytesnbits.co.uk y se resume a la función touch_calibrate() que debe ser lanzada en el setup.

Luego para recoger las coordenadas correctas para utilizar, se utiliza la función getScreenCoords() que devolverá el X e Y correcto y alineado. Puedes ver la excelente explicación en detalle de cómo funciona en éste video.

El ejemplo de aquí abajo iniciará la función de calibración nada más iniciar el programa y luego mostrará un recuadro rojo que se posicionará en donde toquemos la pantalla táctil.

Recuerda que para que funcione el ejemplo, debes configurar los pines correctamente.

/***************************************************  
  Ejemplo básico pantalla LCD táctil ILI9341   XPT2046
  Basado en el GFX example ILI9341 de Adafruit / Limor Fried/Ladyada for Adafruit Industries.  
  Oscar Gonzalez - BricoGeek.com 
  https://tienda.bricogeek.com
 ****************************************************/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <XPT2046_Touchscreen.h>

// Configuración de pines (Hardrware SPI)
#define TFT_DC 9
#define TFT_CS 5
#define TOUCH_CS 10     //touch screen chip select
#define TOUCH_IRQ 2     //touch screen interrupt

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);

// calibration values
float xCalM = 0.0, yCalM = 0.0; // gradients
float xCalC = 0.0, yCalC = 0.0; // y axis crossing points

int8_t blockWidth = 20; // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0; // block position (pixels)

class ScreenPoint {
  public:
    int16_t x;
    int16_t y;

    // default constructor
    ScreenPoint(){
    }

    ScreenPoint(int16_t xIn, int16_t yIn){
      x = xIn;
      y = yIn;
     }
};

ScreenPoint getScreenCoords(int16_t x, int16_t y){
  int16_t xCoord = round((x * xCalM)   xCalC);
  int16_t yCoord = round((y * yCalM)   yCalC);
  
  if(xCoord < 0) xCoord = 0;
  if(xCoord >= tft.width()) xCoord = tft.width() - 1;
  
  if(yCoord < 0) yCoord = 0;
  if(yCoord >= tft.height()) yCoord = tft.height() - 1;
  
  return(ScreenPoint(xCoord, yCoord));
}

void setup() {
  Serial.begin(9600);
  while (!Serial && (millis() <= 1000));
  
  Serial.println("ILI9341 Test!"); 

  // Init screen
  tft.begin();
  tft.setRotation(2);

  // Init touch
  ts.begin();
  ts.setRotation(0);

  Serial.print("width: "); 
  Serial.print(tft.width()); 
  Serial.print("Height: "); 
  Serial.println(tft.height());

  touch_calibrate(); // Calibración de la pantalla táctil

  dibujaGraficos();
}

void touch_calibrate()
{
  TS_Point p;
  int16_t x1,y1,x2,y2;
  
  tft.fillScreen(ILI9341_BLACK);
  
  // wait for no touch
  while(ts.touched());
  tft.drawFastHLine(10,20,20,ILI9341_RED);
  tft.drawFastVLine(20,10,20,ILI9341_RED);
  while(!ts.touched());
  delay(50);
  p = ts.getPoint();
  x1 = p.x;
  y1 = p.y;
  tft.drawFastHLine(10,20,20,ILI9341_BLACK);
  tft.drawFastVLine(20,10,20,ILI9341_BLACK);
  delay(500);
  while(ts.touched());
  tft.drawFastHLine(tft.width() - 30,tft.height() - 20,20,ILI9341_RED);
  tft.drawFastVLine(tft.width() - 20,tft.height() - 30,20,ILI9341_RED);
  while(!ts.touched());
  delay(50);
  p = ts.getPoint();
  x2 = p.x;
  y2 = p.y;
  tft.drawFastHLine(tft.width() - 30,tft.height() - 20,20,ILI9341_BLACK);
  tft.drawFastVLine(tft.width() - 20,tft.height() - 30,20,ILI9341_BLACK);
  
  int16_t xDist = tft.width() - 40;
  int16_t yDist = tft.height() - 40;
  
  // translate in form pos = m x val   c
  // x
  xCalM = (float)xDist / (float)(x2 - x1);
  xCalC = 20.0 - ((float)x1 * xCalM);
  // y
  yCalM = (float)yDist / (float)(y2 - y1);
  yCalC = 20.0 - ((float)y1 * yCalM);
  
  Serial.print("x1 = ");Serial.print(x1);
  Serial.print(", y1 = ");Serial.print(y1);
  Serial.print("x2 = ");Serial.print(x2);
  Serial.print(", y2 = ");Serial.println(y2);
  Serial.print("xCalM = ");Serial.print(xCalM);
  Serial.print(", xCalC = ");Serial.print(xCalC);
  Serial.print("yCalM = ");Serial.print(yCalM);
  Serial.print(", yCalC = ");Serial.println(yCalC);
}

void dibujaGraficos()
{
  tft.fillScreen(ILI9341_BLACK);
  yield();

  // Titulo
  tft.fillRect(0, 0, 240, 30, ILI9341_BLUE);
  tft.drawRect(0, 0, 240, 30, ILI9341_YELLOW);  

  tft.setCursor(4, 7);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.println("BricoGeek.com");    

  // Bat level
  char texto[15];  
  sprintf(texto, "ILI9341 test");
  tft.setCursor(238-(strlen(texto)*6), 5);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);  
  tft.println(texto);

  // Separdor horizontal
  tft.drawLine(0, 90, 240, 90, ILI9341_WHITE); 

  // ---------------
  // Botón
  tft.fillRect(50, 160, 100, 50, ILI9341_RED);   // P
  
  tft.setCursor(75, 175);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.println("BOTON");    

}

unsigned long lastFrame = millis();
int state = 0;

void loop(void) {

  // Limita la frecuencia de actualización
  while((millis() - lastFrame) < 500);
  lastFrame = millis();  

  while (!ts.touched()) { } // Esperamos a tocar la pantalla  
      
  TS_Point p = ts.getPoint();

  // Aquí se convierte en corrdenadas reales y calibradas
  ScreenPoint sp = ScreenPoint();
  sp = getScreenCoords(p.x, p.y);  

  // Mostramos las coodernadas en el monitor serial
  Serial.print("sp.x: "); Serial.print(sp.x); Serial.print(" sp.y: "); Serial.println(sp.y);

  // Botón
  if ( (sp.x >= 50) && (sp.x < 150) && (sp.y >= 160) && (sp.y < 210) ) // Coordenadas del botón
  {
    if (state == 1) { state = 0; }
    else { state = 1; } 
  }  

  if (state == 0)
  {
      // Verde
      tft.fillRect(50, 160, 100, 50, ILI9341_GREEN);
      tft.setCursor(75, 175);
      tft.setTextColor(ILI9341_BLACK);  tft.setTextSize(2);
      tft.println("BOTON");        
  }
  else
  {
      // Rojo
      tft.fillRect(50, 160, 100, 50, ILI9341_RED);
      tft.setCursor(75, 175);
      tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
      tft.println("BOTON");              
  }
}

Con éste ejemplo hemos implementado una función para calibrar la pantalla y obtener las coordenadas correctas. De esa forma, podemos interactuar con los gráficos de una forma coherente. Si abres el monitor serie (9600bps), cada vez que tocas la pantalla, puedes ver las coordenadas X e Y.

El programa de ejemplo ILI9341 en acción

El programa de ejemplo ILI9341 en acción

Cómo hacer gráficos y menús avanzados con la pantalla ILI9341

Para crear gráficos avanzados como menús o entornos de usuario que tengan mucha más información, es bastante tedioso (aunque no imposible).

Existen librerías que se encargan de la gestión de gráficos como botones, sliders o hasta teclados en pantalla que sería una locura hacerlo a mano.

Como recomendación, una buena librería que además soporta muchos modelos de pantalla, es la Guislice. La puedes encontrar en Github y está bastante bien documentada. Lo bueno que tiene esa librería es que dispone de un programa externo llamado Gluislice Builder para poder generar entornos gráficos completos. Ese mismo programa luego genera un sketch de Arduino con todos los elementos incluidos y listos para utilizar.

Del punto de vista de la programación, es algo más compleja de utilizar pero si quieres crear un entorno gráfico complejo, es muy recomendable. Abajo puedes ver algunos ejemplos.

Ejemplo de gráficos complejos con GUIslice

Ejemplo de gráficos complejos con GUIslice

GUIslice Builder

GUIslice Builder