No text on buttons/labels


My issue is quite similar to I have no text on the button label since I am also unable to draw text on my display.

So far I implemented a driver (not perfect, but I have something on my display) and got everything compiling.

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

The controller is a Philips LPC1788 and I am using a (heavy modified?) GCC toolchain (based on Mentor Sourcery)

The controller already has a “base” firmware that handles some background tasks. (Not going to name any brands).

What do you want to achieve?

Get some text on the labels/buttons.

What have you tried so far?

Enable the logging. However this resulted in issues in the toolchain that I was unable to resolve. (So I don’t have any logging (yet)).

Increase the stack size, however the compiler/linker does not seem to have an option for this.

Below the output of ld.exe --help

Usage: C:\COMMONS\C\armgcc2\arm-none-eabi\bin\ld.exe [options] file...
  -a KEYWORD                  Shared library control for HP/UX compatibility
  -A ARCH, --architecture ARCH
                              Set architecture
  -b TARGET, --format TARGET  Specify target for following input files
  -c FILE, --mri-script FILE  Read MRI format linker script
  -d, -dc, -dp                Force common symbols to be defined
  -e ADDRESS, --entry ADDRESS Set start address
  -E, --export-dynamic        Export all dynamic symbols
  --no-export-dynamic         Undo the effect of --export-dynamic
  -EB                         Link big-endian objects
  -EL                         Link little-endian objects
  -f SHLIB, --auxiliary SHLIB Auxiliary filter for shared object symbol table
  -F SHLIB, --filter SHLIB    Filter for shared object symbol table
  -g                          Ignored
  -G SIZE, --gpsize SIZE      Small data size (if no size, same as --shared)
                              Set internal name of shared library
  -I PROGRAM, --dynamic-linker PROGRAM
                              Set PROGRAM as the dynamic linker to use
  -l LIBNAME, --library LIBNAME
                              Search for library LIBNAME
  -L DIRECTORY, --library-path DIRECTORY
                              Add DIRECTORY to library search path
  --sysroot=<DIRECTORY>       Override the default sysroot location
  -m EMULATION                Set emulation
  -M, --print-map             Print map file on standard output
  -n, --nmagic                Do not page align data
  -N, --omagic                Do not page align data, do not make text readonly
  --no-omagic                 Page align data, make text readonly
  -o FILE, --output FILE      Set output file name
  -O                          Optimize output file
  -plugin PLUGIN              Load named plugin
  -plugin-opt ARG             Send arg to last-loaded plugin
  -flto                       Ignored for GCC LTO option compatibility
  -flto-partition=            Ignored for GCC LTO option compatibility
  -fuse-ld=                   Ignored for GCC linker option compatibility
  -Qy                         Ignored for SVR4 compatibility
  -q, --emit-relocs           Generate relocations in final output
  -r, -i, --relocatable       Generate relocatable output
  -R FILE, --just-symbols FILE
                              Just link symbols (if directory, same as --rpath)
  -s, --strip-all             Strip all symbols
  -S, --strip-debug           Strip debugging symbols
  --strip-discarded           Strip symbols in discarded sections
  --no-strip-discarded        Do not strip symbols in discarded sections
  -t, --trace                 Trace file opens
  -T FILE, --script FILE      Read linker script
  --default-script FILE, -dT  Read default linker script
  -u SYMBOL, --undefined SYMBOL
                              Start with undefined reference to SYMBOL
  --unique [=SECTION]         Don't merge input [SECTION | orphan] sections
  -Ur                         Build global constructor/destructor tables
  -v, --version               Print version information
  -V                          Print version and emulation information
  -x, --discard-all           Discard all local symbols
  -X, --discard-locals        Discard temporary local symbols (default)
  --discard-none              Don't discard any local symbols
  -y SYMBOL, --trace-symbol SYMBOL
                              Trace mentions of SYMBOL
  -Y PATH                     Default search path for Solaris compatibility
  -(, --start-group           Start a group
  -), --end-group             End a group
  --accept-unknown-input-arch Accept input files whose architecture cannot be determined
                              Reject input files whose architecture is unknown
  --as-needed                 Only set DT_NEEDED for following dynamic libs if used
  --no-as-needed              Always set DT_NEEDED for dynamic libraries mentioned on
                                the command line
  -assert KEYWORD             Ignored for SunOS compatibility
  -Bdynamic, -dy, -call_shared
                              Link against shared libraries
  -Bstatic, -dn, -non_shared, -static
                              Do not link against shared libraries
  -Bsymbolic                  Bind global references locally
  -Bsymbolic-functions        Bind global function references locally
  --check-sections            Check section addresses for overlaps (default)
  --no-check-sections         Do not check section addresses for overlaps
  --copy-dt-needed-entries    Copy DT_NEEDED links mentioned inside DSOs that follow
  --no-copy-dt-needed-entries Do not copy DT_NEEDED links mentioned inside DSOs that follow
  --cref                      Output cross reference table
  --defsym SYMBOL=EXPRESSION  Define a symbol
  --demangle [=STYLE]         Demangle symbol names [using STYLE]
  --embedded-relocs           Generate embedded relocs
  --fatal-warnings            Treat warnings as errors
  --no-fatal-warnings         Do not treat warnings as errors (default)
  -fini SYMBOL                Call SYMBOL at unload-time
  --force-exe-suffix          Force generation of file with .exe suffix
  --gc-sections               Remove unused sections (on some targets)
  --no-gc-sections            Don't remove unused sections (default)
  --print-gc-sections         List removed unused sections on stderr
  --no-print-gc-sections      Do not list removed unused sections
  --hash-size=<NUMBER>        Set default hash table size close to <NUMBER>
  --help                      Print option help
  -init SYMBOL                Call SYMBOL at load-time
  -Map FILE                   Write a map file
  --no-define-common          Do not define Common storage
  --no-demangle               Do not demangle symbol names
  --no-keep-memory            Use less memory and more disk I/O
  --no-undefined              Do not allow unresolved references in object files
  --allow-shlib-undefined     Allow unresolved references in shared libraries
  --no-allow-shlib-undefined  Do not allow unresolved references in shared libs
  --allow-multiple-definition Allow multiple definitions
  --no-undefined-version      Disallow undefined version
  --default-symver            Create default symbol version
  --default-imported-symver   Create default symbol version for imported symbols
  --no-warn-mismatch          Don't warn about mismatched input files
  --no-warn-search-mismatch   Don't warn on finding an incompatible library
  --no-whole-archive          Turn off --whole-archive
  --noinhibit-exec            Create an output file even if errors occur
  -nostdlib                   Only use library directories specified on
                                the command line
  --oformat TARGET            Specify target of output file
  --print-output-format       Print default output format
  -qmagic                     Ignored for Linux compatibility
  --reduce-memory-overheads   Reduce memory overheads, possibly taking much longer
  --relax                     Reduce code size by using target specific optimizations
  --no-relax                  Do not use relaxation techniques to reduce code size
  --retain-symbols-file FILE  Keep only symbols listed in FILE
  -rpath PATH                 Set runtime shared library search path
  -rpath-link PATH            Set link time shared library search path
  -shared, -Bshareable        Create a shared library
  -pie, --pic-executable      Create a position independent executable
  --sort-common [=ascending|descending]
                              Sort common symbols by alignment [in specified order]
  --sort-section name|alignment
                              Sort sections by name or maximum alignment
  --spare-dynamic-tags COUNT  How many tags to reserve in .dynamic section
  --split-by-file [=SIZE]     Split output sections every SIZE octets
  --split-by-reloc [=COUNT]   Split output sections every COUNT relocs
  --stats                     Print memory usage statistics
  --target-help               Display target specific options
  --task-link SYMBOL          Do task level linking
  --traditional-format        Use same format as native linker
  --section-start SECTION=ADDRESS
                              Set address of named section
  -Tbss ADDRESS               Set address of .bss section
  -Tdata ADDRESS              Set address of .data section
  -Ttext ADDRESS              Set address of .text section
  -Ttext-segment ADDRESS      Set address of text segment
  -Trodata-segment ADDRESS    Set address of rodata segment
  -Tldata-segment ADDRESS     Set address of ldata segment
                              How to handle unresolved symbols.  <method> is:
                                ignore-all, report-all, ignore-in-object-files,
  --verbose [=NUMBER]         Output lots of information during link
  --version-script FILE       Read version information script
  --version-exports-section SYMBOL
                              Take export symbols list from .exports, using
                                SYMBOL as the version.
  --dynamic-list-data         Add data symbols to dynamic list
  --dynamic-list-cpp-new      Use C++ operator new/delete dynamic list
  --dynamic-list-cpp-typeinfo Use C++ typeinfo dynamic list
  --dynamic-list FILE         Read dynamic list
  --warn-common               Warn about duplicate common symbols
  --warn-constructors         Warn if global constructors/destructors are seen
  --warn-multiple-gp          Warn if the multiple GP values are used
  --warn-once                 Warn only once per undefined symbol
  --warn-section-align        Warn if start of section changes due to alignment
  --warn-shared-textrel       Warn if shared object has DT_TEXTREL
  --warn-alternate-em         Warn if an object has alternate ELF machine code
  --warn-unresolved-symbols   Report unresolved symbols as warnings
  --error-unresolved-symbols  Report unresolved symbols as errors
  --whole-archive             Include all objects from following archives
  --wrap SYMBOL               Use wrapper functions for SYMBOL
                              Do not warn for -L options using system directories
                              Give an error for -L options using system directories
  --ignore-unresolved-symbol SYMBOL
                              Unresolved SYMBOL will not cause an error or warning
  @FILE                       Read options from FILE
