FreeRTOS

Usage

To enable LVGL's FreeRTOS support, just set LV_USE_OS LV_OS_FREERTOS in lv_conf.h. After this, LVGL:

  • GPUs will yield the task while waiting for the GPU to complete (lowering CPU usage)

  • lv_os_get_idle_percent will return the OS-aware CPU usage

  • lv_sleep_ms will be a wrapper to vTaskDelay

Note that LVGL itself doesn't contain FreeRTOS, meaning it's assumed that it's added and configured externally.

CPU Usage Measurement

If FreeRTOS is enabled, LVGL can measure the CPU usage by measuring the time spent in the idle function. To enable this, the following needs to be added in User_FreeRTOSConfig.h:

void lv_freertos_task_switch_in(const char * name);
void lv_freertos_task_switch_out(void);

#define traceTASK_SWITCHED_IN()   lv_freertos_task_switch_in(pxCurrentTCB->pcTaskName);
#define traceTASK_SWITCHED_OUT()  lv_freertos_task_switch_out();

Alternatively, on multi-core environments (like the ESP32 SMP), this default implementation can be insufficient or inaccurate. In this case, you can set LV_OS_IDLE_PERCENT_CUSTOM to 1 in lv_conf.h to disable the default implementation. Then, you can provide a custom implementation of lv_os_get_idle_percent tailored to your environment (e.g., using uxTaskGetSystemState()) without modifying LVGL source files.

Example: Custom implementation for ESP32 SMP

For multi-core environments like the ESP32 (which uses dual-core SMP), the default implementation might not track all idle tasks. Below is an example of a custom implementation using uxTaskGetSystemState(). This requires configGENERATE_RUN_TIME_STATS and configUSE_TRACE_FACILITY to be enabled in your FreeRTOSConfig.h (or via menuconfig for ESP-IDF).

#include "FreeRTOS.h"
#include "task.h"
#include <string.h>

uint32_t lv_os_get_idle_percent(void) {
    static uint32_t last_idle_time = 0;
    static uint32_t last_total_time = 0;

    uint32_t ulTotalRunTime;
    uint32_t ulIdleTime = 0;

    TaskStatus_t *pxTaskStatusArray;
    UBaseType_t uxArraySize, x;

    uxArraySize = uxTaskGetNumberOfTasks();
    pxTaskStatusArray = pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));

    if (pxTaskStatusArray == NULL) {
        return 0;
    }

    uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, &ulTotalRunTime);
    for (x = 0; x < uxArraySize; x++) {
        /* ESP32 has IDLE0 and IDLE1 tasks */
        if (strcmp(pxTaskStatusArray[x].pcTaskName, "IDLE0") == 0 ||
            strcmp(pxTaskStatusArray[x].pcTaskName, "IDLE1") == 0) {
            ulIdleTime += pxTaskStatusArray[x].ulRunTimeCounter;
        }
    }
    vPortFree(pxTaskStatusArray);

    uint32_t idle_diff = ulIdleTime - last_idle_time;
    uint32_t total_diff = ulTotalRunTime - last_total_time;

    last_idle_time = ulIdleTime;
    last_total_time = ulTotalRunTime;

    if (total_diff == 0) return 0;

    /* Combined idle percentage across both cores (0-200 on dual-core) */
    uint32_t idle_pct = (idle_diff * 100) / total_diff;

    /* For Sysmon, we usually want average load across all cores (0-100) */
    return idle_pct / 2; /* Adjust divisor based on your CPU core count */
}