Skip to content

lupyuen/pinephone-lvgl-zig

Repository files navigation

LVGL for PinePhone (and WebAssembly) with Zig and Apache NuttX RTOS

LVGL for PinePhone (and WebAssembly) with Zig and Apache NuttX RTOS

Read the articles...

Can we build an LVGL App in Zig for PinePhone... That will run on Apache NuttX RTOS?

Can we preview a PinePhone App with Zig, LVGL and WebAssembly in a Web Browser? To make the UI Coding a little easier?

Let's find out!

LVGL Zig App

Let's run this LVGL Zig App on PinePhone...

/// Create the LVGL Widgets that will be rendered on the display. Calls the
/// LVGL API that has been wrapped in Zig. Based on
/// https://docs.lvgl.io/master/widgets/label.html?highlight=lv_label_create#line-wrap-recoloring-and-scrolling
fn createWidgetsWrapped() !void {
debug("createWidgetsWrapped: start", .{});
defer { debug("createWidgetsWrapped: end", .{}); }
// Get the Active Screen
var screen = try lvgl.getActiveScreen();
// Create a Label Widget
var label = try screen.createLabel();
// Wrap long lines in the label text
label.setLongMode(c.LV_LABEL_LONG_WRAP);
// Interpret color codes in the label text
label.setRecolor(true);
// Center align the label text
label.setAlign(c.LV_TEXT_ALIGN_CENTER);
// Set the label text and colors
label.setText(
"#ff0000 HELLO# " ++ // Red Text
"#00aa00 LVGL ON# " ++ // Green Text
"#0000ff PINEPHONE!# " // Blue Text
);
// Set the label width
label.setWidth(200);
// Align the label to the center of the screen, shift 30 pixels up
label.alignObject(c.LV_ALIGN_CENTER, 0, -30);
}

How is createWidgetsWrapped called?

createWidgetsWrapped will be called by the LVGL Widget Demo lv_demo_widgets, which we'll replace by this Zig version...

