Screen rotation like a smartphone in runtime

Description

I have been happy too soon with a previous thread that I posted at Screen orientation change in runtime, like a smartphone. The result is valid only for a full-screen button! When a smaller button is created, screen-rotation does not work.

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

STM32L4xx

What LVGL version are you using?

v7.3.x

What do you want to achieve?

Screen rotation in runtime for four angles DISP_0_DEG, DISP_90_DEG, DISP_180_DEG, and DISP_270_DEG

What have you tried so far?

Rotation working for full-screen button only but not a smaller button

Code to reproduce

/*Function below shows how a point (x,y) transformed in my code*/
static lv_point_t orientation_change(lv_disp_t *disp, disp_orientation_t orientation, lv_point_t lv_point)
{
	(void) disp;
    //scanning direction in OLED's device space is not changed for different LVGL orientation
    //we need to transform a point in VDB space (lv_point) to device space (dev_point)
	lv_point_t dev_point; 

	switch(orientation)
	{
	case DISP_270_DEG:
		dev_point.x = lv_point.y;
		dev_point.y = (lv_disp_get_hor_res(NULL)-1)- lv_point.x;
		break;
	case DISP_180_DEG:
		dev_point.x = (lv_disp_get_hor_res(NULL)-1)- lv_point.x;
		dev_point.y = (lv_disp_get_ver_res(NULL)-1)- lv_point.y;
		break;
	case DISP_90_DEG:
		dev_point.x = (lv_disp_get_ver_res(NULL)-1)- lv_point.y;
		dev_point.y = lv_point.x;
		break;
	default:
		dev_point.x = lv_point.x;
		dev_point.y = lv_point.y;
	}

	return dev_point;
}

/* flush_cb*/
void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
	disp_orientation_t orientation = display_orientation_get();

	lv_point_t _lv_point = {area->x1, area->y1};

	//In device space, the top left point dev_point1 is...
	lv_point_t dev_point1 = orientation_change(lv_disp, orientation, _lv_point);
	_lv_point.x = area->x2;
	_lv_point.y = area->y2;
	//In device space, the lower right point dev_point2 is...
	lv_point_t dev_point2 = orientation_change(lv_disp, orientation, _lv_point);

	switch(orientation)
	{
	case DISP_270_DEG:
	case DISP_180_DEG:
		oled_flush(dev_point2.y, dev_point1.y, (const color_t *)color_p);
		break;
	case DISP_90_DEG:
		oled_flush(dev_point1.y, dev_point2.y, (const color_t *)color_p);
		break;
	default:
		oled_flush(area->y1, area->y2, (const color_t *)color_p);
	}
	lv_disp_flush_ready(&lv_disp_drv);
}

/**
 * @note Converting lv_color_t in byte to bit-mask for BLACK/WHITE display
 */
void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) {
  (void) disp_drv;
  (void) buf_w;
  (void) opa;

  lv_point_t _lv_point = {x, y};

  //Transform orientation from LVGL VDB space to OLED's device space
  lv_point_t dev_point = orientation_change(lv_disp, display_orientation_get(), _lv_point);

  if (lv_color_to1(color) == 1) {
    buf[BUFIDX(dev_point.x, dev_point.y)] |=  PIXIDX(dev_point.x);  //Set VDB pixel bit to 1 for other colors than BLACK
  } else {
    buf[BUFIDX(dev_point.x, dev_point.y)] &= ~PIXIDX(dev_point.x);  //Set VDB pixel bit to 0 for BLACK color
  }
}

/**
 * @brief Callback to round display update for a whole line
 */
void my_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area)
{
	(void) disp_drv;

	/* Round area to a whole line */
	if((display_orientation_get()==DISP_0_DEG) || (display_orientation_get()==DISP_180_DEG))
	{
		area->x1 = 0;
		area->x2 = 63;
	}
	else
	{
		area->y1 = 0;
		area->y2 = 63;
	}
}

Screenshot and/or video

This video shows the screen initialized with orientation set to DISP_270_DEG

Problem

The OLED is a portrait-type of native resolution 64(w)*128(h) with scanning from top left to lower right. As you can see, the screen was initialized OK with a button in the middle with screen orientation init to DISP_270_DEG. The problem came when it was touched. I believe the flush callback should allow for data shift with color_p+=offset in (const color_t *) color_p but I am not sure.

Do you use the rotated flag of the display driver to swap the screens horizontal and vertical resolution?

Also, note that both LV_HOR_RES_MAX and LV_VER_MAX should be 128 in your case (the greatest size).

