Screen rotation

Description

I try to rotate the display in the same orientation as the touchscreen. When I use the lv_display_set_rotation() function, my display is rotated, but the screen width is truncated to 240 pixels, while it should be 320 pixels. In fact, I’m getting a 240 x 240 display. When I use the corresponding TF_eSPI function tft.setRotation(), the display rotation is performed as expected.
I tried different rotation settings. The content of the display is rotated as required, but the display is always 240x240.

What MCU/Processor/Board and compiler are you using?

CYD = ESP32 with XPT2046 touch controler and ILI9341 display controller

What LVGL version are you using?

9.2

What do you want to achieve?

support rotation in all 4 orientations

What have you tried so far?

see description section
I also tried without swapping height and width in the #define section, but the result is the same.

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

/*You code here*/

#define LV_DISPLAY_ROTATION_0 0 // portrait, USB bottom side
#define LV_DISPLAY_ROTATION_90 1 // landscape, USB right side
#define LV_DISPLAY_ROTATION_180 2 // portrait, USB top side
#define LV_DISPLAY_ROTATION_270 3 // landscape USB left side

#define TFT_ROTATION LV_DISPLAY_ROTATION_90 // landscape, USB right side

// swap height and width according to rotation setting

#if (TFT_ROTATION == LV_DISPLAY_ROTATION_0) || (TFT_ROTATION == LV_DISPLAY_ROTATION_180)
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#else
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#endif

… some more code

in setup function:
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(TFT_ROTATION);
… some more code …
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
// lv_display_set_rotation(disp, (lv_display_rotation_t)TFT_ROTATION); // use LVGL lib to set rotation doesn’t work!
tft.setRotation(TFT_ROTATION); // use TFT lib to set rotation

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

What about ui elements configuration - for example active screen size ? Is there screen-object size reconfiguarion in your code after rotation activation ?

