DIY Projects

T-Watch. Getting started with the TFT_eSPI library. Display text, shapes, touch detection

Get started with the TFT_eSPI library adapted to the screens of #ESP32 #TTGO @lilygo9 T-Watch. How to display text, shapes, detect pressing on the touch screen. Source code compatible #Arduino and @PlatformIO_Org

The LilyGoWatch library includes a pre-configured version of Badmer’s TFT_eSPI library which allows you to display text, basic geometric shapes (rectangle, circle, ellipse, triangle, line, point …), images and lots of ‘other things.

The examples given in this tutorial are adapted to the TFT screens of ESP32 TTGO T-Watch watches . They are compatible with other Arduino, ESP32 or ESP8266 screens and development boards.

 

In this article we will only use the TFT color touch screen present in the T-Watch Touch and T-Watch 2020. However, you should not experience any difficulty using it on models equipped with an eInk screen (T -Block and T-Bot).

If you are new to app development for ESP32 TTGO T-Watch cases and watches from manufacturer LilyGo, you can start by reading this introductory article.

How to use the TFT_eSPI library in an Arduino T-Watch project?

The LilyGoWatch library includes a pre-configured version (source code) of the TFT _eSPI library  from Bodmer . This is loaded at startup with the parameters that correspond to the on-board screen by the T-Watch used.

The TFT_eSPI library supports the eInk and TFT screens of the different T-Watch models.

So you don’t have to do anything! Everything works out of the box

Color palette

We have a predefined color palette accessible using the following constants

