1===== 2STM32 3===== 4 5LVGL Can be added to `STM32CubeIDE <https://www.st.com/en/development-tools/stm32cubeide.html>`__ 6in a similar fashion to any other Eclipse-based IDE. 7 8 9Including LVGL in a Project 10--------------------------- 11 12- Create or open a project in STM32CubeIDE. 13- Copy the entire LVGL folder to *[project_folder]/Drivers/lvgl*. 14- In the STM32CubeIDE **Project Explorer** pane: right click on the 15 LVGL folder that you copied (you may need to refresh the view first 16 before it will appear), and select **Add/remove include path…**. If 17 this doesn't appear, or doesn't work, you can review your project 18 include paths under the **Project** -> **Properties** menu, and then 19 navigating to **C/C++ Build** -> **Settings** -> **Include paths**, and 20 ensuring that the LVGL directory is listed. 21 22Now that the source files are included in your project, follow the instructions to 23:ref:`add_lvgl_to_your_project` and to create the ``lv_conf.h`` file, and 24initialise the display. 25 26 27Bare Metal Example 28------------------ 29 30A minimal example using STM32CubeIDE, and HAL. \* When setting up 31**Pinout and Configuration** using the **Device Configuration Tool**, 32select **System Core** -> **SYS** and ensure that **Timebase Source** is 33set to **SysTick**. \* Configure any other peripherals (including the 34LCD panel), and initialise them in *main.c*. \* ``#include "lvgl.h"`` in 35the *main.c* file. \* Create some frame buffer(s) as global variables: 36 37.. code-block:: c 38 39 /* Frame buffers 40 * Static or global buffer(s). The second buffer is optional 41 * TODO: Adjust color format and choose buffer size. DISPLAY_WIDTH * 10 is one suggestion. */ 42 #define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565)) /* will be 2 for RGB565 */ 43 #define BUFF_SIZE (DISPLAY_WIDTH * 10 * BYTES_PER_PIXEL) 44 static uint8_t buf_1[BUFF_SIZE]; 45 static uint8_t buf_2[BUFF_SIZE]; 46 47- In your ``main()`` function, after initialising your CPU, 48 peripherals, and LCD panel, call :cpp:func:`lv_init` to initialise LVGL. 49 You can then create the display driver using 50 :cpp:func:`lv_display_create`, and register the frame buffers using 51 :cpp:func:`lv_display_set_buffers`. 52 53 .. code-block:: c 54 55 //Initialise LVGL UI library 56 lv_init(); 57 58 lv_display_t * disp = lv_display_create(WIDTH, HEIGHT); /* Basic initialization with horizontal and vertical resolution in pixels */ 59 lv_display_set_flush_cb(disp, my_flush_cb); /* Set a flush callback to draw to the display */ 60 lv_display_set_buffers(disp, buf_1, buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL); /* Set an initialized buffer */ 61 62- Create some dummy Widgets to test the output: 63 64 .. code-block:: c 65 66 /* Change Active Screen's background color */ 67 lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN); 68 lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN); 69 70 /* Create a spinner */ 71 lv_obj_t * spinner = lv_spinner_create(lv_screen_active(), 1000, 60); 72 lv_obj_set_size(spinner, 64, 64); 73 lv_obj_align(spinner, LV_ALIGN_BOTTOM_MID, 0, 0); 74 75 76- Add a call to :cpp:func:`lv_timer_handler` inside your ``while(1)`` loop: 77 78 .. code-block:: c 79 80 /* Infinite loop */ 81 while (1) 82 { 83 lv_timer_handler(); 84 HAL_Delay(5); 85 } 86 87 88- Add a call to :cpp:func:`lv_tick_inc` inside the :cpp:func:`SysTick_Handler` function. Open the *stm32xxxx_it.c* 89 file (the name will depend on your specific MCU), and update the :cpp:func:`SysTick_Handler` function: 90 91 .. code-block:: c 92 93 void SysTick_Handler(void) 94 { 95 /* USER CODE BEGIN SysTick_IRQn 0 */ 96 97 HAL_SYSTICK_IRQHandler(); 98 lv_tick_inc(1); 99 #ifdef USE_RTOS_SYSTICK 100 osSystickHandler(); 101 #endif 102 103 /* USER CODE END SysTick_IRQn 0 */ 104 HAL_IncTick(); 105 /* USER CODE BEGIN SysTick_IRQn 1 */ 106 107 /* USER CODE END SysTick_IRQn 1 */ 108 } 109 110 111- Finally, write the callback function, ``my_flush_cb``, which will send the display buffer to your LCD panel. Below is 112 one example, but it will vary depending on your setup. 113 114 .. code-block:: c 115 116 void my_flush_cb(lv_display_t * disp, const lv_area_t * area, lv_color_t * color_p) 117 { 118 //Set the drawing region 119 set_draw_window(area->x1, area->y1, area->x2, area->y2); 120 121 int height = area->y2 - area->y1 + 1; 122 int width = area->x2 - area->x1 + 1; 123 124 //We will do the SPI write manually here for speed 125 HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_SET); 126 //CS low to begin data 127 HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); 128 129 //Write colour to each pixel 130 for (int i = 0; i < width * height; i++) { 131 uint16_t color_full = (color_p->red << 11) | (color_p->green << 5) | (color_p->blue); 132 parallel_write(color_full); 133 134 color_p++; 135 } 136 137 //Return CS to high 138 HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); 139 140 /* IMPORTANT!!! 141 * Inform the graphics library that you are ready with the flushing */ 142 lv_display_flush_ready(disp); 143 } 144 145 146FreeRTOS Example 147---------------- 148 149A minimal example using STM32CubeIDE, HAL, and CMSISv1 (FreeRTOS). 150*Note that we have not used Mutexes in this example, however LVGL is* **NOT** 151*thread safe and so Mutexes should be used*. See: :ref:`threading` 152\* ``#include "lvgl.h"`` \* Create your frame buffer(s) as global variables: 153 154.. code-block:: c 155 156 /* Frame buffers 157 * Static or global buffer(s). The second buffer is optional */ 158 #define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565)) /* will be 2 for RGB565 */ 159 /* TODO: Declare your own BUFF_SIZE appropriate to your system. */ 160 static lv_color_t buf_1[BUFF_SIZE]; 161 #define BUFF_SIZE (DISPLAY_WIDTH * 10 * BYTES_PER_PIXEL) 162 static uint8_t buf_1[BUFF_SIZE]; 163 static lv_color_t buf_2[BUFF_SIZE]; 164 165- In your ``main`` function, after your peripherals (SPI, GPIOs, LCD 166 etc) have been initialised, initialise LVGL using :cpp:func:`lv_init`, 167 create a new display driver using :cpp:func:`lv_display_create`, and 168 register the frame buffers using :cpp:func:`lv_display_set_buffers`. 169 170 .. code-block:: c 171 172 /* Initialise LVGL UI library */ 173 lv_init(); 174 lv_display_t *display = lv_display_create(WIDTH, HEIGHT); /* Create the display */ 175 lv_display_set_flush_cb(display, my_flush_cb); /* Set a flush callback to draw to the display */ 176 lv_display_set_buffers(disp, buf_1, buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL); /* Set an initialized buffer */ 177 178 /* Register the touch controller with LVGL - Not included here for brevity. */ 179 180 181- Create some dummy Widgets to test the output: 182 183 .. code-block:: c 184 185 /* Change Active Screen's background color */ 186 lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN); 187 lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN); 188 189 /* Create a spinner */ 190 lv_obj_t * spinner = lv_spinner_create(lv_screen_active(), 1000, 60); 191 lv_obj_set_size(spinner, 64, 64); 192 lv_obj_align(spinner, LV_ALIGN_BOTTOM_MID, 0, 0); 193 194- Create two threads to call :cpp:func:`lv_timer_handler`, and 195 :cpp:func:`lv_tick_inc`.You will need two ``osThreadId`` handles for 196 CMSISv1. These don't strictly have to be globally accessible in this 197 case, however STM32Cube code generation does by default. If you are 198 using CMSIS and STM32Cube code generation it should look something 199 like this: 200 201 .. code-block:: c 202 203 //Thread Handles 204 osThreadId lvgl_tickHandle; 205 osThreadId lvgl_timerHandle; 206 207 /* definition and creation of lvgl_tick */ 208 osThreadDef(lvgl_tick, LVGLTick, osPriorityNormal, 0, 1024); 209 lvgl_tickHandle = osThreadCreate(osThread(lvgl_tick), NULL); 210 211 //LVGL update timer 212 osThreadDef(lvgl_timer, LVGLTimer, osPriorityNormal, 0, 1024); 213 lvgl_timerHandle = osThreadCreate(osThread(lvgl_timer), NULL); 214 215- And create the thread functions: 216 217 .. code-block:: c 218 219 /* LVGL timer for tasks. */ 220 void LVGLTimer(void const * argument) 221 { 222 for(;;) 223 { 224 lv_timer_handler(); 225 osDelay(20); 226 } 227 } 228 /* LVGL tick source */ 229 void LVGLTick(void const * argument) 230 { 231 for(;;) 232 { 233 lv_tick_inc(10); 234 osDelay(10); 235 } 236 } 237 238- Finally, create the ``my_flush_cb`` function to output the frame 239 buffer to your LCD. The specifics of this function will vary 240 depending on which MCU features you are using. Below is an example 241 for a typical MCU interface. 242 243 .. code-block:: c 244 245 void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map); 246 { 247 uint16_t * color_p = (uint16_t *)px_map; 248 249 //Set the drawing region 250 set_draw_window(area->x1, area->y1, area->x2, area->y2); 251 252 int height = area->y2 - area->y1 + 1; 253 int width = area->x2 - area->x1 + 1; 254 255 //Begin SPI Write for DATA 256 HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_SET); 257 HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); 258 259 //Write colour to each pixel 260 for (int i = 0; i < width * height; i++) { 261 parallel_write(color_p); 262 color_p++; 263 } 264 265 //Return CS to high 266 HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); 267 268 /* IMPORTANT!!! 269 * Inform the graphics library that you are ready with the flushing */ 270 lv_display_flush_ready(display); 271 } 272 273.. _dma2d: 274 275DMA2D Support 276------------- 277 278LVGL supports DMA2D - a feature of some STM32 MCUs which can improve performance 279when blending fills and images. Some STM32 product lines such as STM32F4 STM32F7, STM32L4, 280STM32U5, and STM32H7 include models with DMA2D support. 281 282LVGL's integration with DMA2D can be enabled by setting ``LV_USE_DRAW_DMA2D`` 283to ``1`` in ``lv_conf.h`` 284 285With ``LV_USE_DRAW_DMA2D_INTERRUPT`` set to ``0`` and ``LV_USE_OS`` set to ``LV_OS_NONE``, 286DMA2D will draw some fills and images concurrently with the software render where 287possible. If ``LV_USE_DRAW_DMA2D_INTERRUPT`` is set to ``1`` and ``LV_USE_OS`` set to 288``LV_OS_FREERTOS`` (or another OS) the main difference will be that the core will idle 289instead of "busywait" while waiting for a DMA2D transfer to complete. 290 291If ``LV_USE_DRAW_DMA2D_INTERRUPT`` is enabled then you are required to call 292:cpp:expr:`lv_draw_dma2d_transfer_complete_interrupt_handler` whenever the DMA2D 293"transfer complete" global interrupt is received. 294 295DMA2D also makes possible to mix layers that have color format on 296:c:macro:`LV_COLOR_FORMAT_ARGB1555` on top of :c:macro:`LV_COLOR_FORMAT_RGB565` 297layers. 298 299If your STM device has a NeoChrom GPU, you can use the :ref:`Nema GFX renderer <nema_gfx>` instead. 300