ESP32C3 - how to run a non-lvgl task in Freertos with LVGL

Description

I have LVGL working on an ESP32C3 with ESP IDF 5.1.2. I have a basic hello world thing going. Im using an ST7735 SPI display 160x128pixels. What im trying to accomplish is to read a temperature sensor and display that on my TFT. I have a task that runs every 10 seconds and does read the temperature (I2C) and display it on the console. However when this task runs, it seems to wipe out the display. The lv_task_handler while loop is still working (verified with printf) but the screen goes blank and never comes back. If i disable the temperature gathering task, the display will stay on with the ‘hello world’ indefinitely. So something is happening when the temperature task is called. Any idea what might be causing this? Ive spent days, read everythign i could find (including here) but cant seem to figure out how to get lvgl and FREERTOS non-lvgl tasks to work together.

What MCU/Processor/Board and compiler are you using?

ESP32C3

What LVGL version are you using?

8.3

What do you want to achieve?

What have you tried so far?

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

#include <stdio.h>
#include "string.h"

#include "freertos/FreeRTOS.h"
//#include "freertos/queue.h"
#include "freertos/task.h"
//#include "freertos/event_groups.h"
#include "freertos/semphr.h"

#include "nvs_flash.h"
#include "esp_flash.h"
#include "esp_chip_info.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "esp_log.h"

#include "driver/i2c.h"
#include "sht3x.h"

#include "lvgl/lvgl.h"
//#include "lvgl/examples/lv_examples.h"
#include "lvgl_esp32_drivers/lvgl_helpers.h"

#define I2C_MASTER_NUM 0 
#define I2C_MASTER_SCL_IO 10
#define I2C_MASTER_SDA_IO 9
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_TX_BUF_DISABLE 0 
#define I2C_MASTER_RX_BUF_DISABLE 0  
#define SHT3X_ADDRESS 0x44

#define DISP_HOR_RES 128
#define DISP_VER_RES 160
#define LVGL_TICK_PERIOD_MS 1

static sht3x_t sht3x_dev;
float temperature;
float humidity;
SemaphoreHandle_t xGuiSemaphore;
lv_disp_draw_buf_t draw_buf;
lv_disp_drv_t disp_drv;  

static void measure_temphum(void *pvParameters)
{
    TickType_t last_wakeup = xTaskGetTickCount();
    uint8_t duration = sht3x_get_measurement_duration(SHT3X_HIGH);
    while (1)
    {
        sht3x_start_measurement(&sht3x_dev, SHT3X_SINGLE_SHOT, SHT3X_HIGH);
        printf("MARKER: measure temp\n");
        // Wait until measurement is ready (constant time of at least 30 ms
        // or the duration returned from *sht3x_get_measurement_duration*).
        vTaskDelay(duration);

        sht3x_get_results(&sht3x_dev, &temperature, &humidity);
        printf("SHT3x Sensor: %.2f °C, %.2f %%\n", temperature, humidity);
        // perform one measurement and do something with the results

        vTaskDelayUntil(&last_wakeup, 10000/portTICK_PERIOD_MS);
        printf( "Task Name\tStatus\tPrio\tHWM\tTask\tAffinity\n");
        char stats_buffer[1024];
        vTaskList(stats_buffer);
        printf("%s\n", stats_buffer);
        printf("MARKER: end of main\n");
    }
}

static void gui_app(void)
{
    lv_obj_t * scr1 = lv_obj_create(NULL);
    lv_obj_set_style_bg_color(scr1, lv_color_make(0,0,63), LV_PART_MAIN);
    lv_obj_t * label = lv_label_create(scr1);
    lv_label_set_text(label, "Hello world!");
    lv_obj_set_style_text_color(label, lv_color_make(0,63,0), LV_PART_MAIN);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
    lv_scr_load(scr1);
}

static void lvgl_tick_task(void *arg)
{
    (void) arg;
    lv_tick_inc(LVGL_TICK_PERIOD_MS);
}

