Problem using animations in C++ class

Description

I have a C++ class that creates and updates an LVGL meter. Everything works fine if I don’t use animations. When I do use the animation in updateValue(), it crashes. Maybe the approach I’m using is completely wrong, I couldn’t find many examples online.

Edit: The reason I want to use a class like I did is to give the ability to the user to create X number of Meters and display them. In other words I want to create my meters when nessecary.

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

ESP32 / Platform IO / Arduino framework

What do you want to achieve?

Use the animation in updateValue().

What have you tried so far?

I had a lot of trouble making lv_anim_set_custom_exec_cb work, I started using lv_anim_set_exec_cb but couldn’t pass animCallback(void *indic, int32_t val) because it expected a free function. With some help I found that I could use a lamda with no capture like I did .

Right now, Serial.println(“before”); and Serial.println(“AFTER”); executes 2 times before it crashes so I know the code gets to animCallback().

The error seems to happen inside lv_timer_exec() which is in my loop.

Here is the only error message that I manage to get:

Backtrace: 0x00000012:0x3ffb2200 0x400e900c:0x3ffb2220 0x400d2b10:0x3ffb2250 0x400fbd65:0x3ffb2290

  #0  0x00000012:0x3ffb2200 in ?? ??:0
  #1  0x400e900c:0x3ffb2220 in lv_timer_exec at .pio/libdeps/esp32-3248S035C/lvgl/src/misc/lv_timer.c:313
      (inlined by) lv_timer_handler at .pio/libdeps/esp32-3248S035C/lvgl/src/misc/lv_timer.c:109
  #2  0x400d2b10:0x3ffb2250 in loop() at src/main.cpp:56
  #3  0x400fbd65:0x3ffb2290 in loopTask(void*) at C:/Users/Emilio/.platformio/packages/framework-arduinoespressif32/cores/esp32/main.cpp:50

Code to reproduce

*** meter.cpp
#include "meter.hpp"
#include "lvgl.h"
#include <Arduino.h>

Meter::Meter(lv_obj_t *parent, int min, int max)
    : meter(nullptr), indicator(nullptr), min_value(min), max_value(max), current_value(0), peak_value(0)
{
    meter = lv_meter_create(parent);
    lv_obj_set_size(meter, 200, 200);
    lv_obj_center(meter);

    lv_meter_scale_t *scale = lv_meter_add_scale(meter);
    lv_meter_set_scale_ticks(meter, scale, 41, 2, 10, lv_palette_main(LV_PALETTE_GREY));
    lv_meter_set_scale_major_ticks(meter, scale, 8, 4, 15, lv_color_black(), 10);

    lv_meter_indicator_t *indic;

    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_BLUE), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_RED), 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_RED), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    indicator = lv_meter_add_needle_line(meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);
}

void Meter::animCallback(void *indic, int32_t val)
{
    Serial.println("before");
    lv_meter_set_indicator_value(this->meter, static_cast<lv_meter_indicator_t *>(indic), val);
    Serial.println("AFTER");
}

void Meter::updateValue(int value)
{
    if (value > peak_value)
    {
        peak_value = value;
    }

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, indicator);

    lv_anim_set_time(&a, 10);
    lv_anim_set_user_data(&a, this);

    lv_anim_set_custom_exec_cb(&a, [](lv_anim_t *anim, int32_t val)
                               {
        auto *self = static_cast<Meter *>(lv_anim_get_user_data(anim)); // retrieve this
        self->animCallback(anim->var, val); });

    lv_anim_set_values(&a, current_value, value);
    lv_anim_start(&a);

    current_value = value;
}

void Meter::resetPeak()
{
    peak_value = current_value;
}


*** main.cpp

#include <Arduino.h>
#include <esp32_smartdisplay.h>
#include <ui/ui.h>
#include <random>
#include <iomanip>
#include "custom_ui/lv_custom_meter.h"
#include "custom_ui/parameter/meter.hpp"

std::random_device rd;
std::mt19937 gen(rd());
lv_obj_t *meter;
Meter *p_meter;

void onSettingsClick(lv_event_t *e)
{
  lv_disp_load_scr(ui_Gauge);
  Serial.println("ALLO222");
}

void onHamburgerClick(lv_event_t *e)
{
  lv_disp_load_scr(ui_Navigation);
  Serial.println("ALLO222111");
}

double generateRandomNumber(double lower_bound, double upper_bound)
{

  std::uniform_real_distribution<double> distribution(lower_bound, upper_bound);

  double random_number = distribution(gen);

  return random_number;
}