C:\COMMONS\C\armgcc2\arm-none-eabi\bin\ld.exe: supported targets: elf32-littlearm elf32-bigarm elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
C:\COMMONS\C\armgcc2\arm-none-eabi\bin\ld.exe: supported emulations: armelf
C:\COMMONS\C\armgcc2\arm-none-eabi\bin\ld.exe: emulation specific options:
  --audit=AUDITLIB            Specify a library to use for auditing
  -Bgroup                     Selects group name lookup rules for DSO
  --build-id[=STYLE]          Generate build ID note
  -P AUDITLIB, --depaudit=AUDITLIB
                              Specify a library to use for auditing dependencies
  --shared-comdat             Use comdat groups from shared objects
  --disable-new-dtags         Disable new dynamic tags
  --enable-new-dtags          Enable new dynamic tags
  --eh-frame-hdr              Create .eh_frame_hdr section
  --exclude-libs=LIBS         Make all symbols in LIBS hidden
  --hash-style=STYLE          Set hash style to sysv, gnu or both
  -z combreloc                Merge dynamic relocs into one section and sort
  -z common-page-size=SIZE    Set common page size to SIZE
  -z defs                     Report unresolved symbols in object files.
  -z execstack                Mark executable as requiring executable stack
  -z global                   Make symbols in DSO available for subsequently
                               loaded objects
  -z initfirst                Mark DSO to be initialized first at runtime
  -z interpose                Mark object to interpose all DSOs but executable
  -z lazy                     Mark object lazy runtime binding (default)
  -z loadfltr                 Mark object requiring immediate process
  -z max-page-size=SIZE       Set maximum page size to SIZE
  -z muldefs                  Allow multiple definitions
  -z nocombreloc              Don't merge dynamic relocs into one section
  -z nocopyreloc              Don't create copy relocs
  -z nodefaultlib             Mark object not to use default search paths
  -z nodelete                 Mark DSO non-deletable at runtime
  -z nodlopen                 Mark DSO not available to dlopen
  -z nodump                   Mark DSO not available to dldump
  -z noexecstack              Mark executable as not requiring executable stack
  -z norelro                  Don't create RELRO program header
  -z now                      Mark object non-lazy runtime binding
  -z origin                   Mark object requiring immediate $ORIGIN
                                processing at runtime
  -z relro                    Create RELRO program header
  -z stacksize=SIZE           Set size of stack segment
  --thumb-entry=<sym>         Set the entry point to be Thumb symbol <sym>
  --be8                       Output BE8 format image
  --target1-rel               Interpret R_ARM_TARGET1 as R_ARM_REL32
  --target1-abs               Interpret R_ARM_TARGET1 as R_ARM_ABS32
  --target2=<type>            Specify definition of R_ARM_TARGET2
  --fix-v4bx                  Rewrite BX rn as MOV pc, rn for ARMv4
  --fix-v4bx-interworking     Rewrite BX rn branch to ARMv4 interworking veneer
  --use-blx                   Enable use of BLX instructions
  --vfp11-denorm-fix          Specify how to fix VFP11 denorm erratum
  --no-enum-size-warning      Don't warn about objects with incompatible
                                enum sizes
  --no-wchar-size-warning     Don't warn about objects with incompatible
                                wchar_t sizes
  --pic-veneer                Always generate PIC interworking veneers
  --stub-group-size=N         Maximum size of a group of input sections that
                               can be handled by one stub section.  A negative
                               value locates all stubs after their branches
                               (with a group size of -N), while a positive
                               value allows two groups of input sections, one
                               before, and one after each stub section.
                               Values of +/-1 indicate the linker should
                               choose suitable defaults.
  --[no-]fix-cortex-a8        Disable/enable Cortex-A8 Thumb-2 branch erratum fix
  --no-merge-exidx-entries    Disable merging exidx entries
  --[no-]fix-arm1176          Disable/enable ARM1176 BLX immediate erratum fix

