TFT Shields are touch-sensitive LCD screens for displaying images and creating user interfaces, with more or less complex graphics, to drive Arduino microcontrollers. In this tutorial, we use the Kuman TFT 3.5″ shield (very close to the 2.8″ shield) but this tutorial can be applied to other Shields or LCD modules. Check carefully the pins used and the compatibility of the library.
Hardware
- Computer
- Arduino UNO or Mega
- USB cable A Male to B Male
- TFT LCD Shield
- SD Card
Principle of operation
The TFT shield is equipped with an LCD touch screen that allows colour images to be displayed and interfaces with curves and buttons to interact and view data from the Arduino. It is also equipped with a micro SD card reader that can be used to save images or other data.
Schematic
The TFT shield is placed directly on an Arduino UNO or Mega card. The shield uses almost all pins of the Arduino UNO. Make sure you do not use the same pins for other modules.
For your information the available brooches are:
- analog pin A5
- digital pin 0
- digital pin 1
If you do not use the touch functionality of the display, you can also use pins: A1, A2, 6 and 7.
If you are using another Shield or LCD module, be sure to check the pins used (pinout).
Code
To use the TftScreen object, we use the SD.h, Adafruit_GFX.h, MCUFRIEND_kbv.h, et TouchScreen.h libraries, which allow us to manage communication with the SD card, the creation of graphics, screen management and the touch screen. This example shows how easy it is to use the screen.
//Libraries #include <SD.h>//https://www.arduino.cc/en/reference/SD #include <Adafruit_GFX.h>//https://github.com/adafruit/Adafruit-GFX-Library #include <MCUFRIEND_kbv.h>//https://github.com/prenticedavid/MCUFRIEND_kbv #include <TouchScreen.h> //https://github.com/adafruit/Adafruit_TouchScreen //Constants #define SD_CS 10 #define BLACK 0 #define GREY 21845 #define BLUE 31 #define RED 63488 #define GREEN 2016 #define DARKGREEN 1472 #define CYAN 2047 #define MAGENTA 63519 #define YELLOW 65504 #define GOLD 56768 #define WHITE 65535 //Touch screen configuration #define MINPRESSURE 200 #define MAXPRESSURE 1000 // ALL Touch panels and wiring is DIFFERENT // copy-paste results from TouchScreen_Calibr_native.ino //3.5 Parameters const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486 const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87; //2.8 Parameters //const int XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341 //const int TS_LEFT = 907, TS_RT = 120, TS_TOP = 74, TS_BOT = 913; TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); TSPoint p; bool down; int pixel_x, pixel_y; //Touch_getXY() updates global vars //Variables int currentPage = 0, oldPage = -1; //Objects MCUFRIEND_kbv tft; // Button calibration Adafruit_GFX_Button page1_btn, page2_btn; int margin = 5; int btnWidth = 100; int btnHeight = 40; int btnY = 200; void setup() { //Init Serial USB Serial.begin(9600); Serial.println(F("Initialize System")); //Init tft screen uint16_t ID = tft.readID(); if (ID == 0xD3D3) ID = 0x9486; //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield tft.begin(ID); tft.setRotation(1);//0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE //Uncomment if you are using SD /*if (!SD.begin(SD_CS)) { Serial.println(F("initialization failed!")); return; }*/ currentPage = 0; // Indicates that we are at Home Screen } void loop() { down = Touch_getXY(); switch (currentPage) { case 0: if (currentPage != oldPage) { Serial.println(F("Page 1")); drawPage1(); } page2_btn.press(down && page2_btn.contains(pixel_x, pixel_y)); //if (page2_btn.justReleased()) page2_btn.drawButton(); if (page2_btn.justPressed()) { currentPage = 1; } break; case 1: if (currentPage != oldPage) { Serial.println(F("Page 2")); drawPage2(); } page1_btn.press(down && page1_btn.contains(pixel_x, pixel_y)); //if (page1_btn.justReleased()) page1_btn.drawButton(); if (page1_btn.justPressed()) { currentPage = 0; } break; } } void drawPage1() { /* function drawHomePage */ tft.setRotation(1); tft.fillScreen(BLACK); //Title tft.setCursor(0, 10); tft.setTextSize(3); tft.setTextColor(WHITE, BLACK); tft.print("This is page 1"); // Prints the string on the screen tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line //text tft.setTextSize(3); tft.setTextColor(RED, BLACK); tft.setCursor(tft.width() / 2. - 14 * 3 * 3, 80); tft.print(" Go to page 2 "); //Button page2_btn.initButton(&tft, tft.width() / 2., 200, 2 * btnWidth, btnHeight, WHITE, GREEN, BLACK, "Page 2", 2); page2_btn.drawButton(false); oldPage = currentPage; } void drawPage2() { /* function drawHomePage */ tft.setRotation(1); tft.fillScreen(GREY); //Title tft.setCursor(0, 10); tft.setTextSize(3); tft.setTextColor(WHITE, GREY); tft.print("This is page 2"); // Prints the string on the screen tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line //text tft.setTextSize(3); tft.setTextColor(GREEN, BLACK); tft.setCursor(tft.width() / 2. - 14 * 3 * 3, 80); tft.print(" Go to page 1 "); //Button page1_btn.initButton(&tft, tft.width() / 2., 200, 2 * btnWidth, btnHeight, WHITE, RED, BLACK, "Page 1", 2); page1_btn.drawButton(false); oldPage = currentPage; } /************************************************************************************ UTILITY FUNCTION *************************************************************************************/ bool Touch_getXY(void) { p = ts.getPoint(); pinMode(YP, OUTPUT); //restore shared pins pinMode(XM, OUTPUT); digitalWrite(YP, HIGH); digitalWrite(XM, HIGH); bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE); if (pressed) { if (tft.width() <= tft.height()) { //Portrait pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height()); } else { pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width()); pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height()); } } return pressed; }
Bonus: Example of graphical interface
In this example, we will create a graphical user interface to show different functionalities that can be developed with the TFT shield.
#include <Adafruit_GFX.h> #include <MCUFRIEND_kbv.h> MCUFRIEND_kbv tft; #include <TouchScreen.h> #include <Fonts/FreeSans9pt7b.h> #include <Fonts/FreeSans12pt7b.h> #include <Fonts/FreeSerif12pt7b.h> #include <FreeDefaultFonts.h> #define MINPRESSURE 200 #define MAXPRESSURE 1000 // ALL Touch panels and wiring is DIFFERENT // copy-paste results from TouchScreen_Calibr_native.ino //3.5 Calibration const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486 const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87; /*PORTRAIT CALIBRATION 320 x 480 x = map(p.x, LEFT=144, RT=887, 0, 320) y = map(p.y, TOP=936, BOT=87, 0, 480) Touch Pin Wiring XP=8 XM=A2 YP=A3 YM=9 LANDSCAPE CALIBRATION 480 x 320 x = map(p.y, LEFT=936, RT=87, 0, 480) y = map(p.x, TOP=887, BOT=144, 0, 320)*/ //2.8 Calbiration //const int XP=8,XM=A2,YP=A3,YM=9; //240x320 ID=0x9341 //const int TS_LEFT=907,TS_RT=120,TS_TOP=74,TS_BOT=913; /*PORTRAIT CALIBRATION 240 x 320 x = map(p.x, LEFT=907, RT=120, 0, 240) y = map(p.y, TOP=74, BOT=913, 0, 320) Touch Pin Wiring XP=8 XM=A2 YP=A3 YM=9 LANDSCAPE CALIBRATION 320 x 240 x = map(p.y, LEFT=74, RT=913, 0, 320) y = map(p.x, TOP=120, BOT=907, 0, 240)*/ TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300); TSPoint p; Adafruit_GFX_Button on_btn, off_btn, page1_btn, page2_btn, page3_btn; Adafruit_GFX_Button ok_btn, cncl_btn, plus_btn, minus_btn; Adafruit_GFX_Button menu_btn, info_btn, back_btn; int pixel_x, pixel_y; //Touch_getXY() updates global vars // Button calibration int margin = 5; int btnWidth = 100; int btnHeight = 40; int btnY = 200; // Software variable bool enable_nuit = false; int parameter = 50, old_parameter = 50; long temp0 = 60; long temp1 = 25.5; long temp2 = 40; long temp3 = 35.6; #define BLACK 0x0000 #define GREY 0x5555 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define DARKGREEN 0x05C0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define GOLD 0xDDC0 #define WHITE 0xFFFF enum pageId { MENU, SENSOR, CMD, PARAM, INFO }; unsigned int currentPage = MENU, oldPage = -1; void setup(void) { Serial.begin(9600); //init TFTTouch uint16_t ID = tft.readID(); Serial.print("TFT ID = 0x"); Serial.println(ID, HEX); Serial.println(F("Calibrate for your Touch Panel")); if (ID == 0xD3D3) ID = 0x9486; //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield tft.begin(ID); tft.setRotation(1); //0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE //tft.setFont(&FreeSmallFont); /*if (!SD.begin(SD_CS)) { Serial.println(F("initialization failed!")); return; }*/ currentPage = MENU; // Indicates that we are at Home Screen Serial.println("Home Page"); } bool down ; void loop(void) { switch (currentPage) { case MENU: //Menu page if (currentPage != oldPage) drawMenuScreen(); page1_btn.press(down && page1_btn.contains(pixel_x, pixel_y)); page2_btn.press(down && page2_btn.contains(pixel_x, pixel_y)); page3_btn.press(down && page3_btn.contains(pixel_x, pixel_y)); if (page1_btn.justReleased()) page1_btn.drawButton(); if (page2_btn.justReleased()) page2_btn.drawButton(); if (page3_btn.justReleased()) page3_btn.drawButton(); if (page1_btn.justPressed()) { page1_btn.drawButton(true); currentPage = SENSOR; } if (page2_btn.justPressed()) { page2_btn.drawButton(true); currentPage = CMD; } if (page3_btn.justPressed()) { page3_btn.drawButton(true); currentPage = PARAM; } break; case SENSOR: if (currentPage != oldPage) drawSensorScreen(); readSensor(); updateTemp(); menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y)); info_btn.press(down && info_btn.contains(pixel_x, pixel_y)); if (menu_btn.justReleased()) menu_btn.drawButton(); if (info_btn.justReleased()) info_btn.drawButton(); if (menu_btn.justPressed()) { menu_btn.drawButton(true); currentPage = MENU; } if (info_btn.justPressed()) { info_btn.drawButton(true); currentPage = INFO; } break; case CMD: if (currentPage != oldPage) drawCmdScreen(); on_btn.press(down && on_btn.contains(pixel_x, pixel_y)); off_btn.press(down && off_btn.contains(pixel_x, pixel_y)); back_btn.press(down && back_btn.contains(pixel_x, pixel_y)); if (back_btn.justReleased()) back_btn.drawButton(); if (on_btn.justReleased()) on_btn.drawButton(); if (off_btn.justReleased()) off_btn.drawButton(); if (back_btn.justPressed()) { back_btn.drawButton(true); currentPage = MENU; } if (on_btn.justPressed()) { on_btn.drawButton(true); tft.fillRect(tft.width() / 2. - (btnWidth + 40) / 2, btnY - 4 * margin - (btnHeight) / 2 - (btnHeight + 40), btnWidth + 40, btnHeight + 40, GREEN); Serial.println(F("Set ON")); } if (off_btn.justPressed()) { off_btn.drawButton(true); tft.fillRect(tft.width() / 2. - (btnWidth + 40) / 2, btnY - 4 * margin - (btnHeight) / 2 - (btnHeight + 40), btnWidth + 40, btnHeight + 40, RED); Serial.println(F("Set OFF")); } break; case PARAM: //consigne if (currentPage != oldPage) drawParamScreen(); plus_btn.press(down && plus_btn.contains(pixel_x, pixel_y)); minus_btn.press(down && minus_btn.contains(pixel_x, pixel_y)); ok_btn.press(down && ok_btn.contains(pixel_x, pixel_y)); cncl_btn.press(down && cncl_btn.contains(pixel_x, pixel_y)); if (plus_btn.justReleased()) plus_btn.drawButton(); if (minus_btn.justReleased()) minus_btn.drawButton(); if (ok_btn.justReleased()) ok_btn.drawButton(); if (cncl_btn.justReleased()) cncl_btn.drawButton(); if (plus_btn.justPressed()) { plus_btn.drawButton(true); parameter += 1; Serial.print(F("Consigne : ")); Serial.println(parameter); drawTextInRect(tft.width() / 2., 60 + 3 * 4 + 6 * 8, parameter, 4, RED, BLACK); } if (minus_btn.justPressed()) { minus_btn.drawButton(true); parameter -= 1; Serial.print(F("Consigne : ")); Serial.println(parameter); drawTextInRect(tft.width() / 2., 60 + 3 * 4 + 6 * 8, parameter, 4, RED, BLACK); } if (ok_btn.justPressed()) { ok_btn.drawButton(true); Serial.println(F("Valider")); validateScreen(); delay(1000); currentPage = MENU; } if (cncl_btn.justPressed()) { cncl_btn.drawButton(true); parameter = old_parameter; Serial.println(F("Annule ")); cancelScreen(); delay(1000); currentPage = MENU; } break; case INFO: if (currentPage != oldPage) drawInfoScreen(); if (Touch_getXY()) { currentPage = MENU; } break; } if (oldPage == currentPage){ down = Touch_getXY(); }else{ down=false; } } /************************************************************************************ SCREENS DEFINTION ************************************************************************************/ void drawMenuScreen() { tft.fillScreen(BLACK); tft.setTextSize(2); // Title tft.setTextColor(WHITE, BLACK); tft.setCursor(0, 10); tft.print("Exemple de menu"); // Prints the string on the screen tft.drawLine(0, 32, tft.width() * 0.6, 32, DARKGREEN); // Draws the red line tft.setTextColor(WHITE, BLACK);//((255, 255, 255), (0,0,0)); tft.setCursor(0, 80); tft.setTextColor(GREEN, BLACK);//((255, 255, 255), (0,0,0)); // Button page1_btn.initButton(&tft, tft.width() / 2. , tft.height() / 2. - (1.*btnHeight + margin), 2 * btnWidth, btnHeight, WHITE, GREEN, BLACK, "SENSOR", 2); page2_btn.initButton(&tft, tft.width() / 2., tft.height() / 2., 2 * btnWidth, btnHeight, WHITE, GREEN, BLACK, "COMMAND", 2); page3_btn.initButton(&tft, tft.width() / 2., tft.height() / 2. + (1.*btnHeight + margin), 2 * btnWidth, btnHeight, WHITE, GREEN, BLACK, "PARAMETER", 2); page1_btn.drawButton(false); page2_btn.drawButton(false); page3_btn.drawButton(false); //Button frame tft.drawRoundRect(tft.width() / 2. - 1.5 * btnWidth, tft.height() / 2. - (1.5 * btnHeight + 2 * margin), 2 * btnWidth + btnWidth, 3 * btnHeight + 4 * margin, 10, GREEN); oldPage = currentPage; } void readSensor() { temp0 = temp0 + random(-25, 25) / 10; temp1 = temp1 + random(-25, 25) / 10; temp2 = temp2 + random(-25, 25) / 10; temp3 = temp3 + random(-25, 25) / 10; } void drawSensorScreen() { tft.fillScreen(BLACK); tft.drawLine(tft.width() / 2., 0, tft.width() / 2., tft.height(), WHITE); tft.drawLine(0, tft.height() / 2., tft.width(), tft.height() / 2., WHITE); updateTemp(); tft.setTextSize(1); // bouton centré X,Y menu_btn.initButton(&tft, tft.width() / 2. - btnWidth - margin , tft.height() - btnHeight / 2., btnWidth, btnHeight, WHITE, GREY, BLACK, "MENU", 2); info_btn.initButton(&tft, tft.width() / 2. + btnWidth + margin, tft.height() - btnHeight / 2., btnWidth, btnHeight, WHITE, GREY, BLACK, "INFO", 2); menu_btn.drawButton(false); info_btn.drawButton(false); oldPage = currentPage; } void updateTemp() { //temp1 if (parameter < temp0) { //(abs(parameter-temp0)<1){ drawTextInRect(tft.width() / 4., tft.height() / 4., temp0, 5, GREEN, 255); } else { drawTextInRect(tft.width() / 4., tft.height() / 4., temp0, 5, RED, 255); } drawTextInRect(3 * tft.width() / 4., tft.height() / 4., temp1, 5, RED, 255); drawTextInRect(tft.width() / 4., 3 * tft.height() / 4., temp2, 5, RED, 255); drawTextInRect(3 * tft.width() / 4., 3 * tft.height() / 4., temp3, 5, RED, 255); } void drawCmdScreen() { tft.setRotation(1); //PORTRAIT tft.setTextSize(1); tft.fillScreen(BLACK); back_btn.initButton(&tft, 60 , 20, btnWidth, btnHeight, BLACK, BLACK, WHITE, "<- Back", 2); // bouton centré X,Y on_btn.initButton(&tft, tft.width() / 2. - btnWidth / 2. , btnY, btnWidth, btnHeight, WHITE, GREEN, BLACK, "ON", 2); off_btn.initButton(&tft, tft.width() / 2. + btnWidth / 2. + margin, btnY, btnWidth, btnHeight, WHITE, RED, BLACK, "OFF", 2); back_btn.drawButton(false); on_btn.drawButton(false); off_btn.drawButton(false); tft.fillRect(tft.width() / 2. - (btnWidth + 40) / 2, btnY - 4 * margin - (btnHeight) / 2 - (btnHeight + 40), btnWidth + 40, btnHeight + 40, RED); oldPage = currentPage; } void validateScreen() { tft.setRotation(1); tft.fillScreen(BLACK); tft.setTextSize(5); tft.setTextColor(BLACK, GREEN); tft.setCursor(tft.width() / 2. - 6 * 6 * 4, tft.height() / 2. - 1 * 6); tft.print(" VALIDE "); } void cancelScreen() { tft.setRotation(1); tft.fillScreen(BLACK); tft.setTextSize(5); tft.setTextColor(BLACK, RED); tft.setCursor(tft.width() / 2. - 6 * 6 * 4, tft.height() / 2. - 1 * 6); tft.print(" ANNULE "); } void drawParamScreen() { tft.setRotation(1); tft.fillScreen(BLACK); //Title tft.setTextSize(3); tft.setTextColor(WHITE, BLACK); tft.setCursor(tft.width() / 2. - 9 * 3 * 3, 50); tft.print("Parameter"); ok_btn.initButton(&tft, 2 + btnWidth / 2., btnHeight / 2. + margin, btnWidth, btnHeight, WHITE, DARKGREEN, BLACK, "Valider", 2); cncl_btn.initButton(&tft, tft.width() - btnWidth / 2 - 2, btnHeight / 2. + margin, btnWidth, btnHeight, WHITE, RED, BLACK, "Annuler", 2); ok_btn.drawButton(false); cncl_btn.drawButton(false); drawTextInRect(tft.width() / 2., 60 + 3 * 4 + 6 * 8, parameter, 4, RED, BLACK); plus_btn.initButton(&tft, tft.width() / 2. - btnWidth / 2. , 60 + 3 * 4 + 6 * 8 + (btnWidth - 30), btnWidth - 20, btnWidth - 30, WHITE, GREEN, BLACK, "+", 5); minus_btn.initButton(&tft, tft.width() / 2. + btnWidth / 2. + margin, 60 + 3 * 4 + 6 * 8 + (btnWidth - 30), btnWidth - 20, btnWidth - 30, WHITE, GREEN, BLACK, "-", 5); plus_btn.drawButton(false); minus_btn.drawButton(false); oldPage = currentPage; } void drawInfoScreen() { tft.setRotation(1); tft.fillScreen(BLACK);//(100, 155, 203) tft.drawRoundRect(10, 50, tft.width() - 20, tft.height() - 60, 5, GREEN); //tft.fillRect (10, 10, 60, 36); tft.setTextSize(2); tft.setTextColor(WHITE, BLACK); tft.setCursor(70, 18); tft.print("Information sur l'exemple"); tft.setTextSize(2); tft.setTextColor(WHITE, BLACK); tft.setCursor(25, 80); tft.print("En cas de probleme avec ce tutoriel,\n n'hesitez pas a nous contacter"); tft.setCursor(20, 245); tft.print("www.aranacorp.com" ); oldPage = currentPage; } /************************************************************************************ UTILITY FUNCTION *************************************************************************************/ void drawTextInRect(int x, int y, long temp, int tsize, unsigned int fColor, unsigned int bColor) { int marg = 10; char buf[12]; int nbChar = strlen(itoa(temp, buf, 10)) + 2; if (bColor != 255) tft.fillRect(x - nbChar * 3 * tsize - marg, y - nbChar * 1 * tsize - marg, nbChar * 6 * tsize + 2 * marg, nbChar * 2 * tsize + 2 * marg, bColor); tft.setTextSize(tsize); tft.setTextColor(fColor, BLACK); //tft.setCursor(x-strlen(*text)*3*tsize+marg, y+rheight/2.+marg); tft.setCursor(x - nbChar * 3 * tsize, y - nbChar * 1 * tsize); tft.print(temp); //while(*text) tft.print(*text++); tft.write(0xF7); tft.print("C"); } bool Touch_getXY(void) { p = ts.getPoint(); pinMode(YP, OUTPUT); //restore shared pins pinMode(XM, OUTPUT); digitalWrite(YP, HIGH); digitalWrite(XM, HIGH); bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE); if (pressed) { if (tft.width() <= tft.height()) { //Portrait pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height()); } else { pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width()); pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height()); } } return pressed; }
Applications
- Create a graphical interface to manage your Arduino project