▉ TFT_BLACK
▉ TFT_NAVY (#000080)
▉ TFT_DARKGREEN (#008000)
▉ TFT_DARKCYAN (#008080)
▉ TFT_MAROON (#80000)
▉ TFT_PURPLE #800080)
▉ TFT_OLIVE (#808000)
▉ TFT_LIGHTGREY (#D3D3D3)
▉ TFT_DARKGREY (#808080)
▉ TFT_BLUE (#0000FF)
▉ TFT_GREEN (#00FF00)
▉ TFT_CYAN (#00FFFF)
▉ TFT_RED (#FF0000)
▉ TFT_MAGENTA (#FF00FF)
▉ TFT_YELLOW (#FFFF00)
▢ TFT_WHITE (#FFFFFF)
▉ TFT_ORANGE (#FFB400)
▉ TFT_GREENYELLOW (#B4FF00)
▉ TFT_PINK (#FFC0CB)
▉ TFT_BROWN (#964B00)
▉ TFT_GOLD (#FFD700)
▉ TFT_SILVER (#C0C0C0)
▉ TFT_SKYBLUE (#87CEEB)
▉ TFT_VIOLET (#B42EE2)

Functions of the TFT_eSPI library (API)

Here is a list of the most common functions offered by the TFT_eSPI library. In the absence of official documentation, the functions were extracted directly from the source code of the LilyGoWatch library on GitHub .

Deprecated functions are crossed out.

List of variables used

Screen and cursor

Geometric primitives

Pictures

Fonts

Text, character, string

Display of justified string (Datum)

Positions available

Methods

Conversions

Some useful conversion methods

The library is not yet documented, you have to look directly in the source code.

How to access the functions of the TFT_eSPI library?

In computer science, the LilyGoWatch Library is a wrapper, ie it unifies access to a set of libraries.

The methods offered by the different libraries are accessible from the C ++ TTGOClass object.

To access a device then functions (C ++ methods), we use the arrow operator ->. This is the equivalent of the point. in Javascript for example.

Here is an example of a minimal program that lights the screen and colors the background in red

#include <LilyGoWatch.h>

TTGOClass *ttgo;

void setup() {
    ttgo = TTGOClass::getWatch();
    ttgo->begin();     
    ttgo->openBL();
        
    ttgo->tft->fillScreen(TFT_RED);
}

void loop(){}

We must start by initializing the watch object by calling the getWatch() method. This object will contain all the methods to access the watch peripherals (accelerometer, GPS, RTC clock, TFT or eInk screen, etc.).

watch = TTGOClass::getWatch();

Then we must turn on the TFT screen of the watch by calling the begin() method , then the backlight using the openBL() method.

watch->begin();  // Initialise l'écran couleur TFT
watch->openBL(); // Allume le rétro-éclairage de l'écran

Now we access functions like this

watchobject->driver->function()

Which gives to put the bottom of the screen in red

ttgo->tft->fillScreen(TFT_RED);

Create a TFT_eSPI object containing the screen driver

It is also possible to create an object of type TTGOClass which will contain the screen drivers to reduce the code

TFT_eSPI * tft = ttgo->tft;
tft->fillScreen(TFT_RED);

Know the screen resolution

It is often necessary to know the screen resolution to position the elements. For this, we have the width() and height() methods to know respectively the width and height of the screen in pixels.

  w = ttgo->tft->width();
  h = ttgo->tft->height();
  Serial.printf("Screen size %u * %u", w, h);

How to position the cursor on the screen?

The TFT_eSPI library uses a cursor to position the elements on the screen. The X and Y coordinates of the cursor are determined from the upper left corner of the screen. The origin changes depending on the orientation of the screen.

To manually change the screen orientation, go to this paragraph to learn how.

The cursor is used to display text using the standard functions print, println or printf for example.

The getCursorX and getCursorY functions allow you to know the position of the cursor and setCursor to position the latter at the ready pixel.

However, other drawing methods (including drawing text) simply use coordinates.

Detect when user touch screen and retrieve position

The getTouch(x, y) method detects when the user touches the screen. The method returns a boolean and assigns the X and Y coordinates of the touched point on the screen.

The getTouch() method is directly accessible from the ttgo class of the LiLyGoWatch library

The library is delivered with the Touch Pad example in the BasicUnit folder

TTGOClass *ttgo;

char buf[128];

void setup()
{
    ttgo = TTGOClass::getWatch();
    ttgo->begin();
    ttgo->openBL();
    ttgo->tft->fillScreen(TFT_BLACK);
    ttgo->tft->setTextFont(2);
    ttgo->tft->setTextColor(TFT_WHITE, TFT_BLACK);
    ttgo->tft->drawString("T-Watch Touch Test", 62, 90);
}

void loop()
{
    int16_t x, y;
    if (ttgo->getTouch(x, y)) {
        sprintf(buf, "x:%03d  y:%03d", x, y);
        ttgo->tft->drawString(buf, 80, 118);
    }
    delay(5);
}

How does the TFT_eSPI library build the screen?

Before going any further, it’s important to understand how the library constructs the screen. You have to think of the screen as an array of points. When we draw a shape (or text or an image), we “only light” points in the table with a certain color.

That is to say that the order of execution of the functions will have a direct impact on the construction of the display. Here for example the blue circle hides part of the white text placed itself on the green rectangle.

Even if the TFT_eSPI library is very powerful, it does not manage the graphic elements.

When updating the display, we have two solutions

Rebuild the entire screen with each modification Only rebuild what is strictly necessary.

For example erase (give the background color) of the hand of a clock then draw the new position.

Advantage Easy More complex
Disadvantage High risk of flickering.

The flashing increases with the number of items to be displayed and the refresh rate.

Efficient, little flickering

Display text with the C ++ print(), println() or printf() functions

To display text, you can use the same C ++ methods that write to the serial port print(), println() or printf().

The macro F() which allows to place the string in the Flash memory is supported but it is however of no interest.

The text is displayed at the cursor position. Here for example, we vary the size of the font between 1 and 7 (it is not possible to go beyond).

  TFT_eSPI * tft = ttgo->tft;
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE);
    for (size_t i = 1; i <= 7; i++){  // La taille doit être comprise entre 1 et 7
      tft->setTextFont(1);     // Uniquement 1
      tft->setTextSize(i);     // change la taille        
      tft->setCursor(0, 0);
      txt = "Text with size "; txt += i;
      Serial.print("Display"); Serial.println(txt);
      tft->println(txt);
      delay(1000);
      tft->fillScreen(TFT_BLACK);
    }

Display a scrolling list

In the previous example the cursor remains at the origin (0,0) and the screen is erased (repainted in black) at each iteration of the for loop. The println() method moves the cursor to the next line. However, the TFT_eSPI library does not manage the vertical scrolling of the print function.

To display a list that exceeds the height of the screen, the trick is to test the position of the cursor and return the latter to the top of the screen as soon as the current position exceeds the height of the screen.

We saw previously how to recover the screen resolution.

Here is an example of code that displays a list of 20 items on the screen.

  TFT_eSPI * tft = ttgo->tft;
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE, TFT_BLACK);
  tft->setTextSize(2);
  tft->setCursor(0,0);
  
  for (size_t i = 0; i <= 20; i++) {
    // Renvoi le curseur en haut de l'écran 
    if ( tft->getCursorY() >= h ) {
      tft->fillScreen(TFT_BLACK);
      tft->setCursor(0,0);
    }
    tft->printf("This is the line %u \n", i);
    delay(200);
  }
  tft->setTextSize(1);
  tft->println(" \n *** End of the list ***");

Advanced methods to justify the position of the text

The standard methods do not allow you to manage the justification of the text. That is to say that the text is necessarily positioned on the left.

To justify the text to display, all you have to do is define the desired position using the setTextDatum( DATUM_TYPE ) method . We then display (draw) the text using the drawString() function .

Here is an example of text centered in the center of the screen

ttgo->tft->fillScreen(TFT_BLACK);
ttgo->tft->setTextColor(TFT_WHITE);
ttgo->tft->setTextDatum(MC_DATUM);
ttgo->tft->drawString("Text in center", w / 2, h / 2, 2);

Draw geometric shapes, rectangle, circle, ellipse, line, point …

Now let’s move on to creating simple geometric shapes. The library allows you to draw the outline or fill the following simple shapes: rectangle, triangle, circle, ellipse.

The outline has the same color as the background.

You can also draw arbitrary lines between two points, horizontal or vertical.

Here is an example that draws a horizontal line 10 pixels from the edge of the screen

  tft->drawFastHLine(0, 10, 240, TFT_DARKGREY);

Go to the end of the article to find a complete example.

Change screen orientation

By default, the LilyGoWatch library orients the screen in the direction of the bracelet for each type of T-Watch. Nothing prevents you from changing the screen orientation for your application. The rotation is done clockwise like this.

Position 0 Position 1 Position 2 (default) Position 3

Here is the source code with which the images were created. It puts into practice most of the concepts discussed previously:

#include <LilyGoWatch.h>

// C++ object which will allow access to the functions of the Watch 
TTGOClass *ttgo;
// C++ object that wil contain TFT_eSPI methods for quick access 
TFT_eSPI *tft;

int w, h, orientation; // Screen resolution and orientation 
int p = 5 ;  // padding | marge

// Prototype
void drawReference();

void setup() {
  Serial.begin(115200);
  // Get ttgp object and set up the display 
  ttgo = TTGOClass::getWatch();
  ttgo->begin();     
  ttgo->openBL();

  // Easy access to TFT library functions  
  tft = ttgo->tft;
  
  // Get Screen resolution | récupère le résolution de l'écran
  w = tft->width();     // width  | largeur
  h = tft->height();    // height | hauteur

  // Current screen orientation 
  orientation = tft->getRotation();
  drawReference();
}

void loop() {
  int16_t x, y;
  if (ttgo->getTouch(x, y)) {
    if ( orientation == 3 ) {
      orientation = 0;
    } else {
      orientation += 1;
    }
    tft->setRotation(orientation);
    Serial.printf("Change screen orientation %u \n", orientation);
    drawReference();
    delay(100);
  }
}

void drawReference(){
    // Clean Screen  
  tft->fillScreen(TFT_BLACK);  // Fill background in black 
  tft->setTextFont(1);
  tft->setTextSize(2);
  tft->setTextColor(TFT_WHITE, TFT_BLACK);
  tft->setCursor(0, 0);

  // Draw reference | dessine le référentiel X/Y
  tft->drawFastHLine(p,p, w - 2*p, TFT_WHITE);
  tft->drawFastVLine(p,p, h - 2*p, TFT_WHITE);

  // Draw X and Y, Use http://www.asciitable.com/ (for example) to find ascii char code
  // drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size)
  tft->drawChar(w - 3*p, 3*p, 88, TFT_WHITE, TFT_BLACK, 2); // X
  tft->fillTriangle(w, p, w - 2*p, 0, w - 2*p, 2*p, TFT_RED ); // Triangle -> X arrow | flèche X
  tft->drawChar(3*p, w - 3*p, 89, TFT_WHITE, TFT_BLACK, 2);  // Y
  tft->fillTriangle(p, h, 0, h - 2*p, 2*p, h - 2*p, TFT_RED ); // Y arraw | flèche Y
  // Origine
  tft->fillCircle(p,p,p,TFT_RED);

  // Indicate user to touch screen to rotate || 
  tft->setTextDatum(MC_DATUM); 
  tft->drawString("Touch Screen", w / 2, h / 2 - 10, 1);
  tft->drawString("to rotate", w / 2, h / 2 + 10, 1);
  tft->drawString(String(orientation), w / 2, h / 3 + 10, 1);
}

Full code of examples

Here is the complete code for all the examples presented previously.

You can also get the source code directly from the GitHub repository

Getting started with the TFT_eSPI library and the T-Watch

Screen orientation

The code is compatible with the Arduino or PlatfomIO IDE.

/* Arduino IDE - uncomment your watch */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define  LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK=1
//#define LILYGO_WATCH_2020_V1

/* PlatformIO -> Select your watch in platformio.ini file */
#include <Arduino.h>
#include <LilyGoWatch.h>

// C++ object which will allow access to the functions of the Watch 
TTGOClass *ttgo;
// C++ object that wil contain TFT_eSPI methods for quick access 
TFT_eSPI *tft;

String txt; 
int w = 240;    // Screen width 
int h = 240;    // Screen height 
int16_t x, y;
char buf[128];

/* Prototypes */
void fillScreenBackground();
void displayFontSize();
void drawBasic();
void drawStringDemo();
void screenOrientationDemo();
void printAListToScreen();

void setup() {
    Serial.begin(115200);
    // Get Watch object and set up the display 
    ttgo = TTGOClass::getWatch();
    ttgo->begin();     
    ttgo->openBL();
    tft = ttgo->tft;
    // Uncomment function you want to test 
    fillScreenBackground();
    displayFontSize();
    drawBasic();
    drawStringDemo();
    screenOrientationDemo();
    printAListToScreen();
    
    // Clean screen | efface l'écran 
    tft->fillScreen(TFT_BLACK);
    tft->setTextColor(TFT_WHITE, TFT_BLACK);
    tft->setTextDatum(MC_DATUM);
}

void loop(){
  // Display cursor position each time the screen is touched 
  if (ttgo->getTouch(x, y)) {
    sprintf(buf, "  x:%u  y:%u   ", x, y);
    tft->drawString(buf, w / 2, h / 2);
    Serial.println(buf);
  }
}

// Available colors
/* 
 * TFT_BLACK         0,   0,   0 
 * TFT_NAVY          0,   0, 128 
 * TFT_DARKGREEN     0, 128,   0 
 * TFT_DARKCYAN      0, 128, 128 
 * TFT_MAROON      128,   0,   0 
 * TFT_PURPLE      128,   0, 128 
 * TFT_OLIVE       128, 128,   0 
 * TFT_LIGHTGREY   211, 211, 211 
 * TFT_DARKGREY    128, 128, 128 
 * TFT_BLUE          0,   0, 255 
 * TFT_GREEN         0, 255,   0 
 * TFT_CYAN          0, 255, 255 
 * TFT_RED         255,   0,   0 
 * TFT_MAGENTA     255,   0, 255 
 * TFT_YELLOW      255, 255,   0 
 * TFT_WHITE       255, 255, 255 
 * TFT_ORANGE      255, 180,   0 
 * TFT_GREENYELLOW 180, 255,   0 
 * TFT_PINK        255, 192, 203     
 * TFT_BROWN       150,  75,   0 
 * TFT_GOLD        255, 215,   0 
 * TFT_SILVER      192, 192, 192 
 * TFT_SKYBLUE     135, 206, 235 
 * TFT_VIOLET      180,  46, 226 
*/
void fillScreenBackground(){
    Serial.println("Red");
    tft->fillScreen(TFT_RED);
    delay(1000);
    Serial.println("Green");
    tft->fillScreen(TFT_GREEN);
    delay(1000);
    Serial.println("Black");
    tft->fillScreen(TFT_BLACK);
    delay(1000);
}

void getScreenSize(){
  w = tft->width();
  h = tft->height();
  Serial.printf("Screen size %u * %u", w, h);
}

// Draw primitives 
void drawBasic(){
  int margin = 20;
  int _delay = 500;
  getScreenSize();
  
  Serial.println("Draw a rectangle");
  tft->fillScreen(TFT_BLACK);
  tft->drawRect(margin,margin, w - ( 2* margin ), h - ( 2 * margin),TFT_RED);
  delay(_delay);

  Serial.println("Draw a filled rectangle");
  // fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
  tft->fillRect(2 * margin, 2 * margin, w - ( 4 * margin), h - (4 * margin), TFT_VIOLET);
  delay(_delay);

  Serial.println("Draw a rounded corner rectangle outline");
  //drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color)
  tft->drawRoundRect(3 * margin, 3 * margin, w - ( 6 * margin), h - (6 * margin), 8, TFT_YELLOW);
  delay(_delay);

  Serial.println("Draw a line");
  // drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
  tft->drawLine(0, 0, w, h, TFT_DARKGREY);
  delay(_delay);  

  Serial.println("Draw a triangle outline using 3 arbitrary points");
  // drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color)
  tft->drawTriangle(w,h - 1, w - 40 , h, w - 20, h - 30, TFT_PURPLE);
  delay(_delay); 

  Serial.println("Draw a circle outline");
  //drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color)
  tft->drawCircle(w / 2, h / 2, 80, TFT_SKYBLUE);
  delay(_delay);

  Serial.println("Draw a ellipse outline");
  //drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color)
  tft->drawEllipse(w, h, 80, 60, TFT_SKYBLUE);
  delay(_delay);

  Serial.println("Draw horizontal line");
  // drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
  tft->drawFastHLine(0, 0, w, TFT_DARKGREY);
  delay(_delay);  
  
  Serial.println("Draw Vertical line");
  // drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
  tft->drawFastVLine(0, 0, h, TFT_DARKGREY);
  delay(_delay);
  
  Serial.println("Draw A char (ascii 65)");
  //drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size)
  // Use this to find char code http://www.asciitable.com/
  tft->drawChar(margin, margin, 65, TFT_BLUE, TFT_WHITE, 2);
  delay(_delay);

  Serial.println("Draw a string");
  //drawString(const char *string, int32_t poX, int32_t poY)
  tft->drawString("T-Watch",margin, margin * 2);
  delay(_delay);

  Serial.println("Draw a pixel");
  //drawPixel(int32_t x, int32_t y, uint32_t color)
  tft->drawPixel(w / 2 , h / 2, TFT_RED);
  delay(_delay);
  /* Other usefull functions 
      invertDisplay(bool i)
      decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining)
  */
}

// Display font 
void displayFontSize(){
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE);
    for (size_t i = 1; i <= 7; i++){ // Size from 1 to 7 (includes) tft->setTextFont(1);     // Only font 1 is available 
      tft->setTextSize(i);     // Change size         
      tft->setCursor(0, 0);
      txt = "Text with size "; txt += i;
      Serial.print("Display"); Serial.println(txt);
      tft->println(txt);
      delay(1000);
      tft->fillScreen(TFT_BLACK);
    }
}

void drawStringDemo(){
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE);
  tft->setTextSize(2); 
  for (size_t i = 0; i < 12; i++) { tft->setTextDatum(i);
    Serial.printf("Display string with justification %u \n", tft->getTextDatum());
    tft->drawString("TFT_eSPI Demo", w / 2, h / 2, 2);
    delay(1000);
    tft->fillScreen(TFT_BLACK);
  }
}

void screenOrientationDemo(){
  Serial.printf("Current screen orientation %u \n", tft->getRotation());
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE);
  tft->setTextDatum(MC_DATUM);
  tft->setTextSize(2); 
  tft->drawString("Rotate Screen", w / 2, h / 2, 2);
  for (size_t i = 1; i < 4; i++) { // rotate the screen orientation m = 0-3 or 4-7 for BMP drawing //setRotation(uint8_t m) Serial.printf("Rotate the screen to %u \n", i); tft->setRotation(i);
    tft->fillScreen(TFT_BLACK);
    tft->drawString("Rotate Screen", w / 2, h / 2, 2);
    delay(1000);
  }
  
  tft->setRotation(2);
  tft->fillScreen(TFT_BLACK);
  tft->drawString("Rotate Screen", w / 2, h / 2, 2);
}

// Use printf or println to display a list to screen 
void printAListToScreen(){
  //TFT_eSPI * tft = tft;
  tft->fillScreen(TFT_BLACK);
  tft->setTextColor(TFT_WHITE, TFT_BLACK);
  tft->setTextSize(2);
  tft->setCursor(0,0);
  
  for (size_t i = 0; i <= 20; i++) { // Return cursor on the top of the screen if ( tft->getCursorY() >= h ) {
      tft->fillScreen(TFT_BLACK);
      tft->setCursor(0,0);
    }
    tft->printf("This is the line %u \n", i);
    delay(200);
  }
  tft->setTextSize(1);

  tft->println(" \n *** End of the list ***");
  delay(2000);
}

The platformio.ini configuration file

env:ttgo-t-watch]
platform = espressif32
board = ttgo-t-watch
framework = arduino
build_flags =
    ;-D LILYGO_WATCH_2019_WITH_TOUCH=1
    ;-D LILYGO_WATCH_2019_NO_TOUCH=1
    ;-D LILYGO_WATCH_BLOCK=1
    -D LILYGO_WATCH_2020_V1=1
lib_deps =
    TTGO TWatch Library
upload_speed = 2000000
monitor_speed = 115200

And the little demo video that goes well

Updates

2020/11/25 Add drawXBitamp function

2020/11/16 Publication of the article

Version française

Click to rate this post!
[Total: 4 Average: 5]
Exit mobile version