TFT TouchScreen Calibration

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.

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.

LCD_ID_Reader Version 1.2

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.

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.

tftouchscreen-data

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.

regular_calibration

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.

tftouchscreen_calibrationcorners

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;

Leave a Reply

Your email address will not be published. Required fields are marked *