Need help with intermittent crashes

Description

I have adapted an example ESP-IDF project but I am getting intermittent crashes and cannot figure out what the issue is. I have trimmed back the code and put all into one source file to test and make it easier to share.

I thought the issue could be with using structures for the LVGL objects as the app worked ok when not using them but that could just be due to there being less code and different memory usage. The code allows me to easily switch between the 2 methods.

URL for ESP-IDF example:

This is the error I get.


I (560) example: m 51 3 30
Guru Meditation Error: Core  0 panic'ed (StoreProhibited). Exception was unhandled.

Core  0 register dump:
PC      : 0x4203b516  PS      : 0x00060a33  A0      : 0x8203b600  A1      : 0x3fc9ad10  
--- 0x4203b516: timer_process_alarm at C:/Espressif/v5.4/esp-idf/components/esp_timer/src/esp_timer.c:404 (discriminator 3)

A2      : 0x00000000  A3      : 0x00000001  A4      : 0x00000000  A5      : 0x00000000
A6      : 0x00000000  A7      : 0x3fca4e24  A8      : 0x00060023  A9      : 0x00000000
A10     : 0x00049787  A11     : 0x00049773  A12     : 0x3fc94bc0  A13     : 0x00060c23
A14     : 0x0000001b  A15     : 0x4037ad84  SAR     : 0x0000000a  EXCCAUSE: 0x0000001d
--- 0x4037ad84: systimer_ticks_to_us at C:/Espressif/v5.4/esp-idf/components/esp_hw_support/port/esp32s3/systimer.c:15

EXCVADDR: 0x00060023  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000


Backtrace: 0x4203b513:0x3fc9ad10 0x4203b5fd:0x3fc9ad40 0x4037be31:0x3fc9ad60
--- 0x4203b513: timer_process_alarm at C:/Espressif/v5.4/esp-idf/components/esp_timer/src/esp_timer.c:404 (discriminator 3)
0x4203b5fd: timer_task at C:/Espressif/v5.4/esp-idf/components/esp_timer/src/esp_timer.c:461 (discriminator 1)
0x4037be31: vPortTaskWrapper at C:/Espressif/v5.4/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:139

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

What LVGL version are you using?

9.2

Code to reproduce

Full code

