This example demonstrates how to calibrate the touch screen on the ESP32‑WROOM‑32E development board with a 2.8‑inch TFT LCD touch screen. The program guides the user to touch five specified positions on the screen, automatically collects raw ADC data from the touch controller, calculates calibration parameters, and outputs them to the serial monitor. After calibration, the program tests the calibration results in four screen rotation orientations, helping users verify the correctness of the calibration parameters. This example is suitable for all resistive touch screen projects using the TFT_Touch library.
Touch_Calibrate.ino fileSerial.begin(38400) in the code)First point : Raw x,y = 345, 3780
Second point : Raw x,y = 2048, 2048
...
//This is the calibration line to use in your sketch
//you can copy and paste into your sketch setup()
touch.setCal(380, 3800, 350, 3750, 320, 240, 0);
The TFT screen (landscape 320×240) displays red crosses at five calibration points in sequence:
| Calibration Point | Screen Coordinates | Description |
|---|---|---|
| Point 1 | (30, 30) | Top‑left corner |
| Point 2 | (160, 120) | Screen centre |
| Point 3 | (290, 210) | Bottom‑right corner |
| Point 4 | (160, 120) | Screen centre (again) |
| Point 5 | (30, 210) | Bottom‑left corner |
In test mode, the screen displays:
| Area | Description |
|---|---|
| Top Text | Shows current screen rotation orientation |
| Coordinates | Displays current touch point X and Y values |
| Green Cross | Follows touch position, verifying calibration effect |
⚠️ Key Notes:
touch.setCal() parameters output after calibration can be copied directly into other projectsThis example code is based on the ESP32‑WROOM‑32E development board. The program implements the complete touch screen calibration flow, including data acquisition, parameter calculation, calibration verification, and rotation testing.
#include <TFT_eSPI.h>
#include <SPI.h>
#include <TFT_Touch.h>
TFT_eSPI my_lcd = TFT_eSPI();
#define RTP_DOUT 39
#define RTP_DIN 32
#define RTP_SCK 25
#define RTP_CS 33
TFT_Touch touch = TFT_Touch(RTP_CS, RTP_SCK, RTP_DIN, RTP_DOUT);
int X_Raw = 0, Y_Raw = 0;
TFT_eSPI.h: TFT screen driver library for display graphics and textTFT_Touch.h: Touch screen library for reading touch coordinates and raw ADC valuesmy_lcd: TFT screen object for display operationstouch: Touch screen object for reading touch dataX_Raw, Y_Raw: Store the current touch point's raw ADC values (0~4095)void setup()
{
Serial.begin(38400);
my_lcd.init();
my_lcd.setRotation(1);
my_lcd.setTextDatum(TC_DATUM);
my_lcd.setTextColor(TFT_WHITE, TFT_BLACK);
}
Serial.begin(38400): Initializes serial communication at 38400 baud. Note: The calibration program uses 38400 instead of the common 115200 – set the serial monitor correctlymy_lcd.init(): Initializes TFT screen hardwaremy_lcd.setRotation(1): Sets screen to landscape mode (320×240)my_lcd.setTextDatum(TC_DATUM): Sets text alignment to Top Centremy_lcd.setTextColor(TFT_WHITE, TFT_BLACK): White text on black backgroundThe loop() function executes the complete five‑point calibration process.
touch.setCal(0, 4095, 0, 4095, 320, 240, 0);
my_lcd.setRotation(1);
touch.setRotation(1);
my_lcd.fillScreen(TFT_BLACK);
drawPrompt();
touch.setCal(0, 4095, 0, 4095, 320, 240, 0): Resets touch calibration to raw ADC range (0~4095) with no coordinate transformationtouch.setRotation(1): Sets touch rotation to match LCD (landscape)drawPrompt(): Calls custom function to display calibration instructions on screen// Point 1: Top‑left (30, 30)
drawCross(30, 30, TFT_RED);
while (!touch.Pressed());
delay(100);
getCoord();
drawCross(30, 30, TFT_BLACK);
x1 = X_Raw;
y1 = Y_Raw;
// Point 2: Screen centre (160, 120)
drawCross(my_lcd.width()/2, my_lcd.height()/2, TFT_RED);
while (getCoord());
drawCross(my_lcd.width()/2, my_lcd.height()/2, TFT_BLACK);
x2 = X_Raw;
y2 = Y_Raw;
// Point 3: Bottom‑right (290, 210)
drawCross(my_lcd.width()-30, my_lcd.height()-30, TFT_RED);
while (!getCoord());
delay(10);
getCoord();
drawCross(my_lcd.width()-30, my_lcd.height()-30, TFT_BLACK);
x2 = X_Raw;
y2 = Y_Raw;
// Point 4: Screen centre (again)
drawCross(my_lcd.width()/2, my_lcd.height()/2, TFT_RED);
while (getCoord());
drawCross(my_lcd.width()/2, my_lcd.height()/2, TFT_BLACK);
// Point 5: Bottom‑left (30, 210)
drawCross(30, my_lcd.height()-30, TFT_RED);
while (!getCoord());
delay(10);
getCoord();
drawCross(30, my_lcd.height()-30, TFT_BLACK);
x3 = X_Raw;
y3 = Y_Raw;
Collection flow for each calibration point:
drawCross(x, y, TFT_RED): Draws a red cross at the specified positionwhile (!touch.Pressed()): Waits for the user to touch (blocking)delay(100): Brief delay to prevent bouncinggetCoord(): Captures raw ADC values into X_Raw and Y_RawdrawCross(x, y, TFT_BLACK): Erases the red cross with blackSerial.print(): Outputs the raw values to the serial monitorX_Raw, Y_Raw into variables x1/y1, x2/y2, x3/y3int temp;
if (abs(x1 - x3) > 1000) {
xyswap = 1;
temp = x1; x1 = y1; y1 = temp;
temp = x2; x2 = y2; y2 = temp;
temp = x3; x3 = y3; y3 = temp;
}
else xyswap = 0;
int hmin = x1;
int hmax = x2;
int vmin = y1;
int vmax = y2;
abs(x1 - x3) > 1000: Checks if the X‑coordinate difference between top‑left and bottom‑left points is greater than 1000
xyswap = 1)x1/y1, x2/y2, x3/y3 are exchangedhmin = Minimum horizontal value (top‑left X)hmax = Maximum horizontal value (bottom‑right X)vmin = Minimum vertical value (top‑left Y)vmax = Maximum vertical value (bottom‑right Y)Serial.println();
Serial.println(" //This is the calibration line to use in your sketch");
Serial.println(" //you can copy and paste into your sketch setup()");
Serial.print(" touch.setCal(");
Serial.print(hmin); Serial.print(", ");
Serial.print(hmax); Serial.print(", ");
Serial.print(vmin); Serial.print(", ");
Serial.print(vmax); Serial.print(", ");
Serial.print(my_lcd.width()); Serial.print(", ");
Serial.print(my_lcd.height()); Serial.print(", ");
Serial.print(xyswap);
Serial.println(");");
Example output:
//This is the calibration line to use in your sketch
//you can copy and paste into your sketch setup()
touch.setCal(380, 3800, 350, 3750, 320, 240, 0);
Parameter descriptions:
hmin: Minimum horizontal raw ADC value (left edge)hmax: Maximum horizontal raw ADC value (right edge)vmin: Minimum vertical raw ADC value (top edge)vmax: Maximum vertical raw ADC value (bottom edge)320: Screen width240: Screen height0: Whether to swap X/Y axes (0=no swap, 1=swap)touch.setCal(hmin, hmax, vmin, vmax, my_lcd.width(), my_lcd.height(), xyswap);
my_lcd.setRotation(1);
touch.setRotation(1);
test();
my_lcd.setRotation(2);
touch.setRotation(2);
test();
my_lcd.setRotation(3);
touch.setRotation(3);
test();
my_lcd.setRotation(0);
touch.setRotation(0);
test();
touch.setCal(...): Applies the calculated calibration parameters to the touch librarytest() for rotation orientations 1, 2, 3, and 0drawPrompt() Functionvoid drawPrompt(void)
{
my_lcd.setTextColor(TFT_WHITE, TFT_BLACK);
int centre = my_lcd.width()/2;
my_lcd.drawString("CALIBRATION", centre, 20, 2);
my_lcd.drawString("Touch the red cross accurately", centre, 61, 2);
my_lcd.drawString("( using a cocktail stick works well! )", centre, 81, 2);
}
drawCross() Functionvoid drawCross(int x, int y, unsigned int color)
{
my_lcd.drawLine(x - 5, y, x + 5, y, color);
my_lcd.drawLine(x, y - 5, x, y + 5, color);
}
(x, y) (horizontal and vertical lines, 10 pixels each)getCoord() Functionbool getCoord()
{
bool Xwait = 1, Ywait = 1;
int X_Temp1 = 9999, Y_Temp1 = 9999;
int X_Temp2 = -1, Y_Temp2 = -1;
X_Raw = -1;
Y_Raw = -1;
while (Xwait || Ywait) {
if (touch.Pressed()) {
X_Temp1 = touch.RawX();
Y_Temp1 = touch.RawY();
}
delay(5);
if (touch.Pressed()) {
X_Temp2 = touch.RawX();
Y_Temp2 = touch.RawY();
}
#define RAW_ERROR 10
if ( (abs(X_Temp1 - X_Temp2) < RAW_ERROR) && Xwait ) {
X_Raw = (X_Temp1 + X_Temp2) / 2;
Xwait = 0;
}
if ( (abs(Y_Temp1 - Y_Temp2) < RAW_ERROR) && Ywait ) {
Y_Raw = (Y_Temp1 + Y_Temp2) / 2;
Ywait = 0;
}
}
if ((X_Raw > 1365) && (X_Raw < 2731) && (Y_Raw > 1365) && (Y_Raw < 2371)) return 0;
else return 1;
}
This is the core function of the calibration program – key points to understand:
Two‑sample verification:
X_Temp1/Y_Temp1 and X_Temp2/Y_Temp2)delay(5))RAW_ERROR (10), the data is considered stableData stability check:
if ( (abs(X_Temp1 - X_Temp2) < RAW_ERROR) && Xwait )
abs(X_Temp1 - X_Temp2) < 10: Difference between two samples is less than 10Xwait: X‑axis data has not been collected yetReturn value meaning:
return 0: Touch point is in the centre area (for centre point collection)return 1: Touch point is at the edge (for corner collection)Centre area detection:
if ((X_Raw > 1365) && (X_Raw < 2731) && (Y_Raw > 1365) && (Y_Raw < 2371)) return 0;
test() Functionvoid test(void)
{
my_lcd.fillScreen(TFT_BLACK);
drawCross(30, 30, TFT_WHITE);
drawCross(my_lcd.width() - 30, my_lcd.height() - 30, TFT_WHITE);
int centre = my_lcd.width()/2;
String text;
text+= "Screen rotation = ";
text+= my_lcd.getRotation();
char buffer[30];
text.toCharArray(buffer,30);
my_lcd.drawString(buffer, centre, 50, 2);
my_lcd.drawString("Touch anywhere on screen", centre, 70, 2);
my_lcd.drawString("to test settings", centre, 90, 2);
my_lcd.drawString("Send a character from the", centre, 120, 2);
my_lcd.drawString("IDE Serial Monitor to", centre, 140, 2);
my_lcd.drawString("continue!", centre, 160, 2);
while (Serial.available()) Serial.read();
while (!Serial.available()) {
if (touch.Pressed()) {
int X_Coord = touch.X();
int Y_Coord = touch.Y();
drawCross(X_Coord, Y_Coord, TFT_GREEN);
my_lcd.setCursor(centre, 0, 2);
my_lcd.print("X = "); my_lcd.print(X_Coord); my_lcd.print(" ");
my_lcd.setCursor(centre, 20, 2);
my_lcd.print("Y = "); my_lcd.print(Y_Coord); my_lcd.print(" ");
}
}
}
while (Serial.available()) Serial.read(): Clears the serial bufferwhile (!Serial.available()): Waits for the user to send any character to advanceThis example program implements a complete five‑point touch screen calibration process:
hmin, hmax, vmin, vmax, and xyswap from collected datatouch.setCal() format to the serial monitorKey functions:
touch.setCal(): Sets touch calibration parameterstouch.Pressed(): Detects touchtouch.RawX() / touch.RawY(): Reads raw ADC values (0~4095)touch.X() / touch.Y(): Reads calibrated coordinatestouch.setRotation(): Sets touch rotation orientationdrawCross(): Draws cross markersgetCoord(): Collects stable raw touch valuestest(): Tests calibration results#define RTP_DOUT 39
#define RTP_DIN 32
#define RTP_SCK 25
#define RTP_CS 33
Serial.begin(38400); // Change to other baud rates (must match serial monitor)
my_lcd.setRotation(1); // 0:portrait, 1:landscape, 2:portrait flipped, 3:landscape flipped
touch.setRotation(1); // Must match LCD
#define RAW_ERROR 10 // Smaller = stricter, larger = more tolerance for jitter
drawCross(30, 30, TFT_RED); // Top‑left
drawCross(my_lcd.width()-30, my_lcd.height()-30, TFT_RED); // Bottom‑right
drawCross(30, my_lcd.height()-30, TFT_RED); // Bottom‑left
test(); // Orientation 1
test(); // Orientation 2
test(); // Orientation 3
test(); // Orientation 0
Comment out the test() calls at the end of loop():
// my_lcd.setRotation(1);
// touch.setRotation(1);
// test();
// ...
No serial monitor output
Touch not responding
Inaccurate calibration parameters
Touch coordinates are reversed after calibration
xyswap parameter is correct (0 or 1)hmin/hmax and vmin/vmax are in the correct orderTouch offset in test mode
getCoord() function hangs
RAW_ERROR (10)RAW_ERROR valueScreen display abnormal
User_Setup.h is correctly configured for ILI9341