Autor: Oscar Gonzalez
Tiempo de lectura: 10 minutos
En éste tutorial aprenderás cómo conectar una pantalla LCD táctil ILI9341 con Arduino paso a paso
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.
/***************************************************
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");
}
}
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
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
GUIslice Builder