static void lvgl_gui_init()
{
    /*Setup LVGL*/
    lv_init();
    lvgl_driver_init();

    /*Declare a buffer for 1/10 screen size*/
    static lv_color_t buf1[DISP_HOR_RES * DISP_VER_RES / 10];
    assert(buf1);
    static lv_color_t buf2[DISP_HOR_RES * DISP_VER_RES / 10];
    assert(buf2);

    /*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_HOR_RES * DISP_VER_RES / 10);

    //static lv_disp_drv_t disp_drv;  
    lv_disp_drv_init(&disp_drv);            /*Basic initialization*/
    disp_drv.flush_cb = st7735s_flush;      /*Set your driver function*/
    disp_drv.draw_buf = &draw_buf;          /*Assign the buffer to the display*/
    disp_drv.hor_res = DISP_HOR_RES;        /*Set the horizontal resolution of the display*/
    disp_drv.ver_res = DISP_VER_RES;        /*Set the vertical resolution of the display*/
    disp_drv.rotated = 1;
    lv_disp_drv_register(&disp_drv);        /*Finally register the driver*/
    /*Create timer to call lv_tick*/
    static const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lvgl_tick_task,
        .name = "periodic_gui"
    };

    static esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LVGL_TICK_PERIOD_MS * 1000)); //time in microseconds

    gui_app();
    TickType_t last_wakeup;
    xGuiSemaphore = xSemaphoreCreateMutex();
    while (1) {

        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        //vTaskDelay(1000 / portTICK_PERIOD_MS);
        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {
            //printf("tick count before:%d\n",(int)xTaskGetTickCount());
            lv_timer_handler();
            //printf("tick count after:%d\n",(int)xTaskGetTickCount());
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            xSemaphoreGive(xGuiSemaphore);
        }
    }
    free(buf1);
    free(buf2);
    vTaskDelete(NULL);
}

void app_main(void)
{
    //Initialize NVS
    printf("MARKER: starting main\n");
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret); 

    //Check SOC Chip Specs
    printf("MARKER: check chip\n");
    check_chip();

    //Initialize I2C
    printf("MARKER: initialize I2C\n");
    ESP_ERROR_CHECK(i2c_master_init());
    printf("I2C:I2C Initialized SDA:%d SCL:%d CLK:%d\n",I2C_MASTER_SDA_IO,I2C_MASTER_SCL_IO,I2C_MASTER_FREQ_HZ);

    //Scan for I2C devices
    printf("MARKER: scan I2C\n");
    scan_i2c();
    i2c_driver_delete(I2C_NUM_0);

    //Initialize task for temphum measurement
    printf("MARKER: initialize I2C for temphum\n");
    ESP_ERROR_CHECK(i2cdev_init());
    memset(&sht3x_dev, 0, sizeof(sht3x_t));
    ESP_ERROR_CHECK(sht3x_init_desc(&sht3x_dev, SHT3X_ADDRESS, 0, I2C_MASTER_SDA_IO, I2C_MASTER_SCL_IO));
    ESP_ERROR_CHECK(sht3x_init(&sht3x_dev));


    xTaskCreate(&lvgl_gui_init, "lvgl_gui_init", 8192*4, NULL, 3, NULL);
    xTaskCreate(&measure_temphum, "measure_temphum", configMINIMAL_STACK_SIZE * 2, NULL, 0, NULL);

}

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

Primary in while loop for lv handler you cant use bigger delay as in lv_conf refresh time (default 30ms) Most use delay 5ms here… Too your code in while seems be not ok read examples for thread use in lvgl docu.

Thank you very much for your reply.

My apologies… I did have a faster timeout of 10ms in the lv_timer_handler and it was still the same result.

Here is the weird thing. Again, if lv_timer_handler is the only task, the display runs fine. The minute i introduce another task, the display will stop working as soon as the new task runs for the second time.

I even created a simple task like this:

static void print_something(void *arg)
{
    while (1) 
    {
        printf("task running\n");
        vTaskDelay(10000 / portTICK_PERIOD_MS);  
    }
}

and ran it with…

xTaskCreate(&print_something, "print_something", 4096, NULL, 0, NULL);