/*
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_examples.h"
#include <string.h>
#include "sys/time.h"

#if CONFIG_EXAMPLE_LCD_CONTROLLER_ILI9341
#include "esp_lcd_ili9341.h"
#elif CONFIG_EXAMPLE_LCD_CONTROLLER_GC9A01
#include "esp_lcd_gc9a01.h"
#endif

static const char *TAG = "example";

// Using SPI2 in the example
#define LCD_HOST  SPI2_HOST

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ     (20 * 1000 * 1000)
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_SCLK           10
#define EXAMPLE_PIN_NUM_MOSI           11
#define EXAMPLE_PIN_NUM_MISO           -1
#define EXAMPLE_PIN_NUM_LCD_DC         8
#define EXAMPLE_PIN_NUM_LCD_RST        12
#define EXAMPLE_PIN_NUM_LCD_CS         9
#define EXAMPLE_PIN_NUM_BK_LIGHT       40
#define EXAMPLE_PIN_NUM_TOUCH_CS       -1

// The pixel number in horizontal and vertical
#if CONFIG_EXAMPLE_LCD_CONTROLLER_ILI9341
#define EXAMPLE_LCD_H_RES              240
#define EXAMPLE_LCD_V_RES              320
#elif CONFIG_EXAMPLE_LCD_CONTROLLER_GC9A01
#define EXAMPLE_LCD_H_RES              240
#define EXAMPLE_LCD_V_RES              240
#endif
// Bit number used to represent command and parameter
#define EXAMPLE_LCD_CMD_BITS           8
#define EXAMPLE_LCD_PARAM_BITS         8

#define EXAMPLE_LVGL_DRAW_BUF_LINES    24 // number of display lines in each draw buffer
#define EXAMPLE_LVGL_TICK_PERIOD_MS    2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE   (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY     2

// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
static _lock_t lvgl_api_lock;

#define PV1_USE_STRUCT 1
#define PV1_DIALS 4 //Maximum of 4

static lv_obj_t * scr;
static lv_obj_t * dial;
static lv_obj_t * hourHand;
static lv_anim_t hourHandAnim;
static lv_obj_t * minuteHand;
static lv_anim_t minuteHandAnim;

typedef struct {
    lv_obj_t * dial;
    char *user_data;
    lv_obj_t * hourHand;
    lv_anim_t hourHandAnim;
    lv_obj_t * minuteHand;
    lv_anim_t minuteHandAnim;
} pv1_struct;

static pv1_struct pv1[PV1_DIALS];

static void set_hour_value(void * obj, int32_t v)
{
    if (PV1_USE_STRUCT) {
        char * user_data=lv_obj_get_user_data(obj);
        int d=user_data[0]-'0';
        ESP_LOGI(TAG, "h %d %d %ld",user_data[0],d,v);
        lv_scale_set_line_needle_value(obj, pv1[d].hourHand, 35, v);
    } else {
        lv_scale_set_line_needle_value(obj, hourHand, 35, v);
    }
}
static void set_minute_value(void * obj, int32_t v)
{
    if (PV1_USE_STRUCT) {
        char * user_data=lv_obj_get_user_data(obj);
        int d=user_data[0]-'0';
        ESP_LOGI(TAG, "m %d %d %ld",user_data[0],d,v);
        lv_scale_set_line_needle_value(obj, pv1[d].minuteHand, 35, v);
    } else {
        lv_scale_set_line_needle_value(obj, minuteHand, 35, v);
    }
}

void pv1_setup(lv_display_t *disp,int32_t number)
{
    static int32_t pos_x[] = {22,120,22,120};
    static int32_t pos_y[] = {22,22,120,120};

    lv_disp_set_rotation(disp, LV_DISP_ROTATION_270);
    scr = lv_display_get_screen_active(disp);
    lv_obj_set_style_bg_color(scr, lv_palette_main(LV_PALETTE_GREY), LV_PART_MAIN);

    if (PV1_USE_STRUCT) {
        for (int d=0;d<PV1_DIALS;d++) {
            pv1[d].dial = lv_scale_create(scr);
            pv1[d].user_data=malloc(10);
            pv1[d].user_data[0]='0'+d;
            pv1[d].user_data[1]='\0';

            lv_obj_set_size(pv1[d].dial , 98,98);
            lv_scale_set_mode(pv1[d].dial, LV_SCALE_MODE_ROUND_INNER);
            lv_obj_set_style_bg_opa(pv1[d].dial, LV_OPA_100, 0);
            lv_obj_set_style_bg_color(pv1[d].dial , lv_palette_main(LV_PALETTE_BLUE), 0);
            lv_obj_set_style_radius(pv1[d].dial, LV_RADIUS_CIRCLE, 0);
            lv_obj_set_style_clip_corner(pv1[d].dial, true, 0);
            lv_scale_set_label_show(pv1[d].dial,false);
            lv_scale_set_total_tick_count(pv1[d].dial, 13);
            lv_obj_set_pos(pv1[d].dial , pos_x[d],pos_y[d]);
            lv_obj_set_style_length(pv1[d].dial, 5, LV_PART_ITEMS);
            lv_obj_set_style_length(pv1[d].dial, 10, LV_PART_INDICATOR);
            lv_scale_set_range(pv1[d].dial, 0, 60);
            lv_scale_set_angle_range(pv1[d].dial, 360);
            lv_scale_set_rotation(pv1[d].dial, 270);
            lv_obj_set_user_data(pv1[d].dial,pv1[d].user_data);
            //lv_obj_update_layout(pv1[d].dial);

            pv1[d].hourHand = lv_line_create(pv1[d].dial);
            lv_obj_set_style_line_width(pv1[d].hourHand, 6, LV_PART_MAIN);
            lv_obj_set_style_line_rounded(pv1[d].hourHand, true, LV_PART_MAIN);
            lv_scale_set_line_needle_value(pv1[d].dial, pv1[d].hourHand, 35, 0+(number*10));
            //lv_obj_update_layout(pv1[d].hourHand);
            
            pv1[d].minuteHand = lv_line_create(pv1[d].dial);
            lv_obj_set_style_line_width(pv1[d].minuteHand, 6, LV_PART_MAIN);
            lv_obj_set_style_line_rounded(pv1[d].minuteHand, true, LV_PART_MAIN);
            lv_scale_set_line_needle_value(pv1[d].dial, pv1[d].minuteHand, 35, 60-(number*10));
            //lv_obj_update_layout(pv1[d].minuteHand);

            lv_anim_init(&pv1[d].hourHandAnim);
            lv_anim_set_var(&pv1[d].hourHandAnim, pv1[d].dial);
            lv_anim_set_exec_cb(&pv1[d].hourHandAnim, set_hour_value);
            lv_anim_set_duration(&pv1[d].hourHandAnim, 500);
            lv_anim_set_values(&pv1[d].hourHandAnim, 30, 5);
            lv_anim_start(&pv1[d].hourHandAnim);

            lv_anim_init(&pv1[d].minuteHandAnim);
            lv_anim_set_var(&pv1[d].minuteHandAnim, pv1[d].dial);
            lv_anim_set_exec_cb(&pv1[d].minuteHandAnim, set_minute_value);
            lv_anim_set_duration(&pv1[d].minuteHandAnim, 500);
            lv_anim_set_values(&pv1[d].minuteHandAnim, 30, 55);
            lv_anim_start(&pv1[d].minuteHandAnim);
        }
    } else {
        dial = lv_scale_create(scr);
        lv_obj_set_size(dial , 98,98);
        lv_scale_set_mode(dial, LV_SCALE_MODE_ROUND_INNER);
        lv_obj_set_style_bg_opa(dial, LV_OPA_100, 0);
        lv_obj_set_style_bg_color(dial , lv_palette_main(LV_PALETTE_BLUE), 0);
        lv_obj_set_style_radius(dial, LV_RADIUS_CIRCLE, 0);
        lv_obj_set_style_clip_corner(dial, true, 0);
        lv_scale_set_label_show(dial,false);
        lv_scale_set_total_tick_count(dial, 12);
        lv_obj_set_pos(dial , 30,30);
        lv_obj_set_style_length(dial, 5, LV_PART_ITEMS);
        lv_obj_set_style_length(dial, 10, LV_PART_INDICATOR);
        lv_scale_set_range(dial, 0, 60);
        lv_scale_set_angle_range(dial, 360);
        lv_scale_set_rotation(dial, 270);
        //lv_obj_update_layout(dial);

        hourHand = lv_line_create(dial);
        lv_obj_set_style_line_width(hourHand, 6, LV_PART_MAIN);
        lv_obj_set_style_line_rounded(hourHand, true, LV_PART_MAIN);
        lv_scale_set_line_needle_value(dial, hourHand, 35, 0+(number*10));
        //lv_obj_update_layout(hourHand);
        
        minuteHand = lv_line_create(dial);
        lv_obj_set_style_line_width(minuteHand, 6, LV_PART_MAIN);
        lv_obj_set_style_line_rounded(minuteHand, true, LV_PART_MAIN);
        lv_scale_set_line_needle_value(dial, minuteHand, 35, 60-(number*10));
        //lv_obj_update_layout(minuteHand);

        lv_anim_init(&hourHandAnim);
        lv_anim_set_var(&hourHandAnim, dial);
        lv_anim_set_exec_cb(&hourHandAnim, set_hour_value);
        lv_anim_set_duration(&hourHandAnim, 500);
        lv_anim_set_values(&hourHandAnim, 30, 5);
        lv_anim_start(&hourHandAnim);

        lv_anim_init(&minuteHandAnim);
        lv_anim_set_var(&minuteHandAnim, dial);
        lv_anim_set_exec_cb(&minuteHandAnim, set_minute_value);
        lv_anim_set_duration(&minuteHandAnim, 500);
        lv_anim_set_values(&minuteHandAnim, 30, 55);
        lv_anim_start(&minuteHandAnim);
    }
}

void pv1_tick(lv_display_t *disp,int32_t number)
{
    ESP_LOGI(TAG, "Tick %ld %ld ",number,(60-number));

    if (PV1_USE_STRUCT) {
        for (int d=0;d<PV1_DIALS;d++) {
            lv_anim_set_values(&pv1[d].hourHandAnim, (number-5)%60, number);
            lv_anim_start(&pv1[d].hourHandAnim);
            lv_anim_set_values(&pv1[d].minuteHandAnim, (60-number+5)%60, (60-number)%60);
            lv_anim_start(&pv1[d].minuteHandAnim);
        }
    } else {
        lv_anim_set_values(&hourHandAnim, (number-5)%60, number);
        lv_anim_start(&hourHandAnim);
        lv_anim_set_values(&minuteHandAnim, (60-number+5)%60, (60-number)%60);
        lv_anim_start(&minuteHandAnim);
    }
}

void pv1_run(lv_display_t *disp)
{   
    _lock_acquire(&lvgl_api_lock);
    pv1_setup(disp,0);
    _lock_release(&lvgl_api_lock);
    sleep(2);

    int i=1;
    while (1) {
        _lock_acquire(&lvgl_api_lock);
        pv1_tick(disp,i%60);
        _lock_release(&lvgl_api_lock);
        i+=5;
        sleep(1);
    }
}

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp);
    return false;
}

/* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */
static void example_lvgl_port_update_callback(lv_display_t *disp)
{
    esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
    lv_display_rotation_t rotation = lv_display_get_rotation(disp);
    //ESP_LOGI(TAG, "Rotation: %d",rotation); //This is 0
    switch (rotation) {
    case LV_DISPLAY_ROTATION_0:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, false);
        esp_lcd_panel_mirror(panel_handle, true, false);
        break;
    case LV_DISPLAY_ROTATION_90:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, true);
        esp_lcd_panel_mirror(panel_handle, true, true);
        break;
    case LV_DISPLAY_ROTATION_180:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, false);
        esp_lcd_panel_mirror(panel_handle, false, true);
        break;
    case LV_DISPLAY_ROTATION_270:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, true);
        esp_lcd_panel_mirror(panel_handle, false, false);
        break;
    }
}

