Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions binding/emit_c_micropython_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,17 @@ def emit_c(ctx):
return result;
}

// How many Python callbacks LVGL is inside right now. The MicroPython port's
// assert handler reads it: at zero, an exception raised from a failed LVGL
// assert unwinds out of every LVGL frame, so the handler first clears the
// state LVGL holds for the length of a pass; above zero, the callback's own
// nlr frame catches it and LVGL carries on, so that state must stay.
int mp_lv_nesting_depth(void);
int mp_lv_nesting_depth(void)
{
return _nesting;
}

// Function pointers wrapper

static mp_obj_t mp_lv_funcptr(const mp_lv_obj_fun_builtin_var_t *mp_fun, void *lv_fun, void *lv_callback, qstr func_name, void *user_data)
Expand Down
11 changes: 11 additions & 0 deletions generated/lvgl_circuitpython.c
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,17 @@ static mp_obj_t mp_lv_nesting_call(mp_obj_t func, size_t n_args, const mp_obj_t
return result;
}

// How many Python callbacks LVGL is inside right now. The MicroPython port's
// assert handler reads it: at zero, an exception raised from a failed LVGL
// assert unwinds out of every LVGL frame, so the handler first clears the
// state LVGL holds for the length of a pass; above zero, the callback's own
// nlr frame catches it and LVGL carries on, so that state must stay.
int mp_lv_nesting_depth(void);
int mp_lv_nesting_depth(void)
{
return _nesting;
}

// Function pointers wrapper

static mp_obj_t mp_lv_funcptr(const mp_lv_obj_fun_builtin_var_t *mp_fun, void *lv_fun, void *lv_callback, qstr func_name, void *user_data)
Expand Down
11 changes: 11 additions & 0 deletions generated/lvgl_micropython.c
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,17 @@ static mp_obj_t mp_lv_nesting_call(mp_obj_t func, size_t n_args, const mp_obj_t
return result;
}

// How many Python callbacks LVGL is inside right now. The MicroPython port's
// assert handler reads it: at zero, an exception raised from a failed LVGL
// assert unwinds out of every LVGL frame, so the handler first clears the
// state LVGL holds for the length of a pass; above zero, the callback's own
// nlr frame catches it and LVGL carries on, so that state must stay.
int mp_lv_nesting_depth(void);
int mp_lv_nesting_depth(void)
{
return _nesting;
}

// Function pointers wrapper

static mp_obj_t mp_lv_funcptr(const mp_lv_obj_fun_builtin_var_t *mp_fun, void *lv_fun, void *lv_callback, qstr func_name, void *user_data)
Expand Down
14 changes: 13 additions & 1 deletion lv_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,21 @@
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
__attribute__((weak)) void lv_circuitpython_assert_failed(void);
#define LV_ASSERT_HANDLER { if (lv_circuitpython_assert_failed) { lv_circuitpython_assert_failed(); } while(1); }
#else
#elif defined(LV_CPYTHON_BUILD)
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
#else
/* On MicroPython a spinning assert locks the scheduler out: USB serial and
* WebREPL go silent until a power cycle. The MicroPython port's handler
* raises RuntimeError naming the function, file and line instead, which
* the nearest Python frame or LVGL callback reports, and the board stays
* reachable. It is weak here, so a build without it still links and
* halts as before. */
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#if !defined(PYCPARSER) && !defined(__ASSEMBLY__)
__attribute__((weak)) void lv_micropython_assert_failed(const char *file, int line, const char *func);
#endif
#define LV_ASSERT_HANDLER { if (lv_micropython_assert_failed) { lv_micropython_assert_failed(__FILE__, __LINE__, __func__); } while(1); }
#endif

/*-------------
Expand Down
2 changes: 1 addition & 1 deletion ports/micropython/micropython.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ if(NOT "${USER_C_MODULES}" MATCHES "jpegio")
endif()

add_library(lv_micropython INTERFACE)
target_sources(lv_micropython INTERFACE ${LVMP_C} ${LVMP_DIR}/src/lvgl_micropython_build.c)
target_sources(lv_micropython INTERFACE ${LVMP_C} ${LVMP_DIR}/src/lvgl_micropython_build.c ${LVMP_DIR}/src/lv_assert_micropython.c)

# --- which commit this firmware was built from ---------------------
# Computed at build time from this repository's git, never stored; "unknown" when
Expand Down
3 changes: 3 additions & 0 deletions ports/micropython/micropython.mk
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ CFLAGS_USERMOD += -I$(BINDINGS_DIR) -I$(LVMP_DIR) -Wno-unused-function
SRC_USERMOD_LIB_C += $(SOURCES)
SRC_USERMOD_C += $(LVMP_C)
SRC_USERMOD_C += $(LVMP_DIR)/src/lvgl_micropython_build.c
# What a failed LVGL assert does (lv_conf.h). Here rather than with LVGL's
# sources so its message text is scanned like the binding's.
SRC_USERMOD_C += $(LVMP_DIR)/src/lv_assert_micropython.c

# --- which commit this firmware was built from ---------------------
# Computed at build time from this repository's git, never stored; "unknown" when
Expand Down
62 changes: 62 additions & 0 deletions ports/micropython/src/lv_assert_micropython.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @file lv_assert_micropython.c
*
* What a failed LVGL assertion does on MicroPython (lv_conf.h names this
* function in LV_ASSERT_HANDLER for MicroPython builds). LVGL's default is
* to spin forever, and on a board that locks the MicroPython scheduler out:
* USB serial and WebREPL go silent and only a power cycle brings it back.
*
* Instead this raises RuntimeError("LVGL assert failed in <function>
* (<file>:<line>)"). LVGL's log is off in MicroPython builds, so the
* function and line are how to find the assertion's message in LVGL's source.
*
* Where the exception lands decides what LVGL state needs repair:
*
* - Inside a Python callback (a flush callback, an event, a timer), the
* binding's callback wrapper catches it, prints it and returns to LVGL,
* which carries on with its pass. The call that asserted is abandoned and
* nothing else changes. This is the usual case: Python code changing a
* widget while LVGL renders.
* - With no Python callback in between, it unwinds out of every LVGL frame
* to whoever called LVGL from Python. A pass in progress is abandoned, so
* the flags LVGL holds for its length are cleared first: otherwise
* lv_timer_handler() would return at once forever after, or every later
* invalidation would assert again, and neither resets on a soft reset.
*
* With no Python frame at all to catch it, nlr_jump() ends in the port's
* nlr_jump_fail(), the port's own fatal path.
*/
#include "py/runtime.h"

#include "lvgl/lvgl.h"
#include "lvgl/src/core/lv_global.h"
#include "lvgl/src/display/lv_display_private.h"

#include <string.h>

void lv_micropython_assert_failed(const char *file, int line, const char *func);

// From the generated binding: how many Python callbacks LVGL is inside.
int mp_lv_nesting_depth(void);

static void lv_micropython_end_pass(void) {
if (!lv_is_initialized()) {
return;
}
LV_GLOBAL_DEFAULT()->timer_state.already_running = false;
for (lv_display_t *disp = lv_display_get_next(NULL); disp != NULL; disp = lv_display_get_next(disp)) {
disp->rendering_in_progress = 0;
}
}

void lv_micropython_assert_failed(const char *file, int line, const char *func) {
const char *base = strrchr(file, '/');
if (base == NULL) {
base = strrchr(file, '\\');
}
base = base ? base + 1 : file;
if (mp_lv_nesting_depth() == 0) {
lv_micropython_end_pass();
}
mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("LVGL assert failed in %s (%s:%d)"), func, base, line);
}
Loading