[EN] How to used RPi 3.5″ TFT LCD&Touch Shield with esp32?

Because we bought a 3.5″ display for the Raspberry Pi board to use and wonder if it can be used with the ESP32 board or not. Therefore, this article talks about how to use a TFT LCD Shield designed for Waveshare’s Raspberry Pi with the TTGO T8 ESP32 microcontroller via the TFT_eSPI library to display and operate a touch screen system as shown in Figure 1.

(Figure. 1 When use with ESP32)

Raspberry Pi 3.5″ TFT LCD Shield Touch Screen

The Raspberry Pi 3.5″ TFT LCD Shield Touch Screen is a 3.5″ TFT LCD with touch panel designed for use with the Raspberry Pi board shown in Figure 2. It is manufactured by Waveshare.

(Figure. 2 3.5inch RPi LCD (A) module)

Properties

The properties of the display module are as follows.

  • Display resolution 480×320 dots.
  • Use a connection via the SPI bus.
  • The display control module is ILI9486 (ILI9486 Driver).
  • Resistive touch control system.
  • Compatible with Raspberry Pi boards by plugging directly onto the board.
  • Drivers are compatible with Raspbian, Ubuntu and Kali Linux.
  • The touch panel control module uses XPT2046.
  • Displays 16-bit or 65,535 colors.
  • Display aspect ratio 8:5

TFT_eSPI setting

The connection between the pins of the LCD module and the ESP32 is as follows.

RPi 3.5″ TFT&TouchESP32
5V5V
GNDGND
LCD_RS (pin 18)gpio15
LCD_SI/TP_SI (pin 19)gpio12
TP_SO (pin 21)gpio2
RST (pin 22)gpio13
LCD_SCK/TP_SCK (pin 23)gpio14
LCD_CS (pin 24)gpio5
TFT_CS (pin 26)gpio18

The settings in the TFT_eSPI header file are as follows.

#define RPI_DISPLAY_TYPE
#define ILI9486_DRIVER

#define TOUCH_CS 18

#define TFT_MISO 2 
#define TFT_MOSI 12
#define TFT_SCLK 14
#define TFT_CS   5 
#define TFT_DC   15
#define TFT_RST  13

#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF

#define SMOOTH_FONT

#define SPI_FREQUENCY  26000000 

#define SPI_TOUCH_FREQUENCY  2500000

Example Code

Settings in the header file of TFT_eSPI example program to test display and get value of TFT_eSPI named Keypad_480x320 with the following code and the result screen as shown in Figure 3.

Code

/*
  The TFT_eSPI library incorporates an Adafruit_GFX compatible
  button handling class, this sketch is based on the Arduin-o-phone
  example.

  This example diplays a keypad where numbers can be entered and
  send to the Serial Monitor window.

  The sketch has been tested on the ESP8266 (which supports SPIFFS)

  The minimum screen size is 320 x 240 as that is the keypad size.

  TOUCH_CS and SPI_TOUCH_FREQUENCY must be defined in the User_Setup.h file
  for the touch functions to do anything.
*/

// The SPIFFS (FLASH filing system) is used to hold touch screen
// calibration data

#include "FS.h"

#include <SPI.h>
#include <TFT_eSPI.h>      // Hardware-specific library

TFT_eSPI tft = TFT_eSPI(); // Invoke custom library

// This is the file name used to store the calibration data
// You can change this to create new calibration files.
// The SPIFFS file name must start with "/".
#define CALIBRATION_FILE "/TouchCalData2"

// Set REPEAT_CAL to true instead of false to run calibration
// again, otherwise it will only be done once.
// Repeat calibration if you change the screen rotation.
#define REPEAT_CAL false

// Keypad start position, key sizes and spacing
#define KEY_X 40 // Centre of key
#define KEY_Y 96
#define KEY_W 62 // Width and height
#define KEY_H 30
#define KEY_SPACING_X 18 // X and Y gap
#define KEY_SPACING_Y 20
#define KEY_TEXTSIZE 1   // Font size multiplier

// Using two fonts since numbers are nice when bold
#define LABEL1_FONT &FreeSansOblique12pt7b // Key label font 1
#define LABEL2_FONT &FreeSansBold12pt7b    // Key label font 2

// Numeric display box size and location
#define DISP_X 1
#define DISP_Y 10
#define DISP_W 238
#define DISP_H 50
#define DISP_TSIZE 3
#define DISP_TCOLOR TFT_CYAN

// Number length, buffer for storing it and character index
#define NUM_LEN 12
char numberBuffer[NUM_LEN + 1] = "";
uint8_t numberIndex = 0;

