Skip to content
This repository was archived by the owner on Sep 23, 2026. It is now read-only.

Repository files navigation

cmods

Retired and archived, 2026-09-23. Nothing builds from this repository any more. Its job is done by upstream MicroPython's and CircuitPython's own make with the layout the PyDevices repositories assume: every repository a sibling of the interpreter checkout, and micropython-pydevices carrying the presets (manifests/, one c_module() line per module), the out-of-tree variants and boards, and the patch series with tools/prepare-micropython.sh to apply it once. CircuitPython builds from each C-carrying repository's apply_cp_patches.sh against one checkout, then make. The workspace that installs the desktop interpreters keeps its build_interpreters.sh and provenance.py under its own tools/. Everything below is history.

An optional multi-module workspace layout and build helper for MicroPython and CircuitPython.

cmods makes it easy to build custom firmware containing multiple user C modules side-by-side (such as displayif, lvgl-micropython, and pygraphics):

  • MicroPython external C modules (USER_C_MODULES, micropython.mk / micropython.cmake)
  • CircuitPython native extensions (shared-bindings / shared-module, via each repo's apply_cp_patches.sh)

Note for Experienced Developers: cmods is completely optional. If you already have an established build habit (such as standard make USER_C_MODULES=... or custom CMake workflows), you can continue building MicroPython and CircuitPython exactly as you always have. This workspace is provided as a friction-free aggregator to make multi-module firmware development faster and more convenient.

🚀 Workspace setup

1. Get the tooling

Option A — clone this repo (recommended when starting fresh):

git clone https://github.com/PyDevices/cmods.git
cd cmods

Option B — copy into an existing build workspace (when you already have a directory with MicroPython / CircuitPython / usermods):

Copy the contents of this repo into that workspace root (the directory that should contain build_mp.sh, manifest-*.py, and optionally patches/). Do not nest a second cmods/ folder unless you intend that to be the workspace root.

2. Add the repos you need

Either clone them into the workspace, or clone them as siblings and symlink:

# Into the workspace
git clone https://github.com/micropython/micropython.git micropython
cd micropython && git submodule update --init --recursive && cd ..

# Or as siblings + symlink (example layout: ../micropython next to the workspace)
ln -s ../micropython micropython

Repeat for each usermod or interpreter you want (displayif, pygraphics, lvgl-micropython, circuitpython, …). Each MicroPython usermod must be an immediate subdirectory of the workspace (clone or symlink) and provide a micropython.mk there (optional manifest.py for frozen Python).

For audio, link two: audiodsp is the DSP nodes and the audio pump's portable engine, and audioif is the pump's platform drivers. With only the first the firmware still builds and plays, but audiopump.driver() says 'none' and audiodev goes out through machine.I2S.

Patches (optional; naming convention)

patches/ is optional. When present, build_mp.sh applies every file whose name contains micropython-<port> for the selected --port (e.g. micropython-unix, micropython-windows). They are temporary working-tree overlays which are reversed on every exit; the upstream checkout stays at its pinned revision with no generated commits. Details: patches/README.md.

Quick build (after setup)

# Optional — only for LVGL
git clone https://github.com/PyDevices/lvgl-micropython.git lvgl-micropython
git clone https://github.com/PyDevices/lvgl-bindings.git lvgl-bindings
cd lvgl-bindings && git submodule update --init lvgl && cd ..
./lvgl-bindings/regenerate_all.sh --target micropython

./build_mp.sh --port unix --variant standard

The LVGL clone and regenerate_all.sh steps are optional — use them only when building with LVGL. For other user C modules, add those repos (or symlinks) instead.

How it works

  • Since MicroPython 1.29 the frozen manifest names every C module (c_module()), so USER_C_MODULES is not passed at all; each sibling's manifest.py names its own C half and manifest-micropython.py includes them
  • manifest-micropython.py — frozen Python from cmod sibling repos, then includes the MicroPython upstream freeze via FROZEN_MANIFEST_UPSTREAM
  • manifest-circuitpython.py — same aggregator shape for CircuitPython (build_cp.sh)
  • build_mp.sh — sets FROZEN_MANIFEST_UPSTREAM to the freeze file MicroPython would use for the selected port/board/variant (same as a manual make without override)
  • build_cp.sh — auto-discovers */apply_cp_patches.sh (optional extensions) and uses manifest-circuitpython.py for all ports
  • There is no aggregator micropython.cmake any more: CMake ports get their modules from the manifest's c_module() lines like the Make ports

Build scripts

Script Role
build_mp.sh Any MicroPython port (interactive or --port / --board / --variant)
build_cp.sh CircuitPython ports (interactive or --port / --board / --variant)
build_interpreters.sh Desktop/wasm interpreters for local pydevices-examples work — see below

Examples:

./build_mp.sh                                          # interactive
./build_mp.sh --port unix --variant standard
./build_mp.sh --port rp2 --board RPI_PICO2_W
./build_mp.sh --port esp32 --board ESP32_GENERIC_P4 --variant C6_WIFI
./build_cp.sh                                          # interactive
./build_cp.sh --port unix --variant coverage
./build_interpreters.sh --only mp-unix,cp-unix

build_interpreters.sh builds the host interpreters used by PyDevices and installs them under workspace bin/ (micropython, micropython.exe, circuitpython, and the wasm micropython.{mjs,wasm} pair). Targets are mp-unix, mp-windows, mp-wasm, and cp-unix. The mp-wasm target builds the external pydevices variant into build-pydevices. When the pydevices core repo sits as a sibling, the script copies the binaries into pydevices/bin/; when the portal is a sibling, it installs the WebAssembly pair under PyDevices.github.io/vendor/micropython/. Use --only to build a subset, or --install-only to refresh installs from an existing build.

Desktop SDL (usdl2): when displayif is present, MicroPython unix / windows and CircuitPython unix link native import usdl2 from that repo (not a separate usdl2 usermod). Unix needs libsdl2-dev. Windows needs an unpacked SDL2 MinGW development ZIP under the workspace (e.g. SDL2-2.30.10/); build_mp.sh auto-sets SDL2_DEV or you can export it (see displayif tools/sdl2_dev_env.sh).

🎨 Hardware example: ESP32-P4 display + touch

End-to-end bring-up for the Waveshare ESP32-P4-WIFI6-Touch-LCD-4B (4″ 720×720 ST7703 on MIPI DSI, GT911 on I2C) using displayif + pydevices-examples. This is not stock MicroPython — firmware must include the displayif mipidsi cmod.

Board configs (pydevices):

Interpreter Path
MicroPython board_configs/fbdisplay/esp32-p4-wifi6-touch-lcd-4b
CircuitPython board_configs/cp/fbdisplay/esp32-p4-wifi6-touch-lcd-4b
git clone https://github.com/PyDevices/displayif.git displayif

# C6_WIFI — this board’s ESP32-C6 WiFi/BLE coprocessor (use C5_WIFI if yours is C5)
./build_mp.sh --port esp32 --board ESP32_GENERIC_P4 --variant C6_WIFI

build_mp.sh can flash when the build finishes (offset from board.json, 0x2000 for ESP32_GENERIC_P4). Manual flash:

esptool -b 460800 --before default_reset --after hard_reset \
  write_flash 0x2000 micropython/ports/esp32/build-ESP32_GENERIC_P4/firmware.bin

The cmods build discovers pydevices/manifest.py and freezes the core product packages. Install the matching board config (and optional examples from pydevices-examples) with mpremote:

mpremote mip install --target "." \
  "github:PyDevices/pydevices/board_configs/fbdisplay/esp32-p4-wifi6-touch-lcd-4b"
# optional: mpremote mip install --target "./examples" "github:PyDevices/pydevices-examples/packages/examples.json"

Smoke checks:

mpremote run displayif/tools/test_mipidsi_smoke.py
import board_config
import appdev

display_drv = board_config.display_drv
app = appdev.App(board_config)
display_drv.fill_rect(0, 0, 200, 200, 0xF800)
display_drv.show()

# Touch: poll until quit
while not app.quit_requested:
    for e in app.poll():
        print(e)

Pinout matches the Waveshare BSP (reset 27, backlight 26, I2C 7/8, GT911 @ 0x5D). If the panel stays black, check backlight polarity (backlight_on_high=False in the board config) and the displayif P4 DSI LDO path (channel 3 @ 2.5 V). Validate display/touch over USB serial before WiFi.

Related repos

For the full, always-current map of PyDevices repos and how they relate, see the generated ecosystem map — rendered from dotgithub/data/repos_db.json, so it cannot drift out of date the way a hand-maintained list here would.

CircuitPython does not use USER_C_MODULES. Clone lvgl-circuitpython into this workspace if you want CP and MP trees side by side.

CircuitPython (optional extensions; see lvgl-circuitpython README for CP clone setup):

Native CP modules here follow Adafruit’s Extending CircuitPython architecture (shared-bindings / shared-module / CIRCUITPY_*), but stay out-of-tree: each extension keeps spikes in its own repo and apply_cp_patches.sh copies them into a local (uncommitted) CircuitPython tree. Adafruit’s Learn guide assumes in-tree edits; there is no official out-of-tree C-module path. Per-repo READMEs map Learn steps → spikes/patches.

./build_cp.sh --port unix --variant standard

build_cp.sh runs every sibling */apply_cp_patches.sh when present (pygraphics, LVGL, …). Clone only the extensions you need. Optional: place a user_post_mpconfigport.mk at the workspace root (CircuitPython’s user-config hook; build_cp.sh passes -I when it exists) to freeze Adafruit asyncio/ticks for multimer.AsyncTimer. See CircuitPython building.md and multimer internals.

MicroPython frozen asyncio (required for multimer.AsyncTimer on unix/windows):

# pydevices/manifest.py is discovered automatically.
# Add only personal extras to the optional manifest-user.py.
./build_mp.sh --port unix --variant standard
./build_mp.sh --port windows --variant dev

Direct build (without this tooling)

Create any workspace directory, clone micropython and the usermods you need as siblings (or symlink them), and build from micropython/ with USER_C_MODULES pointing at the workspace root. See each usermod’s README (e.g. lvgl-micropython).

About

Optional build workspace and tooling for MicroPython, CircuitPython, and native PyDevices modules — a convenience aggregator; no public build requires it.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Contributors

Languages