static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    example_lvgl_port_update_callback(disp);
    esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // because SPI LCD is big-endian, we need to swap the RGB bytes order
    lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}

static void example_increase_lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

static void example_lvgl_port_task(void *arg)
{
    ESP_LOGI(TAG, "Starting LVGL task");
    uint32_t time_till_next_ms = 0;
    uint32_t time_threshold_ms = 1000 / CONFIG_FREERTOS_HZ;
    while (1) {
        _lock_acquire(&lvgl_api_lock);
        time_till_next_ms = lv_timer_handler();
        _lock_release(&lvgl_api_lock);
        // in case of triggering a task watch dog time out
        time_till_next_ms = MAX(time_till_next_ms, time_threshold_ms);
        usleep(1000 * time_till_next_ms);
    }
}

void app_main(void)
{
    ESP_LOGI(TAG, "Turn off LCD backlight");
    gpio_config_t bk_gpio_config = {
        .mode = GPIO_MODE_OUTPUT,
        .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
    };
    ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

    ESP_LOGI(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {
        .sclk_io_num = EXAMPLE_PIN_NUM_SCLK,
        .mosi_io_num = EXAMPLE_PIN_NUM_MOSI,
        .miso_io_num = EXAMPLE_PIN_NUM_MISO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t),
    };
    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));

    ESP_LOGI(TAG, "Install panel IO");
    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,
        .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
        .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
        .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    // Attach the LCD to the SPI bus
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

    esp_lcd_panel_handle_t panel_handle = NULL;
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
        .bits_per_pixel = 16,
    };
