This is a sketch that works fine on my ILI9341 touchscreen lcd shield:
// Included libraries
#include <Adafruit_GFX.h>
#include <Adafruit_TFTLCD.h>
#include <TouchScreen.h>
#define DEBUG // Prints helpful data via Serial if defined
// Color definitions for the tft (RGB565)
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xf81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
// These lines define the pins being used by the Arduino
// and the TFT touchscreen shield
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
#define YP A3
#define XM A2
#define YM 9
#define XP 8
// Values used to calibrate the touchscreen
#define TS_MINX 125 // Original 118
#define TS_MAXX 880 // Original 950
#define TS_MINY 76 // Original 106
#define TS_MAXY 905 // Original 933
// Values for screen width and height in landscape(!) mode
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
// tft and touchscreen declaration and initialization
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
// Enumerations for different screens and status of the application
enum statusType {WELCOME, HOME, B_SCREEN, R_SCREEN, UNDEFINED};
// prevScreen stores the last active status. It assists to recognize when actScreen has changed!
// actScreen holds the actual (or new) active status
// UNDEFINED can be used to mark an unknown/undefined status. If recognized the application may go to e.g. WELCOME
statusType prevScreen = UNDEFINED;
statusType actScreen = WELCOME;
// Variables of type "boxType" hold coordinates, dimensions and color of a box to be drawn
// A well chosen name for the box makes it easy to follow the code.
// The box data are also used to check whether a touch was inside or not.
struct boxType {
int x;
int y;
int width;
int height;
uint16_t color;
};
// The variable touch of type "touchType" holds the x,y coordinates of a touch mapped to
// the dimensions and rotation(!) of the screen. The data are only considered valid if the
// boolean variable "detected" is true.
struct touchType {
int x;
int y;
boolean detected = false;
} touch;
// The struct screenType is close to a class ;-). It holds the wanted screen rotation and
// - if the rotation is changed - automatically adopts the internal values for width and height
// External access to write or read data is only possible via specific functions to ensure
// correct matching of rotation/width/height.
// setRotation() ignores values higher than 3.
struct screenType {
private:
byte rotation = 1;
int width = SCREEN_WIDTH;
int height = SCREEN_HEIGHT;
public:
int getWidth() {
return width;
}
int getHeight() {
return height;
}
byte getRotation() {
return rotation;
}
void setRotation(byte aRot) {
switch (aRot) {
case 0:
case 2:
rotation = aRot;
width = SCREEN_HEIGHT;
height = SCREEN_WIDTH;
break;
case 1:
case 3:
rotation = aRot;
width = SCREEN_WIDTH;
height = SCREEN_HEIGHT;
break;
}
}
} screen;
// As the controller is very quick we use a time restriction for checking the touchscreen after
// a status change. This is realized in checkTouchScreen() and lasts 500 ms.
// changeTime is the global variable that keeps the time of the last status change.
unsigned long changeTime = 0;
// Here a number of "input boxes" are defined as boxType(s)
// boyType exampleBox { x coordinate, y coordinate, length, height, color}
// Most of them will be drawn but they must not ...
// Their dimensions are used later also for the boolean function "touchInside(aBox)" to detected
// whether a touch was inside their boundings or not
//
// The virtualHomeBox is never drawn, however it is used to detect a touch on a page
// inside a smaller box than the whole screen (some touchscreens create spurios touches
// at their borders ...) The color has to be set but is never used in this case.
// Use readable names (these are just examples ...) it makes live easier ...
boxType welcomeBox { 50, 180, 210, 40, BLACK};
boxType virtualHomeBox { 20, 20, 280, 200, BLACK}; // Used only for "handleBackHome" function ;-)
boxType applicationBox1 { 10, 10, 75, 75, BLUE};
boxType applicationBox2 {125, 10, 75, 75, BLUE};
boxType applicationBox3 {240, 10, 75, 75, BLUE};
// In Setup we use screen.setRotation() first to ensure the correct setting of screen.getWidth/getHeight for
// later use
//
// To keep consistency we use screen. getRotation() with tft.setRotation().
void setup() {
Serial.begin(115200);
screen.setRotation(1);
tft.reset();
uint16_t identifier = tft.readID();
tft.begin(identifier);
tft.setRotation(screen.getRotation());
}
// loop() is quite short (how I like it) so that one can identify the main functions easily
//
// checkTouchScreen()handles the touchscreen data
//
// drawNewScreen() draws a new screen if necessary
//
// screenMachine() handles the application depending on actScreen
//
void loop() {
checkTouchscreen();
drawNewScreen();
screenMachine();
}
// drawNewScreen() checks if actScreen differs from prevScreen. If not -> it relaxes and does nothing
// If they differ it draws the newly requested screen and "memorizes" this in prevScreen to avoid
// drawing the same screen again in the next loop().
void drawNewScreen() {
if (actScreen == prevScreen) { // if not new -> return immediately
return;
}
touch.detected = false;
prevScreen = actScreen;
switch (actScreen) {
case WELCOME:
drawWelcome();
break;
case HOME:
drawHome();
break;
case B_SCREEN:
drawBScreen();
break;
case R_SCREEN:
drawRScreen();
break;
default: // otherwise do nothing
break;
}
changeTime = millis(); // Store the time when the screen has changed to ignore touchscreen input for a short while ...
}
void drawHome() {
Serial.println("Home");
tft.fillScreen(RED);
printXcentered(100, "CHOOSE APPLICATION", WHITE, 2);
drawBox(applicationBox1);
drawBox(applicationBox2);
drawBox(applicationBox3);
}
void drawWelcome() {
Serial.println("Welcome");
tft.fillScreen(WHITE);
tft.drawRect(0, 0, 319, 240, WHITE);
printAt(5, 5, "WELCOME LKAT", BLACK, 2);
drawBox(welcomeBox);
tft.drawRect(50, 180, 210, 40, GREEN);
}
void drawBScreen() {
Serial.println("LEDB");
tft.fillScreen(BLUE);
printXcentered(120, "HELLO", WHITE, 2);
}
void drawRScreen() {
Serial.println("LEDR");
tft.fillScreen(GREEN);
printXcentered(120, "HELLO", BLACK, 2);
}
// screenMachine() uses the variable actScreen to choose what to do now.
// Each different status signalized by actState can (but must not necessarily) have it's own handling routine.
// These routines are coded separately later.
void screenMachine() {
switch (actScreen) {
case WELCOME:
handleWelcome();
break;
case HOME:
handleHome();
break;
case B_SCREEN:
handleBackHome();
break;
case R_SCREEN:
handleBackHome();
break;
default: // Works for UNDEFINED and others
actScreen = WELCOME;
prevScreen = UNDEFINED;
break;
}
}
void handleWelcome() {
if (touchInside(welcomeBox)) {
actScreen = HOME;
}
}
void handleBackHome() {
if (touchInside(virtualHomeBox)) {
actScreen = HOME;
}
}
void handleHome() {
if (touchInside(applicationBox1)) {
Serial.println("APP 1");
actScreen = B_SCREEN;
return;
}
if (touchInside(applicationBox2)) {
Serial.println("APP 2");
actScreen = R_SCREEN;
return;
}
if (touchInside(applicationBox3)) {
Serial.println("APP 3");
actScreen = WELCOME;
return;
}
}
// checkTouchScreen() reads touchscreen data, and - if any - maps them in accordance with
// the screen data and rotation.
void checkTouchscreen() {
if (millis() - changeTime < 500) { // To give the user some time to release the touchscreen!
touch.detected = false; // If the change was within the last 500 ms we ignore all
return; // touches
}
// Read the toucscreen
TSPoint p = ts.getPoint();
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
// Let us assume we have nothing detected ...
touch.detected = false;
if (p.z > ts.pressureThreshhold) {
// we use constrain to avoid values below TS_MIN and above TS_MAX
// in case that p.x. or p.y are outside our calibration data
int x = constrain(p.x, TS_MINX, TS_MAXX);
int y = constrain(p.y, TS_MINY, TS_MAXY);
// Assignment of touchscreen x,y and pixel x,y as well as
// the zero position depends on the screen rotation
// For reasons of comprehensibility it has been written here
// in a readable way. Sometimes TS_MIN, TS_MAX have been exchanged
// to cope with the situation that e.g. pixel.x goes from left to
// right while p.x goes from right to left etc.
// The same applies to pixel.y and p.y . Also sometimes
// touch.y has to be calculated from p.x and so on.
switch (screen.getRotation()) {
case 0 :
touch.x = map(x, TS_MAXX, TS_MINX, 0, SCREEN_HEIGHT);
touch.y = map(y, TS_MAXY, TS_MINY, 0, SCREEN_WIDTH);
break;
case 1 :
touch.x = map(y, TS_MAXY, TS_MINY, 0, SCREEN_WIDTH);
touch.y = map(x, TS_MINX, TS_MAXX, 0, SCREEN_HEIGHT);
break;
case 2 :
touch.x = map(x, TS_MINX, TS_MAXX, 0, SCREEN_HEIGHT);
touch.y = map(y, TS_MINY, TS_MAXY, 0, SCREEN_WIDTH);
break;
case 3 :
touch.x = map(y, TS_MINY, TS_MAXY, 0, SCREEN_WIDTH);
touch.y = map(x, TS_MAXX, TS_MINX, 0, SCREEN_HEIGHT);
break;
default:
touch.x = map(x, TS_MINX, TS_MAXX, 0, SCREEN_HEIGHT);
touch.y = map(y, TS_MINY, TS_MAXY, 0, SCREEN_WIDTH);
break;
}
touch.detected = true;
// if DEBUG is defined above, these helpful data
// are printed with every touch detected ...
// It also allows to check or identify the
// original touchscreen coordinates and
// to change the calibration data if required.
#ifdef DEBUG
Serial.print(p.x);
Serial.print(',');
Serial.print(p.y);
Serial.print(" - ");
Serial.print(touch.x);
Serial.print(',');
Serial.println(touch.y);
#endif
}
}
// touchInside immediately returns false if no touch was detected.
// If the touch data are valid, it checks the touch coordinates
// against the top and left boundaries of aBox,
// calculates the bottom and the right boundary of aBox
// by adding width and height to the respective coordinates
// If touch was inside it returns true, else false.
boolean touchInside(boxType &aBox) {
if (!touch.detected) {
return false;
}
return (touch.x > aBox.x && touch.x < (aBox.x + aBox.width) &&
touch.y > aBox.y && touch.y < (aBox.y + aBox.height));
}
// drawBox() draws a filled rectangle using the data of the given
// boxType structure
void drawBox(boxType &aBox) {
tft.fillRect(aBox.x, aBox.y, aBox.width, aBox.height, aBox.color);
}
// printAt() combines text color, size, position and text in one call
void printAt(int x, int y, char txt[80], uint16_t color, int tSize) {
tft.setTextColor(color);
tft.setTextSize(tSize);
tft.setCursor(x, y);
tft.print(txt);
}
// printXcenteres() combines text color, size, y position and text in one call
// It uses screen.getWidth() and the text length to calculate the x position
// where to start and then calls printAt to perform the rest.
// Just be aware: Text is limited to 80 char by this definition ...
//
void printXcentered(int y, char txt[80], uint16_t color, int tSize) {
int len = strlen(txt);
int x = screen.getWidth()/2 - (6 * tSize * len) / 2;
if (x < 0) {
x = 0;
}
printAt(x, y, txt, color, tSize);
}
I rearranged your sketch. At least on my lcd the rotation is now taken care of "automatically" so that the touch positions are mapped in accordance to the fixed touchscreen and the variable pixel coordinate system.
Using structs to store and handle the "box" dimensions allows to define their data at one place and use them for drawing as well as for detecting touches inside the boundaries.
I have not put any effort in
- changing from defines to constexpr
- using PROGMEM to store text data
- or any other optimization that might be possible
Feel free to check it out, I've put a lot of comments to allow you to follow the ideas behind.
It was an interesting task for me and I hope it is of any use for you now ... 
Good luck!
ec2021
P.S.: The function printXcentered() uses hardcoded character width that works for the given font. The function may have to be adapted to other character fonts ... As it is not a significant function I'll leave this to the final user ... 
[Edit: I just removed hardcoded data in checkTouchScreen() to make it more flexible. But not tested with other dimensions ... ]