A note about the option -z stacksize=SIZE. This is only for emulation, if I add this to the linker arguments ld simply ignores it.

Code to reproduce

The current version of lv_example_get_started_1 (added the line and added the pictures)

void lv_example_get_started_1_img(void)
    /*Change the active screen's background color*/
    lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x852478), LV_PART_MAIN);

    /*Create a white label, set its text and align it to the center*/
    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello world");
    lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0xffffff), LV_PART_MAIN);
    lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0);

    /*Create an array for the points of the line*/
    static lv_point_t line_points[] = { {5, 5}, {70, 70}, {120, 10}, {180, 60}, {240, 10} };

    /*Create style*/
    static lv_style_t style_line;
    lv_style_set_line_width(&style_line, 8);
    lv_style_set_line_color(&style_line, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_line_rounded(&style_line, true);

    /*Create a line and apply the new style*/
    lv_obj_t * line1;
    line1 = lv_line_create(lv_scr_act());
    lv_line_set_points(line1, line_points, 5);     /*Set the points*/
    lv_obj_add_style(line1, &style_line, 0);

    lv_obj_t * img1 = lv_img_create(lv_scr_act());
    lv_img_set_src(img1, &windowClosed);
    lv_obj_align(img1, LV_ALIGN_TOP_LEFT, 0, 0);

    lv_obj_t * img2 = lv_img_create(lv_scr_act());
    lv_img_set_src(img2, &windowClosedEnabled);
    lv_obj_align(img2, LV_ALIGN_TOP_RIGHT, 0, 0);

    lv_obj_t * img3 = lv_img_create(lv_scr_act());
    lv_img_set_src(img3, &windowOpen);
    lv_obj_align(img3, LV_ALIGN_BOTTOM_LEFT, 0, 0);

    lv_obj_t * img4 = lv_img_create(lv_scr_act());
    lv_img_set_src(img4, &windowOpenEnabled);
    lv_obj_align(img4, LV_ALIGN_BOTTOM_RIGHT, 0, 0);

Screenshot and/or video

Other things

Note: The “default” links lead to pages that don’t exist.
hxxps:// and hxxps:// (hxxps to get around the 3 link limit)

Hi @theprogram01 ,

Thank you for your post… I am assuming you are using LVGL v8.

For the community to help you we are going to need a bit more information please.

Can you share the parts of your code which call the functions lv_tick_inc() & lv_timer_handler() as this part needs to be right first…

Looking at the image you have posted the display driver flush callback doesn’t look to be far out :slight_smile:

With regard to your stack size, (sorry I am not familiar with your development environment) most development environments have a linker file in the project somewhere which specifies stack sizes memory map etc. it is often called somename.ld , you may be able to alter your stack requirements by editing that file if you can locate it…

I am not sure where you are seeing these links but they are very old… the links I have included above to the functions I have mentioned will show you the latest released documentation set.

I hope that is helpful.

Kind Regards,


Hi @pete-pjb,

Your assumption is correct I am using LGVL V8 at b1bbb95.

As requested, the calling code for lv_timer_handler() and lv_tick_inc()

void lv_display_task()
        //Run lvgl
        //Wait for 5 ms for the next run

void lv_timer_task()

int main()
    //Initialize lvgl
    //Initialize the display for lvgl

    //Start the tasks
    exec_task(lv_timer_task, 5, 1);
    exec_task(lv_display_task, 6, 1);



Technically you should now be able to find the brand :wink:

The reason the flush callback looks ok is that LVGL is just writing to the display buffer directly so I don’t need any real flushing (however this seems to have some performance cost but that is an issue for later).

Thanks for the suggestion. Than I have to look into my ld file (I did find that) in order to increase the stack. I’ll let you know how that goes.

The (very old) links are in the default post template that you are instructed to remove after reading.

Hi @theprogram01 ,

I have no idea of your level of expertise so if I am telling you things you already know, feel free to ignore me :slight_smile:
No sorry I am not able to identify the brand I am clearly missing something but no worries :blush:

The first thing I would try is something like this:

void lv_display_task()

    lv_example_get_started_1_img(); // Move this here to keep the GUI operations in just one thread

        //Run lvgl
        //Wait for 5 ms for the next run
        flash an LED here / Or output a character to a UART...

void lv_timer_task()   // This should be a completely independent thread so all good...
        flash a different LED here / Or output a different character to a UART...

int main()
    //Initialize lvgl
    //Initialize the display for lvgl

    //Start the tasks
    exec_task(lv_timer_task, 5, 1);
    exec_task(lv_display_task, 6, 1);


This will allow us to gain confidence everything is behaving as expected. It is likely to reduce the performance having to output to a UART or toggle an LED but a least we know things are working.

Also you should really use a mutex in this type of scenario but for the time being the moving of the lv_example_get_started_1_img() function call negates any need for it for test purposes…

There are ways to avoid using a mutex which I can explain later if required, but for now let’s get things working…

Let me know how it goes.

PS I have notified @kisvegabor about the links, thank you for pointing them out.

Kind Regards,


You cannot call lv_task_handler from an ISR because it allocates memory. It needs to be done from the main loop.

Hi @pete-pjb,

Currently all the tips are welcome on how to get LVGL working on my system :slight_smile:.

I just shifted the lv_example_get_started_1_img() call to lv_display_task(), however so far no luck.

I’ll have to add the blinking LED’s/UART output but for now I’ll assume that part is working. (It works in other projects so I have no reason to assume it works here (for now))

@kdschlosser, I assume you mean lv_timer_handler() instead of lv_task_handler(). Currently these are called in a thread so I assume a thread is able to allocate memory.
But for the sake of “why not” I just moved the call to my main() loop. However, this made no difference.

LVGL is not thread safe either.

lv_timer_handler was wrapped in lv_task_handler in later versions so sorry about that goof up there. It’s the same thing tho in the event I accidentally do it agian.

No problem, I found it in the source that it was the same thing :slight_smile:

Hi @theprogram01 ,

This is the legacy name for the lv_timer_handler function prior to version 8… :blush: and I agree you should definitely not call lv_timer_handler() from an ISR!

We really need to make sure the timer is working…
Another thing you can do is to call the lv_tick_get() in your main while loop and print the result and check it is incrementing as expected, something like this:

int main()
    //Initialize lvgl
    //Initialize the display for lvgl

    //Start the tasks
    exec_task(lv_timer_task, 5, 1);
    exec_task(lv_display_task, 6, 1);

        printf( "Tick Count: %d\r\n", lv_tick_get() );

Until we verify this it is hard to speculate what to do next…

Kind Regards,



Can you tell me what the second and third arguments are in these function calls please or provide a link to some on-line documentation which I can check… That would also be helpful.

Probably should also see what your flush function looks like as well. I know in later versions of LVGL you have to call a function in LVGL to let it know that the buffer has been written to the display. IDK if this exists in the version of LVGL you are using.

Ok, so I managed to get tracing to work (sending data over UDP is easyer than writing to file on my platform :man_facepalming:) so the UDP output (level trace) can be found here.

In order to get this to work I updated the code to the following

//lvgl includes
#include "lvgl/lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

#include "Pictures/pictures.h"


void logData(const char * buf)
    size_t bufSize = strlen(buf);

void lv_example_get_started_1_img(void)

/// @brief The task for lvgl
void lv_display_task()
    char logmessage[50];
        //Run lvgl
        //Wait for 5 ms for the next run
        sprintf(logmessage, "Display Task tick %d\r\n", lv_tick_get());

void lv_timer_task()
    //char logmessage[50];

        //sprintf(logmessage, "Timer Task tick %d\r\n", lv_tick_get());

int winmain()

    //Initialize lvgl
    //Initialize the display for lvgl
    //Start lvgl as a task
    exec_task(lv_timer_task, 5, 1);
    exec_task(lv_display_task, 6, 4);

    char logmessage[50];

        sprintf(logmessage, "Main tick %d\r\n", lv_tick_get());

@pete-pjb, It seems you are correct, my lv_display_task() is not correctly looping. I’ll have to investigate this.

And, as requested, the documentation of exec_task. If you want I can send you a DM to the full toolchain. (windows only, registration required)

@kdschlosser, as requested, the disp_flush() implementation. (yes, it is not yet compete, double buffering still has issues)

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
    if(disp_flush_enabled) {

     *Inform the graphics library that you are ready with the flushing*/

There is nothing being written to the display at all.

Hi @theprogram01 ,

It seems you are making progress, thanks for the info. The exec_task() function looks pretty straight forward…

It looks like the tick is incrementing even if not by the correct amount your loop waits 500mS and the main loop count is clocking between 300 & 312 mS this is a bit suspicious? but might be down to the load associated with generating a huge number of logs?

Would you also be able to post your lv_conf.h file as it was when you created the UDP log please? That would also be helpful.

Although the log shows no sign of printing from the display task the fact all the trace events appear to be present suggest lv_timer_hander() probably is executing so that is a little weird!

How did you get on with locating your stack and heap sizes with the linker script etc… If the stack stack or heap is running out of space this can also break things in undefined ways… So we probably want to check this for sure. Are there any functions for the ‘task scheduler’ to retrieve the heap and stack usage you can call to check them at runtime?

Is there any stack settings associated with the executed tasks for the system, mentioned in the documentation?

If there is anything you can share privately my email is

Kind Regards,


Hi @kdschlosser ,

My understanding is the system is using DMA and the buffer addresses have been assigned to the DMA controller and LVGL is rendering directly to the buffers, there may be some screen tearing and things with a basic setup like this but it should at least put everything on the screen…

Hopefully @theprogram01 can confirm this for sure…

Kind Regards,


Hi @kdschlosser, @pete-pjb is indeed correct, LVGL is writing directly to the ram location where the display driver is looking for the display information.
Also, is there any way to tell LVGL, only use 1 full screen buffer and don’t care about flushing or swapping buffers?

I am afraid though that something is still wrong in my driver since lv_timer_handler() never seems to return. This I need to investigate.

@pete-pjb, I just send you an e-mail containing more platform details. For now I need to contact someone to see if we can increase the stack/heap settings in any way.

The current assumption I have for the fact that the main loop is a bit off is just the CPU load. This gives issues for the RTOS and so lv_tick_inc(1); is not called every milisecond.

Finally, as requested, the log section of lv_conf.h

/*Enable the log module*/
#define LV_USE_LOG 1

    /*How important log should be added:
    *LV_LOG_LEVEL_TRACE       A lot of logs to give detailed information
    *LV_LOG_LEVEL_INFO        Log important events
    *LV_LOG_LEVEL_WARN        Log if something unwanted happened but didn't cause a problem
    *LV_LOG_LEVEL_ERROR       Only critical issue, when the system may fail
    *LV_LOG_LEVEL_USER        Only logs added by the user
    *LV_LOG_LEVEL_NONE        Do not log anything*/

    /*1: Print the log with 'printf';
    *0: User need to register a callback with `lv_log_register_print_cb()`*/
    #define LV_LOG_PRINTF 0

    /*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
    #define LV_LOG_TRACE_MEM        1
    #define LV_LOG_TRACE_TIMER      1
    #define LV_LOG_TRACE_INDEV      1
    #define LV_LOG_TRACE_DISP_REFR  1
    #define LV_LOG_TRACE_EVENT      1
    #define LV_LOG_TRACE_LAYOUT     1
    #define LV_LOG_TRACE_ANIM       1

#endif  /*LV_USE_LOG*/

Thanks so far for the help.

So far I confirmed my CPU load theory. Once lv_timer_handler() crashes the main thread and the timer task restore themselves.

Main tick 124103
Main tick 124596
Main tick 125089
Main tick 125583
Main tick 126075
Main tick 126567
Main tick 127061
Main tick 127553
Main tick 128046
Main tick 128540
Main tick 129034

Has a diff of about 493ms. So not perfect but close enough for me (for now).

Ok, I did some more testing and it seems there is something wrong with my driver configuration. Step 1 was just to litter trace statements all over the code until I found what crashed the code.


 * Flush the content of the draw buffer
static void draw_buf_flush(lv_disp_t * disp)
    lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);


    /*Flush the rendered content to the display*/
    lv_draw_ctx_t * draw_ctx = disp->driver->draw_ctx;
    if(draw_ctx->wait_for_finish) draw_ctx->wait_for_finish(draw_ctx);


    /* In partial double buffered mode wait until the other buffer is freed
     * and driver is ready to receive the new buffer */
    bool full_sized = draw_buf->size == (uint32_t)disp_refr->driver->hor_res * disp_refr->driver->ver_res;

    REFR_TRACE("3.1 full = %d, size = %d, actsize = %d", full_sized, draw_buf->size, (uint32_t)disp_refr->driver->hor_res * disp_refr->driver->ver_res);

    if(draw_buf->buf1 && draw_buf->buf2 && !full_sized) {
        while(draw_buf->flushing) {


    draw_buf->flushing = 1;

    if(disp_refr->driver->draw_buf->last_area && disp_refr->driver->draw_buf->last_part) draw_buf->flushing_last = 1;
    else draw_buf->flushing_last = 0;


    bool flushing_last = draw_buf->flushing_last;


    if(disp->driver->flush_cb) {
        /*Rotate the buffer to the display's native orientation if necessary*/
        if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) {
            draw_buf_rotate(draw_ctx->buf_area, draw_ctx->buf);
        else {
            call_flush_cb(disp->driver, draw_ctx->buf_area, draw_ctx->buf);

    /*If there are 2 buffers swap them. With direct mode swap only on the last area*/
    if(draw_buf->buf1 && draw_buf->buf2 && (!disp->driver->direct_mode || flushing_last)) {
        if(draw_buf->buf_act == draw_buf->buf1)
            draw_buf->buf_act = draw_buf->buf2;
            draw_buf->buf_act = draw_buf->buf1;

Results in

[Trace]  (0.477, +1)   refr_area_part: 7   (in lv_refr.c line #771)
[Trace]  (0.479, +2)   draw_buf_flush: 1   (in lv_refr.c line #1261)
[Trace]  (0.481, +2)   draw_buf_flush: 2   (in lv_refr.c line #1265)
[Trace]  (0.483, +2)   draw_buf_flush: 3   (in lv_refr.c line #1271)
[Trace]  (0.484, +1)   draw_buf_flush: 3.1 full = 0, size = 0, actsize = 384000   (in lv_refr.c line #1277)
[Trace]  (0.487, +3)   draw_buf_flush: 3.2   (in lv_refr.c line #1281)
[Trace]  (0.488, +1)   draw_buf_flush: 3.2   (in lv_refr.c line #1281)
[Trace]  (0.489, +1)   draw_buf_flush: 3.2   (in lv_refr.c line #1281)
[Trace]  (0.491, +2)   draw_buf_flush: 3.2   (in lv_refr.c line #1281)
[Trace]  (0.493, +2)   draw_buf_flush: 3.2   (in lv_refr.c line #1281)
[To Infinity]

It seems as if it wants to call the flush async, but it forgets the actual call to the flush code.

Currently I am looking at draw_buf_flush(lv_disp_t * disp) and I notice it uses a mix of the disp that was given as argument and the global disp_refr variables. I assume this is intentional but I do wonder why this is done.

Edit 2:
Looked a bit closer at the (only) call of draw_buf_flush(lv_disp_t * disp) and it is called with the global disp_refr variable as argument. So makes no difference.
It might be an area to clean.

Ok, I found the reason for lv_timer_handler() "crashing ".
In my lv_port_disp.c file I wrote an incorrect display init.

   //Init the buffer
    static lv_disp_draw_buf_t draw_buf_dsc_3;
    lv_disp_draw_buf_init(&draw_buf_dsc_3, framebuffer0, NULL, MY_DISP_HOR_RES * MY_DISP_VER_RES);   /*Initialize the display buffer*/

     * Register the display in LVGL

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;
    disp_drv.physical_hor_res = -1;
    disp_drv.physical_ver_res = -1;
    disp_drv.offset_x = 0;
    disp_drv.offset_y = 0;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;
    disp_drv.wait_cb = disp_wait;
    //The bug was here
    disp_drv.draw_buf = &draw_buf_dsc_3;
    //The original code was
    disp_drv.draw_buf = &framebuffer0;

The reason for my error mostly had to do with the handling of the 2 buffers. In the samples it is unclear how to swap the 2 buffers. (Or tell LVGL that it needs to write to buffer 0/buffer 1).

Ok, the system is now up and running. My latest logfile can be found here.

Hi @theprogram01 ,

I see… I was about to ask you to post your driver initialisation code, but it seems you have found the problem… that’s great!

Kind Regards,