void setup()
{
  smartdisplay_init();
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  auto disp = lv_disp_get_default();
  lv_disp_set_rotation(disp, LV_DISP_ROT_90);
  // lv_disp_set_rotation(disp, LV_DISP_ROT_180);
  // lv_disp_set_rotation(disp, LV_DISP_ROT_270);
  ui_init();

  lv_obj_t *screen = lv_scr_act();

  // Use the custom meter
  //meter = lv_custom_meter_create(screen, 0, 100);
  p_meter = new Meter(screen, 0, 100);
}

void loop()
{
  lv_timer_handler();
  Serial.println(lv_scr_act() == ui_Gauge);
  double generated_number = generateRandomNumber(0, 30);
  char buffer[20]; // Adjust the size based on your needs
  snprintf(buffer, sizeof(buffer), "%.2f", generated_number);
  Serial.println(generated_number);
  int myInt = round(generated_number);
   p_meter->updateValue(6);
  if (lv_scr_act() == ui_Gauge)
  {
    delay(1000);

    // Serial.println(buffer);
    // lv_label_set_text(ui_GaugeValueLabel, buffer);
    // lv_arc_set_value(ui_GaugeArc, generated_number);
  }
}

Screenshot and/or video

I don’t have any but the only thing that’s happening is that the display turns on, the error happens, turns off and reloads.

Your code is full many mistakes. Primary you dont show error, but i mean memory stack… Secondary you complete dont handle static lvgl objects and too delay(1000) is nonsense.
And call start anim in loop on max loop speed is next unusabe think.
As minimum your update must handle check if previous started anim is on time …
and this must be global too or exported static

lv_meter_indicator_t *indicator;

Thanks for the answer.

“Primary you dont show error, but i mean memory stack…”. I don’t really know what this means. Yes I don’t have the error. I showed the memory stack has this is the only thing I could get.

Clearly delay(1000); is not good. I tried without it. I only used it to slow down the crashing, this has nothing to do with my question.

Of course the animation won’t be called in the loop at “full speed”. I was testing, and if you are sure this is the problem, you could mention it.

“As minimum your update must handle check if previous started anim is on time …” makes zero sense. I still get what you mean, if you mean waiting for the 100ms animation to be over, I will try that. But if there is a way to check state of a previous animation, you could have provided a link or code snippet.

I think the only useful part of your response is “and this must be global too or exported static”, I don’t understand why it should be static and I wish there was a way to make it non-static so I could use it as a member of my class.

I appreciate the answer, but pointing out obvious stuff without even giving a solution is not helpful at all. If calling the animation too often is the problem, just say that. Please use ChatGPT or something to re-write your answers before posting. I am not a native English speaker either and I fully understand how those mistakes are made but some parts of you answer are unreadable.

When you dont know what means crash source info , then is hard to help. Backtrace is only info about point of error, but not reason.

And from your loop is clean you completely dont understand how lvgl work.
All job (anim too) is executed in one line big func repeated call

lv_timer_handler

next lvgl is C system, maybe first try meter in C do work…

Ok since you can’t get over it. I don’t have “crash source info”. Let’s say I had never added the backtrace. No backtrace, I didn’t provide any of it.

You are actually not helping at all.

“next lvgl is C system, maybe first try meter in C do work…”. This part actually made me laugh. I will not answer to any of your next response since clearly you are not mentally capable of helping someone.

Yes I understand lv_timer_handler() thanks.

For anyone else that can help me I will add:
My Meter class works very well when I don’t use the animation. I can update the value with lv_meter_set_indicator_value, everything displays just fine. Only when I start using the animation, it crashes. I just tried adding a timer of 1 second between every value change, still having the same problem. *A timer not delay() Marian_M. Next step, I will try a simpler animation on a label or something similar.

Hello,

I believe I understand what your animation function is supposed to do but I have no clue how this lambda works. Perhaps the issue lies there?

Could you try copying the implementation from the example here? Animations — LVGL documentation

So just make a static function for now without coupling it to that class and put that in lv_anim_set_custom_exec_cb() function. If that still does not work we can look further.

1 Like

Hello Tinus,
Thanks for your reply!

You are right, I also suspect the lamda of causing issues here. But I haven’t found a better way to do it.

The problem I’m having with the callback is that lv_meter_set_indicator_value(lv_obj_t *obj, lv_meter_indicator_t *indic, int32_t value) needs a pointer to the meter object as a first parameter, and takes a total of 3 parameters unlike (I think) most other lv_obj_set_x() type functions that only take 2 params. The animation’s callback only provides 2 params (void *, int32_t), so I need a way to also pass the meter to the callback.

I tried creating a static function, but had to get the meter from somewhere so I added it to the lamda :confused:. Didn’t solve it.

