Skip to content

Commit 509742f

Browse files
committed
Add example that demonstrates screen rotation using canvas
1 parent 5c44034 commit 509742f

File tree

7 files changed

+903
-0
lines changed

7 files changed

+903
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# For more information about build system see
2+
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
3+
# The following five lines of boilerplate have to be in your project's
4+
# CMakeLists in this exact order for cmake to work correctly
5+
cmake_minimum_required(VERSION 3.5)
6+
7+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
8+
project(epaper_lvgl_rotate)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# _LVGL Rotate_
2+
3+
(See the README.md file in the upper level 'examples' directory for more information about examples.)
4+
5+
This example demonstrates rotation of the screen on an eInk display. It displays a long text string
6+
at rotations of 0, 90, 180 and 270 degrees. It will work on a Waveshare 2.7" eInk display that natively
7+
has screen dimensions of 176W x 264H; it should also work on a Waveshare 1.54" display with dimensions
8+
200 x 200 (not tested by me). Changing the width and height constants (search main.c for DISPLAY_X)
9+
should work with other displays that have SSD1680 or SSD1681 controllers.
10+
11+
Rotating eInk displays is not the same as more dyanmic displays such as LCD.
12+
The screen can be rotated only in multiples of 90 degrees: 0, 90, 180, 270.
13+
An eInk display should not be updated often, in fact the manufacturers (Waveshare and GooDisplay)
14+
state that displays should be rewritten no more often than 3 minutess. During development you can
15+
rewrite more often, but this can shorten the lifetime of the display.
16+
With some eInk displays it is possible to only update portions of the screen, but manufacturers
17+
warn that this should not be done for too many cycles - eventually you should refresh the entire screen.
18+
This demo only does full screen refreshes.
19+
20+
An eInk display should usually be thought of as static - for example you will display
21+
a weather forecast and update it every 15 minutes.
22+
23+
Let's discuss eInk rotation.
24+
25+
- eInk displays handled by this example have either a SSD1680 or SSD1681 controller.
26+
27+
- The controller cannot perform hardware rotation (unlike some LCD).
28+
29+
- Some eInk display examples in ESP-IDF and ESP-BSP use a 1.54 inch square display, 200x200 pixels.
30+
You can rotate a square screen by multiples of 90 degrees by using mirror and swap axes operations.
31+
32+
- If the display is not square, then most rotation schemes result in a clipped output.
33+
Swapping X&Y axes is poorly defined.
34+
35+
- LVGL can do software rotation (sw_rotate=true), but it is unusable for our needs. Why?
36+
Suppose the display is 200W x 100H. When you rotate by 90, many algorithms don't know
37+
where to put the pixels from 101 to 200, so the screen image becomes a square measuring 100 x 100
38+
and the screen image is clipped.
39+
40+
Waveshare eInk displays have a default orientation, portrait or landscape. AFAIK,
41+
the SSD1680 controller cannot be programmed to change the orientation (sure would be nice).
42+
Many non-square displays are portrait, even though many applications would prefer landscape.
43+
So what to do?
44+
45+
- The controller does not perform hardware rotation.
46+
47+
- LVGL software rotation has problems (see below).
48+
49+
- Since we want to change orientation between portrait and landscape, we need a function that
50+
moves the pixels from rows to columns.
51+
52+
## What are the problems with LVGL sw_rotate?
53+
54+
If **sw_rotate=true**, then **full_refresh** must be *false*. This results in the full screen image
55+
getting rotated in pieces and written to the device by DMA. While this is fine for an LCD,
56+
eInk displays need to refresh when each DMA transfer completes, resulting in flashing of the disdplay
57+
multiple times until all the chunks have been transferred. Furthermore, **sw_rotate** will clip the screen
58+
image as mentioned above.
59+
60+
## How can we change from portrait to landscape orientation?
61+
62+
The answer is to use a *canvas*. We create a canvas in the desired orientation (for example: landscape)
63+
and write to it. If you write a landscape canvas to a portrait display, it will be clipped. If you rotate
64+
the landscape canvas by 90 or 270 degrees, you expect that it should appear on the screen without clipping.
65+
You should be able to rotate a canvas with **lv_canvas_transform()**, but I was unable to get it to work
66+
with LVGL v. 8.3.0. Therefore I wrote function **rotate_buffer()** to perform a 90 degree rotation.
67+
270 degree roatation is performed with 90 degree rotation plus mirror on both X and Y axes.
68+
69+
## How to use example
70+
We encourage the users to use the example as a template for new projects.
71+
A recommended way is to follow the instructions on a [docs page](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project).
72+
73+
## Example folder contents
74+
75+
The project **sample_project** contains one source file in C language [main.c](main/main.c). The file is located in folder [main](main).
76+
77+
ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt`
78+
files that provide set of directives and instructions describing the project's source files and targets
79+
(executable, library, or both).
80+
81+
Below is short explanation of remaining files in the project folder.
82+
83+
```
84+
├── CMakeLists.txt
85+
├── main
86+
│   ├── CMakeLists.txt
87+
│   ├── main.c
88+
│ └── lvgl_demo_ui.c
89+
└── README.md This is the file you are currently reading
90+
```
91+
Additionally, the sample project contains Makefile and component.mk files, used for the legacy Make based build system.
92+
They are not used or needed when building with CMake and idf.py.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRCS "main.c" "lvgl_demo_ui.c"
2+
INCLUDE_DIRS ".")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
dependencies:
2+
idf: ">=5.0"
3+
lvgl/lvgl: "~8.3.0"
4+
esp_lcd_ssd1681:
5+
# I am specifying the path of the component because the component
6+
# had not been published to the ESP Component Registry by the time
7+
# I write this example.
8+
# Development will be better if you add esp_lcd_ssd1681 as a component to your project.
9+
path: "../../../"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: CC0-1.0
5+
*/
6+
7+
// This demo UI is adapted from LVGL official example: https://docs.lvgl.io/master/widgets/extra/meter.html#simple-meter
8+
// This demo writes text onto canvas that can later be rotated to appear on a portrait or landscape oriened display.
9+
10+
#define LV_FONT_MONTSERRAT_28 1 // 28 pt font. Must precede "lvgl.h".
11+
12+
#include "lvgl.h"
13+
#include "esp_heap_caps.h"
14+
15+
#define CANVAS_COLOR_FORMAT LV_IMG_CF_ALPHA_1BIT // desginates 1 bit per pixel display.
16+
17+
extern void rotate_buffer(uint32_t rotate, int xlen, int ylen, uint8_t *in_buf, uint8_t *out_buf);
18+
19+
static lv_style_t style_28;
20+
static uint8_t *canvas_buffer = NULL;
21+
/**
22+
*
23+
* @brief Use LVGL canvas to generate display and demonstrate rotation
24+
* @param width pixels of non-rotated display
25+
* @param height pixels of non-rotated display
26+
* @param rotate LVGL rotation value, [0, 1, 2, 3] for LV_DISP_ROT_<NONE|90|180|270]>
27+
* @note A canvas is created with no parent. Text is written to the canvas.
28+
* The canvas is then assigned the active screen as parent.
29+
* This apparently causes lvgl_flush_callback to be invoked.
30+
*/
31+
void lvgl_canvas_ui(int width, int height, uint32_t rotate)
32+
33+
{
34+
int disp_x, disp_y;
35+
if (rotate == LV_DISP_ROT_90 || rotate == LV_DISP_ROT_270) {
36+
disp_x = height;
37+
disp_y = width;
38+
} else {
39+
disp_y = height;
40+
disp_x = width;
41+
}
42+
if (!canvas_buffer) {
43+
// Needs 8 extra bytes for monochrome displays?
44+
canvas_buffer = heap_caps_malloc(disp_x * disp_y / 8 + 8, MALLOC_CAP_DMA);
45+
}
46+
memset(canvas_buffer, 0x00, disp_x * disp_y / 8 + 8);
47+
48+
// Create a screen (a necessary, non-visible step in LVGL)
49+
lv_obj_t *scr = lv_obj_create(NULL); // canvas must not be attached to display until later
50+
// Create the canvas on the screen
51+
lv_obj_t *canvas = lv_canvas_create(scr);
52+
// Assign canvas_buffer to the canvas. The canvas is configured to be either portrait or landscape orientation.
53+
lv_canvas_set_buffer(canvas, canvas_buffer, disp_x, disp_y, CANVAS_COLOR_FORMAT);
54+
lv_canvas_fill_bg(canvas, lv_color_hex(0x000000), LV_OPA_0); // 0x000000 is a white background on eInk displays
55+
56+
#if 0
57+
// NOTE: palette is only used with indexed color formats, not monochrome.
58+
// Might be used for multi-color eInk displays?
59+
lv_canvas_set_palette(canvas, 0, LV_COLOR_WHITE);
60+
lv_canvas_set_palette(canvas, 1, LV_COLOR_BLACK);
61+
#endif
62+
63+
lv_style_init(&style_28);
64+
lv_style_set_text_font(&style_28, &lv_font_montserrat_28);
65+
lv_draw_label_dsc_t label_dsc; // label properties descriptor
66+
lv_draw_label_dsc_init(&label_dsc);
67+
label_dsc.color = lv_color_make(0xFF, 0xFF, 0xFF); // Black color (or use lv_color_hex(0xFFFFFF))
68+
label_dsc.font = &lv_font_montserrat_28; // Use a built-in font
69+
label_dsc.align = LV_TEXT_ALIGN_LEFT;
70+
71+
int xoff = 10, yoff = 10; // offset for start of text string
72+
// canvas_draw_text automatically wraps text when right margin is reached
73+
lv_canvas_draw_text(canvas, xoff, yoff, disp_x - xoff, &label_dsc, "This text goes to end of line and then wraps. Isn't that cool?");
74+
75+
// Now we need a second canvas if we want to rotate
76+
lv_obj_t *rot_canvas = lv_canvas_create(lv_obj_create(NULL));
77+
uint8_t *rot_buf = heap_caps_malloc(disp_x * disp_y / 8 + 8, MALLOC_CAP_DMA);
78+
memset(rot_buf, 0x00, disp_x * disp_y / 8 + 8);
79+
80+
if (rotate == LV_DISP_ROT_90 || rotate == LV_DISP_ROT_270) {
81+
#if 1 // use my rotate function
82+
// ROT_270 is performed by ROT90 and then mirror both X & Y (in main.c)
83+
rotate_buffer(rotate, disp_x, disp_y, canvas_buffer, rot_buf);
84+
lv_canvas_set_buffer(canvas, rot_buf, width, height, LV_IMG_CF_ALPHA_1BIT);
85+
86+
#else // use lvgl_canvas_transform
87+
// Display of 90 appears as 0 degrees, and clipped on right side.
88+
// Display of 270 appears as 180 degrees, and clipped on left side of display.
89+
// The text is first written to a landscape canvas. Thus it seems that no rotation occurs,
90+
// thereby resulting in the text clipping.
91+
int angle = 900 * rotate;
92+
93+
lv_canvas_set_buffer(rot_canvas, rot_buf, height, width, LV_IMG_CF_ALPHA_1BIT);
94+
lv_img_dsc_t img_desc = *lv_canvas_get_img(canvas);
95+
lv_canvas_transform(rot_canvas, &img_desc, angle, 256, 0, 264, width / 2, height / 2, true);
96+
lv_canvas_copy_buf(canvas, rot_buf, 0, 0, lv_obj_get_height(rot_canvas), lv_obj_get_width(rot_canvas));
97+
#endif
98+
}
99+
// Must clear LVGL display, else we get "double-exposure" image on screen
100+
lv_obj_clean(lv_scr_act());
101+
// Attach the canvas to the active screen (the display device).
102+
// This presumably triggers LVGL flush callback (see flush_cb in main.c)
103+
lv_obj_set_parent(canvas, lv_scr_act());
104+
}

0 commit comments

Comments
 (0)