From 70ed004ef3972e799c945af859b553a6022b7c35 Mon Sep 17 00:00:00 2001 From: Fabian Blatz Date: Sat, 1 Jun 2024 04:46:13 +0200 Subject: [PATCH] feat(demos): add demo for the OSAL (#6182) Co-authored-by: Gabor Kiss-Vamosi --- docs/porting/os.rst | 31 +++++--- examples/lv_examples.h | 1 + examples/porting/osal/lv_example_osal.c | 95 +++++++++++++++++++++++++ examples/porting/osal/lv_example_osal.h | 38 ++++++++++ lv_conf_template.h | 1 + src/core/lv_global.h | 5 ++ src/lv_conf_internal.h | 1 + src/lv_init.c | 4 ++ src/misc/lv_timer.c | 5 ++ src/osal/lv_os.c | 71 ++++++++++++++++++ src/osal/lv_os.h | 23 ++++++ src/osal/lv_os_private.h | 47 ++++++++++++ 12 files changed, 311 insertions(+), 11 deletions(-) create mode 100644 examples/porting/osal/lv_example_osal.c create mode 100644 examples/porting/osal/lv_example_osal.h create mode 100644 src/osal/lv_os.c create mode 100644 src/osal/lv_os_private.h diff --git a/docs/porting/os.rst b/docs/porting/os.rst index 81249bd56..6c68d43ee 100644 --- a/docs/porting/os.rst +++ b/docs/porting/os.rst @@ -7,8 +7,10 @@ Operating system and interrupts LVGL is **not thread-safe** by default. However, in the following conditions it's valid to call LVGL related -functions: - In *events*. Learn more in :ref:`events`. - -In *lv_timer*. Learn more in :ref:`timer`. +functions: + +- In *events*. Learn more in :ref:`events`. +- In *lv_timer*. Learn more in :ref:`timer`. Tasks and threads ----------------- @@ -20,19 +22,26 @@ around every LVGL (``lv_...``) related function call and code. This way you can use LVGL in a real multitasking environment. Just make use of a mutex to avoid the concurrent calling of LVGL functions. +LVGL has a built-in mutex which can be used with: +- :cpp:func:`lv_lock()` and :cpp:func:`lv_lock_isr()` +- :cpp:func:`lv_unlock()` + +These functions are called internally in :cpp:func:`lv_timer_handler` +and the users need to call them only from their own therads. + +To enable ``lv_lock/lv_unlock`` ``LV_USE_OS`` needs to be set to other +than ``LV_OS_NONE``. + + Here is some pseudocode to illustrate the concept: .. code:: c - static mutex_t lvgl_mutex; - void lvgl_thread(void) { while(1) { uint32_t time_till_next; - mutex_lock(&lvgl_mutex); - time_till_next = lv_timer_handler(); - mutex_unlock(&lvgl_mutex); + time_till_next = lv_timer_handler(); /*lv_lock/lv_unlock is called internally*/ thread_sleep(time_till_next); /* sleep for a while */ } } @@ -40,15 +49,15 @@ Here is some pseudocode to illustrate the concept: void other_thread(void) { /* You must always hold the mutex while using LVGL APIs */ - mutex_lock(&lvgl_mutex); + lv_lock(); lv_obj_t *img = lv_image_create(lv_screen_active()); - mutex_unlock(&lvgl_mutex); + lv_unlock(); while(1) { - mutex_lock(&lvgl_mutex); + lv_lock(); /* change to the next image */ lv_image_set_src(img, next_image); - mutex_unlock(&lvgl_mutex); + lv_unlock(); thread_sleep(2000); } } diff --git a/examples/lv_examples.h b/examples/lv_examples.h index dfdd94c35..b4f6c91b5 100644 --- a/examples/lv_examples.h +++ b/examples/lv_examples.h @@ -21,6 +21,7 @@ extern "C" { #include "layouts/lv_example_layout.h" #include "libs/lv_example_libs.h" #include "others/lv_example_others.h" +#include "porting/osal/lv_example_osal.h" #include "scroll/lv_example_scroll.h" #include "styles/lv_example_style.h" #include "widgets/lv_example_widgets.h" diff --git a/examples/porting/osal/lv_example_osal.c b/examples/porting/osal/lv_example_osal.c new file mode 100644 index 000000000..a854d6e96 --- /dev/null +++ b/examples/porting/osal/lv_example_osal.c @@ -0,0 +1,95 @@ +/** + * @file lv_example_osal.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../lv_examples.h" + +#if LV_BUILD_EXAMPLES + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void counter_button_event_cb(lv_event_t * e); +static void increment_thread_entry(void * user_data); + +/********************** + * STATIC VARIABLES + **********************/ +static lv_thread_sync_t press_sync; +static lv_thread_t increment_thread; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_example_osal(void) +{ + lv_obj_t * counter_button; + + counter_button = lv_button_create(lv_screen_active()); + lv_obj_align(counter_button, LV_ALIGN_CENTER, 0, -15); + lv_obj_add_event_cb(counter_button, counter_button_event_cb, LV_EVENT_CLICKED, NULL); + + if(lv_thread_sync_init(&press_sync) != LV_RESULT_OK) { + LV_LOG_ERROR("Error initializing thread sync"); + } + + if(lv_thread_init(&increment_thread, LV_THREAD_PRIO_MID, increment_thread_entry, 2048, NULL) != LV_RESULT_OK) { + LV_LOG_ERROR("Error initializing thread"); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void counter_button_event_cb(lv_event_t * e) +{ + LV_UNUSED(e); + if(lv_thread_sync_signal(&press_sync) != LV_RESULT_OK) { + LV_LOG_ERROR("Error signaling thread sync"); + } +} + +static void increment_thread_entry(void * user_data) +{ + LV_UNUSED(user_data); + lv_obj_t * counter_label; + uint32_t press_count = 0; + + lv_lock(); + counter_label = lv_label_create(lv_scr_act()); + lv_obj_align(counter_label, LV_ALIGN_CENTER, 0, 0); + lv_label_set_text_fmt(counter_label, "Pressed %u times", press_count); + lv_unlock(); + + while(true) { + if(lv_thread_sync_wait(&press_sync) != LV_RESULT_OK) { + LV_LOG_ERROR("Error awaiting thread sync"); + } + press_count += 1; + + lv_lock(); + lv_label_set_text_fmt(counter_label, "Pressed %u times", press_count); + lv_unlock(); + } +} + + +#endif diff --git a/examples/porting/osal/lv_example_osal.h b/examples/porting/osal/lv_example_osal.h new file mode 100644 index 000000000..2de069bd3 --- /dev/null +++ b/examples/porting/osal/lv_example_osal.h @@ -0,0 +1,38 @@ +/** + * @file lv_example_osal.h + * + */ + +#ifndef LV_EXAMPLE_OSAL_H +#define LV_EXAMPLE_OSAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_osal(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_EXAMPLE_OSAL_H*/ diff --git a/lv_conf_template.h b/lv_conf_template.h index 7580efb0f..e5b8ae3ec 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -1028,6 +1028,7 @@ /*Vector graphic demo*/ #define LV_USE_DEMO_VECTOR_GRAPHIC 0 + /*--END OF LV_CONF_H--*/ #endif /*LV_CONF_H*/ diff --git a/src/core/lv_global.h b/src/core/lv_global.h index 02df3eec7..d64b14e7a 100644 --- a/src/core/lv_global.h +++ b/src/core/lv_global.h @@ -27,6 +27,7 @@ extern "C" { #include "../misc/lv_log.h" #include "../misc/lv_style.h" #include "../misc/lv_timer.h" +#include "../osal/lv_os.h" #include "../others/sysmon/lv_sysmon.h" #include "../stdlib/builtin/lv_tlsf.h" @@ -210,6 +211,10 @@ typedef struct _lv_global_t { struct _lv_nuttx_ctx_t * nuttx_ctx; #endif +#if LV_USE_OS != LV_OS_NONE + lv_mutex_t lv_general_mutex; +#endif + void * user_data; } lv_global_t; diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index ca603e9de..134c48e7f 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -3349,6 +3349,7 @@ #endif + /*---------------------------------- * End of parsing lv_conf_template.h -----------------------------------*/ diff --git a/src/lv_init.c b/src/lv_init.c index cfc46cf15..afedb8a1c 100644 --- a/src/lv_init.c +++ b/src/lv_init.c @@ -24,6 +24,8 @@ #include "draw/lv_draw.h" #include "misc/lv_async.h" #include "misc/lv_fs.h" +#include "osal/lv_os_private.h" + #if LV_USE_DRAW_VGLITE #include "draw/nxp/vglite/lv_draw_vglite.h" #endif @@ -160,6 +162,8 @@ void lv_init(void) lv_profiler_builtin_init(&profiler_config); #endif + lv_os_init(); + _lv_timer_core_init(); _lv_fs_init(); diff --git a/src/misc/lv_timer.c b/src/misc/lv_timer.c index 5537e5910..57d201fe9 100644 --- a/src/misc/lv_timer.c +++ b/src/misc/lv_timer.c @@ -78,6 +78,8 @@ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) } LV_PROFILER_BEGIN; + lv_lock(); + uint32_t handler_start = lv_tick_get(); if(handler_start == 0) { @@ -139,7 +141,10 @@ LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) state_p->already_running = false; /*Release the mutex*/ LV_TRACE_TIMER("finished (%" LV_PRIu32 " ms until the next timer call)", time_until_next); + lv_unlock(); + LV_PROFILER_END; + return time_until_next; } diff --git a/src/osal/lv_os.c b/src/osal/lv_os.c new file mode 100644 index 000000000..7b775f034 --- /dev/null +++ b/src/osal/lv_os.c @@ -0,0 +1,71 @@ +/** + * @file lv_os.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_os.h" +#include "lv_os_private.h" +#include "../core/lv_global.h" + +/********************* + * DEFINES + *********************/ +#define lv_general_mutex LV_GLOBAL_DEFAULT()->lv_general_mutex + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_os_init(void) +{ +#if LV_USE_OS != LV_OS_NONE + lv_mutex_init(&lv_general_mutex); +#endif /*LV_USE_OS != LV_OS_NONE*/ +} + +void lv_lock(void) +{ +#if LV_USE_OS != LV_OS_NONE + lv_mutex_lock(&lv_general_mutex); +#endif /*LV_USE_OS != LV_OS_NONE*/ +} + +lv_result_t lv_lock_isr(void) +{ +#if LV_USE_OS != LV_OS_NONE + return lv_mutex_lock_isr(&lv_general_mutex); +#else /*LV_USE_OS != LV_OS_NONE*/ + return LV_RESULT_OK; +#endif /*LV_USE_OS != LV_OS_NONE*/ +} + +void lv_unlock(void) +{ +#if LV_USE_OS != LV_OS_NONE + lv_mutex_unlock(&lv_general_mutex); +#endif /*LV_USE_OS != LV_OS_NONE*/ +} + +/********************** + * STATIC FUNCTIONS + **********************/ + diff --git a/src/osal/lv_os.h b/src/osal/lv_os.h index 708ca3807..e9d745d87 100644 --- a/src/osal/lv_os.h +++ b/src/osal/lv_os.h @@ -145,6 +145,29 @@ lv_result_t lv_thread_sync_signal(lv_thread_sync_t * sync); */ lv_result_t lv_thread_sync_delete(lv_thread_sync_t * sync); +/** + * Lock LVGL's general mutex. + * LVGL is not thread safe, so a mutex is used to avoid executing multiple LVGL functions at the same time + * from different threads. It shall be called when calling LVGL functions from threads + * different than lv_timer_handler's thread. It doesn't need to be called in LVGL events because + * they are called from lv_timer_handler(). + * It is called internally in lv_timer_handler(). + */ +void lv_lock(void); + +/** + * Same as `lv_lock()` but can be called from an interrupt. + * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure + */ +lv_result_t lv_lock_isr(void); + +/** + * The pair of `lv_lock()` and `lv_lock_isr()`. + * It unlocks LVGL general mutex. + * It is called internally in lv_timer_handler(). + */ +void lv_unlock(void); + /********************** * MACROS **********************/ diff --git a/src/osal/lv_os_private.h b/src/osal/lv_os_private.h new file mode 100644 index 000000000..4fe08cd2f --- /dev/null +++ b/src/osal/lv_os_private.h @@ -0,0 +1,47 @@ +/** + * @file lv_os_private.h + * + */ + +#ifndef LV_OS_PRIVATE_H +#define LV_OS_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * OS OPTIONS + *********************/ + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the OS layer + */ +void lv_os_init(void); + + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_OS_PRIVATE_H*/