at very low priority and a 10 second cycle… The second that task runs for the second time, the display will go blank. It doesnt stop the lv_timer_handler task, but the display goes blank.

Ive been working on this for days but just cant figure it out. Either this sort of configuration cannot run on a single core ESP32, or im getting some sort of memory corruption somehow or i dont know what.

My next thought was to try to run the temperature collection as an lv_task instead of a FREERTOS task and see if that makes any difference.

I appreciate your help.

Interesting. i changed the lv_timer_handler back to 5ms… now i get a watchdog error

    xGuiSemaphore = xSemaphoreCreateMutex();
    while (1) {

        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        //vTaskDelay(1000 / portTICK_PERIOD_MS);
        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {
            //printf("tick count before:%d\n",(int)xTaskGetTickCount());
            lv_timer_handler();
            //printf("tick count after:%d\n",(int)xTaskGetTickCount());
            vTaskDelay(5 / portTICK_PERIOD_MS);
            xSemaphoreGive(xGuiSemaphore);
        }
    }

and the error

I (641) ST7735S: ST7735S initialization.
E (6021) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (6021) task_wdt:  - IDLE (CPU 0)
E (6021) task_wdt: Tasks currently running:
E (6021) task_wdt: CPU 0: lvgl_gui_init
E (6021) task_wdt: Print CPU 0 (current core) registers

Could this be because the temperature gathering task takes more than 5ms? i don’t know if the FREERTOS tasks are preemptive or not, need to check that.\

try change to

   while (1) {

            lv_timer_handler();
            vTaskDelay(5 / portTICK_PERIOD_MS);
    }

or if you plan update gui in other thread move outside if

vTaskDelay(5 / portTICK_PERIOD_MS);

but i dont recom use multithread gui manipulate

Hello again, well i tried just about everything. I changed the lv_task_handler to 5ms as you suggested. No change.

I monitored heap size and high water mark, no problem. Looked at task stack sizes, no problem there either.

Maybe there is some sort of DMA issue or something. I just cannot get it to work. I cant seem to find any examples either where someone is using ESP-IDF, LVGL 5.X, ESP32 and running FREERTOS tasks other than tasks associated with LVGL. There is something getting corrupted but i cant figure out. Or there is a timing issue somewhere.

I can verify that lv_tick_task is still running and so is lv_task_handler after the screen goes blank. Im maybe wondering if the objects im creating are somehow getting wiped with the task switching.

I think my next steps are to try to create an lv_task for my I2C temperature measurement task. If that doesnt work, then ill probably ditch the LVGL_ESP32_DRV drivers and try the LOVYANGFX drivers, possibly with LVGL, see if that makes a diff.

Maybe my problem is using the ESP32C3 with a single core. Maybe if i go to a ESP32S3 with two cores i can pin the LVGL stuff to one core and my temperature stuff to another.

Thanks for your help up to now though. I appreciate it.

might have made a significant discovery here.

