Una extensión muy útil al utilizar pantallas TFT, es la capacidad táctil que proporcionan para poder interactuar a través de una interfaz diseñada para estos casos y poder incorporar un modo de aplicación como el de un móvil.
Para estos casos podemos diferenciar entre dos tipos de pantallas principalmente.
- Resistivas
- capaciticas
El caso que puede traer verdaderos quebraderos de cabeza es que existe una gran variedad de pantallas TFT; cada una con un chip controlador diferente; muchas librerías para controlarlas y muchísimos ejemplos en Internet que pueden sobresaturar nuestra comprensión y paciencia.
El caso concreto a mostrar es el uso de una pantalla TFT SPDF5408.
Para empezar a poner a prueba el funcionamiento de una pantalla TFT se pueden seguir los pasos que se mencionan en el siguiente enlace y sacar el identificador del controlador de la pantalla con el siguiente código.
Una vez resuelto este pequeño detalle. Nuestro interés es obtener datos de nuestra pantalla tactil en cada momento que presionemos sobre ella. Y aunque esté muy bien seguir los ejemplos que se comparten de todas las librerías que nos proporcionan; mejor vamos a ir por partes.
Por una parte, la mayoría de librerías para pantallas TFT parten de una librería genérica de Adafruit llamada Adafruit_GFX. Para reutilizar esta librería genérica, los fabricantes o desarrolladores crean otras librerías con el nombre del chip controlador que hemos sacado anteriormente y la implementamos.
Algunos ejemplos son.
- SPFD5408_Adafruit_TFTLCD
- Adafruit_ILI9341
- Adafruit_ST7735
- Adafruit_TFTLCD ILI9325, ILI9328
Una de las cuestiones es que todas te muestran un ejemplo muy chulo para pintar sobre la pantalla llamado tftpaint. Pero cuál es la sorpresa cuando no funciona correctamente y no podemos obtener ninguna información a través del puerto serie para debugear.
Bien, pues para empezar vayamos a otra librería básica llamada TouchScreen.
Con esta librería no incorporaremos ningún dibujo en la pantalla, de esta manera dejaremos de lado otras funciones que no sean de interés. Solo queremos obtener datos de las coordenadas X, Y y la presión que aplicamos sobre ella.
//#include <stdint.h>; #include "TouchScreen.h" // These are the pins for the shield! #define YP A1 // must be an analog pin, use "An" notation! #define XM A2 // must be an analog pin, use "An" notation! #define YM 7 // can be a digital pin #define XP 6 // can be a digital pin #define MINPRESSURE 10 #define MAXPRESSURE 1000 // For better pressure precision, we need to know the resistance // between X+ and X- Use any multimeter to read it // For the one we're using, its 300 ohms across the X plate TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); void setup(void) { Serial.begin(9600); } void loop(void) { TSPoint p = ts.getPoint(); // we have some minimum pressure we consider 'valid' // pressure of 0 means no pressing! if (p.z < MINPRESSURE || p.z > MAXPRESSURE) { Serial.print("X = "); Serial.print(p.x); Serial.print("\tY = "); Serial.print(p.y); Serial.print("\tPressure = "); Serial.println(p.z); }// a point object holds x y and z coordinates }
Con este código muy fácil podemos acceder a estas coordenadas para después manipularlas a nuestro placer.
Este código lo único que hace es escribir las coordenadas cada vez que se presiona la pantalla y se mide la presión entre un valor de 10 y 1000. Lo normal es que se situe cerca de 200.
*Los pines definidos en la pantalla se corresponden con el modelo utilizado SPFD5408.
Arduino Pin Connections
Arduino Pin | LCD Shield Pin | Use |
3.3V | 3.3V | Power |
5V | 5V | Power |
GND | GND | Power |
A0 | LCD_RD | LCD Control |
A1 | LCD_WR TOUCH_YP | LCD Control / Touch Data |
A2 | LCD_RS TOUCH_XM | LCD Control / Touch Data |
A3 | LCD_CS | LCD Control |
A4 | LCD_RST | LCD Reset |
D2 | LCD_D2 | LCD Data |
D3 | LCD_D3 | LCD Data |
D4 | LCD_D4 | LCD Data |
D5 | LCD_D5 | LCD Data |
D6 | LCD_D6 / TOUCH XP | LCD Data/ Touch Data |
D7 | LCD_D7 / TOUCH YM | LCD Data / Touch Data |
D8 | LCD_D0 | LCD Data |
D9 | LCD_D1 | LCD Data |
D10 | SD_CS | SD Select |
D11 | SD_DI | SD Data |
D12 | SD_DO | SD Data |
D13 | SD_SCK | SD Clock |
El problema es que estas coordenadas hay que saber interpretarlas de alguna manera. Como se ve en la imagen, las coordenadas X e Y tienen un valor, pero hay que hacer una correspondencia entre los píxeles de la pantalla y estos valores.
En concreto hay que hacer un mapeo con la función map() en el que el valor de una esquina que es el más pequeño, se corresponda con la esquina (X,Y) = (0,0). Y la esquina contraria que contendrá los valores máximos se correspondan con los límites superiores de la pantalla que en este caso son 240×320.
Para ello, hay un ejercicio de calibración. Pero este no funciona porque contiene funciones de la pantalla que dibujan sobre la pantalla y bloquean la comunicación en algunos pines. Para ello dejo el siguiente código un poco más limpio
#include <SPFD5408_Adafruit_GFX.h> // Core graphics library #include <SPFD5408_Adafruit_TFTLCD.h> // Hardware-specific library #include <SPFD5408_TouchScreen.h> // Touch library // These are the pins for the shield! #define YP A1 // must be an analog pin, use "An" notation! #define XM A2 // must be an analog pin, use "An" notation! #define YM 9 // can be a digital pin #define XP 8 // can be a digital pin #define MINPRESSURE 10 #define MAXPRESSURE 1000 short TS_MINX=140; short TS_MINY=130; short TS_MAXX=625; short TS_MAXY=480; // For better pressure precision, we need to know the resistance // between X+ and X- Use any multimeter to read it // For the one we're using, its 300 ohms across the X plate TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); // LCD Pin #define LCD_CS A3 #define LCD_CD A2 #define LCD_WR A1 #define LCD_RD A0 #define LCD_RESET A4 // Optional : otherwise connect to Arduino's reset pin // Assign human-readable names to some common 16-bit color values: #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET); // Buttons Adafruit_GFX_Button btn; uint16_t width = 0; uint16_t height = 0; void setup(void) { Serial.begin(9600); tft.reset(); tft.begin(0x9341); //Change with ID of TFT Display tft.setRotation(0); // Need for the Mega, please changed for your choice or rotation initial width = tft.width() - 1; height = tft.height() - 1; Serial.println(F("TFT LCD test")); Serial.print("TFT size is "); Serial.print(tft.width()); Serial.print("x"); Serial.println(tft.height()); int d_x= 10; int d_y = 20; int margin_x=40; int margin_y=30; int textSize = 2; char msg[] = "Button"; int msg_w = sizeof(msg)*textSize*5; int msg_h = textSize*7; int btn_w = msg_w+2*margin_x; int btn_h = msg_h+2*margin_y; // Center of Button int btn_x = btn_w/2+d_x; int btn_y = btn_h/2+d_y; btn.initButton(&tft,btn_x,btn_y,btn_w,btn_h,BLACK,GREEN,BLACK, msg, textSize); btn.drawButton(true); waitOneTouch(); calibrate_TS(); waitOneTouch(); } void loop(void) { TSPoint p = ts.getPoint(); int16_t dp_x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width());; int16_t dp_y = map(p.y, TS_MINX, TS_MAXX, 0, tft.height()); ; Serial.print( p.x); Serial.print( " \t P_X -> "); Serial.print( dp_x); Serial.print("\t"); Serial.print(p.y); Serial.print( " \t P_Y -> "); Serial.println( dp_y); delay(100); } // Calibration of Touch Screen (resistive) void calibrate_TS(void) { // Based in code posted in https://forum.arduino.cc/index.php?topic=223769.15 TSPoint p1, p2; int16_t temp; int32_t tempL; tft.fillCircle(4,4,4,BLUE); //show the first point tft.setCursor(5, 30); tft.setTextColor(BLUE); tft.setTextSize(1); Serial.println("Please touch the dot"); uint16_t limit = 40; //Calibra los ejes de una esquina que se corresponden con los valores mas bajos do { p1 = waitOneTouch(); } while (!(mapXValue(p1) < limit && mapYValue(p1) < limit)); tft.fillCircle(234,314,4,BLUE); //show the 2nd point tft.setCursor(50, 280); Serial.println("Please touch the other dot"); delay (500); // debunce do { p2 = waitOneTouch(); } while (!(mapXValue(p2) > (width - limit) && mapYValue(p2) > (height - limit))); delay (300); temp=p2.x-p1.x; // Calculate the new coefficients, get X difference tempL=((long)temp*1024)/(tft.width()-20); TS_MINX=p1.x-( (tempL*10)>>10);// 10 pixels du bord TS_MAXX=p1.x+( (tempL*tft.width())>>10);// 220 pixels entre points temp=p2.y-p1.y; // ¨get Y difference tempL=((long)temp*1024)/(tft.height()-20); TS_MINY=p1.y-( (tempL*10)>>10);// 10 pixels du bord TS_MAXY=TS_MINY+( (tempL*tft.height())>>10); // Show results showResults(); // p1.x = map(p1.x, TS_MAXX,TS_MINX, tft.width(), 0); // p1.y = map(p1.y, TS_MAXY,TS_MINY, tft.height(), 0); // p2.x = map(p2.x, TS_MAXX,TS_MINX, tft.width(), 0); // p2.y = map(p2.y, TS_MAXY,TS_MINY, tft.height(), 0); p1.x = mapXValue(p1); p1.y = mapYValue(p1); p2.x = mapXValue(p2); p2.y = mapYValue(p2); Serial.println(); Serial.println("Last touched points: "); Serial.print("Pt 1: ");Serial.print(p1.x);Serial.print(" : ");Serial.println(p1.y); Serial.print("Pt 2: ");Serial.print(p2.x);Serial.print(" : ");Serial.println(p2.y); Serial.println(); // Wait a touch Serial.println("Touch to proceed"); waitOneTouch(); } void showResults() { // Results Serial.println("After calibration: "); Serial.print("TS_MINX= ");Serial.println(TS_MINX); Serial.print("TS_MINY= ");Serial.println(TS_MINY); Serial.println(); Serial.print("TS_MAXX= ");Serial.println(TS_MAXX); Serial.print("TS_MAXY= ");Serial.println(TS_MAXY); } TSPoint waitOneTouch() { TSPoint p; do { p= ts.getPoint(); pinMode(XM, OUTPUT); //Pins configures again for TFT control pinMode(YP, OUTPUT); } while((p.z < MINPRESSURE )|| (p.z > MAXPRESSURE)); Serial.print("Screen Touched: x ->; "); Serial.print(p.x); Serial.print(" y->; "); Serial.print(p.y); Serial.print("\t pressure->; "); Serial.println(p.z); return p; } // Map the coordinate X uint16_t mapXValue(TSPoint p) { uint16_t x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width()); return x; } // Map the coordinate Y uint16_t mapYValue(TSPoint p) { uint16_t y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height()); return y; }
Hay que notar que ahora estamos utilizando la librería SPFD5408_TouchScreen.h; que es exactamente la misma que TouchScreen.h, pero para esta pantalla especificamente da fallo por definición múltiple.
Hay que observar especificamente los valores siguientes.
short TS_MINX=50; short TS_MINY=1000; short TS_MAXX=500; short TS_MAXY=1002;
Estos valores tienen que ser cercanos a los mínimos y máximos obtenidos desde el ejercicio anterior que se muestran por el monitor serie para establecer una calibración aproximada.
Lo que haremos despues es establecer las dos esquinas como referencia con sus valores máximos y mínimos y estas variables se recalibrarán para darnos una correspondencia con los límites de la pantalla.
A partir del momento en el que se hace la calibración habrá que utilizar la función map para conocer el pixel sobre el que se presiona.
Posibles roturas y fallos
Todo este proceso se ha de realizar debido a que no todas las pantallas son iguales y por distintas causas poseen una resistencia diferente; por este motivo no existe un código único que no requiera de calibración.
De todas maneras pueden ocurrir otras situaciones en las que la pantalla no parezca responder bien.
Rangos del eje X o de Y
Los valores mínimo y máximo de los distintos ejes deben ser cuanto más amplios mejor. Ya que a la hora de utilizar la función map, la precisión depende de la extensión de este rango.
En este caso, el eje X dispone de un rango desde 53 que se corresponderá con el 0 y de 318 que se corresponderá con el valor máximo en ese eje de 240.
Mientras que el eje Y tiene un rango de 1000 a 991. No solo va en sentido decreciente, sino que un lado a otro solo puede detectar 9 unidades de diferencia. Y si esa es la máxima precisión que nos puede aportar en todo un eje es que algo anda mal.
La razón de que funcione mal es que estas pantallas son muy frágiles a la rotura y es muy fácil que se rompan con los golpes o que la resistencia se vea modificada por algún factor externo o puede que el problema sea aún más obvio. Los pines digitales a los que comunican esta información no sean los adecuados. Se puede probar con el 7 o el 6 para verificarlos.
#define YP A1 // must be an analog pin, use “An” notation!
#define XM A2 // must be an analog pin, use “An” notation!
#define YM 7 // can be a digital pin
#define XP 6 // can be a digital pin
La orientación de la pantalla
Por otra parte, la pantalla táctil no se ve modificada en función de la orientación de la pantalla. Por lo que al no haber ninguna correspondencia, podemos estar confundiendo los ejes a la hora de presionar con la disposición de los elementos de la pantalla.
Esto se ve cambiando el número de
tft.setRotation(0); // cambiándolo por 1, 2 ó 3
*Esto es un poco peliagudo, porque parece que no responde con los mismos pines digitales(9 ó 8) en una orientación concreta u otra dejando el modo táctil inactivo.
Bótones táctiles
La librería de Adafruit se guarda una integración para botones que pueden ser presionados para modelar una interfaz y en función de ésta extender las opciones del código.
En el código anterior se ha visto la forma de introducir un botón, pero ahora vamos a ver como complementar el presionado del botón con el calibrado inicial.
#include <SPFD5408_Adafruit_GFX.h> // Core graphics library #include <SPFD5408_Adafruit_TFTLCD.h> // Hardware-specific library #include <SPFD5408_TouchScreen.h> // Touch library // These are the pins for the shield! #define YP A1 // must be an analog pin, use "An" notation! #define XM A2 // must be an analog pin, use "An" notation! #define YM 9 // can be a digital pin #define XP 8 // can be a digital pin #define MINPRESSURE 10 #define MAXPRESSURE 1000 short TS_MINX=140; short TS_MINY=130; short TS_MAXX=625; short TS_MAXY=480; // For better pressure precision, we need to know the resistance // between X+ and X- Use any multimeter to read it // For the one we're using, its 300 ohms across the X plate TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); // LCD Pin #define LCD_CS A3 #define LCD_CD A2 #define LCD_WR A1 #define LCD_RD A0 #define LCD_RESET A4 // Optional : otherwise connect to Arduino's reset pin // Assign human-readable names to some common 16-bit color values: #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET); uint16_t width = 0; uint16_t height = 0; // Buttons Adafruit_GFX_Button btn; void setup(void) { Serial.begin(9600); tft.reset(); tft.begin(0x9341); tft.setRotation(0); // Need for the Mega, please changed for your choice or rotation initial width = tft.width() - 1; height = tft.height() - 1; Serial.println(F("TFT LCD test")); Serial.print("TFT size is "); Serial.print(tft.width()); Serial.print("x"); Serial.println(tft.height()); int d_x= 10; int d_y = 20; int margin_x=40; int margin_y=30; int textSize = 2; char msg[] = "Button"; int msg_w = sizeof(msg)*textSize*5; int msg_h = textSize*7; int btn_w = msg_w+2*margin_x; int btn_h = msg_h+2*margin_y; // Center of Button int btn_x = btn_w/2+d_x; int btn_y = btn_h/2+d_y; btn.initButton(&tft,btn_x,btn_y,btn_w,btn_h,BLACK,GREEN,BLACK, msg, textSize); btn.drawButton(true); Serial.print("Interval_X: "); Serial.print(btn_x-btn_w/2); Serial.print("\t to: "); Serial.println(btn_x+btn_w/2); Serial.print("Interval_Y: "); Serial.print(btn_y-btn_h/2); Serial.print("\t to: "); Serial.println(btn_y+btn_h/2); waitOneTouch(); calibrate_TS(); waitOneTouch(); } void loop(void) { TSPoint p = ts.getPoint(); int16_t dp_x; int16_t dp_y; // we have some minimum pressure we consider 'valid' // pressure of 0 means no pressing! if (p.z > MINPRESSURE && p.z < MAXPRESSURE) { //Serial.print("X = "); Serial.print(p.x); //Serial.print("\tY = "); Serial.print(p.y); dp_x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width()); dp_y = map(p.y, TS_MINX, TS_MAXX, 0, tft.height()); //Serial.print("X = "); Serial.print(map(p.x, TS_MINX, TS_MAXX, 0, tft.width())); //Serial.print("\tY = "); Serial.print(map(p.y, TS_MINX, TS_MAXX, 0, tft.height())); //Serial.print("\tPressure = "); Serial.println(p.z); }// a point object holds x y and z coordinates //Para que esta funcion funcione hay que calibrarlo primero. Los limites del boton se disponen en valores de pixeles w y h, mientras que los puntos obtenidos de la pantalla no están calibrados ni mapeados. if(btn.contains(dp_x, dp_y)){ btn.press(true); delay(200); //Serial.println("PRESSED"); }else{ btn.press(false); delay(100); //Serial.println("NOT PRESSED"); } if (btn.isPressed()){ Serial.print("PRESSED x->"); Serial.print(dp_x); Serial.print(" y-> "); Serial.println(dp_y); } if (btn.justPressed()){ Serial.println("JUST PRESSED"); } if (btn.justReleased()){ Serial.println("JUST RELEASED"); } } // Calibration of Touch Screen (resistive) void calibrate_TS(void) { // Based in code posted in https://forum.arduino.cc/index.php?topic=223769.15 TSPoint p1, p2; int16_t temp; int32_t tempL; tft.fillCircle(4,4,4,BLUE); //show the first point tft.setCursor(5, 30); tft.setTextColor(BLUE); tft.setTextSize(1); Serial.println("Please touch the dot"); uint16_t limit = 40; //Calibra los ejes de una esquina que se corresponden con los valores mas bajos do { p1 = waitOneTouch(); } while (!(mapXValue(p1) < limit && mapYValue(p1) > limit)); tft.fillCircle(234,314,4,BLUE); //show the 2nd point tft.setCursor(50, 280); Serial.println("Please touch the other dot"); delay (500); // debunce do { p2 = waitOneTouch(); } while (!(mapXValue(p2) > (width - limit) && mapYValue(p2) > (height - limit))); delay (300); temp=p2.x-p1.x; // Calculate the new coefficients, get X difference tempL=((long)temp*1024)/(tft.width()-20); TS_MINX=p1.x-( (tempL*10)>>10);// 10 pixels du bord TS_MAXX=p1.x+( (tempL*tft.width())>>10);// 220 pixels entre points temp=p2.y-p1.y; // ¨get Y difference tempL=((long)temp*1024)/(tft.height()-20); TS_MINY=p1.y-( (tempL*10)>>10);// 10 pixels du bord TS_MAXY=TS_MINY+( (tempL*tft.height())>>10); // Show results showResults(); // p1.x = map(p1.x, TS_MAXX,TS_MINX, tft.width(), 0); // p1.y = map(p1.y, TS_MAXY,TS_MINY, tft.height(), 0); // p2.x = map(p2.x, TS_MAXX,TS_MINX, tft.width(), 0); // p2.y = map(p2.y, TS_MAXY,TS_MINY, tft.height(), 0); p1.x = mapXValue(p1); p1.y = mapYValue(p1); p2.x = mapXValue(p2); p2.y = mapYValue(p2); Serial.println(); Serial.println("Last touched points: "); Serial.print("Pt 1: ");Serial.print(p1.x);Serial.print(" : ");Serial.println(p1.y); Serial.print("Pt 2: ");Serial.print(p2.x);Serial.print(" : ");Serial.println(p2.y); Serial.println(); // Wait a touch Serial.println("Touch to proceed"); waitOneTouch(); } void showResults() { // Results Serial.println("After calibration: "); Serial.print("TS_MINX= ");Serial.println(TS_MINX); Serial.print("TS_MINY= ");Serial.println(TS_MINY); Serial.println(); Serial.print("TS_MAXX= ");Serial.println(TS_MAXX); Serial.print("TS_MAXY= ");Serial.println(TS_MAXY); } TSPoint waitOneTouch() { TSPoint p; do { p= ts.getPoint(); pinMode(XM, OUTPUT); //Pins configures again for TFT control pinMode(YP, OUTPUT); } while((p.z < MINPRESSURE )|| (p.z > MAXPRESSURE)); Serial.print("Screen Touched: x -> "); Serial.print(p.x); Serial.print(" y-> "); Serial.print(p.y); Serial.print("\t pressure-> "); Serial.println(p.z); return p; } // Map the coordinate X uint16_t mapXValue(TSPoint p) { uint16_t x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width()); return x; } // Map the coordinate Y uint16_t mapYValue(TSPoint p) { uint16_t y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height()); return y; }
A partir de ahora solo hay que extender un poco más los botones en la pantalla e ir apretando para darle dinamicidad a nuestro proyecto.
*La librería SPFD5408_TouchScreen tiene un fallo en la función de detección de eventos de botones Adafruit_GFX_Button en la linea 608. Por lo que es conveniente corregirla.
if ((y < (_y – _h/2)) || (y > (_y + _h/2))) return false;