LVGL Support

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderators: barthess, RoccoMarco

dynfer
Posts: 11
Joined: Sun Aug 25, 2024 6:11 pm
Has thanked: 2 times
Been thanked: 5 times

LVGL Support

Postby dynfer » Sun Apr 13, 2025 9:09 pm

Hello,

Im currently playing around with LVGL on ChibiOS, I wanted to ask some professional minds if im adapting the required RTOS functions correctly. I've based my code on the present FreeRTOS implementation in LVGL.

Code below:

Code: Select all

/**
 * @file lv_chibios.h
 */

 #ifndef LV_CHIBIOS_H
 #define LV_CHIBIOS_H
 
 #include "hal.h"
 #include "ch.h"
 #include "lvgl/src/osal/lv_os.h"

 typedef struct {
     void (*pvStartRoutine)(void *);   /**< Application thread function */
     void * pTaskArg;                  /**< Argument to the thread function */
     thread_t * thread;                /**< Pointer to the created thread */
     void * stack;                     /**< Pointer to dynamically allocated stack memory */
 } lv_thread_t;

 typedef struct {
     bool xIsInitialized;        /**< pdTRUE if the mutex is initialized */
     mutex_t   xMutex;                 /**< Underlying ChibiOS mutex object */
     thread_t * owner;                 /**< The current owner (if any) */
     uint32_t  rec_count;              /**< Recursion count */
 } lv_mutex_t;

 typedef struct {
     bool xIsInitialized;            /**< pdTRUE if the condition variable is initialized */
     bool xSyncSignal;               /**< pdTRUE if a signal has been sent */
     semaphore_t xCondWaitSemaphore;       /**< Semaphore on which threads wait */
     uint32_t ulWaitingThreads;            /**< Count of threads waiting on the condition */
     mutex_t xSyncMutex;                   /**< Mutex to protect access to the condition variable */
 } lv_thread_sync_t;

 #endif /* LV_CHIBIOS_H */
 


Code: Select all

/**
 * @file lv_chibios.c
 */
/*********************
 *      INCLUDES
 *********************/
#include "hal.h"
#include "ch.h"
#include "lvgl/src/osal/lv_os.h"
#include "lvgl/src/misc/lv_log.h"
#include "lvgl/src/core/lv_global.h"

#define globals LV_GLOBAL_DEFAULT()
static msg_t prvRunThread(void * arg);

static void prvMutexInit(lv_mutex_t * pxMutex);
static void prvCheckMutexInit(lv_mutex_t * pxMutex);

static void prvCondInit(lv_thread_sync_t * pxCond);
static void prvCheckCondInit(lv_thread_sync_t * pxCond);
static void prvCheckCondInitIsr(lv_thread_sync_t * pxCond);
lv_result_t lv_thread_init(lv_thread_t * pxThread, const char * const name,
                           lv_thread_prio_t xSchedPriority,
                           void (*pvStartRoutine)(void *), size_t usStackSize,
                           void * xAttr)
{
    pxThread->pTaskArg = xAttr;
    pxThread->pvStartRoutine = pvStartRoutine;

    pxThread->stack = chHeapAlloc(NULL, usStackSize);
    if(pxThread->stack == NULL) {
        LV_LOG_ERROR("chHeapAlloc failed!");
        return LV_RESULT_INVALID;
    }
    pxThread->thread = chThdCreateFromHeap(NULL, usStackSize, name, xSchedPriority,
                                           prvRunThread, pxThread);
    if(pxThread->thread == NULL) {
        LV_LOG_ERROR("chThdCreateFromHeap failed!");
        chHeapFree(pxThread->stack);
        return LV_RESULT_INVALID;
    }

    return LV_RESULT_OK;
}

lv_result_t lv_thread_delete(lv_thread_t * pxThread)
{
    chThdTerminate(pxThread->thread);
    chHeapFree(pxThread->stack);
    return LV_RESULT_OK;
}

lv_result_t lv_mutex_init(lv_mutex_t * pxMutex)
{
    prvCheckMutexInit(pxMutex);
    return LV_RESULT_OK;
}

lv_result_t lv_mutex_lock(lv_mutex_t * pxMutex)
{
    prvCheckMutexInit(pxMutex);
    thread_t * current = chThdGetSelfX();
    /* If the current thread already owns the mutex, increase the recursion count */
    if(pxMutex->owner == current) {
        pxMutex->rec_count++;
        return LV_RESULT_OK;
    }
    chMtxLock(&pxMutex->xMutex);
    pxMutex->owner = current;
    pxMutex->rec_count = 1;
    return LV_RESULT_OK;
}