If i remove any printf() statements from my other task (the one that measures temperature, the display does not reset.

Here is that other task for reference:

static void measure_temphum(void *pvParameters)
{
    TickType_t last_wakeup;

    uint8_t duration = sht3x_get_measurement_duration(SHT3X_HIGH);

    while (1)
    {
        last_wakeup = xTaskGetTickCount();
        sht3x_start_measurement(&sht3x_dev, SHT3X_SINGLE_SHOT, SHT3X_HIGH);
        // Wait until measurement is ready (constant time of at least 30 ms
        // or the duration returned from *sht3x_get_measurement_duration*).
        vTaskDelay(duration);

        sht3x_get_results(&sht3x_dev, &temperature, &humidity);
        //printf("SHT3x Sensor: %.2f °C, %.2f %%\n", temperature, humidity);
        // perform one measurement and do something with the results
        //printf("mem: %d\n",(int)uxTaskGetStackHighWaterMark(guitask));
        vTaskDelayUntil(&last_wakeup, 10000/portTICK_PERIOD_MS);
        // printf( "Task Name\tStatus\tPrio\tHWM\tTask\tAffinity\n");
        // char stats_buffer[1024];
        // vTaskList(stats_buffer);
        // printf("%s\n", stats_buffer);
        // printf("MARKER: end of main\n");
    }
}

So can you can see, i have commented out all printf() statements. Now it runs and the screen stays active, even though this task is in fact running every 10 seconds.

So why would printf() throw this whole thing off? Again, if i uncomment those printf() statements then the display will go blank after the first 10 seconds and a run of the above task.

Any thoughts?

1 Like

Interesting… tried to use the following as a way of getting output to the console… still tripped up the display… as soon as this message displayed, screen went blank.

ESP_LOGI("TEMP","Test Message");

Then i tried this instead:

heap_caps_check_integrity_all(true);

And once the message pops up on the console the display goes blank again.

There is someting with the output to the console that is throwing off the lvgl screen. Just cant figure it out

Seems as something in your idf configuration is in conflict. Building in IDF require perfect setup in menuconfig. For example slow uart speed for console or pin conflicts vs display can crash your code as you see. Hard to handle.
I more like here arduino use.

Thanks for that. I was now even able to verify that a printf in the lv_task_handler task will also affect the display. So basically any output after the display is working will disrupt the display and blank it out. NO errors, nothing interesting in the lvgl logs, nothing. Strangest thing.

Ill keep digging but i appreciate your continued input.
Tom

I had exactly the same problem with Serial.printf. Display went black, tasks (lv_timers) still runnning.
no Errors.
LVGL 8.
I found no solution yet.
It seems Serial.println works though - but I´m still not through with debugging since I’m still quite new in bussiness and started with a (too) big project.

Its crazy. Ive been pulling my hair out to try to solve this one but cant figure it out. I have written multi task esp-idf based programs and never seen this before. It seems to be pretty unique to using LVGL. It could be specific to my ESP32C3 board or some configuration in the ESP-IDF but so far i havent found anything. I may try the LOVYANGFX driver and see if that does the same thing.

esp-idf/COMPATIBILITY.md at master · espressif/esp-idf · GitHub

ESP32-C3

v0.2, v0.3

Supported since ESP-IDF v4.3.

v0.4

To be added.

And Tom why you dont use scope and locate signal , that black display. You write here about … Your display Various Interfaces -Parallel 8080-series MCU Interface
(8-bit, 9-bit, 16-bit & 18-bit) -3-line serial interface -4-line serial interface
What you use ??? How backlight control This isnt LEGO

You’re right i should have added those details about the display.

Its a 160x128 pixel 18-bit display. SPI interface, using MOSI/DC/CS/CLK/RST pins. Clocked at 4Mhz on the SPI bus. Has no backlight control (no pin or PWM support). ST7735S chip.

I could dig out the scope but i do think the issue is in software or most likely on the driver side. It seems the LVGL-ESP32-Drivers are a bit behind in support for LVGL 8.X let alone ESP-IDF 5.X. Im going to keep digging. I am quite new to this level of embedded troubleshooting but im learning fast :slight_smile:

Displays with memory as your cant go black without command… or RST or loast power.
First you need locate hw source of black, doabke only wuth scope or logic analyzer

Too 4MHz is veeeery slow clock for real FPS…

SOLVED

Ok i am embarrassed to admit this one. I guess when i was wiring this thing up, i wanted to use IO0 for the Reset pin on my display. I thought i had wired the RST on my ST7735 display to IO0.

Well if you look at the pinout on the WSP-C3-32S Kit, on the other side of the board from IO0 is U0TX, a UART line. This is not the first time that ive looked at a pinout diagram that shows the board from one side vs the other. I got all the other pins wired correctly except i ended up wiring RST->U0TX. My guess is that as soon as something was set to the console, the U0TX was signal’d and it reset the display.

And one thing i should clarify. I believe i said the display went blank, not black. By blank, i meant white, just backlight only.

So i have fully rewired this device and fixed the RST line and everything is working. I’ve used printf commands and the display is not affected.

Im sorry for not catching this before.

1 Like