#if CONFIG_EXAMPLE_LCD_CONTROLLER_ILI9341
    ESP_LOGI(TAG, "Install ILI9341 panel driver");
    ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(io_handle, &panel_config, &panel_handle));
#elif CONFIG_EXAMPLE_LCD_CONTROLLER_GC9A01
    ESP_LOGI(TAG, "Install GC9A01 panel driver");
    ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
#endif
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
#if CONFIG_EXAMPLE_LCD_CONTROLLER_GC9A01
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
#endif
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));

    // user can flush pre-defined pattern to the screen before we turn on the screen or backlight
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    ESP_LOGI(TAG, "Turn on LCD backlight");
    gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);

    ESP_LOGI(TAG, "Initialize LVGL library");
    lv_init();

    // create a lvgl display
    lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES);

    // alloc draw buffers used by LVGL
    // it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
    size_t draw_buffer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * sizeof(lv_color16_t);

    void *buf1 = spi_bus_dma_memory_alloc(LCD_HOST, draw_buffer_sz, 0);
    assert(buf1);
    void *buf2 = spi_bus_dma_memory_alloc(LCD_HOST, draw_buffer_sz, 0);
    assert(buf2);
    // initialize LVGL draw buffers
    lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
    // associate the mipi panel handle to the display
    lv_display_set_user_data(display, panel_handle);
    // set color depth
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
    // set the callback which can copy the rendered image to an area of the display
    lv_display_set_flush_cb(display, example_lvgl_flush_cb);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &example_increase_lvgl_tick,
        .name = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

    ESP_LOGI(TAG, "Register io panel event callback for LVGL flush ready notification");
    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = example_notify_lvgl_flush_ready,
    };
    /* Register done callback */
    ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display));

    ESP_LOGI(TAG, "Create LVGL task");
    xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
   
    ESP_LOGI(TAG, "LVGL example");
    pv1_run(display);
}