lv_result_t lv_mutex_lock_isr(lv_mutex_t * pxMutex)
{
    LV_LOG_ERROR("lv_mutex_lock_isr not supported in ChibiOS");
    return LV_RESULT_INVALID;
}

lv_result_t lv_mutex_unlock(lv_mutex_t * pxMutex)
{
    prvCheckMutexInit(pxMutex);
    thread_t * current = chThdGetSelfX();
    if(pxMutex->owner != current) {
        LV_LOG_ERROR("Mutex unlock called by non-owner");
        return LV_RESULT_INVALID;
    }
    if(pxMutex->rec_count > 1) {
        pxMutex->rec_count--;
    }
    else {
        pxMutex->owner = NULL;
        pxMutex->rec_count = 0;
        chMtxUnlock(&pxMutex->xMutex);
    }
    return LV_RESULT_OK;
}

lv_result_t lv_mutex_delete(lv_mutex_t * pxMutex)
{
    if(pxMutex->xIsInitialized == FALSE){
        return LV_RESULT_INVALID;
    }
    /* No explicit deletion needed because the mutex is allocated as part of the struct. */
    pxMutex->xIsInitialized = FALSE;
    return LV_RESULT_OK;
}

/*------------------------------
 * Condition variable functions
 *------------------------------*/
lv_result_t lv_thread_sync_init(lv_thread_sync_t * pxCond)
{
    prvCheckCondInit(pxCond);
    return LV_RESULT_OK;
}

lv_result_t lv_thread_sync_wait(lv_thread_sync_t * pxCond)
{
    lv_result_t lvRes = LV_RESULT_OK;
    prvCheckCondInit(pxCond);
   
    chMtxLock(&pxCond->xSyncMutex);
    while(pxCond->xSyncSignal == FALSE) {
        pxCond->ulWaitingThreads++;
        chMtxUnlock(&pxCond->xSyncMutex);
        chSemWait(&pxCond->xCondWaitSemaphore);
        chMtxLock(&pxCond->xSyncMutex);
    }
    /* Reset the signal after waking up */
    pxCond->xSyncSignal = FALSE;
    chMtxUnlock(&pxCond->xSyncMutex);
    return lvRes;
}

lv_result_t lv_thread_sync_signal(lv_thread_sync_t * pxCond)
{
    prvCheckCondInit(pxCond);
    chMtxLock(&pxCond->xSyncMutex);
    pxCond->xSyncSignal = TRUE;
    uint32_t waiting = pxCond->ulWaitingThreads;
    pxCond->ulWaitingThreads = 0;
    chMtxUnlock(&pxCond->xSyncMutex);

    /* Unblock all waiting threads by releasing the semaphore */
    for(uint32_t i = 0; i < waiting; i++) {
        chSemSignal(&pxCond->xCondWaitSemaphore);
    }
    return LV_RESULT_OK;
}

lv_result_t lv_thread_sync_delete(lv_thread_sync_t * pxCond)
{
    pxCond->xIsInitialized = FALSE;
    return LV_RESULT_OK;
}

lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * pxCond)
{
    pxCond->xSyncSignal = TRUE;
    uint32_t waiting = pxCond->ulWaitingThreads;
    pxCond->ulWaitingThreads = 0;
    chSysLockFromISR();
    for(uint32_t i = 0; i < waiting; i++) {
        chSemSignalI(&pxCond->xCondWaitSemaphore);
    }
    chSysUnlockFromISR();
    return LV_RESULT_OK;
}

static msg_t prvRunThread(void * arg)
{
    lv_thread_t * pxThread = (lv_thread_t *)arg;
    pxThread->pvStartRoutine(pxThread->pTaskArg);
    chThdExit(MSG_OK);
    return MSG_OK; /* Not reached */
}

static void prvMutexInit(lv_mutex_t * pxMutex)
{
    chMtxObjectInit(&pxMutex->xMutex);
    pxMutex->owner = NULL;
    pxMutex->rec_count = 0;
    pxMutex->xIsInitialized = TRUE;
}

static void prvCheckMutexInit(lv_mutex_t * pxMutex)
{
    if(pxMutex->xIsInitialized == FALSE) {
        chSysLock();
        prvMutexInit(pxMutex);
        chSysUnlock();
    }
}

static void prvCondInit(lv_thread_sync_t * pxCond)
{
    pxCond->xIsInitialized = TRUE;
    pxCond->xSyncSignal = FALSE;
    chSemObjectInit(&pxCond->xCondWaitSemaphore, 0);
    chMtxObjectInit(&pxCond->xSyncMutex);
    pxCond->ulWaitingThreads = 0;
}

