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