Yes, together with lv_coord_t (x,y) position transformed in the driver level plus some register change to shift the origin of the OLED from top left to lower right and vice-versa.

I tried setting both LV_HOR_RES_MAX and LV_VER_RES_MAX to 128 but the result is not as expected.

Anyway, I am making progress now with demonstration:

How the init code looks like:

void my_hal_init(void)
{
   hw_init(void); //initialization for OLED, hardware specific
   static lv_disp_buf_t lv_disp_buf;
   static lv_color_t buf1[(OLED_VER_RES)*(OLED_HOR_RES>>3)];
   lv_disp_buf_init(&lv_disp_buf, buf1, NULL, (LV_HOR_RES_MAX)*(LV_VER_RES_MAX));

	lv_disp_drv_init(&lv_disp_drv);

	if((display_orientation_get()==DISP_90_DEG) || (display_orientation_get()==DISP_270_DEG))
		lv_disp_drv.rotated = 1;
	else
		lv_disp_drv.rotated=0;

	lv_disp_drv.buffer = &lv_disp_buf;
	lv_disp_drv.flush_cb = my_flush_cb;
	lv_disp_drv.set_px_cb = my_set_px_cb;
	lv_disp_drv.rounder_cb = my_rounder_cb;
}

Skeleton of the main program is:

//First create a button in the center of the screen
void demo_orientation_chg(void)
{
	lv_obj_t* btn = lv_btn_create(lv_scr_act(), NULL);
	lv_obj_align(btn, NULL, LV_ALIGN_CENTER, 0, 0);
	lv_btn_set_fit2(btn, LV_FIT_TIGHT, LV_FIT_NONE);
	lv_obj_set_event_cb(btn, event_handler);
	//lv_btn_set_checkable(btn1, true);
	lv_obj_t* label = lv_label_create(btn, NULL);
	lv_label_set_text(label, "Click");

	lv_group_t *g = lv_group_create();
	lv_group_add_obj(g, btn);
	lv_indev_set_group(touch_indev, g);
}

