(Solved) Touchscreen display, graphics rotated but touch input is not.

I have a 2.8 inch TFT touch screen from Elegoo that I am using on a Mega2560. I am trying to make a touch display dashboard for a car project. At the moment I am just trying to get the display and input oriented. It normally displays in Portrait mode, but I have rotated in the code to Landscape as I intended. The problem is that the touch input still acts as if it is Portrait mode, and I am not sure how to adjust it. I attached my project file.

I have made 4 buttons, defined at Line 35, and created at Line 58 and 111.
I attached an image of what I got so far. The buttons are aligned as I want them, but the touch input is as if the screen was rotated 90 degrees CCW.

The rotation is set at Line 107.
I have messed with the code in Lines 138, 139, and 144 for the p.x and p.y assignments by swapping them, and it has shown to change the touch input’s position without altering the display, but in odd areas.

The code is based from an example provided from Elegoo that I modified, so there may be redundant or unneeded lines in it.

Can this be fixed in the project code, or have to be adjusted in the library?
Thanks for any help.

dash_01.ino (5.66 KB)

Currently, you have the following code that performs the touch remap:

    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = (tft.height()-map(p.y, TS_MINY, TS_MAXY, tft.height(), 0));

If you alter the touch calc along the following lines, is the rotation mapping closer to what you're after? (Note: you will likely still need to flip the X & Y ranges according to your display orientation)

    int16_t nTmpX = p.x;
    p.x = map(p.y, TS_MINY, TS_MAXY, 0, 320);
    p.y = 240-map(nTmpX, TS_MINX, TS_MAXX, 0, 240);  
    tft.fillRect(p.x-1,p.y-1,2,2,YELLOW); // DEBUG touch feedback

I recommend inserting visual feedback into the touch handling logic (as above) to help debug the rotation / calibration, as opposed to testing buttons for response.

Thank you very much, this code seems to have done the trick. The only thing I had to do was remove the “240-” from the “p.y =” line, as that inverted it to my screen. The yellow touch tracking is working.

I had to tweak the touch screen area values for perfect fit at Line 49:

#define TS_MINX 120
#define TS_MAXX 920

#define TS_MINY 100
#define TS_MAXY 900

An odd problem I’ve run into is that there is a certain area of the screen where it shows it pushing the buttons, even though I am touching in a different area. [See attached image] The yellow line indicates the area I am touching with the stylus, but the buttons show to activate, with the top of the line linked to the rightmost button, following down to the second from the left button. I attached the touch feedback line into the original code from which mine is derived from, and it still does that in the same area, but linking to other buttons. Since the touch feedback appears to be working, I’n assuming this is code related to work out.

Much appreciated.

Personally, I find it easier to think in terms of TS_LEFT, TS_RT, TS_TOP, TS_BOT.

The majority of TFT controllers are native Portrait e.g. 240x320
So I identify the XP, XM Touch pins in the short (240) direction. And YP, YM in the long (320) direction.

You can display in Portrait, Landscape, Portrait_Rev, Landscape_Rev to suit your project (in software).

The Touch wiring does not change. It is fixed in hardware.

I calibrate in the (native) Portrait mode to get the TS_LEFT, TS_RT, TS_TOP, TS_BOT values.
Then you simply have to map the ADC value returned by the Touchscreen library to the pixel x, y coordinates. e.g.

pixel_x = map(p.x, TS_LEFT, TS_RT, 0, width);
pixel_y = map(p.y, TS_TOP, TS_BOT, 0, height);

Touch Panels are wired forwards or backwards and sometimes with swapped x, y.
Yes, you can rearrange TS_MINX, TS_MAXX values that are not very intuitive (to me)
Surely TS_LEFT, TS_RT are easier to visualise.

When you rotate the screen e.g. from Portrait to Landscape, Portrait_Rev, Landscape_Rev the Touchscreen library still gives you the same Portrait p.x, p.y ADC values but you map them to pixels.

Draw a portrait rectangle on paper. Mark L, R, T, B on each edge.
Now rotate to Landscape, Portrait_Rev, Landscape_Rev. You can see exactly how to do the mapping.

It all comes down to what you feel most comfortable with.

  1. LEFT, RT, ...
  2. MINX, MAXX, ... with FLIP_X, FLIP_Y, FLIP_XY booleans

My brain is happier with LEFT, RT, TOP, BOT
Software is easier to manipulate with the 3 different FLIP logical values.

IMHO, contortions like this make my brain hurt:

    p.y = (tft.height()-map(p.y, TS_MINY, TS_MAXY, tft.height(), 0));

Surely this is better:

    p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());

But this is intuitive:

    pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());


Allen42B – David provides a great explanation of a more intuitive way to think about touch “calibration”. I also agree that LEFT, RT, TOP, BOT are much clearer, though Adafruit, Elegoo and others tend to follow the MIN/MAX convention. David, if you can believe it, the Elegoo library example code actually uses the “contortion” p.y = (tft.height()-map(p.y, TS_MINY, TS_MAXY, tft.height(), 0))!

However, from your test result using the yellow markers, I think we have already successfully calibrated the display, suggesting that there is another issue at play with the button mapping.