Just the part related to rendering the dials.

#define PV1_USE_STRUCT 1
#define PV1_DIALS 4 //Maximum of 4

static lv_obj_t * scr;
static lv_obj_t * dial;
static lv_obj_t * hourHand;
static lv_anim_t hourHandAnim;
static lv_obj_t * minuteHand;
static lv_anim_t minuteHandAnim;

typedef struct {
    lv_obj_t * dial;
    char *user_data;
    lv_obj_t * hourHand;
    lv_anim_t hourHandAnim;
    lv_obj_t * minuteHand;
    lv_anim_t minuteHandAnim;
} pv1_struct;

static pv1_struct pv1[PV1_DIALS];

static void set_hour_value(void * obj, int32_t v)
{
    if (PV1_USE_STRUCT) {
        char * user_data=lv_obj_get_user_data(obj);
        int d=user_data[0]-'0';
        ESP_LOGI(TAG, "h %d %d %ld",user_data[0],d,v);
        lv_scale_set_line_needle_value(obj, pv1[d].hourHand, 35, v);
    } else {
        lv_scale_set_line_needle_value(obj, hourHand, 35, v);
    }
}
static void set_minute_value(void * obj, int32_t v)
{
    if (PV1_USE_STRUCT) {
        char * user_data=lv_obj_get_user_data(obj);
        int d=user_data[0]-'0';
        ESP_LOGI(TAG, "m %d %d %ld",user_data[0],d,v);
        lv_scale_set_line_needle_value(obj, pv1[d].minuteHand, 35, v);
    } else {
        lv_scale_set_line_needle_value(obj, minuteHand, 35, v);
    }
}