void main(void)
{
   demo_orientation_chg();

   while(1)
   {
      static int i=0;
	  if(i++ == 5)
	  {
		  lv_tick_inc(5);
		  i=0;
	  }
	  lv_task_handler();
	  HAL_Delay(1);

        //simple timer based orientation change to demonstrate...will implement hardware based with accelerometer later
		static int msec_cnt = 0;
		if(msec_cnt++ == 3000)
		{
			msec_cnt = 0;
			if (display_orientation_get()==DISP_270_DEG)
			{
				display_orientation_set(DISP_0_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_0_DEG)
			{
				display_orientation_set(DISP_90_DEG);
				lv_disp_drv.rotated = 1;
			}
			else if (display_orientation_get()==DISP_90_DEG)
			{
				display_orientation_set(DISP_180_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_180_DEG)
			{
				display_orientation_set(DISP_270_DEG);
				lv_disp_drv.rotated = 1;
			}
			lv_disp_drv_update(lv_disp, &lv_disp_drv);
			lv_obj_invalidate(lv_scr_act());
		}
   }
}

Problem

Somehow the button is not centered when orientation changed to 90 deg and 270 deg. But DISP_90_DEG and DISP_270_DEG works OK with button centered in the middle in the freshly initialized state. Don’t know why.

I think you may need to realign the button each time the display rotates. The simplest way to do it is probably to use lv_obj_realign(btn).

1 Like

Thanks a lot. It works. The second mandatory function is lv_disp_drv_update(lv_disp, &lv_disp_drv). Without this function the same alignment issue persists.

Code snippet:

static lv_obj_t* btn;

void demo_orientation_chg(void)
{
	btn = lv_btn_create(lv_scr_act(), NULL);
	lv_obj_align(btn, NULL, LV_ALIGN_CENTER, 0, 0);
	lv_btn_set_fit2(btn, LV_FIT_TIGHT, LV_FIT_NONE);
	lv_obj_set_event_cb(btn, event_handler);
	lv_obj_t* label = lv_label_create(btn, NULL);
	lv_label_set_text(label, "Click");

	lv_group_t *g = lv_group_create();
	lv_group_add_obj(g, btn);
	lv_indev_set_group(touch_indev, g);
}

void main(void)
{
STM32_HAL_Init();
lv_init(); //Initialize LittlevGL
my_hal_init(); //OLED display init (hw)
demo_orientation_chg(); //create the button and attach it to encoder

while(1)
{
	static int i=0;
	  if(i++ == 5)
	  {
		  lv_tick_inc(5);
		  i=0;
	  }
	  lv_task_handler();
	  HAL_Delay(1);
	  
	  static int msec_cnt = 0;
	  
	  if(msec_cnt++ == 2000)
	  {
			msec_cnt = 0;
			if (display_orientation_get()==DISP_270_DEG)
			{
				display_orientation_set(DISP_0_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_0_DEG)
			{
				display_orientation_set(DISP_90_DEG);
				lv_disp_drv.rotated = 1;
			}
			else if (display_orientation_get()==DISP_90_DEG)
			{
				display_orientation_set(DISP_180_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_180_DEG)
			{
				display_orientation_set(DISP_270_DEG);
				lv_disp_drv.rotated = 1;
			}
			lv_disp_drv_update(lv_disp, &lv_disp_drv); //this is mandatory after rotation.
			lv_obj_realign(btn);		  
	  }
}
}

Video clip shows the result below:

Alternative

Besides lv_obj_realign() I find object delete and recreate is also possible. This may be good for pages with a lot of child widgets that make re-alignment for each object tedious. However, this method requires the application to save the states of widgets and get them restored after page recreation. Not sure how an Android app handles this since I am not an Android programmer. How a smartphone app handles screen orientation change and save the states? Anyway…

Snippet :

lv_obj_t * page;
void lv_ex_page_1(void)
{
    /*Create a page*/
	page = lv_page_create(lv_scr_act(), NULL);
    lv_obj_set_size(page, lv_disp_get_hor_res(lv_disp), lv_disp_get_ver_res(lv_disp));
    lv_obj_align(page, NULL, LV_ALIGN_CENTER, 0, 0);

    /*Create a label on the page*/
    lv_obj_t * label = lv_label_create(page, NULL);
    lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK);            //Automatically break long lines
    lv_obj_set_width(label, lv_page_get_width_fit(page));          //Set the label width to max value to not show hor. scroll bars
    lv_label_set_text(label, "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n"
                             "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"
                             "Ut enim ad minim veniam, quis nostrud exercitation ullamco\n"
                             "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\n"
                             "dolor in reprehenderit in voluptate velit esse cillum dolore\n"
                             "eu fugiat nulla pariatur.");

    lv_group_t *g = lv_group_create();
    lv_group_add_obj(g, page);
    lv_indev_set_group(touch_indev, g);
}

void main(void)
{
STM32_HAL_Init();
lv_init(); //Initialize LittlevGL
my_hal_init(); //OLED display init (hw)
lv_ex_page_1();
while(1)
{
	static int i=0;
	  if(i++ == 5)
	  {
		  lv_tick_inc(5);
		  i=0;
	  }
	  lv_task_handler();
	  HAL_Delay(1);
	  
	  static int msec_cnt = 0;
	  
	  if(msec_cnt++ == 2000)
	  {
			msec_cnt = 0;
            lv_obj_del(page); //delete the whole page
			if (display_orientation_get()==DISP_270_DEG)
			{
				display_orientation_set(DISP_0_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_0_DEG)
			{
				display_orientation_set(DISP_90_DEG);
				lv_disp_drv.rotated = 1;
			}
			else if (display_orientation_get()==DISP_90_DEG)
			{
				display_orientation_set(DISP_180_DEG);
				lv_disp_drv.rotated = 0;
			}
			else if (display_orientation_get()==DISP_180_DEG)
			{
				display_orientation_set(DISP_270_DEG);
				lv_disp_drv.rotated = 1;
			}
			lv_disp_drv_update(lv_disp, &lv_disp_drv); //this is mandatory after rotation.
			lv_ex_page_1(); //now, simply recreate the page with all objects aligned well according to new hor_res and ver_res.  
	  }
}
}

Video clip shows the result:

Right now there is flicker when orientation is swapped. This can be solved with FR synchronization (the next step).

The Android paradigm is to store state separately from the objects, or save the state from the objects before tearing down the previous screen. That enables the UI to be recreated after the screen rotates.

Yea I think that recreating the widget instead of realign will make you loose the state. You could Also use a rotate animation to do the screen rotation instead of switching the resolution. That would make it less sudden. Lvgl can automatically trim overflow so it shouldn’t be a problem making the container size/content overflow the screen as it would be trimmed and then when it rotates it gets displayed. After the animation is done you do the orientation switch so that the device knows which way it is facing now. This step isn’t really necessary but it will make other alignments and orientation dependent functions easier.

I tried to find the APIs but it is all about image rotation or transform_angle that is image-like. Can we rotate a page or button? Any documentation on that?

Normal widgets cannot be rotated directly.