static void testCallback(void *indic, int32_t val, lv_obj_t *meter)
{
    lv_meter_set_indicator_value(meter, static_cast<lv_meter_indicator_t *>(indic), val);
}

...

void Meter::updateValue(int value)
{
    if (value > peak_value)
    {
        peak_value = value;
    }
    // lv_meter_set_indicator_value(this->meter, this->indicator, value);
    // return;
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, indicator);

    lv_anim_set_time(&a, 10);
    lv_anim_set_user_data(&a, this);

    lv_anim_set_custom_exec_cb(&a, [](lv_anim_t *anim, int32_t val)
                               {
        auto *self = static_cast<Meter *>(lv_anim_get_user_data(anim)); // retrieve this
        testCallback(anim->var, val, self->getMeter()); });

    lv_anim_set_values(&a, current_value, value);
    lv_anim_start(&a);

    current_value = value;
}

Next

I will try another implementation with a static function and no lamda if I can find a way. (I did manage to use a lamda expression in a button’s event callback, so I hope there is a way to make it work here too.) I will also verify that the Meter object stays in memory until the animation as ended.

Hello again,

Have you looked at the meter example in the documentation? They have a bit of example code that actually animates the indicator on a meter…
I feel like that is exactly what you are wanting to do.

See here Meter (lv_meter) — LVGL documentation

It seems you have to animate the indicator object of your meter and you can just ignore the meter itself. Let me know if this helps.

EDIT: nevermind, they just have the meter object as a global variable instead of passing it along.

This might sound ridiculous, but seeing as you can pass a void* along as the var variable for lv_anim_set_var… why not pass a pointer to some struct that contains pointers to both your meter and its indicator? You have to make sure that it stays in memory of course. In your exec_cb you can then cast the void* back to this struct and use that. This might be what the lamda does already but I do not know for sure.

1 Like

Thank you for your responses!

Yeah the only examples I can find are declaring the meter as a global variable. Maybe my whole approach is wrong, but I don’t have a set amount of meters so I can’t have them declared globally.

The void* indicator/meter struct idea is pretty good! Would probably be a little simpler than the lamda. I will try it. Also just thought about using lv_obj_get_parent() on the indicator to get the meter. I will reply later with the results.

… so I need a way to also pass the meter to the callback.

Maybe I don’t understand your problem, but you already set this (Meter *) as user data for the animation object and your Meter class already has lv_obj_t *meter member, so:

auto *self = static_cast<Meter *>(lv_anim_get_user_data(anim));
lv_obj_t *meter = self->meter; /* here is your meter object :) */

Forget about my previous post. This is how you can fix your code:

  1. In function Meter:updateValue remove lv_anim_set_var:

        ...
        lv_anim_t a;
        lv_anim_init(&a);
        // lv_anim_set_var(&a, indicator);
    
        lv_anim_set_time(&a, 10);
        lv_anim_set_user_data(&a, this);
        ...
    
  2. Function Meter::animCallback change like this:

    void Meter::animCallback(void *indic, int32_t val)
    {
        Serial.println("before");
        //lv_meter_set_indicator_value(this->meter, static_cast<lv_meter_indicator_t *>(indic), val);
        lv_meter_set_indicator_value(this->meter, this->indicator, val);
        Serial.println("AFTER");
    }
    

The reason why your code didn’t work is that lv_anim_set_custom_exec_cb changes animation var to animation object itself, so after this call animation var is not indicator anymore. Here is how lv_anim_set_custom_exec_cb is implemented in LVGL:

static inline void lv_anim_set_custom_exec_cb(lv_anim_t * a, lv_anim_custom_exec_cb_t exec_cb)
{
    a->var     = a;
    a->exec_cb = (lv_anim_exec_xcb_t)exec_cb;
}
1 Like

Sir, you are a genius! It works very well.

I will also remove void *indic from animCallback since it is not used. I don’t know why I didn’t think about using this->indicator, it’s a great solution.

You don’t know how much time I’ve spent trying to debug this with no debugger.

Thanks for providing examples and also explaining what was the problem.

Not about the technical part:

In the diverse community surrounding LVGL, we welcome individuals from various backgrounds and expertise. It’s natural for misunderstandings to occur given our different perspectives. Should you find yourself in a conversation that seems unproductive, becomes frustrating, or makes you uncomfortable, remember that it’s completely acceptable to step back from that discussion. Our primary aim is to foster meaningful and respectful conversations; however, if a discussion ceases to be constructive, seeking engagement elsewhere may be more beneficial.

Should a situation escalate beyond what seems manageable, please don’t hesitate to reach out to me privately. We’re here to ensure a positive and supportive environment for everyone.