void pv1_setup(lv_display_t *disp,int32_t number)
{
    static int32_t pos_x[] = {22,120,22,120};
    static int32_t pos_y[] = {22,22,120,120};

    lv_disp_set_rotation(disp, LV_DISP_ROTATION_270);
    scr = lv_display_get_screen_active(disp);
    lv_obj_set_style_bg_color(scr, lv_palette_main(LV_PALETTE_GREY), LV_PART_MAIN);

    if (PV1_USE_STRUCT) {
        for (int d=0;d<PV1_DIALS;d++) {
            pv1[d].dial = lv_scale_create(scr);
            pv1[d].user_data=malloc(10);
            pv1[d].user_data[0]='0'+d;
            pv1[d].user_data[1]='\0';

            lv_obj_set_size(pv1[d].dial , 98,98);
            lv_scale_set_mode(pv1[d].dial, LV_SCALE_MODE_ROUND_INNER);
            lv_obj_set_style_bg_opa(pv1[d].dial, LV_OPA_100, 0);
            lv_obj_set_style_bg_color(pv1[d].dial , lv_palette_main(LV_PALETTE_BLUE), 0);
            lv_obj_set_style_radius(pv1[d].dial, LV_RADIUS_CIRCLE, 0);
            lv_obj_set_style_clip_corner(pv1[d].dial, true, 0);
            lv_scale_set_label_show(pv1[d].dial,false);
            lv_scale_set_total_tick_count(pv1[d].dial, 13);
            lv_obj_set_pos(pv1[d].dial , pos_x[d],pos_y[d]);
            lv_obj_set_style_length(pv1[d].dial, 5, LV_PART_ITEMS);
            lv_obj_set_style_length(pv1[d].dial, 10, LV_PART_INDICATOR);
            lv_scale_set_range(pv1[d].dial, 0, 60);
            lv_scale_set_angle_range(pv1[d].dial, 360);
            lv_scale_set_rotation(pv1[d].dial, 270);
            lv_obj_set_user_data(pv1[d].dial,pv1[d].user_data);
            //lv_obj_update_layout(pv1[d].dial);

            pv1[d].hourHand = lv_line_create(pv1[d].dial);
            lv_obj_set_style_line_width(pv1[d].hourHand, 6, LV_PART_MAIN);
            lv_obj_set_style_line_rounded(pv1[d].hourHand, true, LV_PART_MAIN);
            lv_scale_set_line_needle_value(pv1[d].dial, pv1[d].hourHand, 35, 0+(number*10));
            //lv_obj_update_layout(pv1[d].hourHand);
            
            pv1[d].minuteHand = lv_line_create(pv1[d].dial);
            lv_obj_set_style_line_width(pv1[d].minuteHand, 6, LV_PART_MAIN);
            lv_obj_set_style_line_rounded(pv1[d].minuteHand, true, LV_PART_MAIN);
            lv_scale_set_line_needle_value(pv1[d].dial, pv1[d].minuteHand, 35, 60-(number*10));
            //lv_obj_update_layout(pv1[d].minuteHand);

            lv_anim_init(&pv1[d].hourHandAnim);
            lv_anim_set_var(&pv1[d].hourHandAnim, pv1[d].dial);
            lv_anim_set_exec_cb(&pv1[d].hourHandAnim, set_hour_value);
            lv_anim_set_duration(&pv1[d].hourHandAnim, 500);
            lv_anim_set_values(&pv1[d].hourHandAnim, 30, 5);
            lv_anim_start(&pv1[d].hourHandAnim);

            lv_anim_init(&pv1[d].minuteHandAnim);
            lv_anim_set_var(&pv1[d].minuteHandAnim, pv1[d].dial);
            lv_anim_set_exec_cb(&pv1[d].minuteHandAnim, set_minute_value);
            lv_anim_set_duration(&pv1[d].minuteHandAnim, 500);
            lv_anim_set_values(&pv1[d].minuteHandAnim, 30, 55);
            lv_anim_start(&pv1[d].minuteHandAnim);
        }
    } else {
        dial = lv_scale_create(scr);
        lv_obj_set_size(dial , 98,98);
        lv_scale_set_mode(dial, LV_SCALE_MODE_ROUND_INNER);
        lv_obj_set_style_bg_opa(dial, LV_OPA_100, 0);
        lv_obj_set_style_bg_color(dial , lv_palette_main(LV_PALETTE_BLUE), 0);
        lv_obj_set_style_radius(dial, LV_RADIUS_CIRCLE, 0);
        lv_obj_set_style_clip_corner(dial, true, 0);
        lv_scale_set_label_show(dial,false);
        lv_scale_set_total_tick_count(dial, 12);
        lv_obj_set_pos(dial , 30,30);
        lv_obj_set_style_length(dial, 5, LV_PART_ITEMS);
        lv_obj_set_style_length(dial, 10, LV_PART_INDICATOR);
        lv_scale_set_range(dial, 0, 60);
        lv_scale_set_angle_range(dial, 360);
        lv_scale_set_rotation(dial, 270);
        //lv_obj_update_layout(dial);

        hourHand = lv_line_create(dial);
        lv_obj_set_style_line_width(hourHand, 6, LV_PART_MAIN);
        lv_obj_set_style_line_rounded(hourHand, true, LV_PART_MAIN);
        lv_scale_set_line_needle_value(dial, hourHand, 35, 0+(number*10));
        //lv_obj_update_layout(hourHand);
        
        minuteHand = lv_line_create(dial);
        lv_obj_set_style_line_width(minuteHand, 6, LV_PART_MAIN);
        lv_obj_set_style_line_rounded(minuteHand, true, LV_PART_MAIN);
        lv_scale_set_line_needle_value(dial, minuteHand, 35, 60-(number*10));
        //lv_obj_update_layout(minuteHand);

        lv_anim_init(&hourHandAnim);
        lv_anim_set_var(&hourHandAnim, dial);
        lv_anim_set_exec_cb(&hourHandAnim, set_hour_value);
        lv_anim_set_duration(&hourHandAnim, 500);
        lv_anim_set_values(&hourHandAnim, 30, 5);
        lv_anim_start(&hourHandAnim);

        lv_anim_init(&minuteHandAnim);
        lv_anim_set_var(&minuteHandAnim, dial);
        lv_anim_set_exec_cb(&minuteHandAnim, set_minute_value);
        lv_anim_set_duration(&minuteHandAnim, 500);
        lv_anim_set_values(&minuteHandAnim, 30, 55);
        lv_anim_start(&minuteHandAnim);
    }
}