// We have a status line for messages
#define STATUS_X 120 // Centred on this
#define STATUS_Y 65

// Create 15 keys for the keypad
char keyLabel[15][5] = {"New", "Del", "Send", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "#" };
uint16_t keyColor[15] = {TFT_RED, TFT_DARKGREY, TFT_DARKGREEN,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE,
                         TFT_BLUE, TFT_BLUE, TFT_BLUE
                        };

// Invoke the TFT_eSPI button class and create all the button objects
TFT_eSPI_Button key[15];

//------------------------------------------------------------------------------------------

void setup() {
  // Use serial port
  Serial.begin(9600);

  // Initialise the TFT screen
  tft.init();

  // Set the rotation before we calibrate
  tft.setRotation(1);

  // Calibrate the touch screen and retrieve the scaling factors
  touch_calibrate();

  // Clear the screen
  tft.fillScreen(TFT_BLACK);

  // Draw keypad background
  tft.fillRect(0, 0, 240, 320, TFT_DARKGREY);

  // Draw number display area and frame
  tft.fillRect(DISP_X, DISP_Y, DISP_W, DISP_H, TFT_BLACK);
  tft.drawRect(DISP_X, DISP_Y, DISP_W, DISP_H, TFT_WHITE);

  // Draw keypad
  drawKeypad();
}

//------------------------------------------------------------------------------------------

void loop(void) {
  uint16_t t_x = 0, t_y = 0; // To store the touch coordinates

  // Pressed will be set true is there is a valid touch on the screen
  bool pressed = tft.getTouch(&t_x, &t_y);

  // / Check if any key coordinate boxes contain the touch coordinates
  for (uint8_t b = 0; b < 15; b++) {
    if (pressed && key[b].contains(t_x, t_y)) {
      key[b].press(true);  // tell the button it is pressed
    } else {
      key[b].press(false);  // tell the button it is NOT pressed
    }
  }

  // Check if any key has changed state
  for (uint8_t b = 0; b < 15; b++) {

    if (b < 3) tft.setFreeFont(LABEL1_FONT);
    else tft.setFreeFont(LABEL2_FONT);

    if (key[b].justReleased()) key[b].drawButton();     // draw normal

    if (key[b].justPressed()) {
      key[b].drawButton(true);  // draw invert

      // if a numberpad button, append the relevant # to the numberBuffer
      if (b >= 3) {
        if (numberIndex < NUM_LEN) {
          numberBuffer[numberIndex] = keyLabel[b][0];
          numberIndex++;
          numberBuffer[numberIndex] = 0; // zero terminate
        }
        status(""); // Clear the old status
      }

      // Del button, so delete last char
      if (b == 1) {
        numberBuffer[numberIndex] = 0;
        if (numberIndex > 0) {
          numberIndex--;
          numberBuffer[numberIndex] = 0;//' ';
        }
        status(""); // Clear the old status
      }

      if (b == 2) {
        status("Sent value to serial port");
        Serial.println(numberBuffer);
      }
      // we dont really check that the text field makes sense
      // just try to call
      if (b == 0) {
        status("Value cleared");
        numberIndex = 0; // Reset index to 0
        numberBuffer[numberIndex] = 0; // Place null in buffer
      }

      // Update the number display field
      tft.setTextDatum(TL_DATUM);        // Use top left corner as text coord datum
      tft.setFreeFont(&FreeSans18pt7b);  // Choose a nicefont that fits box
      tft.setTextColor(DISP_TCOLOR);     // Set the font colour

      // Draw the string, the value returned is the width in pixels
      int xwidth = tft.drawString(numberBuffer, DISP_X + 4, DISP_Y + 12);

      // Now cover up the rest of the line up by drawing a black rectangle.  No flicker this way
      // but it will not work with italic or oblique fonts due to character overlap.
      tft.fillRect(DISP_X + 4 + xwidth, DISP_Y + 1, DISP_W - xwidth - 5, DISP_H - 2, TFT_BLACK);

      delay(10); // UI debouncing
    }
  }
}

//------------------------------------------------------------------------------------------

void drawKeypad()
{
  // Draw the keys
  for (uint8_t row = 0; row < 5; row++) {
    for (uint8_t col = 0; col < 3; col++) {
      uint8_t b = col + row * 3;

      if (b < 3) tft.setFreeFont(LABEL1_FONT);
      else tft.setFreeFont(LABEL2_FONT);

      key[b].initButton(&tft, KEY_X + col * (KEY_W + KEY_SPACING_X),
                        KEY_Y + row * (KEY_H + KEY_SPACING_Y), // x, y, w, h, outline, fill, text
                        KEY_W, KEY_H, TFT_WHITE, keyColor[b], TFT_WHITE,
                        keyLabel[b], KEY_TEXTSIZE);
      key[b].drawButton();
    }
  }
}

