1.. _threading: 2 3======================== 4Threading Considerations 5======================== 6 7.. _threading_definitions: 8 9Definitions 10*********** 11 12.. _thread: 13 14Thread 15 In the context of this document, a thread is any sequence of CPU instructions. 16 In "bare-metal" implementations (i.e. no OS), threads include: 17 18 - the main thread executing a while(1) loop that runs the system, and 19 - interrupt service routines (ISRs). 20 21 When running under an OS, threads include: 22 23 - each task (or process), 24 - ISRs, and 25 - advanced OSes can have multiple "execution threads" within a processes. 26 27.. _atomic operation: 28 29Atomic Operation 30 If operation X is atomic, that means that any thread observing the operation will 31 see it either as not yet started, or as completed, and not in any state that is 32 partially completed. 33 34 If other threads can see the operation in a partially performed state, or 35 interfere with it, then operation X is not atomic. 36 37 If an atomic operation can fail, its implementation must return the the resource 38 back to the state before the operation was started. To other threads it must 39 appear as though the operation had not yet started. 40 41.. _atomic data: 42.. _atomic: 43.. _non-atomic data: 44 45Atomic Data 46 A datum (i.e. contents of a variable or data structure) is atomic if any thread 47 observing it will always see it in a consistent state, as if operations on it 48 have either not yet started, or have been successfully completed, and not in a 49 state that is partially changed or otherwise inconsistent. 50 51 When reading or writing a value is started and completed with 1 CPU instruction, 52 it is automatically atomic, since it can never been seen in an inconsistent 53 (partially-changed) state, even from a CPU interrupt or exception. With such 54 values, no special protection is required by programmers to ensure all threads 55 see it in a consistent state. 56 57 58 59.. _lvgl_and_threads: 60 61LVGL and Threads 62**************** 63 64LVGL is **not thread-safe**. 65 66That means it is the programmer's responsibility to see that no LVGL function is 67called while another LVGL call is in progress in another thread. This includes calls 68to :cpp:func:`lv_timer_handler`. 69 70.. note:: 71 Assuming the above is the case, it is safe to call LVGL functions in 72 73 - :ref:`event callbacks <events>`, and in 74 - :ref:`timer callbacks <timer>` 75 76 because the thread that drives both of these is the thread that calls 77 :cpp:func:`lv_timer_handler`. 78 79Reason: 80 81LVGL manages many complex data structures, and those structures are "system 82resources" that must be protected from being "seen" by other threads in an 83inconsistent state. A high percentage LVGL functions (functions that start with 84``lv_``) either read from or change those data structures. Those that change them 85place the data in an inconsistent state during execution (because such changes are 86multi-step sequences), but return them to a consistent state before those functions 87return. For this reason, execution of each LVGL function must be allowed to complete 88before any other LVGL function is started. 89 90.. _os_exception: 91 92.. admonition:: Exceptions to the Above: 93 94 These two LVGL functions may be called from any thread: 95 96 - :cpp:func:`lv_tick_inc` (if writing to a ``uint32_t`` is atomic on your 97 platform; see :ref:`tick_interface` for more information) and 98 - :cpp:func:`lv_display_flush_ready` (:ref:`flush_callback` for more information) 99 100 The reason this is okay is that the LVGL data changed by them is :ref:`atomic <atomic>`. 101 102 If an interrupt MUST convey information to part of your application that calls 103 LVGL functions, set a flag or other atomic value that your LVGL-calling thread 104 (or an :ref:`LVGL Timer <timer>` you create) can read from and take action. 105 106 If you are using an OS, there are a few other options. See below. 107 108 109Ensuring Time Updates are Atomic 110-------------------------------- 111For LVGL's time-related tasks to be reliable, the time updates via the Tick Interface 112must be reliable and the Tick Value must appear :ref:`atomic <atomic>` to LVGL. See 113:ref:`tick_interface` for details. 114 115 116 117.. _tasks: 118 119Tasks 120***** 121Under an OS, it is common to have many threads of execution ("tasks" in some OSes) 122performing services for the application. In some cases, such threads can acquire 123data that should be shown (or otherwise reflected) in the user interface, and doing 124so requires making LVGL calls to get that data (or change) shown. 125 126Yet it still remains the programmer's responsibility to see that no LVGL function is 127called while another LVGL call is in progress. 128 129How do you do this? 130 131 132.. _gateway thread: 133 134Method 1: Use a Gateway Thread 135------------------------------- 136A "Gateway Thread" (or "Gateway Task" in some OSes) is a thread (task) that the 137system designer designates to *exclusively* manage a system resource. An example is 138management of a remote chip, such as an EEPROM or other device that always needs to 139be brought into a consistent state before something new is started. Another example 140is management of multiple devices on an I2C bus (or any data bus). In this case the 141I2C bus is the "exclusively-managed resource", and having only one thread managing it 142guarantees that each action started is allowed to complete before another action with 143it is started. 144 145LVGL's data structures are a system resource that requires such protection. 146 147Using this method, creation, modification and deletion of all Widgets and other 148LVGL resources (i.e. all LVGL function calls excluding the :ref:`exceptions 149<os_exception>` mentioned above) are called by that thread. That means 150that thread is also the ONLY caller of :cpp:func:`lv_timer_handler`. (See 151:ref:`add_lvgl_to_your_project` for more information.) 152 153This ensures LVGL's data structures "appear" atomic_ (all threads using this data 154"see" it in a consistent state) by the fact that no other threads are "viewing" those 155data structures. This is enforced by programmer discipline that ensures the `Gateway 156Thread`_ is the only thread making LVGL calls (excluding the :ref:`exceptions 157<os_exception>` mentioned above). 158 159If `atomic data`_ relevant to the user interface is updated in another thread (i.e. 160by another task or in an interrupt), the thread calling LVGL functions can read that 161data directly without worry that it is in an inconsistent state. (To avoid 162unnecessary CPU overhead, a mechanism can be provided [such as a flag raised by the 163updating thread] so that the user interface is only updated when it will result in a 164change visible to the end user.) 165 166If `non-atomic data`_ relevant to the user interface is updated in another thread 167(i.e. by another task or in an interrupt), an alternate (and safe) way of convey that 168data to the thread calling LVGL functions is to pass a private copy of that data to 169that thread via a QUEUE or other OS mechanism that protects that data from being seen 170in an inconsistent state. 171 172Use of a `Gateway Thread`_ avoids the CPU-overhead (and coding overhead) of using a 173MUTEX to protect LVGL data structures. 174 175 176Method 2: Use a MUTEX 177---------------------- 178A MUTEX stands for "MUTually EXclusive" and is a synchronization primitive that 179protects the state of a system resource from being modified or accessed by multiple 180threads of execution at once. In other words, it makes data so protected "appear" 181atomic (all threads using this data "see" it in a consistent state). Most OSes 182provide MUTEXes. 183 184The system designer assigns a single MUTEX to product a single system resource. Once 185assigned, that MUTEX performs such protection by programmers: 186 1871. acquiring the MUTEX (a.k.a. locking it) before accessing or modifying that 188 resource, and 189 1902. releasing the MUTEX (a.k.a. unlocking it) after that access or modification 191 is complete. 192 193If a thread attempts to acquire (lock) the MUTEX while another thread "owns" it, 194that thread waits on the other thread to release (unlock) it before it is allowed 195to continue execution. 196 197To be clear: this must be done *both* by threads that READ from that resource, and 198threads that MODIFY that resource. 199 200If a MUTEX is used to protect LVGL data structures, that means *every* LVGL function 201call (or group of function calls) must be preceded by #1, and followed by #2, 202including calls to :cpp:func:`lv_timer_handler`. 203 204.. note:: 205 If your OS is integrated with LVGL (the macro :c:macro:`LV_USE_OS` has a value 206 other than ``LV_OS_NONE`` in ``lv_conf.h``) you can use :cpp:func:`lv_lock()` and 207 :cpp:func:`lv_unlock()` to perform #1 and #2. 208 209 When this is the case, :cpp:func:`lv_timer_handler` calls :cpp:func:`lv_lock()` 210 and :cpp:func:`lv_unlock()` internally, so you do not have to bracket your 211 calls to :cpp:func:`lv_timer_handler` with them. 212 213 If your OS is NOT integrated with LVGL, then these calls either return 214 immediately with no effect, or are optimized away by the linker. 215 216 To enable :cpp:func:`lv_lock()` and :cpp:func:`lv_unlock()`, set ``LV_USE_OS`` 217 to a value other than ``LV_OS_NONE``. 218 219This pseudocode illustrates the concept of using a MUTEX: 220 221.. code-block:: c 222 223 void lvgl_thread(void) 224 { 225 while(1) { 226 uint32_t time_till_next; 227 time_till_next = lv_timer_handler(); /* lv_lock/lv_unlock is called internally */ 228 thread_sleep(time_till_next); /* sleep for a while */ 229 } 230 } 231 232 void other_thread(void) 233 { 234 /* You must always hold (lock) the MUTEX while calling LVGL functions. */ 235 lv_lock(); 236 lv_obj_t *img = lv_image_create(lv_screen_active()); 237 lv_unlock(); 238 239 while(1) { 240 lv_lock(); 241 /* Change to next image. */ 242 lv_image_set_src(img, next_image); 243 lv_unlock(); 244 thread_sleep(2000); 245 } 246 } 247 248 249 250.. _sleep_management: 251 252Sleep Management 253**************** 254 255The MCU can go to sleep when no user input has been received for a certain period. 256In this case, the main ``while(1)`` could look like this: 257 258.. code-block:: c 259 260 while(1) { 261 /* Normal operation (no sleep) in < 1 sec inactivity */ 262 if(lv_display_get_inactive_time(NULL) < 1000) { 263 lv_timer_handler(); 264 } 265 /* Sleep after 1 sec inactivity */ 266 else { 267 timer_stop(); /* Stop the timer where lv_tick_inc() is called */ 268 sleep(); /* Sleep the MCU */ 269 } 270 my_delay_ms(5); 271 } 272 273You should also add the following lines to your input device read 274function to signal a wake-up (press, touch, click, etc.) has happened: 275 276.. code-block:: c 277 278 lv_tick_inc(LV_DEF_REFR_PERIOD); /* Force task execution on wake-up */ 279 timer_start(); /* Restart timer where lv_tick_inc() is called */ 280 lv_timer_handler(); /* Call `lv_timer_handler()` manually to process the wake-up event */ 281 282In addition to :cpp:func:`lv_display_get_inactive_time` you can check 283:cpp:func:`lv_anim_count_running` to see if all animations have finished. 284 285 286 287