Need to do more testing to sort this out. I replaced the tft.rotateScreen() function with lv_display_set_rotation(). Now it is working (kindof…). My button on the screen is rotated, but the touch area which belongs to that button is not rotated. Maybe I have to destroy the existing button and simply create it again after rotating?
Note: my initial thought to use the tft function iso the lvgl function to rotate the screen comes from the fact that the lvgl function did not work for the initial totation setting in setup(). the tft function did the job. Just out of curiosity I enabled the lvgl rotation function again after doing the tft rotation is setup() and my screen remains intact. I know it’s silly to set the same rotation using 2 different functions, but I just wanted to know if the lvgl rotation is working or not. After setting the rotation using tft I can use the lvgl function for any subsequent rotation (except for the fact that the touch areas aer not rotated with their visual object (like a button). Looks like something is not properly initialized upon first use?

I have made a sample code for the Lilygo T-Display S3. The display rotation works very well.
See here

Thomas, thanks for trying, but I’m working with CYD and ESP32 on Arduino IDE.

Meanwhile I started from scratch again. First a few tutorial sketches with the basics for CYD and ESP32. No problems. Then I started using the example which comes with the Arduino (v 2.3.4) IDE: File/examples/lgvl/arduino/LGVL_Arduino. The sketch is basically a template with suggestions how to add lgvl-related funtions to it. Went OK up to the point where I’m mapping my touch points to display points. Still OK for all rotation values. Next step in that function (my_touchpad_read( lv_indev_t * indev, lv_indev_data_t * data ) is to pass the display points x/y to lvgl indev function by populating data->point.x and data->point.y with the diaply coordinates of my touch point. And that’s where things seem to wrong. Either I’m passing bad data to LVGL or something is very wrong with the way how LVGL Indev() is processing the data. I’ts probably me, but either way, I’m stuck.
When I follow the Random Nerd Tutorials in their CYD eBook, things don’t work the way I expect (results on display look OK, but serial port in the IDE is drowning in error messages) using their code and I’m going totally overboard when I try to adapt their sketch for a different orientation. Looks like setting rotation for touch screen, lvgl and my user code are fighting each other.
Small consolation: looks like I’m not the only one having trouble using screen rotation with LGVL.
Code:
``
/Read the touchpad/
void my_touchpad_read( lv_indev_t * indev, lv_indev_data_t * data )
{
// Checks if Touchscreen was touched, and reports Display.x, Display.y and Touch.Pressure (z)
if(touchscreen.tirqTouched() && touchscreen.touched()) {

// Get RAW Touchscreen points

TS_Point p = touchscreen.getPoint();

// translate RAW TS points to calibrated Display points

switch(TFT_ROTATION) {
  case LV_DISPLAY_ROTATION_0 :                                              // rotation = 0 - portrait, USB @ bottom side
    x = map(p.x, 280, 3850, 0, TFT_WIDTH);
    y = map(p.y, 200, 3730, 0, TFT_HEIGHT);
    x = constrain(x, 0, TFT_WIDTH - 1);
    y = constrain(y, 0, TFT_HEIGHT - 1);
    break;

  case LV_DISPLAY_ROTATION_90:                                              // rotation = 1 - landscape, USB @ right side
    x = map(p.x, 180, 3750, 0, TFT_HEIGHT);
    y = map(p.y, 275, 3850, 0, TFT_WIDTH);
    x = constrain(x, 0, TFT_HEIGHT - 1);
    y = constrain(y, 0, TFT_WIDTH - 1);
    break;

  case LV_DISPLAY_ROTATION_180:                                             // rotation = 2 - portrait, USB @ top side
    x = map(p.x, 275, 3850, 0, TFT_WIDTH);
    y = map(p.y, 350, 3900, 0, TFT_HEIGHT);
    x = constrain(x, 0, TFT_WIDTH - 1);
    y = constrain(y, 0, TFT_HEIGHT - 1);
    break;

  case LV_DISPLAY_ROTATION_270:                                             // rotation = 3 - landscape, USB @ left side
    x = map(p.x, 380, 3800, 0, TFT_HEIGHT);
    y = map(p.y, 260, 3780, 0, TFT_WIDTH);
    x = constrain(x, 0, TFT_HEIGHT - 1);
    y = constrain(y, 0, TFT_WIDTH - 1);
    break;

  default:
    Serial.printf("Error: invalid rotation value = %d", TFT_ROTATION);
    x = 0;
    y = 0;
    break;
}

Serial.printf("\ntouch points p.x, p.y mapped to x = %d, y = %d\n", x, y);

// pass those DP points to indev.data

data->state = LV_INDEV_STATE_PRESSED;
data->point.x = x;
data->point.y = y;

}
else{
data->state = LV_INDEV_STATE_RELEASED;
}
}

Hmm i dont understand your query. Indev cb isnt part of LVGL. Manage rotation require special display and special touch code. Plus map used x and p.x require after rotatio x and p.y …

Hi Marian_M,
the use of the indev callback is documented here: Input Device (lv_indev) — LVGL documentation. It is also present in many examples and tutorials, a.o. in the (only) example contained in the Arduino IDE (v 2.3.4).
I’m using LVGL version 9.2.2. Maybe you’re referring to an older version?
Meanwhile I’m testing what seems to be a fix. I’ll get back to this topic when I can confirm that my fix does the job.
If you can elaborate on your suggestion about special display and special touch code, please show a code example. I haven’t seen anything special in the tutorials. All tuto’s I’ve seen map or calibrate the x/y values before populating the data->x/y values.

Yes documented is as ready to use interface, but no working code. As you can see i mark it for you

and here in EXAMPLE no chaos map or orientation. And too here example dont show how touchpad_x is get.
Seems many peaple mean, that example code is for skip learning .

Been there, tried that and it doesn’t work.
When I feed the data->point struct with the touch coordinates, I get an endless stream of warnings from indev:

23:39:27.210 → [Warn] (8.310, +30) indev_pointer_proc: X is 2143 which is greater than hor. res lv_indev.c:699
23:39:27.243 → [Warn] (8.310, +30) indev_pointer_proc: X is 2143 which is greater than hor. res lv_indev.c:699

This confirms my suspicion that indev expects display coordinates, not touch coordinates.
Looks like a bug to me. But I’m an LGVL noob…
When I create a macro #define LV_INDEV_POINTER_ROTATED and bypass the rotation code in lv_indev.c like this (lines 679…)

#ifndef LV_INDEV_POINTER_ROTATED

    if(disp->rotation == LV_DISPLAY_ROTATION_180 || disp->rotation == LV_DISPLAY_ROTATION_270) {
        data->point.x = disp->hor_res - data->point.x - 1;
       data->point.y = disp->ver_res - data->point.y - 1;
    }

    if(disp->rotation == LV_DISPLAY_ROTATION_90 || disp->rotation == LV_DISPLAY_ROTATION_270) {
        int32_t tmp = data->point.y;
        data->point.y = data->point.x;						// swap x, y
        data->point.x = disp->ver_res - tmp - 1;
    }

#endif

then all tuto’s work like they are supposed to.
I may be completely wrong, but then I’d like to see some code that really works on a CYD device, nut just an example code that obviously hasn’t been tried :wink:

Finally sorted it…
The function indev_pointer_proc(…) rotates the display coordinates calculated by the user’s code which maps touchscreen coordinates to display coordinates.
But there are two issues:
(1) It is assumed that the input/output mapping is done based on the default orientation (= portrait, origin of input and output in the top left corner), regardless of the actual rotation setting for the display. The indev_pointer_proc(…) function will then transpose those display coordinates to the display coordinates which belong to the currently set rotation for the display.
(2) The documentation in “Input Device (lv_indev) — LVGL documentation” is incorrect. The example on that page says:

if(touchpad_pressed) {
    data->point.x = **touchpad_x**;
    data->point.y = **touchpad_y**;
    data->state = LV_INDEV_STATE_PRESSED;
} else {
    data->state = LV_INDEV_STATE_RELEASED;
}

This suggests that the x/y values passed to the data struct are touchpad x/y values. But the values processed by that function are display x/y values!

Recommendation:
1 - fix the documentation by using a correct code example
2 - add a warning to the documentation that final rotation is performed by LVGL, hence the input device (i.e. the touch screen) must NOT be rotated to the same value as the output defvice (i.e. the display). If needed the input device can be rotated (usually by 180°) to put the origin for the input device in the same corner as the output device, again without additional rotation to synchronize with the rotation (if any) of the display.

There is more to it…
The code in function
static void indev_pointer_proc(lv_indev_t * i, lv_indev_data_t * data)
in file …\Arduino\libraries\lvgl\src\indev\lv_indev.c
doesn’ do the rotation correcly.
Fix:
(1) fix the documentation by emphasizing that the touchscreen and the display must have their origin in the same corner (upper left corner). If that’s not the case rotate the touchscreen 180° using the function touchscreen.setRotation(2).
(2) Never rotate the touchscreen when rotating the display.
(3) Fix the code. replace this section of code in file “\lv_indev.c”

    if(disp->rotation == LV_DISPLAY_ROTATION_180 || disp->rotation == LV_DISPLAY_ROTATION_270) {		// mirror x,y
        data->point.x = disp->hor_res - data->point.x - 1;
        data->point.y = disp->ver_res - data->point.y - 1;
    }

    if(disp->rotation == LV_DISPLAY_ROTATION_90 || disp->rotation == LV_DISPLAY_ROTATION_270) {			// swap x, y
        int32_t tmp = data->point.y;
        data->point.y = data->point.x;
        data->point.x = disp->ver_res - tmp - 1;
    }

with this:

switch(disp->rotation) {
	case LV_DISPLAY_ROTATION_90:
		tmp = data->point.y;
		data->point.y = disp->hor_res - data->point.x - 1;
		data->point.x = tmp;
	break;
		
	case LV_DISPLAY_ROTATION_180:
		data->point.x = disp->hor_res - data->point.x - 1;
		data->point.y = disp->ver_res - data->point.y - 1;
	break;
		
	case LV_DISPLAY_ROTATION_270:
		tmp = data->point.y;
		data->point.y = data->point.x;
		data->point.x = disp->ver_res - tmp - 1;
	break;
	}
2 Likes

Hi Guilbert54,
I also have a CYD. Needed to rotate the screen in landscape and spent long hours playing with different configurations trying to get touchscreen coordinates to match screen coordinates until I came across your post.

Thanks to your clear explaination of the problem and the code to fix it, it works perfectly now!

Thanks again!

Hi Guilbert54,
I had the same problem with a ST7789 Display and a FT6336U Touch driver.
My routine to convert the Coordinates worked perfectly with TFT_eSPI only. But as soon as I wanted to use it with LVGL the touch coordinates did not match the LVGL Objects until i changed the lv_indev.c file according to your explanations.
Is there a way to keep your changes within lv_indev.c permanent for further projects or newer lvgl versions?

Thank you very much

Hi,
I’m not a member of the LVGL team, so I have no say in their dev program. That rotation works as expected with TFT_ESPI is normal, because you have to perform rotation on the touch screen and the display separately. But LVGL does all the rotating for you.
Step 1: the user code must “translate” the touch screen coordinates to display coordinates, assuming no rotation at all. The “translation” can be done using whatever the user prefers and will be as accurate as the algorithm allows (such as simple mapping or using a more sophisticated code as described in the TI paper (SLYT277).
Step 2: use the resuling display coordinates and feed them to the indev code as data points. LVGL will then rotate the result to the desired rotation. At least that’s the goal. But indev doesn’t do it correctly.
If you rotate the touchscreen point by user code (as you would do using TFT_ESPI) and then feed the result to indev, your touch coordinates will be rotated twice. Hence my inital difficulty to sort out this mess. Documentation can be better. Moreover the LVGL doc is incorrect as it suggests to feed the touchscreen X and Y values to indev i.s.o. the display coordinates.
It took me a while to figure this out and to come up with a fix for the indev code. It doesn’t matter which display your touch controller you uses. The logic which is applied for rotation is the same for all controllers, provided that they are supported by LGVL.

1 Like

Hi,
thank you very much. I forked the lvgl source from github into my github and modified the code there according your suggestion above. I will now use my github fork within PlatformIO in order to create new projects. As soon as I see that a new Version of lvgl is available i will then sync my fork again.
Thank you very much.