static void prvCheckCondInit(lv_thread_sync_t * pxCond)
{
    if(pxCond->xIsInitialized == FALSE) {
        chSysLock();
        prvCondInit(pxCond);
        chSysUnlock();
    }
}

static void prvCheckCondInitIsr(lv_thread_sync_t * pxCond)
{
    if(pxCond->xIsInitialized == FALSE) {
        chSysLockFromISR();
        prvCondInit(pxCond);
        chSysUnlockFromISR();
    }
}

dynfer
Posts: 11
Joined: Sun Aug 25, 2024 6:11 pm
Has thanked: 2 times
Been thanked: 5 times

Re: LVGL Support

Postby dynfer » Thu May 15, 2025 10:56 pm


User avatar
Giovanni
Site Admin
Posts: 14627
Joined: Wed May 27, 2009 8:48 am
Location: Salerno, Italy
Has thanked: 1132 times
Been thanked: 944 times

Re: LVGL Support

Postby Giovanni » Fri May 16, 2025 7:45 am

Hi,

Which is the better way to give this a try? I always wanted to integrate LVGL but never had time...

About the implementation, while considering LVGL I thought that a simpler approach could be to run the whole thing inside a single thread so no synchronization primitives would be required.

In the OSLIB there is a "delegates" mechanisms that allows to delegate function calls to another thread acting as a server, would this be a good approach? Threads in the system would use LVGL by delegating function calls to the display server.

Anyway, I am interested in making LVGL support "official" like we already did for other libraries like FatFS, LittleFS, lwIP. Your work is opening a path to this.

Another topic is the display driver, would be possible to put in the HAL a driver model that LVGL could use directly? is there documentation for making LVGL drivers? that would be very convenient.

Giovanni

dynfer
Posts: 11
Joined: Sun Aug 25, 2024 6:11 pm
Has thanked: 2 times
Been thanked: 5 times

Re: LVGL Support

Postby dynfer » Sun May 18, 2025 5:31 pm

Giovanni wrote:Hi,

Which is the better way to give this a try? I always wanted to integrate LVGL but never had time...

About the implementation, while considering LVGL I thought that a simpler approach could be to run the whole thing inside a single thread so no synchronization primitives would be required.

In the OSLIB there is a "delegates" mechanisms that allows to delegate function calls to another thread acting as a server, would this be a good approach? Threads in the system would use LVGL by delegating function calls to the display server.

Anyway, I am interested in making LVGL support "official" like we already did for other libraries like FatFS, LittleFS, lwIP. Your work is opening a path to this.

Another topic is the display driver, would be possible to put in the HAL a driver model that LVGL could use directly? is there documentation for making LVGL drivers? that would be very convenient.

Giovanni


Thank You for Your input Giovanni.

I will give the delegates mechanism a shot and check how it will play out, but on the other side this would complicate the experience for the end user by creating calls to each delegate function unless we would create the whole API.

In terms of your second question:
General guide lines: https://docs.lvgl.io/latest/en/html/det ... index.html

Unfortunately I haven't found a dedicated documentation for drivers. I will try to reach out on the LVGL forum for it.

User avatar
Giovanni
Site Admin
Posts: 14627
Joined: Wed May 27, 2009 8:48 am
Location: Salerno, Italy
Has thanked: 1132 times
Been thanked: 944 times

Re: LVGL Support

Postby Giovanni » Sun May 18, 2025 6:26 pm

Yes, it would need a proxy API to make delegation transparent.

Giovanni

dynfer
Posts: 11
Joined: Sun Aug 25, 2024 6:11 pm
Has thanked: 2 times
Been thanked: 5 times

Re: LVGL Support

Postby dynfer » Sun May 18, 2025 7:50 pm

Giovanni wrote:Yes, it would need a proxy API to make delegation transparent.

Giovanni


I've updated the documentation I've created for LVGL with an example use of delegates.
https://github.com/lvgl/lvgl/blob/dffc2 ... hibios.rst

In fact im seeing a noticable performance increase in using those instead of mutexes.

User avatar
Giovanni
Site Admin
Posts: 14627
Joined: Wed May 27, 2009 8:48 am
Location: Salerno, Italy
Has thanked: 1132 times
Been thanked: 944 times

Re: LVGL Support

Postby Giovanni » Mon May 19, 2025 5:21 am

The problem I see in that code is: while the serve thread is in the sleep it is not able to dispatch calls, this causes lags when other threads request services.

You could try to make the thread just serve delegates and call the LVGL function using an external timer calling it as a delegate. This should increase responsiveness.

Giovanni


Return to “STM32 Support”

Who is online

Users browsing this forum: No registered users and 36 guests