First, to restate your current calibration using the Elegoo notation (prior to adjusting in line with David’s recommendation, but explicitly expanding out your calibration parameters), I believe you have:

    int16_t nTmpX = p.x;
    p.x = map(p.y, 120, 920, 0, 320);
    p.y = map(nTmpX, 100, 900, 0, 240); 
    tft.fillRect(p.x-1,p.y-1,2,2,YELLOW); // DEBUG touch feedback

Upon a quick look at the Elegoo_GFX_Button code on the Elegoo website, I think there may be a bug in the way the contains() function works with respect to the “y” parameter, but I don’t think this would lead to the larger issue you are seeing.

First, to confirm your observation, I understand that you are seeing that touch over top of your “Stuff” button does not cause the button to toggle BUT touch in the top left corner of the display causes the “Stuff” button to toggle.

Looking at the way the button handling is done with the calls to buttons.contains(), it seems like the code is making assumptions about the p.x & p.y values when there is no touch occurring, instead of testing for p.z. So, unless I misunderstood the way the Elegoo handler is written, I would expect that the following would be more robust. There are much cleaner ways to do this, but it was intended only as a minor adjustment to what you already have:

  bool bTouched = false;
  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    // scale from 0->1023 to tft.width
    int16_t nTmpX = p.x;
    p.x = map(p.y, 120, 920, 0, 320);
    p.y = map(nTmpX, 100, 900, 0, 240);
    bTouched = true;
    tft.fillRect(p.x - 1, p.y - 1, 2, 2, YELLOW); // DEBUG touch feedback

  // go thru all the buttons, checking if they were pressed
  for (uint8_t b = 0; b < 4; b++) {
    if (!bTouched) {
      // Ensure all buttons are released if no press
    } else {
      // Ensure touch press coordinates (x,y) are only tested when pressed
      if (buttons[b].contains(p.x, p.y)) {
        //Serial.print("Pressing: "); Serial.println(b);
        buttons[b].press(true);  // tell the button it is pressed
      } else {
        buttons[b].press(false);  // tell the button it is NOT pressed

I would be interested to know if this changes the behavior you are experiencing. If not, then at least the p.z return value would be handled more explicitly now.

Thank you everyone for your replies. Sorry I didn't get back to them earlier. Been busy with the weekend.

First, to confirm your observation, I understand that you are seeing that touch over top of your "Stuff" button does not cause the button to toggle BUT touch in the top left corner of the display causes the "Stuff" button to toggle.

To clarify, touching each button area does toggle its respective button. This is visually confirmed by the button flashing, and serial output where button activation is displayed. What occurs is that the highlighted area by the touch feedback in the upper left area also causes the buttons toggle. Touching the top of the line to the bottom causes the rightmost button to toggle to the 2nd-from-the-left button respectively.

I have since implemented your code, and the touch feedback shows that the buttons do not unwillingly toggle anymore. Apparently, it was some sort of bug in the original code I based this on. I'll also clear up the code as I go along to make the declarations more user friendly.

Thank you again everyone, this solves my problem.


I have kind of the same problem... I am using ADAFRUIT_GFX.h, Adafruit_TFTLCD.h and Touchscreen.h
I need to change the setting of the rotation of the screen in:

tft.setRotation(1) --> tft.setRotation(3)

So now the screen is horizontally flipped. However, I do not know how to make the same change to the touchscreen. Currently, my p.x and p.y parameters are:

   p.x = map(p.x, TS_MINX, TS_MAXX, tft.height(),0);
   p.y = map(p.y, TS_MINY, TS_MAXY, tft.width(), 0);
   px = p.y;
   py = p.x;

I thought that flipping the parameters in it, would solve my problems... but it did not. I do not know if I am targeting the correct variable to solve my problem or if I was not correct in flipping the parameters. Can anyone help me? Thanks.

tft.setRotation(1) means LANDSCAPE
tft.setRotation(3) means LANDSCAPE_REV i.e. rotated 180 degrees.

TouchScreens will normally expect PORTRAIT i.e. rotation=0

Take a small rectangle of paper
Put it in Portrait i.e. height > width
Draw LEFT, TOP, RIGHT, BOT on each edge of the paper.

Now rotate the paper e.g. LANDSCAPE. Observe how the X and Y directions work now.

Write the appropriate map() statements.


p.s. LEFT and RT are obvious to humans. If you like MINX, MAXX that is your choice. (It makes my head hurt)

Thanks for the advice. I just needed to flip two parameters. I leave the solution here in case anybody is in the same situation as myself:

    p.x = map(p.x, TS_MINX, TS_MAXX, 0, tft.height());
    p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.width()); 
    px = p.y;
    py = p.x;

Your code could be simpler:

    py = map(p.x, TS_MINX, TS_MAXX, 0, tft.height());
    px = map(p.y, TS_MINY, TS_MAXY, 0, tft.width());

Assuming PORTRAIT Calibration. You could make it human readable:

    py = map(p.x, TS_LEFT, TS_RT, 0, tft.height());
    px = map(p.y, TS_BOT, TS_TOP, 0, tft.width());