Tools for building MicroPython and CircuitPython with LVGL user C modules.
cmods/
micropython/ MicroPython clone (local, gitignored)
circuitpython/ CircuitPython clone (local, gitignored; pin 10.2.1)
lv_micropython_cmod/ LVGL + bindings generator
pydisplay_cmods/ Display helpers
manifest.py Frozen Python modules
build_unix.sh Build Unix port
build_esp32.sh Build ESP32 port
build_cp_unix.sh CircuitPython unix port (coverage variant)
- Clone MicroPython and CircuitPython next to this tree (both gitignored; not submodules):
git clone https://github.com/micropython/micropython.git micropython
git clone https://github.com/adafruit/circuitpython.git circuitpython
cd micropython && git submodule update --init --recursive && cd ..Pin CircuitPython to the latest stable release (not main):
cd circuitpython
git fetch --tags
git checkout -B circuitpython-10.2.1 10.2.1
make fetch-all-submodules
cd ..Use git describe --tags --exact-match inside circuitpython/ to confirm 10.2.1. Do not git pull on main — that returns to the development branch.
- Initialize submodules inside
lv_micropython_cmod/(lvgl, pycparser, etc.). - Generate LVGL bindings (required before any build):
./lv_micropython_cmod/regenerate_lvmp.sh- Build a port, e.g.:
./build_unix.shBindings are not committed to the repo. Regenerate after changing:
lv_micropython_cmod/lvgl/(LVGL submodule)lv_micropython_cmod/lv_conf.hlv_micropython_cmod/binding/(modular generator; primary)lv_micropython_cmod/gen_mpy.py(regression reference only)
./lv_micropython_cmod/regenerate_lvmp.shOutput (gitignored):
lv_micropython_cmod/generated/
lvmp.c # MicroPython bindings (compiled into firmware)
lvmp.c.pp # preprocessed lvgl.h
lvmp.c.json # API metadata
lvcp.c # CircuitPython bindings (phase 7; merge via LVCP_MODULE_GLOBALS)
lvcp.c.pp
lvcp.c.json
gen_lv_bindings.py is the supported entry point. It uses the binding/ package:
lv_micropython_cmod/binding/
cli.py argparse, --target micropython|circuitpython
preprocess.py gcc -E preprocessing
context.py BindingContext, regex patterns
parse.py AST helpers (pycparser)
helpers.py name sanitization, LVGL pattern matching
analyze.py AST analysis and metadata extraction
runtime.py shared generation state (sync hub)
emit_c.py MicroPython C source emission
emit_circuitpython.py CircuitPython emission (phases 1–7 → lvcp.c)
circuitpython_spike/ Phase 0 templates (copy into CP tree)
circuitpython_emit_plan.md CP emission phases and API mapping
emit_micropython.py orchestrates analyze + emit_c
generator.py wires context, generation, metadata
metadata.py JSON export
util.py memoize, eprint
gen_mpy.py is kept only for regression testing (compare_bindings.sh). Production builds use gen_lv_bindings.py.
./lv_micropython_cmod/compare_bindings.shCompares gen_mpy.py vs gen_lv_bindings.py output (ignoring the command-line comment).
./lv_micropython_cmod/verify_bindings.shRuns compare_bindings.sh, regenerate_lvcp.sh, and checks generated/lvcp.c size, metadata counts, LVCP_MODULE_GLOBALS, and absence of MP_REGISTER_MODULE.
MicroPython unix smoke test (after ./build_unix.sh):
./micropython/ports/unix/build-standard/micropython ./lv_micropython_cmod/test_lvgl_unix.pyCircuitPython unix smoke test (after ./build_cp_unix.sh with LVGL patched):
./circuitpython/ports/unix/build-coverage/micropython ./lv_micropython_cmod/test_lvgl_cp_unix.pyCovers init, headless display, widgets, event callbacks, and GC visibility (see binding/gc_callback_audit.md).
Before the first CircuitPython build, read binding/cp_flash_budget.md (flash partition headroom + allocator notes).
| Script | Port | Notes |
|---|---|---|
build_unix.sh |
micropython/ports/unix |
Desktop dev / testing |
build_esp32.sh |
micropython/ports/esp32 |
Requires ESP-IDF |
build_cp_unix.sh |
circuitpython/ports/unix |
Default VARIANT=coverage; stock or LVGL-patched tree |
All MicroPython builds pass USER_C_MODULES pointing at this repo root and use manifest.py for frozen modules.
Use a release tree, not Adafruit's main branch. After cloning, pin tag 10.2.1 on local branch circuitpython-10.2.1 (see first-time setup above). Leave the CP tree unpatched until you are ready to integrate LVGL; check status with:
./lv_micropython_cmod/apply_cp_lvgl_patches.sh --statusWhen ready to wire LVGL (default unix / standard port):
./lv_micropython_cmod/apply_cp_lvgl_patches.sh --applyPrimary target: CircuitPython unix port, coverage variant (desktop dev; avoids unix standard + ringio ringbuf mismatch on 10.2.1). Patches wire ports/unix/Makefile and variants/coverage/mpconfigvariant.mk.
Embedded target (later): ESP32-P4-Function-EV-Board (espressif_esp32p4_function_ev) — build_cp_esp32.sh (not yet implemented); patch with PORT=espressif BOARD=espressif_esp32p4_function_ev ./lv_micropython_cmod/apply_cp_lvgl_patches.sh --apply
Build glue and full emission live in lv_micropython_cmod/:
| File | Purpose |
|---|---|
circuitpython.mk |
Port Makefile fragment (LVGL + allocator + lvcp.c) |
apply_cp_lvgl_patches.sh |
Copy spike + patch CP tree (--dry-run / --apply) |
circuitpython_board.snippet.mk |
Manual patch checklist (reference) |
binding/gc_callback_audit.md |
GC roots and callback lifetime notes |
binding/cp_flash_budget.md |
Flash/allocator review before first CP build |
regenerate_lvcp.sh |
Preprocess + --target circuitpython → generated/lvcp.c |
binding/circuitpython_emit_plan.md |
Phased API mapping from metadata |
circuitpython_spike/ |
Hand-written module + merge docs for phase-1 entries |
Current status:
- Phase 0 spike templates (
shared-bindings/lvgl/,shared-module/lvgl/) - Phase 1–7 emitter complete (
max_phase: 7);lvcp.c~39.5k lines (parity withlvmp.c) - GC-aware allocator draft (
lv_mem_core_circuitpython.c, viacircuitpython.mk) - Port wiring + on-tree build (
apply_cp_lvgl_patches.sh→./build_cp_unix.sh) - Display bridge — ON HOLD (Python
displayio/mipidsiflush/tick; resume when requested)
Regenerate and verify bindings:
./lv_micropython_cmod/regenerate_lvcp.sh
./lv_micropython_cmod/verify_bindings.shBuild CircuitPython unix (works before or after patching; does not modify the CP tree):
./build_cp_unix.shOn a clean release tree this builds stock CircuitPython. After patching, run regenerate_lvcp.sh first; the patched Makefile supplies CMODS_DIR and pulls in full LVGL + lvcp.c (make fails if bindings are missing). Each run starts with make clean.
After patching:
./lv_micropython_cmod/regenerate_lvcp.sh
./lv_micropython_cmod/apply_cp_lvgl_patches.sh --apply
./build_cp_unix.shPatch status: ./lv_micropython_cmod/apply_cp_lvgl_patches.sh --status (pending / patched / ok / missing). Defaults to PORT=unix VARIANT=coverage.
Display bridge is ON HOLD. C bindings do not include flush/tick; a future Python displayio bridge (similar to pydisplay_cmods on MicroPython) will be designed when you ask to resume that work.
The binding/ package supports --target circuitpython with max_phase: 7 for full API emission.