//------------------------------------------------------------------------------------------

void touch_calibrate()
{
  uint16_t calData[5];
  uint8_t calDataOK = 0;

  // check file system exists
  if (!SPIFFS.begin()) {
    Serial.println("Formating file system");
    SPIFFS.format();
    SPIFFS.begin();
  }

  // check if calibration file exists and size is correct
  if (SPIFFS.exists(CALIBRATION_FILE)) {
    if (REPEAT_CAL)
    {
      // Delete if we want to re-calibrate
      SPIFFS.remove(CALIBRATION_FILE);
    }
    else
    {
      File f = SPIFFS.open(CALIBRATION_FILE, "r");
      if (f) {
        if (f.readBytes((char *)calData, 14) == 14)
          calDataOK = 1;
        f.close();
      }
    }
  }

  if (calDataOK && !REPEAT_CAL) {
    // calibration data valid
    tft.setTouch(calData);
  } else {
    // data not valid so recalibrate
    tft.fillScreen(TFT_BLACK);
    tft.setCursor(20, 0);
    tft.setTextFont(2);
    tft.setTextSize(1);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);

    tft.println("Touch corners as indicated");

    tft.setTextFont(1);
    tft.println();

    if (REPEAT_CAL) {
      tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.println("Set REPEAT_CAL to false to stop this running again!");
    }

    tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);

    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.println("Calibration complete!");

    // store data
    File f = SPIFFS.open(CALIBRATION_FILE, "w");
    if (f) {
      f.write((const unsigned char *)calData, 14);
      f.close();
    }
  }
}

//------------------------------------------------------------------------------------------

// Print something in the mini status bar
void status(const char *msg) {
  tft.setTextPadding(240);
  //tft.setCursor(STATUS_X, STATUS_Y);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREY);
  tft.setTextFont(0);
  tft.setTextDatum(TC_DATUM);
  tft.setTextSize(1);
  tft.drawString(msg, STATUS_X, STATUS_Y);
}

//------------------------------------------------------------------------------------------

Description

From the sample code there are 3 main parts:

  1. The part of the touch panel setting via the function touch_calibrate() that checks if has it been adjusted before by searching for CALIBRATION_FILE in flash memory via SPIFFS, if you don’t have one yet or want to redo it, call the touchscreen settings via tft.calibrateTouch(). The directive for applying the set value is tft.setTouch()
  2. Screen display
    1. Display keys by calling a function called drawKaypad(), each key using a TFT_eSPI_Button class, which is a UI class of TFT_eSPI to display the following buttons.
      1. New button in the first row
      2. Del button in the first row
      3. Send button in the first row
      4. 1 button in the second row
      5. 2 button in the second row
      6. 3 button in the second row
      7. 4 button in the third row
      8. 5 button in the third row
      9. 6 button in the third row
      10. 7 button in the forth row
      11. 8 button in the forth row
      12. 9 button in the forth row
      13. . button in the bottom row
      14. 0 button in the bottom row
      15. # button in the bottom row
    2. Message display
    3. The status section is next to the message display section.
  3. The part receives the value from the touch screen part and works.
    1. Read the position value (x,y) from tft.getTouch()
    2. If you press the number button, the number will be displayed in the text above.
    3. If you press the New button, it will clear the value of the text part.
    4. Pressing Del will remove the last number pressed from the text section.
    5. If you press Send, the pressed part of the message will be sent to the serial communication port.
    6. Reduce touch bounce with a 10 ms delay.
    7. The message display uses the following settings.
      1. Set Datum to TL_DATUM to display text from left to right.
      2. Choose a font FreeSans18pt7b
      3. The font color as defined in the DISP_TCOLOR constant.
      4. Draw data through the drawString() function by passing the value stored in the numberBuffer variable.
(Figure. 3 Result from Kaypad_480x320)

Conclusion

By applying the Raspberry Pi display module to the ESP32 microcontroller, we found that it can be used without problems and can work with both the display and touch screen. However, the fact that the display driver was not identified in the documentation, we had to decipher the LCD_Show code and try changing the driver that is compatible with the display of the RPi until it was found that the driver that works is ili9486. From this incident, we found that unrecognized programming with the device being used will render the device inoperable. And the more you understand how each device works by reading the documentation, the more efficient the code can be. Finally, have fun programming.

If you want to talk with us, feel free to leave comments below!!

(C) 2020-2021, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2021-12-24