void pv1_tick(lv_display_t *disp,int32_t number)
{
    ESP_LOGI(TAG, "Tick %ld %ld ",number,(60-number));

    if (PV1_USE_STRUCT) {
        for (int d=0;d<PV1_DIALS;d++) {
            lv_anim_set_values(&pv1[d].hourHandAnim, (number-5)%60, number);
            lv_anim_start(&pv1[d].hourHandAnim);
            lv_anim_set_values(&pv1[d].minuteHandAnim, (60-number+5)%60, (60-number)%60);
            lv_anim_start(&pv1[d].minuteHandAnim);
        }
    } else {
        lv_anim_set_values(&hourHandAnim, (number-5)%60, number);
        lv_anim_start(&hourHandAnim);
        lv_anim_set_values(&minuteHandAnim, (60-number+5)%60, (60-number)%60);
        lv_anim_start(&minuteHandAnim);
    }
}

void pv1_run(lv_display_t *disp)
{   
    _lock_acquire(&lvgl_api_lock);
    pv1_setup(disp,0);
    _lock_release(&lvgl_api_lock);
    sleep(2);

    int i=1;
    while (1) {
        _lock_acquire(&lvgl_api_lock);
        pv1_tick(disp,i%60);
        _lock_release(&lvgl_api_lock);
        i+=5;
        sleep(1);
    }
}

Screenshot and/or video

The video below shows how it should look when it is working.

Click here for YouTube video

I am not an expert in c coding so am struggling to track down the issue. Thanks in advance for any help.

I have solved my issue so thought I would post what I did in case others are have similar problems. I have changed my device to a Guition ESP32-S3-4848S040 but the device was not the cause of the issue.

First I created a new ESP-IDF project based on the RGB_LCD example and updated to have the correct LCD and touch screen settings.

Then I added LVGL v9.2 and the esp_lvgl_port component.

You need to ensure LGVL operations need to be enclosed inside a lock/unlock (see below). I did not have to add any additional mutex code.

    lvgl_port_lock(0);
    // Add your LVGL code here
    lvgl_port_unlock();

I have since added WiFi logic using esp-now and had no issues.

Hope this helps someone. :slight_smile:

1 Like