/// We render an LVGL Screen with LVGL Widgets
pub export fn lv_demo_widgets() void {
// Create the widgets for display (with Zig Wrapper)
createWidgetsWrapped()
catch |e| {
// In case of error, quit
std.log.err("createWidgetsWrapped failed: {}", .{e});
return;
};

Where's the Zig Wrapper for LVGL?

Our Zig Wrapper for LVGL is defined here...

We also have a version of the LVGL Zig Code that doesn't call the Zig Wrapper...

Build LVGL Zig App

NuttX Build runs this GCC Command to compile lv_demo_widgets.c for PinePhone...

$ make --trace
...
cd $HOME/PinePhone/wip-nuttx/apps/graphics/lvgl
aarch64-none-elf-gcc
  -c
  -fno-common
  -Wall
  -Wstrict-prototypes
  -Wshadow
  -Wundef
  -Werror
  -Os
  -fno-strict-aliasing
  -fomit-frame-pointer
  -g
  -march=armv8-a
  -mtune=cortex-a53
  -isystem $HOME/PinePhone/wip-nuttx/nuttx/include
  -D__NuttX__ 
  -pipe
  -I $HOME/PinePhone/wip-nuttx/apps/graphics/lvgl
  -I "$HOME/PinePhone/wip-nuttx/apps/include"
  -Wno-format
  -Wno-unused-variable
  "-I./lvgl/src/core"
  "-I./lvgl/src/draw"
  "-I./lvgl/src/draw/arm2d"
  "-I./lvgl/src/draw/nxp"
  "-I./lvgl/src/draw/nxp/pxp"
  "-I./lvgl/src/draw/nxp/vglite"
  "-I./lvgl/src/draw/sdl"
  "-I./lvgl/src/draw/stm32_dma2d"
  "-I./lvgl/src/draw/sw"
  "-I./lvgl/src/draw/swm341_dma2d"
  "-I./lvgl/src/font"
  "-I./lvgl/src/hal"
  "-I./lvgl/src/misc"
  "-I./lvgl/src/widgets"
  "-DLV_ASSERT_HANDLER=ASSERT(0);"   
  lvgl/demos/widgets/lv_demo_widgets.c
  -o  lvgl/demos/widgets/lv_demo_widgets.c.Users.Luppy.PinePhone.wip-nuttx.apps.graphics.lvgl.o

We'll copy the above GCC Options to the Zig Compiler and build this Zig Program for PinePhone...

Here's the Shell Script...

## Build the LVGL Zig App
function build_zig {

  ## Go to LVGL Zig Folder
  pushd ../pinephone-lvgl-zig
  git pull

  ## Check that NuttX Build has completed and `lv_demo_widgets.*.o` exists
  if [ ! -f ../apps/graphics/lvgl/lvgl/demos/widgets/lv_demo_widgets.*.o ] 
  then
    echo "*** Error: Build NuttX first before building Zig app"
    exit 1
  fi

  ## Compile the Zig App for PinePhone 
  ## (armv8-a with cortex-a53)
  ## TODO: Change ".." to your NuttX Project Directory
  zig build-obj \
    --verbose-cimport \
    -target aarch64-freestanding-none \
    -mcpu cortex_a53 \
    -isystem "../nuttx/include" \
    -I "../apps/include" \
    -I "../apps/graphics/lvgl" \
    -I "../apps/graphics/lvgl/lvgl/src/core" \
    -I "../apps/graphics/lvgl/lvgl/src/draw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/font" \
    -I "../apps/graphics/lvgl/lvgl/src/hal" \
    -I "../apps/graphics/lvgl/lvgl/src/misc" \
    -I "../apps/graphics/lvgl/lvgl/src/widgets" \
    lvgltest.zig

  ## Copy the compiled app to NuttX and overwrite `lv_demo_widgets.*.o`
  ## TODO: Change ".." to your NuttX Project Directory
  cp lvgltest.o \
    ../apps/graphics/lvgl/lvgl/demos/widgets/lv_demo_widgets.*.o

  ## Return to NuttX Folder
  popd
}

## Download the LVGL Zig App
git clone https://github.com/lupyuen/pinephone-lvgl-zig

## Build NuttX for PinePhone
cd nuttx
make -j

## Build the LVGL Zig App
build_zig

## Link the LVGL Zig App with NuttX
make -j

(Original Build Script)

(Updated Build Script)

(NuttX Build Files)

And our LVGL Zig App runs OK on PinePhone!

LVGL for PinePhone with Zig and Apache NuttX RTOS

Simulate PinePhone UI with Zig, LVGL and WebAssembly

Read the article...

We're now building a Feature Phone UI for NuttX on PinePhone...

Can we simulate the Feature Phone UI with Zig, LVGL and WebAssembly in a Web Browser? To make the UI Coding a little easier?

We have previously created a simple LVGL App with Zig for PinePhone...

Zig natively supports WebAssembly...

So we might run Zig + JavaScript in a Web Browser like so...

But LVGL doesn't work with JavaScript yet. LVGL runs in a Web Browser by compiling with Emscripten and SDL...

Therefore we shall do this...

  1. Use Zig to compile LVGL from C to WebAssembly (With zig cc)

  2. Use Zig to connect the JavaScript UI (canvas rendering + input events) to LVGL WebAssembly (Like this)

WebAssembly Demo with Zig and JavaScript

We can run Zig (WebAssembly) + JavaScript in a Web Browser like so...

Let's run a simple demo...

To compile Zig to WebAssembly...

git clone --recursive https://github.com/lupyuen/pinephone-lvgl-zig
cd pinephone-lvgl-zig
cd demo
zig build-lib \
  mandelbrot.zig \
  -target wasm32-freestanding \
  -dynamic \
  -rdynamic

(According to this)

This produces the Compiled WebAssembly mandelbrot.wasm.

Start a Local Web Server. (Like Web Server for Chrome)

Browse to demo/demo.html. We should see the Mandelbrot Set yay!

Mandelbrot Set rendered with Zig and WebAssembly

Import JavaScript Functions into Zig

How do we import JavaScript Functions into our Zig Program?

This is documented here...

In our Zig Program, this is how we import and call a JavaScript Function: demo/mandelbrot.zig

/// Import `print` Function from JavaScript
extern fn print(i32) void;
...
// Test printing to JavaScript Console.
// Warning: This is slow!
if (iterations == 1) { print(iterations); }

We define the JavaScript Function print when loading the WebAssembly Module in our JavaScript: demo/game.js

// Export JavaScript Functions to Zig
let importObject = {
    // JavaScript Environment exported to Zig
    env: {
        // JavaScript Print Function exported to Zig
        print: function(x) { console.log(x); }
    }
};

// Load the WebAssembly Module
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming
async function bootstrap() {

    // Store references to WebAssembly Functions and Memory exported by Zig
    Game = await WebAssembly.instantiateStreaming(
        fetch("mandelbrot.wasm"),
        importObject
    );

    // Start the Main Function
    main();
}

// Start the loading of WebAssembly Module
bootstrap();

Will this work for passing Strings and Buffers as parameters?

Nope, the parameter will be passed as a number. (Probably a WebAssembly Data Address)

To pass Strings and Buffers between JavaScript and Zig, see daneelsan/zig-wasm-logger.

Compile Zig LVGL App to WebAssembly

Does our Zig LVGL App lvgltest.zig compile to WebAssembly?

Let's take the earlier steps to compile our Zig LVGL App lvgltest.zig. To compile for WebAssembly, we change...

  • zig build-obj to zig build-lib

  • Target becomes -target wasm32-freestanding

  • Remove -mcpu

  • Add -dynamic and -rdynamic

Like this...

  ## Compile the Zig App for WebAssembly 
  ## TODO: Change ".." to your NuttX Project Directory
  zig build-lib \
    --verbose-cimport \
    -target wasm32-freestanding \
    -dynamic \
    -rdynamic \
    -isystem "../nuttx/include" \
    -I "../apps/include" \
    -I "../apps/graphics/lvgl" \
    -I "../apps/graphics/lvgl/lvgl/src/core" \
    -I "../apps/graphics/lvgl/lvgl/src/draw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/font" \
    -I "../apps/graphics/lvgl/lvgl/src/hal" \
    -I "../apps/graphics/lvgl/lvgl/src/misc" \
    -I "../apps/graphics/lvgl/lvgl/src/widgets" \
    lvgltest.zig

(According to this)

(NuttX Build Files)

OK no errors, this produces the Compiled WebAssembly lvgltest.wasm.

Now we tweak lvgltest.zig for WebAssembly, and call it lvglwasm.zig...

  ## Compile the Zig App for WebAssembly 
  ## TODO: Change ".." to your NuttX Project Directory
  zig build-lib \
    --verbose-cimport \
    -target wasm32-freestanding \
    -dynamic \
    -rdynamic \
    -isystem "../nuttx/include" \
    -I "../apps/include" \
    -I "../apps/graphics/lvgl" \
    -I "../apps/graphics/lvgl/lvgl/src/core" \
    -I "../apps/graphics/lvgl/lvgl/src/draw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/font" \
    -I "../apps/graphics/lvgl/lvgl/src/hal" \
    -I "../apps/graphics/lvgl/lvgl/src/misc" \
    -I "../apps/graphics/lvgl/lvgl/src/widgets" \
    lvglwasm.zig

(According to this)

(NuttX Build Files)

(We removed our Custom Panic Handler, the default one works fine for WebAssembly)

This produces the Compiled WebAssembly lvglwasm.wasm.

Start a Local Web Server. (Like Web Server for Chrome)

Browse to our HTML lvglwasm.html. Which calls our JavaScript lvglwasm.js to load the Compiled WebAssembly.

Our JavaScript lvglwasm.js calls the Zig Function lv_demo_widgets that's exported to WebAssembly by our Zig App lvglwasm.zig.

But the WebAssembly won't load because we haven't fixed the WebAssembly Imports...

Fix WebAssembly Imports

What happens if we don't fix the WebAssembly Imports in our Zig Program?

Suppose we forgot to import puts(). JavaScript Console will show this error when the Web Browser loads our Zig WebAssembly...

Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #0 module="env" function="puts" error:
function import requires a callable

But we haven't compiled the LVGL Library to WebAssembly!

Yep that's why LVGL Functions like lv_label_create are failing when the Web Browser loads our Zig WebAssembly...

Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #1 module="env" function="lv_label_create" error:
function import requires a callable

We need to compile the LVGL Library with zig cc and link it in...

Compile LVGL to WebAssembly with Zig Compiler

How to compile LVGL from C to WebAssembly with Zig Compiler?

We'll use zig cc, since Zig can compile C programs to WebAssembly.

In the previous section, we're missing the LVGL Function lv_label_create in our Zig WebAssembly Module.

lv_label_create is defined in this file...

apps/lvgl/src/widgets/lv_label.c

According to make --trace, lv_label.c is compiled with...

## Compile LVGL in C
## TODO: Change "../../.." to your NuttX Project Directory
cd apps/graphics/lvgl
aarch64-none-elf-gcc \
  -c \
  -fno-common \
  -Wall \
  -Wstrict-prototypes \
  -Wshadow \
  -Wundef \
  -Werror \
  -Os \
  -fno-strict-aliasing \
  -fomit-frame-pointer \
  -ffunction-sections \
  -fdata-sections \
  -g \
  -march=armv8-a \
  -mtune=cortex-a53 \
  -isystem ../../../nuttx/include \
  -D__NuttX__  \
  -pipe \
  -I ../../../apps/graphics/lvgl \
  -I "../../../apps/include" \
  -Wno-format \
  -Wno-format-security \
  -Wno-unused-variable \
  "-I./lvgl/src/core" \
  "-I./lvgl/src/draw" \
  "-I./lvgl/src/draw/arm2d" \
  "-I./lvgl/src/draw/nxp" \
  "-I./lvgl/src/draw/nxp/pxp" \
  "-I./lvgl/src/draw/nxp/vglite" \
  "-I./lvgl/src/draw/sdl" \
  "-I./lvgl/src/draw/stm32_dma2d" \
  "-I./lvgl/src/draw/sw" \
  "-I./lvgl/src/draw/swm341_dma2d" \
  "-I./lvgl/src/font" \
  "-I./lvgl/src/hal" \
  "-I./lvgl/src/misc" \
  "-I./lvgl/src/widgets" \
  "-DLV_ASSERT_HANDLER=ASSERT(0);" \
  ./lvgl/src/widgets/lv_label.c \
  -o  lv_label.c.Users.Luppy.PinePhone.wip-nuttx.apps.graphics.lvgl.o

Let's use the Zig Compiler to compile lv_label.c from C to WebAssembly....

  • Change aarch64-none-elf-gcc to zig cc

  • Remove -march, -mtune

  • Add the target -target wasm32-freestanding

  • Add -dynamic and -rdynamic

  • Add -lc (because we're calling C Standard Library)

  • Add -DFAR= (because we won't need Far Pointers)

  • Add -DLV_MEM_CUSTOM=1 (because we're using malloc instead of LVGL's TLSF Allocator)

  • Set the Default Font to Montserrat 20...

    -DLV_FONT_MONTSERRAT_14=1 \
    -DLV_FONT_MONTSERRAT_20=1 \
    -DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
    -DLV_USE_FONT_PLACEHOLDER=1 \
    
  • Add -DLV_USE_LOG=1 (to enable logging)

  • Add -DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE (for detailed logging)

  • For extra logging...

    -DLV_LOG_TRACE_OBJ_CREATE=1 \
    -DLV_LOG_TRACE_TIMER=1 \
    -DLV_LOG_TRACE_MEM=1 \
    
  • Change "-DLV_ASSERT_HANDLER..." to...

    "-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}"
    

    (To handle Assertion Failures ourselves)

  • Change the output to...

    -o ../../../pinephone-lvgl-zig/lv_label.o`
    

Like this...

## Compile LVGL from C to WebAssembly
## TODO: Change "../../.." to your NuttX Project Directory
cd apps/graphics/lvgl
zig cc \
  -target wasm32-freestanding \
  -dynamic \
  -rdynamic \
  -lc \
  -DFAR= \
  -DLV_MEM_CUSTOM=1 \
  -DLV_FONT_MONTSERRAT_14=1 \
  -DLV_FONT_MONTSERRAT_20=1 \
  -DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
  -DLV_USE_FONT_PLACEHOLDER=1 \
  -DLV_USE_LOG=1 \
  -DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
  -DLV_LOG_TRACE_OBJ_CREATE=1 \
  -DLV_LOG_TRACE_TIMER=1 \
  -DLV_LOG_TRACE_MEM=1 \
  "-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
  -c \
  -fno-common \
  -Wall \
  -Wstrict-prototypes \
  -Wshadow \
  -Wundef \
  -Werror \
  -Os \
  -fno-strict-aliasing \
  -fomit-frame-pointer \
  -ffunction-sections \
  -fdata-sections \
  -g \
  -isystem ../../../nuttx/include \
  -D__NuttX__  \
  -pipe \
  -I ../../../apps/graphics/lvgl \
  -I "../../../apps/include" \
  -Wno-format \
  -Wno-format-security \
  -Wno-unused-variable \
  "-I./lvgl/src/core" \
  "-I./lvgl/src/draw" \
  "-I./lvgl/src/draw/arm2d" \
  "-I./lvgl/src/draw/nxp" \
  "-I./lvgl/src/draw/nxp/pxp" \
  "-I./lvgl/src/draw/nxp/vglite" \
  "-I./lvgl/src/draw/sdl" \
  "-I./lvgl/src/draw/stm32_dma2d" \
  "-I./lvgl/src/draw/sw" \
  "-I./lvgl/src/draw/swm341_dma2d" \
  "-I./lvgl/src/font" \
  "-I./lvgl/src/hal" \
  "-I./lvgl/src/misc" \
  "-I./lvgl/src/widgets" \
  ./lvgl/src/widgets/lv_label.c \
  -o ../../../pinephone-lvgl-zig/lv_label.o

(NuttX Build Files)

This produces the Compiled WebAssembly lv_label.o.

Will Zig Compiler let us link lv_label.o with our Zig LVGL App?

Let's ask Zig Compiler to link lv_label.o with our Zig LVGL App lvglwasm.zig...

  ## Compile the Zig App for WebAssembly 
  ## TODO: Change ".." to your NuttX Project Directory
  zig build-lib \
    --verbose-cimport \
    -target wasm32-freestanding \
    -dynamic \
    -rdynamic \
    -lc \
    -DFAR= \
    -DLV_MEM_CUSTOM=1 \
    -DLV_FONT_MONTSERRAT_14=1 \
    -DLV_FONT_MONTSERRAT_20=1 \
    -DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
    -DLV_USE_FONT_PLACEHOLDER=1 \
    -DLV_USE_LOG=1 \
    -DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
    -DLV_LOG_TRACE_OBJ_CREATE=1 \
    -DLV_LOG_TRACE_TIMER=1 \
    -DLV_LOG_TRACE_MEM=1 \
    "-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
    -I . \
    -isystem "../nuttx/include" \
    -I "../apps/include" \
    -I "../apps/graphics/lvgl" \
    -I "../apps/graphics/lvgl/lvgl/src/core" \
    -I "../apps/graphics/lvgl/lvgl/src/draw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
    -I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
    -I "../apps/graphics/lvgl/lvgl/src/font" \
    -I "../apps/graphics/lvgl/lvgl/src/hal" \
    -I "../apps/graphics/lvgl/lvgl/src/misc" \
    -I "../apps/graphics/lvgl/lvgl/src/widgets" \
    lvglwasm.zig \
    lv_label.o

(Source)

(NuttX Build Files)

Now we see this error in the Web Browser...

Uncaught (in promise) LinkError: 
WebAssembly.instantiate(): 
Import #0 module="env" function="lv_obj_clear_flag" error:
function import requires a callable

lv_label_create is no longer missing, because Zig Compiler has linked lv_label.o into our Zig LVGL App.

Yep Zig Compiler will happily link WebAssembly Object Files with our Zig App yay!

Now we need to compile lv_obj_clear_flag and the other LVGL Files from C to WebAssembly with Zig Compiler...

Compile Entire LVGL Library to WebAssembly

When we track down lv_obj_clear_flag and the other Missing Functions (by sheer tenacity), we get this trail of LVGL Source Files that need to be compiled from C to WebAssembly...

widgets/lv_label.c
core/lv_obj.c
misc/lv_mem.c
core/lv_event.c
core/lv_obj_style.c
core/lv_obj_pos.c
misc/lv_txt.c
draw/lv_draw_label.c
core/lv_obj_draw.c
misc/lv_area.c
core/lv_obj_scroll.c
font/lv_font.c
core/lv_obj_class.c
(And many more)

(Based on LVGL 8.3.3)

So we wrote a script to compile the above LVGL Source Files from C to WebAssembly with zig cc...

## Build the LVGL App (in Zig) and LVGL Library (in C) for PinePhone and WebAssembly
## TODO: Change ".." to your NuttX Project Directory
function build_zig {
## Check that NuttX Build has completed and `lv_demo_widgets.*.o` exists
if [ ! -f ../apps/graphics/lvgl/lvgl/demos/widgets/lv_demo_widgets.*.o ]
then
echo "*** Error: Build NuttX first before building Zig app"
exit 1
fi
## Compile our LVGL Display Driver from C to WebAssembly with Zig Compiler
compile_lvgl ../../../../../pinephone-lvgl-zig/display.c display.o
## Compile LVGL Library from C to WebAssembly with Zig Compiler
compile_lvgl font/lv_font_montserrat_14.c lv_font_montserrat_14.o
compile_lvgl font/lv_font_montserrat_20.c lv_font_montserrat_20.o
compile_lvgl widgets/lv_label.c lv_label.o
compile_lvgl core/lv_obj.c lv_obj.o
compile_lvgl misc/lv_mem.c lv_mem.o
compile_lvgl core/lv_event.c lv_event.o
compile_lvgl core/lv_obj_style.c lv_obj_style.o
compile_lvgl core/lv_obj_pos.c lv_obj_pos.o
compile_lvgl misc/lv_txt.c lv_txt.o
compile_lvgl draw/lv_draw_label.c lv_draw_label.o
compile_lvgl core/lv_obj_draw.c lv_obj_draw.o
compile_lvgl misc/lv_area.c lv_area.o
compile_lvgl core/lv_obj_scroll.c lv_obj_scroll.o
compile_lvgl font/lv_font.c lv_font.o
compile_lvgl core/lv_obj_class.c lv_obj_class.o
compile_lvgl core/lv_obj_tree.c lv_obj_tree.o
compile_lvgl hal/lv_hal_disp.c lv_hal_disp.o
compile_lvgl misc/lv_anim.c lv_anim.o
compile_lvgl misc/lv_tlsf.c lv_tlsf.o
compile_lvgl core/lv_group.c lv_group.o
compile_lvgl core/lv_indev.c lv_indev.o
compile_lvgl draw/lv_draw_rect.c lv_draw_rect.o
compile_lvgl draw/lv_draw_mask.c lv_draw_mask.o
compile_lvgl misc/lv_style.c lv_style.o
compile_lvgl misc/lv_ll.c lv_ll.o
compile_lvgl core/lv_obj_style_gen.c lv_obj_style_gen.o
compile_lvgl misc/lv_timer.c lv_timer.o
compile_lvgl core/lv_disp.c lv_disp.o
compile_lvgl core/lv_refr.c lv_refr.o
compile_lvgl misc/lv_color.c lv_color.o
compile_lvgl draw/lv_draw_line.c lv_draw_line.o
compile_lvgl draw/lv_draw_img.c lv_draw_img.o
compile_lvgl misc/lv_math.c lv_math.o
compile_lvgl hal/lv_hal_indev.c lv_hal_indev.o
compile_lvgl core/lv_theme.c lv_theme.o
compile_lvgl hal/lv_hal_tick.c lv_hal_tick.o
compile_lvgl misc/lv_log.c lv_log.o
compile_lvgl misc/lv_printf.c lv_printf.o
compile_lvgl misc/lv_fs.c lv_fs.o
compile_lvgl draw/lv_draw.c lv_draw.o
compile_lvgl draw/lv_img_decoder.c lv_img_decoder.o
compile_lvgl extra/lv_extra.c lv_extra.o
compile_lvgl extra/layouts/flex/lv_flex.c lv_flex.o
compile_lvgl extra/layouts/grid/lv_grid.c lv_grid.o
compile_lvgl draw/sw/lv_draw_sw.c lv_draw_sw.o
compile_lvgl draw/sw/lv_draw_sw_rect.c lv_draw_sw_rect.o
compile_lvgl draw/lv_img_cache.c lv_img_cache.o
compile_lvgl draw/lv_img_buf.c lv_img_buf.o
compile_lvgl draw/sw/lv_draw_sw_arc.c lv_draw_sw_arc.o
compile_lvgl draw/sw/lv_draw_sw_letter.c lv_draw_sw_letter.o
compile_lvgl draw/sw/lv_draw_sw_blend.c lv_draw_sw_blend.o
compile_lvgl draw/sw/lv_draw_sw_layer.c lv_draw_sw_layer.o
compile_lvgl draw/sw/lv_draw_sw_transform.c lv_draw_sw_transform.o
compile_lvgl draw/sw/lv_draw_sw_polygon.c lv_draw_sw_polygon.o
compile_lvgl draw/sw/lv_draw_sw_line.c lv_draw_sw_line.o
compile_lvgl draw/sw/lv_draw_sw_img.c lv_draw_sw_img.o
compile_lvgl draw/sw/lv_draw_sw_gradient.c lv_draw_sw_gradient.o
compile_lvgl draw/lv_draw_transform.c lv_draw_transform.o
compile_lvgl extra/themes/default/lv_theme_default.c lv_theme_default.o
compile_lvgl font/lv_font_fmt_txt.c lv_font_fmt_txt.o
compile_lvgl draw/lv_draw_layer.c lv_draw_layer.o
compile_lvgl misc/lv_style_gen.c lv_style_gen.o
compile_lvgl misc/lv_gc.c lv_gc.o
compile_lvgl misc/lv_utils.c lv_utils.o
## Compile the Zig LVGL App for WebAssembly
## TODO: Change ".." to your NuttX Project Directory
zig build-lib \
--verbose-cimport \
-target wasm32-freestanding \
-dynamic \
-rdynamic \
-lc \
-DFAR= \
-DLV_MEM_CUSTOM=1 \
-DLV_FONT_MONTSERRAT_14=1 \
-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
-DLV_USE_FONT_PLACEHOLDER=1 \
-DLV_USE_LOG=1 \
-DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
-DLV_LOG_TRACE_OBJ_CREATE=1 \
-DLV_LOG_TRACE_TIMER=1 \
"-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
-I . \
\
-isystem "../nuttx/include" \
-I "../apps/include" \
-I "../apps/graphics/lvgl" \
-I "../apps/graphics/lvgl/lvgl/src/core" \
-I "../apps/graphics/lvgl/lvgl/src/draw" \
-I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
-I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
-I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
-I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
-I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
-I "../apps/graphics/lvgl/lvgl/src/font" \
-I "../apps/graphics/lvgl/lvgl/src/hal" \
-I "../apps/graphics/lvgl/lvgl/src/misc" \
-I "../apps/graphics/lvgl/lvgl/src/widgets" \
\
lvglwasm.zig \
display.o \
lv_font_montserrat_14.o \
lv_font_montserrat_20.o \
lv_label.o \
lv_mem.o \
lv_obj.o \
lv_event.o \
lv_obj_style.o \
lv_obj_pos.o \
lv_txt.o \
lv_draw_label.o \
lv_obj_draw.o \
lv_area.o \
lv_obj_scroll.o \
lv_font.o \
lv_obj_class.o \
lv_obj_tree.o \
lv_hal_disp.o \
lv_anim.o \
lv_tlsf.o \
lv_group.o \
lv_indev.o \
lv_draw_rect.o \
lv_draw_mask.o \
lv_style.o \
lv_ll.o \
lv_obj_style_gen.o \
lv_timer.o \
lv_disp.o \
lv_refr.o \
lv_color.o \
lv_draw_line.o \
lv_draw_img.o \
lv_math.o \
lv_hal_indev.o \
lv_theme.o \
lv_hal_tick.o \
lv_log.o \
lv_printf.o \
lv_fs.o \
lv_draw.o \
lv_img_decoder.o \
lv_extra.o \
lv_flex.o \
lv_grid.o \
lv_draw_sw.o \
lv_draw_sw_rect.o \
lv_img_cache.o \
lv_img_buf.o \
lv_draw_sw_arc.o \
lv_draw_sw_letter.o \
lv_draw_sw_blend.o \
lv_draw_sw_layer.o \
lv_draw_sw_transform.o \
lv_draw_sw_polygon.o \
lv_draw_sw_line.o \
lv_draw_sw_img.o \
lv_draw_sw_gradient.o \
lv_draw_transform.o \
lv_theme_default.o \
lv_font_fmt_txt.o \
lv_draw_layer.o \
lv_style_gen.o \
lv_gc.o \
lv_utils.o \

Which calls compile_lvgl to compile a single LVGL Source File from C to WebAssembly with zig cc...

pinephone-lvgl-zig/build.sh

Lines 226 to 288 in 2e1c97e

## Compile LVGL Library from C to WebAssembly with Zig Compiler
## TODO: Change ".." to your NuttX Project Directory
function compile_lvgl {
local source_file=$1 ## Input Source File (LVGL in C)
local object_file=$2 ## Output Object File (WebAssembly)
pushd ../apps/graphics/lvgl
zig cc \
-target wasm32-freestanding \
-dynamic \
-rdynamic \
-lc \
-DFAR= \
-DLV_MEM_CUSTOM=1 \
-DLV_FONT_MONTSERRAT_14=1 \
-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
-DLV_USE_FONT_PLACEHOLDER=1 \
-DLV_USE_LOG=1 \
-DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
-DLV_LOG_TRACE_OBJ_CREATE=1 \
-DLV_LOG_TRACE_TIMER=1 \
"-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
\
-c \
-fno-common \
-Wall \
-Wstrict-prototypes \
-Wshadow \
-Wundef \
-Werror \
-Os \
-fno-strict-aliasing \
-fomit-frame-pointer \
-ffunction-sections \
-fdata-sections \
-g \
-isystem ../../../nuttx/include \
-D__NuttX__ \
-pipe \
-I ../../../apps/graphics/lvgl \
-I "../../../apps/include" \
-Wno-format \
-Wno-format-security \
-Wno-unused-variable \
"-I./lvgl/src/core" \
"-I./lvgl/src/draw" \
"-I./lvgl/src/draw/arm2d" \
"-I./lvgl/src/draw/nxp" \
"-I./lvgl/src/draw/nxp/pxp" \
"-I./lvgl/src/draw/nxp/vglite" \
"-I./lvgl/src/draw/sdl" \
"-I./lvgl/src/draw/stm32_dma2d" \
"-I./lvgl/src/draw/sw" \
"-I./lvgl/src/draw/swm341_dma2d" \
"-I./lvgl/src/font" \
"-I./lvgl/src/hal" \
"-I./lvgl/src/misc" \
"-I./lvgl/src/widgets" \
lvgl/src/$source_file \
-o ../../../pinephone-lvgl-zig/$object_file
popd
}

What happens after we compile the whole bunch of LVGL Source Files from C to WebAssembly?

Now the Web Browser says that strlen is missing...

Uncaught (in promise) LinkError: 
WebAssembly.instantiate(): 
Import #0 module="env" function="strlen" error: 
function import requires a callable

Let's fix strlen...

Is it really OK to compile only the necessary LVGL Source Files? Instead of compiling ALL the LVGL Source Files?

Be careful! We might miss out some symbols. Zig Compiler happily assumes that they are at WebAssembly Address 0...

And remember to compile the LVGL Fonts!

TODO: Disassemble the Compiled WebAssembly and look for other Undefined Variables at WebAssembly Address 0

C Standard Library is Missing

strlen is missing from our Zig WebAssembly...

But strlen should come from the C Standard Library! (musl)

Not sure why strlen is missing, but we fixed it temporarily by copying from the Zig Library Source Code...

///////////////////////////////////////////////////////////////////////////////
// C Standard Library
// From zig-macos-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/zig/c.zig
export fn memset(dest: ?[*]u8, c2: u8, len: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false);
if (len != 0) {
var d = dest.?;
var n = len;
while (true) {
d.* = c2;
n -= 1;
if (n == 0) break;
d += 1;
}
}
return dest;
}
export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, len: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false);
if (len != 0) {
var d = dest.?;
var s = src.?;
var n = len;
while (true) {
d[0] = s[0];
n -= 1;
if (n == 0) break;
d += 1;
s += 1;
}
}
return dest;
}
export fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
dest[i] = 0;
return dest;
}
export fn strlen(s: [*:0]const u8) callconv(.C) usize {
return std.mem.len(s);
}

This seems to be the same problem mentioned here.

(Referenced by this pull request)

(And this issue)

TODO: Maybe because we didn't export strlen in our Main Program lvglwasm.zig?

TODO: Do we compile C Standard Library ourselves? From musl? Newlib? wasi-libc?

What if we change the target to wasm32-freestanding-musl?

Nope doesn't help, same problem.

What if we use zig build-exe instead of zig build-lib?

Sorry zig build-exe is meant for building WASI Executables. (See this)

zig build-exe is not supposed to work for WebAssembly in the Web Browser. (See this)

LVGL Porting Layer for WebAssembly

LVGL expects us to provide a millis function that returns the number of elapsed milliseconds...

Uncaught (in promise) LinkError: 
WebAssembly.instantiate(): 
Import #0 module="env" function="millis" error: 
function import requires a callable

We implement millis ourselves for WebAssembly...

///////////////////////////////////////////////////////////////////////////////
// LVGL Porting Layer for WebAssembly
/// TODO: Return the number of elapsed milliseconds
export fn millis() u32 {
elapsed_ms += 1;
return elapsed_ms;
}
/// Number of elapsed milliseconds
var elapsed_ms: u32 = 0;
/// On Assertion Failure, print a Stack Trace and halt
export fn lv_assert_handler() void {
@panic("*** lv_assert_handler: ASSERTION FAILED");
}
/// Custom Logger for LVGL that writes to JavaScript Console
export fn custom_logger(buf: [*c]const u8) void {
wasmlog.Console.log("{s}", .{buf});
}

TODO: Fix millis. How would it work in WebAssembly? Using a counter?

In the code above, we defined lv_assert_handler and custom_logger to handle Assertions and Logging in LVGL.

Let's talk about LVGL Logging...

WebAssembly Logger for LVGL

Let's trace the LVGL Execution with a WebAssembly Logger.

(Remember: printf won't work in WebAssembly)

We set the Custom Logger for LVGL, so that we can print Log Messages to the JavaScript Console...

///////////////////////////////////////////////////////////////////////////////
// Main Function
/// We render an LVGL Screen with LVGL Widgets
pub export fn lv_demo_widgets() void {
// TODO: Change to `debug`
wasmlog.Console.log("lv_demo_widgets: start", .{});
defer wasmlog.Console.log("lv_demo_widgets: end", .{});
// Set the Custom Logger for LVGL
c.lv_log_register_print_cb(custom_logger);

The Custom Logger is defined in our Zig Program...

/// Custom Logger for LVGL that writes to JavaScript Console
export fn custom_logger(buf: [*c]const u8) void {
wasmlog.Console.log("custom_logger: {s}", .{buf});
}

wasmlog is our Zig Logger for WebAssembly: wasmlog.zig

(Based on daneelsan/zig-wasm-logger)

jsConsoleLogWrite and jsConsoleLogFlush are defined in our JavaScript...

// Write to JavaScript Console from Zig
// https://github.com/daneelsan/zig-wasm-logger/blob/master/script.js
jsConsoleLogWrite: function(ptr, len) {
console_log_buffer += wasm.getString(ptr, len);
},
// Flush JavaScript Console from Zig
// https://github.com/daneelsan/zig-wasm-logger/blob/master/script.js
jsConsoleLogFlush: function() {
console.log(console_log_buffer);
console_log_buffer = "";
},

wasm.getString also comes from our JavaScript...

// WebAssembly Helper Functions
const wasm = {
// WebAssembly Instance
instance: undefined,
// Init the WebAssembly Instance
init: function (obj) {
this.instance = obj.instance;
},
// Fetch the Zig String from a WebAssembly Pointer
getString: function (ptr, len) {
const memory = this.instance.exports.memory;
return text_decoder.decode(
new Uint8Array(memory.buffer, ptr, len)
);
},
};

Now we can see the LVGL Log Messages in the JavaScript Console yay! (Pic below)

custom_logger: [Warn]	(0.001, +1)
lv_disp_get_scr_act:
no display registered to get its active screen
(in lv_disp.c line #54)

Let's initialise the LVGL Display...

WebAssembly Logger for LVGL

Initialise LVGL Display

According to the LVGL Docs, this is how we initialise and operate LVGL...

  1. Call lv_init()

  2. Register the LVGL Display and LVGL Input Devices

  3. Call lv_tick_inc(x) every x milliseconds in an interrupt to report the elapsed time to LVGL

    (Not required, because LVGL calls millis to fetch the elapsed time)

  4. Call lv_timer_handler() every few milliseconds to handle LVGL related tasks

(Source)

To register the LVGL Display, we should do this...

But we can't do this in Zig...

// Nope! lv_disp_drv_t is an Opaque Type
var disp_drv = c.lv_disp_drv_t{};
c.lv_disp_drv_init(&disp_drv);

Because lv_disp_drv_t is an Opaque Type.

(lv_disp_drv_t contains Bit Fields, hence it's Opaque)

Thus we apply this workaround to create lv_disp_drv_t in C...

And we get this LVGL Display Interface for Zig: display.c

Finally this is how we initialise the LVGL Display in Zig WebAssembly...

/// We render an LVGL Screen with LVGL Widgets
pub export fn lv_demo_widgets() void {
debug("lv_demo_widgets: start", .{});
defer debug("lv_demo_widgets: end", .{});
// Create the Memory Allocator for malloc
memory_allocator = std.heap.FixedBufferAllocator.init(&memory_buffer);
// Set the Custom Logger for LVGL
c.lv_log_register_print_cb(custom_logger);
// Init LVGL
c.lv_init();
// Fetch pointers to Display Driver and Display Buffer
const disp_drv = c.get_disp_drv();
const disp_buf = c.get_disp_buf();
// Init Display Buffer and Display Driver as pointers
c.init_disp_buf(disp_buf);
c.init_disp_drv(disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);
// Register the Display Driver
const disp = c.lv_disp_drv_register(disp_drv);
_ = disp;
// Create the widgets for display (with Zig Wrapper)
createWidgetsWrapped() catch |e| {
// In case of error, quit
std.log.err("createWidgetsWrapped failed: {}", .{e});
return;
};
// Handle LVGL Events
// TODO: Call this from Web Browser JavaScript, so that Web Browser won't block
var i: usize = 0;
while (i < 5) : (i += 1) {
debug("lv_timer_handler: start", .{});
_ = c.lv_timer_handler();
debug("lv_timer_handler: end", .{});
}
}

Now we handle LVGL Tasks...

Handle LVGL Tasks

Earlier we talked about handling LVGL Tasks...

  1. Call lv_tick_inc(x) every x milliseconds (in an Interrupt) to report the Elapsed Time to LVGL

    (Not required, because LVGL calls millis to fetch the Elapsed Time)

  2. Call lv_timer_handler every few milliseconds to handle LVGL Tasks

(From the LVGL Docs)

This is how we call lv_timer_handler in Zig: lvglwasm.zig

/// Main Function for our Zig LVGL App
pub export fn lv_demo_widgets() void {

  // Omitted: Init LVGL Display

  // Create the widgets for display
  createWidgetsWrapped() catch |e| {
    // In case of error, quit
    std.log.err("createWidgetsWrapped failed: {}", .{e});
    return;
  };

  // Handle LVGL Tasks
  // TODO: Call this from Web Browser JavaScript,
  // so that Web Browser won't block
  var i: usize = 0;
  while (i < 5) : (i += 1) {
    _ = c.lv_timer_handler();
  }

We're ready to render the LVGL Display in our HTML Page!

Something doesn't look right...

Yeah we should have called lv_timer_handler from our JavaScript.

(Triggered by a JavaScript Timer or requestAnimationFrame)

But for our quick demo, this will do. For now!

Render LVGL Display in WebAssembly

Render LVGL Display in Web Browser

Let's render the LVGL Display in the Web Browser! (Pic above)

(Based on daneelsan/minimal-zig-wasm-canvas)

LVGL renders the display pixels to canvas_buffer...

/****************************************************************************
* Name: init_disp_buf
*
* Description:
* Initialise the LVGL Display Buffer, because Zig can't access the fields.
*
****************************************************************************/
void init_disp_buf(lv_disp_draw_buf_t *disp_buf)
{
LV_ASSERT(disp_buf != NULL);
lv_disp_draw_buf_init(disp_buf, canvas_buffer, NULL, BUFFER_SIZE);
}

(init_disp_buf is called by our Zig Program)

LVGL calls flushDisplay (in Zig) when the LVGL Display Canvas is ready to be rendered...

// Init LVGL
c.lv_init();
// Fetch pointers to Display Driver and Display Buffer
const disp_drv = c.get_disp_drv();
const disp_buf = c.get_disp_buf();
// Init Display Buffer and Display Driver as pointers
c.init_disp_buf(disp_buf);
c.init_disp_drv(disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);

flushDisplay (in Zig) calls render (in JavaScript) to render the LVGL Display Canvas...

/// LVGL Callback Function to Flush Display
export fn flushDisplay(disp_drv: ?*c.lv_disp_drv_t, area: [*c]const c.lv_area_t, color_p: [*c]c.lv_color_t) void {
_ = area;
_ = color_p;
debug("flushDisplay: start", .{});
defer debug("flushDisplay: end", .{});
// Call the Web Browser JavaScript o render the LVGL Canvas Buffer
render();
// Notify LVGL that the display is flushed
c.lv_disp_flush_ready(disp_drv);
}

(Remember to call lv_disp_flush_ready or Web Browser will hang on reload)

render (in JavaScript) draws the LVGL Display to our HTML Canvas...

// Export JavaScript Functions to Zig
const importObject = {
// JavaScript Functions exported to Zig
env: {
// Render the LVGL Canvas from Zig to HTML
// https://github.com/daneelsan/minimal-zig-wasm-canvas/blob/master/script.js
render: function() { // TODO: Add width and height
// Get the WebAssembly Pointer to the LVGL Canvas Buffer
console.log("render: start");
const bufferOffset = wasm.instance.exports.getCanvasBuffer();
console.log({ bufferOffset });
// Load the WebAssembly Pointer into a JavaScript Image Data
const memory = wasm.instance.exports.memory;
const ptr = bufferOffset;
const len = (canvas.width * canvas.height) * 4;
const imageDataArray = new Uint8Array(memory.buffer, ptr, len)
imageData.data.set(imageDataArray);
// Render the Image Data to the HTML Canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.putImageData(imageData, 0, 0);
console.log("render: end");
},

Which calls getCanvasBuffer (in Zig) and get_canvas_buffer (in C) to fetch the LVGL Canvas Buffer canvas_buffer...

// Canvas Buffer for rendering LVGL Display
#define HOR_RES 720 // Horizontal Resolution
#define VER_RES 1280 // Vertical Resolution
#define BUFFER_ROWS VER_RES // Number of rows to buffer
#define BUFFER_SIZE (HOR_RES * BUFFER_ROWS)
static lv_color_t canvas_buffer[BUFFER_SIZE];
lv_color_t *get_canvas_buffer(void)
{
int count = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (canvas_buffer[i].full != 0xfff5f5f5) { // TODO
// lv_log("get_canvas_buffer: 0x%x", canvas_buffer[i].full);
count++;
}
}
lv_log("get_canvas_buffer: %d non-empty pixels", count);
lv_log("canvas_buffer: %p", canvas_buffer);
return canvas_buffer;
}

Remember to set Direct Mode in the Display Driver!

// Use screen-sized buffers and draw to absolute coordinates
disp_drv->direct_mode = 1;

And the LVGL Display renders OK in our HTML Canvas yay! (Pic below)

Render LVGL Display in Web Browser

Handle LVGL Timer

To execute LVGL Tasks periodically, here's the proper way to handle the LVGL Timer in JavaScript...

// Render Loop
const loop = function() {
// Compute the Elapsed Milliseconds
const elapsed_ms = Date.now() - start_ms;
// Handle LVGL Tasks to update the display
wasm.instance.exports
.handleTimer(elapsed_ms);
// Loop to next frame
window.requestAnimationFrame(loop);
// Previously: window.setTimeout(loop, 100);
};
// Start the Render Loop
loop();

handleTimer comes from our Zig LVGL App, it executes LVGL Tasks periodically...

/// Called by JavaScript to execute LVGL Tasks periodically, passing the Elapsed Milliseconds
export fn handleTimer(ms: i32) i32 {
// Set the Elapsed Milliseconds, don't allow time rewind
if (ms > elapsed_ms) {
elapsed_ms = @intCast(u32, ms);
}
// Handle LVGL Tasks
_ = c.lv_timer_handler();
return 0;
}

Handle LVGL Input

Let's handle Mouse and Touch Input in LVGL!

We create an LVGL Button in our Zig LVGL App...

/// Create an LVGL Button
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
fn createButton() void {
const btn = c.lv_btn_create(c.lv_scr_act());
_ = c.lv_obj_add_event_cb(btn, eventHandler, c.LV_EVENT_ALL, null);
c.lv_obj_align(btn, c.LV_ALIGN_CENTER, 0, 40);
c.lv_obj_add_flag(btn, c.LV_OBJ_FLAG_CHECKABLE);
const label = c.lv_label_create(btn);
c.lv_label_set_text(label, "Button");
c.lv_obj_center(label);
}

eventHandler is our Zig Handler for Button Events...

/// Handle LVGL Button Event
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
export fn eventHandler(e: ?*c.lv_event_t) void {
const code = c.lv_event_get_code(e);
// debug("eventHandler: code={}", .{code});
if (code == c.LV_EVENT_CLICKED) {
debug("eventHandler: clicked", .{});
} else if (code == c.LV_EVENT_VALUE_CHANGED) {
debug("eventHandler: toggled", .{});
}
}

When our app starts, we register the LVGL Input Device...

// Register the Input Device
// https://docs.lvgl.io/8.3/porting/indev.html
indev_drv = std.mem.zeroes(c.lv_indev_drv_t);
c.lv_indev_drv_init(&indev_drv);
indev_drv.type = c.LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = readInput;
_ = c.register_input(&indev_drv);

(We define register_input in C because lv_indev_t is an Opaque Type in Zig)

This tells LVGL to call readInput periodically to poll for input. (More about this below)

indev_drv is our LVGL Input Device Driver...

/// LVGL Input Device Driver (std.mem.zeroes crashes the compiler)
var indev_drv: c.lv_indev_drv_t = undefined;

Now we handle Mouse and Touch Events in our JavaScript...

// Handle Mouse Down on HTML Canvas
canvas.addEventListener("mousedown", (e) => {
// Notify Zig of Mouse Down
const x = e.offsetX;
const y = e.offsetY;
console.log({mousedown: {x, y}});
wasm.instance.exports
.notifyInput(1, x, y);
});
// Handle Mouse Up on HTML Canvas
canvas.addEventListener("mouseup", (e) => {
// Notify Zig of Mouse Up
x = e.offsetX;
y = e.offsetY;
console.log({mouseup: {x, y}});
wasm.instance.exports
.notifyInput(0, x, y);
});
// Handle Touch Start on HTML Canvas
canvas.addEventListener("touchstart", (e) => {
// Notify Zig of Touch Start
e.preventDefault();
const touches = e.changedTouches;
if (touches.length == 0) { return; }
const x = touches[0].pageX;
const y = touches[0].pageY;
console.log({touchstart: {x, y}});
wasm.instance.exports
.notifyInput(1, x, y);
});
// Handle Touch End on HTML Canvas
canvas.addEventListener("touchend", (e) => {
// Notify Zig of Touch End
e.preventDefault();
const touches = e.changedTouches;
if (touches.length == 0) { return; }
const x = touches[0].pageX;
const y = touches[0].pageY;
console.log({touchend: {x, y}});
wasm.instance.exports
.notifyInput(0, x, y);
});

Which calls notifyInput in our Zig App to set the Input State and Input Coordinates...

/// Called by JavaScript to notify Mouse Down and Mouse Up
export fn notifyInput(pressed: i32, x: i32, y: i32) i32 {
if (pressed == 0) {
input_state = c.LV_INDEV_STATE_RELEASED;
} else {
input_state = c.LV_INDEV_STATE_PRESSED;
}
input_x = @intCast(c.lv_coord_t, x);
input_y = @intCast(c.lv_coord_t, y);
input_updated = true;
return 0;
}

LVGL polls our readInput Zig Function periodically to read the Input State and Input Coordinates...

/// LVGL Callback Function to read Input Device
export fn readInput(drv: [*c]c.lv_indev_drv_t, data: [*c]c.lv_indev_data_t) void {
_ = drv;
if (input_updated) {
input_updated = false;
c.set_input_data(data, input_state, input_x, input_y);
debug("readInput: state={}, x={}, y={}", .{ input_state, input_x, input_y });
}
}
/// True if LVGL Input State has been updated
var input_updated: bool = false;
/// LVGL Input State and Coordinates
var input_state: c.lv_indev_state_t = 0;
var input_x: c.lv_coord_t = 0;
var input_y: c.lv_coord_t = 0;

(We define set_input_data in C because lv_indev_data_t is an Opaque Type in Zig)

And the LVGL Button will respond correctly to Mouse and Touch Input in the Web Browser! (Pic below)

(Try the LVGL Button Demo)

(Watch the demo on YouTube)

(See the log)

Handle LVGL Input

Feature Phone UI

Read the article...

Let's create a Feature Phone UI for PinePhone on Apache NuttX RTOS!

We create 3 LVGL Containers for the Display Label, Call / Cancel Buttons and Digit Buttons...

// Create the Containers for Display, Call / Cancel Buttons, Digit Buttons
const display_cont = c.lv_obj_create(c.lv_scr_act()).?;
c.lv_obj_set_size(display_cont, 700, 150);
c.lv_obj_align(display_cont, c.LV_ALIGN_TOP_MID, 0, 5);
c.lv_obj_add_style(display_cont, &display_style, 0);
const call_cont = c.lv_obj_create(c.lv_scr_act()).?;
c.lv_obj_set_size(call_cont, 700, 200);
c.lv_obj_align_to(call_cont, display_cont, c.LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
c.lv_obj_add_style(call_cont, &call_style, 0);
const digit_cont = c.lv_obj_create(c.lv_scr_act()).?;
c.lv_obj_set_size(digit_cont, 700, 800);
c.lv_obj_align_to(digit_cont, call_cont, c.LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
c.lv_obj_add_style(digit_cont, &digit_style, 0);
// Create the Display Label
try createDisplayLabel(display_cont);
// Create the Call and Cancel Buttons
try createCallButtons(call_cont);
// Create the Digit Buttons
try createDigitButtons(digit_cont);

We create the Display Label...

/// Create the Display Label
fn createDisplayLabel(cont: *c.lv_obj_t) !void {
// Get the Container
var container = lvgl.Object.init(cont);
// Create a Label Widget
display_label = try container.createLabel();
// Wrap long lines in the label text
display_label.setLongMode(c.LV_LABEL_LONG_WRAP);
// Interpret color codes in the label text
display_label.setRecolor(true);
// Center align the label text
display_label.setAlign(c.LV_TEXT_ALIGN_CENTER);
// Set the label text and colors
display_label.setText("#ff0000 HELLO# " ++ // Red Text
"#00aa00 LVGL ON# " ++ // Green Text
"#0000ff PINEPHONE!# " // Blue Text
);
// Set the label width
display_label.setWidth(200);
// Align the label to the top middle
display_label.alignObject(c.LV_ALIGN_TOP_MID, 0, 0);
}

Then we create the Call and Cancel Buttons inside the Second Container...

/// Create the Call and Cancel Buttons
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
fn createCallButtons(cont: *c.lv_obj_t) !void {
const labels = [_][]const u8{ "Call", "Cancel" };
var i: usize = 0;
while (i < labels.len) : (i += 1) {
const btn = c.lv_btn_create(cont);
c.lv_obj_set_size(btn, 250, 100);
c.lv_obj_add_flag(btn, c.LV_OBJ_FLAG_CHECKABLE);
_ = c.lv_obj_add_event_cb(btn, eventHandler, c.LV_EVENT_ALL, null);
const label = c.lv_label_create(btn);
c.lv_label_set_text(label, labels[i].ptr);
c.lv_obj_center(label);
}
}

Finally we create the Digit Buttons inside the Third Container...

/// Create the Digit Buttons
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
fn createDigitButtons(cont: *c.lv_obj_t) !void {
const labels = [_][]const u8{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#" };
var i: usize = 0;
while (i < labels.len) : (i += 1) {
const btn = c.lv_btn_create(cont);
c.lv_obj_set_size(btn, 150, 120);
c.lv_obj_add_flag(btn, c.LV_OBJ_FLAG_CHECKABLE);
_ = c.lv_obj_add_event_cb(btn, eventHandler, c.LV_EVENT_ALL, null);
const label = c.lv_label_create(btn);
c.lv_label_set_text(label, labels[i].ptr);
c.lv_obj_center(label);
}
}

(Or use an LVGL Button Matrix)

When we test our Zig LVGL App in WebAssembly, we see this...

Feature Phone UI

(Try the Feature Phone Demo)

(Watch the demo on YouTube)

(See the log)

Handle Buttons in Feature Phone UI

Now that we have rendered the Feature Phone UI in Zig and LVGL, let's wire up the Buttons.

Clicking any Button will call our Button Event Handler...

/// Create the Digit Buttons
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
fn createDigitButtons(cont: *c.lv_obj_t) !void {
var i: usize = 0;
while (i < digit_labels.len) : (i += 1) {
const text = digit_labels[i].ptr;
const btn = c.lv_btn_create(cont);
c.lv_obj_set_size(btn, 150, 120);
_ = c.lv_obj_add_event_cb(btn, eventHandler, c.LV_EVENT_ALL, @intToPtr(*anyopaque, @ptrToInt(text)));

In our Button Event Handler, we identify the Button Clicked...

/// Handle LVGL Button Event
/// https://docs.lvgl.io/8.3/examples.html#simple-buttons
export fn eventHandler(e: ?*c.lv_event_t) void {
const code = c.lv_event_get_code(e);
if (code == c.LV_EVENT_CLICKED) {
// Handle Button Clicked
debug("eventHandler: clicked", .{});
// Get the length of Display Text
const len = std.mem.indexOfSentinel(u8, 0, &display_text);
// Get the Button Text
const data = c.lv_event_get_user_data(e);
const text = @ptrCast([*:0]u8, data);
const span = std.mem.span(text);

If it's a Digit Button, we append the Digit to the Phone Number...

} else {
// Else append the digit clicked to the text
display_text[len] = text[0];
c.lv_label_set_text(display_label.obj, display_text[0.. :0]);
}

If it's the Cancel Button, we erase the last digit of the Phone Number...

} else if (std.mem.eql(u8, span, "Cancel")) {
// If Cancel is clicked, erase the last digit
if (len >= 2) {
display_text[len - 1] = 0;
c.lv_label_set_text(display_label.obj, display_text[0.. :0]);
}
} else {

If it's the Call Button, we call PinePhone's LTE Modem to dial the Phone Number...

if (std.mem.eql(u8, span, "Call")) {
// If Call is clicked, call the number
const call_number = display_text[0..len :0];
debug("Call {s}", .{call_number});
if (builtin.cpu.arch == .wasm32 or builtin.cpu.arch == .wasm64) {
debug("Running in WebAssembly, simulate the Phone Call", .{});
} else {
debug("Running on PinePhone, make an actual Phone Call", {});
}

(Simulated for WebAssembly)

The buttons work OK on WebAssembly. (Pic below)

Let's run the Feature Phone UI on PinePhone and Apache NuttX RTOS!

Feature Phone UI

(Try the Feature Phone Demo)

(Watch the demo on YouTube)

(See the log)

Feature Phone UI for Apache NuttX RTOS

We created an LVGL Feature Phone UI for WebAssembly. Will it run on PinePhone?

Let's refactor the LVGL Feature Phone UI, so that the same Zig Source File will run on BOTH WebAssembly and PinePhone! (With Apache NuttX RTOS)

We moved all the WebAssembly-Specific Functions to wasm.zig...

/// Init the LVGL Display and Input
pub export fn initDisplay() void {
debug("initDisplay: start", .{});
defer debug("initDisplay: end", .{});
// Create the Memory Allocator for malloc
memory_allocator = std.heap.FixedBufferAllocator.init(&memory_buffer);
// Set the Custom Logger for LVGL
c.lv_log_register_print_cb(custom_logger);
// Init LVGL
c.lv_init();
// Fetch pointers to Display Driver and Display Buffer
const disp_drv = c.get_disp_drv();
const disp_buf = c.get_disp_buf();
// Init Display Buffer and Display Driver as pointers
c.init_disp_buf(disp_buf);
c.init_disp_drv(disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);
// Register the Display Driver
const disp = c.lv_disp_drv_register(disp_drv);
_ = disp;
// Register the Input Device
// https://docs.lvgl.io/8.3/porting/indev.html
indev_drv = std.mem.zeroes(c.lv_indev_drv_t);
c.lv_indev_drv_init(&indev_drv);
indev_drv.type = c.LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = readInput;
_ = c.register_input(&indev_drv);
}
/// LVGL Callback Function to Flush Display
export fn flushDisplay(disp_drv: ?*c.lv_disp_drv_t, area: [*c]const c.lv_area_t, color_p: [*c]c.lv_color_t) void {
_ = area;
_ = color_p;
// Call the Web Browser JavaScript to render the LVGL Canvas Buffer
render();
// Notify LVGL that the display is flushed
c.lv_disp_flush_ready(disp_drv);
}
/// Return a WebAssembly Pointer to the LVGL Canvas Buffer for JavaScript Rendering
export fn getCanvasBuffer() [*]u8 {
const buf = c.get_canvas_buffer();
return @ptrCast([*]u8, buf);
}
///////////////////////////////////////////////////////////////////////////////
// LVGL Input
/// Called by JavaScript to execute LVGL Tasks periodically, passing the Elapsed Milliseconds
export fn handleTimer(ms: i32) i32 {
// Set the Elapsed Milliseconds, don't allow time rewind
if (ms > elapsed_ms) {
elapsed_ms = @intCast(u32, ms);
}
// Handle LVGL Tasks
_ = c.lv_timer_handler();
return 0;
}
/// Called by JavaScript to notify Mouse Down and Mouse Up.
/// Return 1 if we're still waiting for LVGL to process the last input.
export fn notifyInput(pressed: i32, x: i32, y: i32) i32 {
// If LVGL hasn't processed the last input, try again later
if (input_updated) {
return 1;
}
// Save the Input State and Input Coordinates
if (pressed == 0) {
input_state = c.LV_INDEV_STATE_RELEASED;
} else {
input_state = c.LV_INDEV_STATE_PRESSED;
}
input_x = @intCast(c.lv_coord_t, x);
input_y = @intCast(c.lv_coord_t, y);
input_updated = true;
return 0;
}
/// LVGL Callback Function to read Input Device
export fn readInput(drv: [*c]c.lv_indev_drv_t, data: [*c]c.lv_indev_data_t) void {
_ = drv;
if (input_updated) {
input_updated = false;
c.set_input_data(data, input_state, input_x, input_y);
debug("readInput: state={}, x={}, y={}", .{ input_state, input_x, input_y });
}
}
/// True if LVGL Input State has been updated
var input_updated: bool = false;
/// LVGL Input State and Coordinates
var input_state: c.lv_indev_state_t = 0;
var input_x: c.lv_coord_t = 0;
var input_y: c.lv_coord_t = 0;
/// LVGL Input Device Driver (std.mem.zeroes crashes the compiler)
var indev_drv: c.lv_indev_drv_t = undefined;
///////////////////////////////////////////////////////////////////////////////
// LVGL Porting Layer for WebAssembly
/// TODO: Return the number of elapsed milliseconds
export fn millis() u32 {
elapsed_ms += 1;
return elapsed_ms;
}
/// Number of elapsed milliseconds
var elapsed_ms: u32 = 0;
/// On Assertion Failure, print a Stack Trace and halt
export fn lv_assert_handler() void {
@panic("*** lv_assert_handler: ASSERTION FAILED");
}
/// Custom Logger for LVGL that writes to JavaScript Console
export fn custom_logger(buf: [*c]const u8) void {
wasmlog.Console.log("{s}", .{buf});
}
///////////////////////////////////////////////////////////////////////////////
// Logging
/// Called by Zig for `std.log.debug`, `std.log.info`, `std.log.err`, ...
/// https://gist.github.com/leecannon/d6f5d7e5af5881c466161270347ce84d
pub fn log(
comptime _message_level: std.log.Level,
comptime _scope: @Type(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
_ = _message_level;
_ = _scope;
// Format the message
var buf: [100]u8 = undefined; // Limit to 100 chars
var slice = std.fmt.bufPrint(&buf, format, args) catch {
wasmlog.Console.log("*** log error: buf too small", .{});
return;
};
// Print the formatted message
wasmlog.Console.log("{s}", .{slice});
}
///////////////////////////////////////////////////////////////////////////////
// Memory Allocator for malloc
/// Zig replacement for malloc
export fn malloc(size: usize) ?*anyopaque {
// TODO: Save the slice length
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic("*** malloc error: out of memory");
};
return mem.ptr;
}
/// Zig replacement for realloc
export fn realloc(old_mem: [*c]u8, size: usize) ?*anyopaque {
// TODO: Call realloc instead
// const mem = memory_allocator.allocator().realloc(old_mem[0..???], size) catch {
// @panic("*** realloc error: out of memory");
// };
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic("*** realloc error: out of memory");
};
_ = memcpy(mem.ptr, old_mem, size);
if (old_mem != null) {
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(old_mem[0..???]);
}
return mem.ptr;
}
/// Zig replacement for free
export fn free(mem: [*c]u8) void {
if (mem == null) {
@panic("*** free error: pointer is null");
}
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(mem[0..???]);
}
/// Memory Allocator for malloc
var memory_allocator: std.heap.FixedBufferAllocator = undefined;
/// Memory Buffer for malloc
var memory_buffer = std.mem.zeroes([1024 * 1024]u8);
///////////////////////////////////////////////////////////////////////////////
// C Standard Library
// From zig-macos-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/zig/c.zig
export fn memset(dest: ?[*]u8, c2: u8, len: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false);
if (len != 0) {
var d = dest.?;
var n = len;
while (true) {
d.* = c2;
n -= 1;
if (n == 0) break;
d += 1;
}
}
return dest;
}
export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, len: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false);
if (len != 0) {
var d = dest.?;
var s = src.?;
var n = len;
while (true) {
d[0] = s[0];
n -= 1;
if (n == 0) break;
d += 1;
s += 1;
}
}
return dest;
}
export fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
dest[i] = 0;
return dest;
}
export fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int {
return std.cstr.cmp(s1, s2);
}
export fn strlen(s: [*:0]const u8) callconv(.C) usize {
return std.mem.len(s);
}
///////////////////////////////////////////////////////////////////////////////
// Imported Functions and Variables
/// JavaScript Functions imported into Zig WebAssembly
extern fn render() void;
/// Aliases for Zig Standard Library
const assert = std.debug.assert;
const debug = std.log.debug;

Our Zig LVGL App imports wasm.zig only when compiling for WebAssembly...

/// Import the functions specific to WebAssembly and Apache NuttX RTOS
pub usingnamespace switch (builtin.cpu.arch) {
.wasm32, .wasm64 => @import("wasm.zig"),
else => @import("nuttx.zig"),
};

In our JavaScript, we call initDisplay (from wasm.zig) to initialise the LVGL Display and LVGL Input for WebAssembly...

// Main Function
function main() {
console.log("main: start");
const start_ms = Date.now();
const zig = wasm.instance.exports;
// Init the LVGL Display and Input
zig.initDisplay();
// Render the LVGL Widgets in Zig
zig.lv_demo_widgets();
// Render Loop
const loop = function() {
// Compute the Elapsed Milliseconds
const elapsed_ms = Date.now() - start_ms;
// Handle LVGL Tasks to update the display
zig.handleTimer(elapsed_ms);
// Loop to next frame
window.requestAnimationFrame(loop);
// Previously: window.setTimeout(loop, 100);
};
// Start the Render Loop
loop();
console.log("main: end");
};

What about PinePhone on Apache NuttX RTOS?

When compiling for NuttX, our Zig LVGL App imports nuttx.zig...

/// Import the functions specific to WebAssembly and Apache NuttX RTOS
pub usingnamespace switch (builtin.cpu.arch) {
.wasm32, .wasm64 => @import("wasm.zig"),
else => @import("nuttx.zig"),
};

Which defines the Custom Panic Handler and Custom Logger specific to NuttX...

///////////////////////////////////////////////////////////////////////////////
// Panic Handler
/// Called by Zig when it hits a Panic. We print the Panic Message, Stack Trace and halt. See
/// https://andrewkelley.me/post/zig-stack-traces-kernel-panic-bare-bones-os.html
/// https://github.com/ziglang/zig/blob/master/lib/std/builtin.zig#L763-L847
pub fn panic(message: []const u8, _stack_trace: ?*std.builtin.StackTrace) noreturn {
// Print the Panic Message
_ = _stack_trace;
_ = puts("\n!ZIG PANIC!");
_ = puts(@ptrCast([*c]const u8, message));
// Print the Stack Trace
_ = puts("Stack Trace:");
var it = std.debug.StackIterator.init(@returnAddress(), null);
while (it.next()) |return_address| {
_ = printf("%p\n", return_address);
}
// Halt
while (true) {}
}
///////////////////////////////////////////////////////////////////////////////
// Logging
/// Called by Zig for `std.log.debug`, `std.log.info`, `std.log.err`, ...
/// https://gist.github.com/leecannon/d6f5d7e5af5881c466161270347ce84d
pub fn log(
comptime _message_level: std.log.Level,
comptime _scope: @Type(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
_ = _message_level;
_ = _scope;
// Format the message
var buf: [100]u8 = undefined; // Limit to 100 chars
var slice = std.fmt.bufPrint(&buf, format, args) catch {
_ = puts("*** log error: buf too small");
return;
};
// Terminate the formatted message with a null
var buf2: [buf.len + 1:0]u8 = undefined;
std.mem.copy(u8, buf2[0..slice.len], slice[0..slice.len]);
buf2[slice.len] = 0;
// Print the formatted message
_ = puts(&buf2);
}
///////////////////////////////////////////////////////////////////////////////
// Imported Functions and Variables
/// For safety, we import these functions ourselves to enforce Null-Terminated Strings.
/// We changed `[*c]const u8` to `[*:0]const u8`
extern fn printf(format: [*:0]const u8, ...) c_int;
extern fn puts(str: [*:0]const u8) c_int;
/// Aliases for Zig Standard Library
const assert = std.debug.assert;
const debug = std.log.debug;

We compile our Zig LVGL App for NuttX (using the exact same Zig Source File for WebAssembly)...

pinephone-lvgl-zig/build.sh

Lines 403 to 437 in 4650bc8

## Compile the Feature Phone Zig LVGL App for Apache NuttX RTOS
function build_feature_phone_nuttx {
## Compile the Zig LVGL App for PinePhone
## (armv8-a with cortex-a53)
## TODO: Change ".." to your NuttX Project Directory
zig build-obj \
--verbose-cimport \
-target aarch64-freestanding-none \
-mcpu cortex_a53 \
\
-isystem "../nuttx/include" \
-I . \
-I "../apps/include" \
-I "../apps/graphics/lvgl" \
-I "../apps/graphics/lvgl/lvgl/src/core" \
-I "../apps/graphics/lvgl/lvgl/src/draw" \
-I "../apps/graphics/lvgl/lvgl/src/draw/arm2d" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp/pxp" \
-I "../apps/graphics/lvgl/lvgl/src/draw/nxp/vglite" \
-I "../apps/graphics/lvgl/lvgl/src/draw/sdl" \
-I "../apps/graphics/lvgl/lvgl/src/draw/stm32_dma2d" \
-I "../apps/graphics/lvgl/lvgl/src/draw/sw" \
-I "../apps/graphics/lvgl/lvgl/src/draw/swm341_dma2d" \
-I "../apps/graphics/lvgl/lvgl/src/font" \
-I "../apps/graphics/lvgl/lvgl/src/hal" \
-I "../apps/graphics/lvgl/lvgl/src/misc" \
-I "../apps/graphics/lvgl/lvgl/src/widgets" \
feature-phone.zig
## Copy the compiled Zig LVGL App to NuttX and overwrite `lv_demo_widgets.*.o`
## TODO: Change ".." to your NuttX Project Directory
cp feature-phone.o \
../apps/graphics/lvgl/lvgl/demos/widgets/lv_demo_widgets.*.o
}

And our Feature Phone UI runs on PinePhone with NuttX yay! (Pic below)

The exact same Zig Source File runs on both WebAssembly and PinePhone, no changes needed! This is super helpful for creating LVGL Apps.

Feature Phone UI on PinePhone and Apache NuttX RTOS

(Watch the demo on YouTube)

(See the PinePhone Log)

LVGL Fonts

Remember to compile the LVGL Fonts! Or nothing will be rendered...

  ## Compile LVGL Library from C to WebAssembly with Zig Compiler
  compile_lvgl font/lv_font_montserrat_14.c lv_font_montserrat_14
  compile_lvgl font/lv_font_montserrat_20.c lv_font_montserrat_20

  ## Compile the Zig LVGL App for WebAssembly 
  zig build-lib \
    -DLV_FONT_MONTSERRAT_14=1 \
    -DLV_FONT_MONTSERRAT_20=1 \
    -DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
    -DLV_USE_FONT_PLACEHOLDER=1 \
    ...
    lv_font_montserrat_14.o \
    lv_font_montserrat_20.o \

(Source)

LVGL Memory Allocation

What happens if we omit -DLV_MEM_CUSTOM=1?

By default, LVGL uses the Two-Level Segregate Fit (TLSF) Allocator for Heap Memory.

But TLSF Allocator fails in block_next...

main: start
loop: start
lv_demo_widgets: start
before lv_init
[Info]	lv_init: begin 	(in lv_obj.c line #102)
[Trace]	lv_mem_alloc: allocating 76 bytes 	(in lv_mem.c line #127)
[Trace]	lv_mem_alloc: allocated at 0x1a700 	(in lv_mem.c line #160)
[Trace]	lv_mem_alloc: allocating 28 bytes 	(in lv_mem.c line #127)
[Trace]	lv_mem_alloc: allocated at 0x1a750 	(in lv_mem.c line #160)
[Warn]	lv_init: Log level is set to 'Trace' which makes LVGL much slower 	(in lv_obj.c line #176)
[Trace]	lv_mem_realloc: reallocating 0x14 with 8 size 	(in lv_mem.c line #196)
[Error]	block_next: Asserted at expression: !block_is_last(block) 	(in lv_tlsf.c line #459)

004a5b4a:0x29ab2 Uncaught (in promise) RuntimeError: unreachable
    at std.builtin.default_panic (004a5b4a:0x29ab2)
    at lv_assert_handler (004a5b4a:0x2ac6c)
    at block_next (004a5b4a:0xd5b3)
    at lv_tlsf_realloc (004a5b4a:0xe226)
    at lv_mem_realloc (004a5b4a:0x20f1)
    at lv_layout_register (004a5b4a:0x75d8)
    at lv_flex_init (004a5b4a:0x16afe)
    at lv_extra_init (004a5b4a:0x16ae5)
    at lv_init (004a5b4a:0x3f28)
    at lv_demo_widgets (004a5b4a:0x29bb9)

Thus we set -DLV_MEM_CUSTOM=1 to use malloc instead of LVGL's TLSF Allocator.

(block_next calls offset_to_block, which calls tlsf_cast. Maybe the Pointer Cast doesn't work for Clang WebAssembly?)

But Zig doesn't support malloc for WebAssembly!

We used Zig's FixedBufferAllocator...

/// We render an LVGL Screen with LVGL Widgets
pub export fn lv_demo_widgets() void {
debug("lv_demo_widgets: start", .{});
defer debug("lv_demo_widgets: end", .{});
// Create the Memory Allocator for malloc
memory_allocator = std.heap.FixedBufferAllocator.init(&memory_buffer);

To implement malloc ourselves...

///////////////////////////////////////////////////////////////////////////////
// Memory Allocator for malloc
/// Zig replacement for malloc
export fn malloc(size: usize) ?*anyopaque {
// TODO: Save the slice length
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic("*** malloc error: out of memory");
};
return mem.ptr;
}
/// Zig replacement for realloc
export fn realloc(old_mem: [*c]u8, size: usize) ?*anyopaque {
// TODO: Call realloc instead
// const mem = memory_allocator.allocator().realloc(old_mem[0..???], size) catch {
// @panic("*** realloc error: out of memory");
// };
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic("*** realloc error: out of memory");
};
_ = memcpy(mem.ptr, old_mem, size);
if (old_mem != null) {
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(old_mem[0..???]);
}
return mem.ptr;
}
/// Zig replacement for free
export fn free(mem: [*c]u8) void {
if (mem == null) {
@panic("*** free error: pointer is null");
}
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(mem[0..???]);
}
/// Memory Allocator for malloc
var memory_allocator: std.heap.FixedBufferAllocator = undefined;
/// Memory Buffer for malloc
var memory_buffer: [1024 * 1024]u8 = undefined;

(Remember to copy the old memory in realloc!)

(If we ever remove -DLV_MEM_CUSTOM=1, remember to set -DLV_MEM_SIZE=1000000)

TODO: How to disassemble Compiled WebAssembly with cross-reference to Source Code? Like objdump --source? See wabt and binaryen

LVGL Screen Not Found

Why does LVGL say "no screen found" in lv_obj_get_disp?

That's because the Display Linked List _lv_disp_ll is allocated by LV_ITERATE_ROOTS in _lv_gc_clear_roots...

And we forgot to compile _lv_gc_clear_roots in lv_gc.c. Duh!

(Zig Compiler assumes that missing variables like _lv_disp_ll are at WebAssembly Address 0)

After compiling _lv_gc_clear_roots and lv_gc.c, the "no screen found" error below no longer appears.

TODO: Disassemble the Compiled WebAssembly and look for other Undefined Variables at WebAssembly Address 0

[Info]	lv_init: begin 	(in lv_obj.c line #102)
[Trace]	lv_init: finished 	(in lv_obj.c line #183)
before lv_disp_drv_register
[Warn]	lv_obj_get_disp: No screen found 	(in lv_obj_tree.c line #290)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x12014 class on 0 parent 	(in lv_obj_class.c line #45)
[Warn]	lv_obj_get_disp: No screen found 	(in lv_obj_tree.c line #290)

(See the Complete Log)

Zig with Rancher Desktop

The Official Zig Download for macOS no longer runs on my 10-year-old MacBook Pro that's stuck on macOS 10.15. 😢

To run the latest version of Zig Compiler, I use Rancher Desktop and VSCode Remote Containers...

Here's how...

  1. Install Rancher Desktop

  2. In Rancher Desktop, click "Settings"...

    Set "Container Engine" to "dockerd (moby)"

    Under "Kubernetes", uncheck "Enable Kubernetes"

    (To reduce CPU Utilisation)

  3. Restart VSCode to use the new PATH

    Install the VSCode Docker Extension

    In VSCode, click the Docker icon in the Left Bar

  4. Under "Containers", click "+" and "New Dev Container"

    Select "Alpine"

  5. In a while, we'll see VSCode running inside the Alpine Linux Container

    We have finally Linux on macOS!

    $ uname -a
    Linux bc0c45900671 5.15.96-0-virt #1-Alpine SMP Sun, 26 Feb 2023 15:14:12 +0000 x86_64 GNU/Linux
    
  6. Now we can download and run the latest and greatest Zig Compiler for Linux x64 (from here)

    wget https://ziglang.org/builds/zig-linux-x86_64-0.11.0-dev.3283+7cb3a6750.tar.xz
    tar -xvf zig-linux-x86_64-0.11.0-dev.3283+7cb3a6750.tar.xz 
    zig-linux-x86_64-0.11.0-dev.3283+7cb3a6750/zig version
  7. To install the NuttX Toolchain on Alpine Linux...

    "Build Apache NuttX RTOS on Alpine Linux"

  8. To forward Network Ports, click the "Ports" tab beside "Terminal"

    To configure other features in the Alpine Linux Container, edit the file .devcontainer/devcontainer.json

Zig Version

(Here's what happens if we don't run Zig in a Container... We're stuck with an older version of Zig)

Which version of Zig are we using?

We're using an older version: 0.10.0-dev.2351+b64a1d5ab

Sadly Zig 0.10.1 (and later) won't run on my 10-year-old MacBook Pro that's stuck on macOS 10.15 😢

→ #  Compile the Zig App for PinePhone
  #  (armv8-a with cortex-a53)
  #  TODO: Change ".." to your NuttX Project Directory
  zig build-obj \
    --verbose-cimport \
    -target aarch64-freestanding-none \
    -mcpu cortex_a53 \
    -isystem "../nuttx/include" \
    -I "../apps/include" \
    lvgltest.zig

dyld: lazy symbol binding faileddyld: lazy symbol binding faileddyld: lazy symbol binding failed: Symbol not found: ___ulock_wai: Symbol not found: ___ulock_wait2
  Referenced from: /Users/Lupt2
  Referenced from: /Users/Lupdyld: lazy symbol binding failedpy/zig-macos-x86_64-0.10.1/zig (py/zig-macos-x86_64-0.10.1/zig (dyld: lazy symbol binding failedwhich was built for Mac OS X 11.: Symbol not found: ___ulock_wai: Symbol not found: ___ulock_waiwhich was built for Mac OS X 11.7)
  Expected in: /usr/lib/libSy: Symbol not found: ___ulock_wai7)
  Expected in: /usr/lib/libSystem.B.dylib

stem.B.dylib

t2
  Referenced from: /Users/Lupt2
  Referenced from: /Users/Lupt2
  Referenced from: /Users/Luppy/zig-macos-x86_64-0.10.1/zig (py/zig-macos-x86_64-0.10.1/zig (py/zig-macos-x86_64-0.10.1/zig (which was built for Mac OS X 11.which was built for Mac OS X 11.which was built for Mac OS X 11.7)
  Expected in: /usr/lib/libSy7)
  Expected in: /usr/lib/libSydyld: Symbol not found: ___ulock7)
  Expected in: /usr/lib/libSystem.B.dylib

stem.B.dylib

_wait2
  Referenced from: /Usersstem.B.dylib

/Luppy/zig-macos-x86_64-0.10.1/zdyld: Symbol not found: ___ulockig (which was built for Mac OS X_wait2
  Referenced from: /Users 11.7)
  Expected in: /usr/lib/ldyld: Symbol not found: ___ulockdyld: Symbol not found: ___ulock/Luppy/zig-macos-x86_64-0.10.1/zibSystem.B.dylib

_wait2
  Referenced from: /Usersig (which was built for Mac OS X_wait2
  Referenced from: /Users/Luppy/zig-macos-x86_64-0.10.1/z 11.7)
  Expected in: /usr/lib/l/Luppy/zig-macos-x86_64-0.10.1/zig (which was built for Mac OS XibSystem.B.dylib

ig (which was built for Mac OS X 11.7)
  Expected in: /usr/lib/l 11.7)
  Expected in: /usr/lib/libSystem.B.dylib

ibSystem.B.dylib

dyld: Symbol not found: ___ulockdyld: lazy symbol binding faileddyld: lazy symbol binding failed[1]    11157 abort      zig build-obj --verbose-cimport -target aarch64-freestanding-none -mcpu    -I

I tried building Zig from source, but it didn't work either...

Build Zig from Source

The Official Zig Download for macOS no longer runs on my 10-year-old MacBook Pro that's stuck on macOS 10.15. (See the previous section)

So I tried building Zig from Source according to these instructions...

Here's what I did...

brew install llvm
git clone --recursive https://github.com/ziglang/zig
cd zig

mkdir build
cd build
cmake .. -DZIG_STATIC_LLVM=ON -DCMAKE_PREFIX_PATH="$(brew --prefix llvm);$(brew --prefix zstd)"
make install

brew install llvm failed...

(Previously here)

UPDATE: This has been fixed

So I tried building LLVM from source (from here)...

cd ~/Downloads
git clone --depth 1 --branch release/16.x https://github.com/llvm/llvm-project llvm-project-16
cd llvm-project-16
git checkout release/16.x

mkdir build-release
cd build-release
cmake ../llvm \
  -DCMAKE_INSTALL_PREFIX=$HOME/local/llvm16-release \
  -DCMAKE_BUILD_TYPE=Release \
  -DLLVM_ENABLE_PROJECTS="lld;clang" \
  -DLLVM_ENABLE_LIBXML2=OFF \
  -DLLVM_ENABLE_TERMINFO=OFF \
  -DLLVM_ENABLE_LIBEDIT=OFF \
  -DLLVM_ENABLE_ASSERTIONS=ON \
  -DLLVM_PARALLEL_LINK_JOBS=1 \
  -G Ninja
ninja install

But LLVM fails to build...

→ ninja install

[1908/4827] Building CXX object lib/Target/AMDGPU/AsmParser/CMakeFiles/LLVMAMDGPUAsmParser.dir/AMDGPUAsmParser.cpp.o
FAILED: lib/Target/AMDGPU/AsmParser/CMakeFiles/LLVMAMDGPUAsmParser.dir/AMDGPUAsmParser.cpp.o
/Applications/Xcode.app/Contents/Developer/usr/bin/g++ -DGTEST_HAS_RTTI=0 -D_DEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/Users/Luppy/llvm-project-16/build-release/lib/Target/AMDGPU/AsmParser -I/Users/Luppy/llvm-project-16/llvm/lib/Target/AMDGPU/AsmParser -I/Users/Luppy/llvm-project-16/llvm/lib/Target/AMDGPU -I/Users/Luppy/llvm-project-16/build-release/lib/Target/AMDGPU -I/Users/Luppy/llvm-project-16/build-release/include -I/Users/Luppy/llvm-project-16/llvm/include -isystem /usr/local/include -fPIC -fvisibility-inlines-hidden -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wstring-conversion -Wctad-maybe-unsupported -fdiagnostics-color -O3 -DNDEBUG -std=c++17 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk  -fno-exceptions -fno-rtti -UNDEBUG -MD -MT lib/Target/AMDGPU/AsmParser/CMakeFiles/LLVMAMDGPUAsmParser.dir/AMDGPUAsmParser.cpp.o -MF lib/Target/AMDGPU/AsmParser/CMakeFiles/LLVMAMDGPUAsmParser.dir/AMDGPUAsmParser.cpp.o.d -o lib/Target/AMDGPU/AsmParser/CMakeFiles/LLVMAMDGPUAsmParser.dir/AMDGPUAsmParser.cpp.o -c /Users/Luppy/llvm-project-16/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
/Users/Luppy/llvm-project-16/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp:5490:13: error: no viable constructor or deduction guide for deduction of template arguments of 'tuple'
          ? std::tuple(HSAMD::V3::AssemblerDirectiveBegin,
            ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:625:5: note: candidate template ignored: requirement '__lazy_and<std::__1::is_same<std::__1::allocator_arg_t, const char *>, std::__1::__lazy_all<> >::value' was not satisfied [with _Tp = <>, _AllocArgT = const char *, _Alloc = char [21], _Dummy = true]
    tuple(_AllocArgT, _Alloc const& __a)
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:641:5: note: candidate template ignored: requirement '_CheckArgsConstructor<true, void>::__enable_implicit()' was not satisfied [with _Tp = <char [17], char [21]>, _Dummy = true]
    tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:659:14: note: candidate template ignored: requirement '_CheckArgsConstructor<true, void>::__enable_explicit()' was not satisfied [with _Tp = <char [17], char [21]>, _Dummy = true]
    explicit tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
             ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:677:7: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [21], _Dummy = true]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:697:7: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [21], _Dummy = true]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:723:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Up = <char const (&)[17], char const (&)[21]>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(_Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:756:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Up = <char const (&)[17], char const (&)[21]>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(_Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:783:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [21], _Up = <>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:803:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [21], _Up = <>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:612:23: note: candidate function template not viable: requires 0 arguments, but 2 were provided
    _LIBCPP_CONSTEXPR tuple()
                      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:615:5: note: candidate function template not viable: requires 1 argument, but 2 were provided
    tuple(tuple const&) = default;
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:616:5: note: candidate function template not viable: requires 1 argument, but 2 were provided
    tuple(tuple&&) = default;
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:822:9: note: candidate function template not viable: requires single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:837:9: note: candidate function template not viable: requires single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:850:9: note: candidate function template not viable: requires 3 arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:864:9: note: candidate function template not viable: requires 3 arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:469:28: note: candidate function template not viable: requires 1 argument, but 2 were provided
class _LIBCPP_TEMPLATE_VIS tuple
                           ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:932:1: note: candidate function template not viable: requires 3 arguments, but 2 were provided
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...> const&) -> tuple<_Args...>;
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:934:1: note: candidate function template not viable: requires 3 arguments, but 2 were provided
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...>&&) -> tuple<_Args...>;
^
/Users/Luppy/llvm-project-16/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp:5492:13: error: no viable constructor or deduction guide for deduction of template arguments of 'tuple'
          : std::tuple(HSAMD::AssemblerDirectiveBegin,
            ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:625:5: note: candidate template ignored: requirement '__lazy_and<std::__1::is_same<std::__1::allocator_arg_t, const char *>, std::__1::__lazy_all<> >::value' was not satisfied [with _Tp = <>, _AllocArgT = const char *, _Alloc = char [29], _Dummy = true]
    tuple(_AllocArgT, _Alloc const& __a)
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:641:5: note: candidate template ignored: requirement '_CheckArgsConstructor<true, void>::__enable_implicit()' was not satisfied [with _Tp = <char [25], char [29]>, _Dummy = true]
    tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:659:14: note: candidate template ignored: requirement '_CheckArgsConstructor<true, void>::__enable_explicit()' was not satisfied [with _Tp = <char [25], char [29]>, _Dummy = true]
    explicit tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
             ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:677:7: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [29], _Dummy = true]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:697:7: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [29], _Dummy = true]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:723:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Up = <char const (&)[25], char const (&)[29]>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(_Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:756:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Up = <char const (&)[25], char const (&)[29]>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(_Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:783:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [29], _Up = <>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:803:9: note: candidate template ignored: substitution failure [with _Tp = <>, _Alloc = char [29], _Up = <>]: cannot reference member of primary template because deduced class template specialization 'tuple<>' is an explicit specialization
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:612:23: note: candidate function template not viable: requires 0 arguments, but 2 were provided
    _LIBCPP_CONSTEXPR tuple()
                      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:615:5: note: candidate function template not viable: requires 1 argument, but 2 were provided
    tuple(tuple const&) = default;
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:616:5: note: candidate function template not viable: requires 1 argument, but 2 were provided
    tuple(tuple&&) = default;
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:822:9: note: candidate function template not viable: requires single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:837:9: note: candidate function template not viable: requires single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:850:9: note: candidate function template not viable: requires 3 arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:864:9: note: candidate function template not viable: requires 3 arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:469:28: note: candidate function template not viable: requires 1 argument, but 2 were provided
class _LIBCPP_TEMPLATE_VIS tuple
                           ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:932:1: note: candidate function template not viable: requires 3 arguments, but 2 were provided
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...> const&) -> tuple<_Args...>;
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/tuple:934:1: note: candidate function template not viable: requires 3 arguments, but 2 were provided
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...>&&) -> tuple<_Args...>;
^
2 errors generated.
[1917/4827] Building CXX object lib/Target/AMDGPU/Disassembler/CMakeFiles/LLVMAMDGPUDisassembler.dir/AMDGPUDisassembler.cpp.o
ninja: build stopped: subcommand failed.

So I can't build Zig from source on my 10-year-old MacBook Pro 😢

Output Log

Here's the log from the JavaScript Console...

 main: start
lv_demo_widgets: start
[Info]	lv_init: begin 	(in lv_obj.c line #102)
[Warn]	lv_init: Log level is set to 'Trace' which makes LVGL much slower 	(in lv_obj.c line #176)
[Trace]	lv_init: finished 	(in lv_obj.c line #183)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating a screen 	(in lv_obj_class.c line #55)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating a screen 	(in lv_obj_class.c line #55)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating a screen 	(in lv_obj_class.c line #55)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
createWidgets: start
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0x39e320 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0x39e320 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_create: begin 	(in lv_obj.c line #206)
[Trace]	lv_obj_class_create_obj: Creating object with 0x1773c class on 0x39e320 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39e57a parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e692 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39e9bc parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e692 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39ec98 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39ef5e parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39f237 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39f4f8 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39f7bd parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39fa86 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x39fd53 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a0024 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a02f9 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a05d2 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a08af parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a0b90 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
[Info]	lv_btn_create: begin 	(in lv_btn.c line #51)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17d4c class on 0x39e792 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_btn_constructor: begin 	(in lv_btn.c line #64)
[Trace]	lv_btn_constructor: finished 	(in lv_btn.c line #69)
[Info]	lv_label_create: begin 	(in lv_label.c line #75)
[Trace]	lv_obj_class_create_obj: Creating object with 0x17720 class on 0x3a0e75 parent 	(in lv_obj_class.c line #45)
[Trace]	lv_obj_class_create_obj: creating normal object 	(in lv_obj_class.c line #82)
[Trace]	lv_obj_constructor: begin 	(in lv_obj.c line #403)
[Trace]	lv_obj_constructor: finished 	(in lv_obj.c line #428)
[Trace]	lv_label_constructor: begin 	(in lv_label.c line #691)
[Trace]	lv_label_constructor: finished 	(in lv_label.c line #721)
createWidgets: end
lv_demo_widgets: end
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	flex_update: update 0x39e57a container 	(in lv_flex.c line #211)
[Info]	flex_update: update 0x39e692 container 	(in lv_flex.c line #211)
[Info]	flex_update: update 0x39e792 container 	(in lv_flex.c line #211)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	flex_update: update 0x39e57a container 	(in lv_flex.c line #211)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
get_canvas_buffer: 803944 non-empty pixels
 main: end
{mousedown: {…}}
readInput: state=1, x=162, y=491
[Info]	(4.322, +4056)	 indev_proc_press: pressed at x:162 y:491 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(4.355, +33)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
{mouseup: {…}}
[Info]	(4.373, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	(4.374, +1)	 flex_update: update 0x39e57a container 	(in lv_flex.c line #211)
[Trace]	(4.375, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
[Info]	(4.376, +1)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(4.377, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
readInput: state=0, x=162, y=491
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
2get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=334, y=494
[Info]	(4.689, +312)	 indev_proc_press: pressed at x:334 y:494 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(4.721, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(4.740, +19)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(4.741, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=334, y=494
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=570, y=493
[Info]	(5.108, +367)	 indev_proc_press: pressed at x:570 y:493 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(5.158, +50)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(5.162, +4)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(5.163, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=570, y=493
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803940 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=186, y=669
[Info]	(6.075, +912)	 indev_proc_press: pressed at x:186 y:669 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(6.106, +31)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(6.124, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(6.125, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=186, y=669
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803940 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=336, y=643
[Info]	(6.474, +349)	 indev_proc_press: pressed at x:336 y:643 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(6.509, +35)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(6.513, +4)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(6.514, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=336, y=643
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
get_canvas_buffer: 803940 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=519, y=631
[Info]	(6.941, +427)	 indev_proc_press: pressed at x:519 y:631 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(6.975, +34)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(6.979, +4)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(6.980, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=519, y=631
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803698 non-empty pixels
get_canvas_buffer: 803956 non-empty pixels
2get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=559, y=282
[Info]	(7.725, +745)	 indev_proc_press: pressed at x:559 y:282 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(7.757, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(7.775, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(7.776, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
get_canvas_buffer: 803956 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=559, y=282
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=559, y=282
[Info]	(8.159, +383)	 indev_proc_press: pressed at x:559 y:282 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(8.191, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(8.209, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(8.210, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
get_canvas_buffer: 803956 non-empty pixels
readInput: state=0, x=559, y=282
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=559, y=282
[Info]	(8.493, +283)	 indev_proc_press: pressed at x:559 y:282 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(8.525, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(8.543, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(8.544, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
get_canvas_buffer: 803956 non-empty pixels
readInput: state=0, x=559, y=282
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=155, y=643
[Info]	(9.376, +832)	 indev_proc_press: pressed at x:155 y:643 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(9.408, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(9.427, +19)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(9.428, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
get_canvas_buffer: 803956 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=155, y=643
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=327, y=640
[Info]	(9.844, +416)	 indev_proc_press: pressed at x:327 y:640 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(9.876, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(9.894, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(9.895, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=327, y=640
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=554, y=637
[Info]	(10.362, +467)	 indev_proc_press: pressed at x:554 y:637 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(10.392, +30)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(10.411, +19)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(10.412, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
get_canvas_buffer: 803956 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=554, y=637
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=197, y=768
[Info]	(11.029, +617)	 indev_proc_press: pressed at x:197 y:768 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(11.059, +30)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(11.081, +22)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(11.082, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
get_canvas_buffer: 803956 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=197, y=768
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803940 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=362, y=766
[Info]	(11.428, +346)	 indev_proc_press: pressed at x:362 y:766 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(11.460, +32)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(11.478, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(11.479, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=362, y=766
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=593, y=769
[Info]	(11.895, +416)	 indev_proc_press: pressed at x:593 y:769 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(11.928, +33)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(11.945, +17)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(11.946, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=593, y=769
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=368, y=915
[Info]	(12.629, +683)	 indev_proc_press: pressed at x:368 y:915 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(12.662, +33)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
[Info]	(12.680, +18)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Trace]	(12.681, +1)	 lv_obj_update_layout: Layout update end 	(in lv_obj_pos.c line #317)
2get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
readInput: state=0, x=368, y=915
get_canvas_buffer: 803956 non-empty pixels
get_canvas_buffer: 803952 non-empty pixels
get_canvas_buffer: 803940 non-empty pixels
get_canvas_buffer: 803944 non-empty pixels
{mousedown: {…}}
readInput: state=1, x=219, y=274
[Info]	(13.328, +647)	 indev_proc_press: pressed at x:219 y:274 	(in lv_indev.c line #819)
get_canvas_buffer: 803944 non-empty pixels
[Info]	(13.361, +33)	 indev_proc_release: released 	(in lv_indev.c line #969)
eventHandler: clicked
Call +1234567890
Running in WebAssembly, simulate the Phone Call
get_canvas_buffer: 803944 non-empty pixels
{mouseup: {…}}
get_canvas_buffer: 803956 non-empty pixels
readInput: state=0, x=219, y=274
get_canvas_buffer: 803952 non-empty pixels
3get_canvas_buffer: 803944 non-empty pixels

PinePhone Log

Here's the log from PinePhone on Apache NuttX RTOS...

DRAM: 2048 MiB
Trying to boot from MMC1
NOTICE:  BL31: v2.2(release):v2.2-904-gf9ea3a629
NOTICE:  BL31: Built : 15:32:12, Apr  9 2020
NOTICE:  BL31: Detected Allwinner A64/H64/R18 SoC (1689)
NOTICE:  BL31: Found U-Boot DTB at 0x4064410, model: PinePhone
NOTICE:  PSCI: System suspend is unavailable


U-Boot 2020.07 (Nov 08 2020 - 00:15:12 +0100)

DRAM:  2 GiB
MMC:   Device 'mmc@1c11000': seq 1 is in use by 'mmc@1c10000'
mmc@1c0f000: 0, mmc@1c10000: 2, mmc@1c11000: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment

starting USB...
No working controllers found
Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
653 bytes read in 3 ms (211.9 KiB/s)
## Executing script at 4fc00000
gpio: pin 114 (gpio 114) value is 1
276622 bytes read in 16 ms (16.5 MiB/s)
Uncompressed size: 10387456 = 0x9E8000
36162 bytes read in 4 ms (8.6 MiB/s)
1078500 bytes read in 50 ms (20.6 MiB/s)
## Flattened Device Tree blob at 4fa00000
   Booting using the fdt blob at 0x4fa00000
   Loading Ramdisk to 49ef8000, end 49fff4e4 ... OK
   Loading Device Tree to 0000000049eec000, end 0000000049ef7d41 ... OK

Starting kernel ...

nsh: mkfatfs: command not found

NuttShell (NSH) NuttX-12.0.3
nsh> lvgldemo
lv_demo_widgets: start
createWidgets: start
createWidgets: end
lv_demo_widgets: end
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
eventHandler: clicked
Call +1234567890
Running on PinePhone, make an actual Phone Call