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.
I am not an expert in c coding so am struggling to track down the issue. Thanks in advance for any help.