picocalc-c-module
This skill should be used when the user asks to "create a C module", "modify the display driver", "modify the terminal emulator", "add native code", "MicroPython C API", "compile firmware", "picocalcdisplay module", "vtterminal module", or wants to create or modify C extension modules compiled into the MicroPython firmware for PicoCalc (RP2350/Pico 2W).
Create or Modify PicoCalc C Modules
Build C extension modules compiled into the PicoCalc MicroPython firmware for RP2350.
Module File Structure
modulename/
├── modulename.c # Implementation + MicroPython bindings
├── modulename.h # Header with hardware defines
├── micropython.cmake # CMake build integration (required)
└── micropython.mk # Make build integration (legacy)
CMake Build Pattern
add_library(usermod_modulename INTERFACE)
target_sources(usermod_modulename INTERFACE
${CMAKE_CURRENT_LIST_DIR}/modulename.c
)
target_include_directories(usermod_modulename INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(usermod INTERFACE usermod_modulename)
MicroPython C API Pattern
1. Define C Functions
#include "py/runtime.h"
static mp_obj_t mymod_init(mp_obj_t fb_obj, mp_obj_t type_obj) {
int color_type = mp_obj_get_int(type_obj);
mp_buffer_info_t buf_info;
mp_get_buffer_raise(fb_obj, &buf_info, MP_BUFFER_READ);
uint8_t *frameBuff = buf_info.buf;
// ... init code ...
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(mymod_init_obj, mymod_init);
2. Build Globals Table
static const mp_rom_map_elem_t mymod_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mymodule) },
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mymod_init_obj) },
};
static MP_DEFINE_CONST_DICT(mymod_globals, mymod_globals_table);
3. Register Module
const mp_obj_module_t mymod_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&mymod_globals,
};
MP_REGISTER_MODULE(MP_QSTR_mymodule, mymod_module);
Existing Modules
picocalcdisplay - Python API: init(framebuf, color_type, auto_refresh), update(), startAutoUpdate(), stopAutoUpdate(), setLUT(buf), drawTxt6x8(text, x, y, color). Uses SPI1+DMA, Core 1 auto-refresh, 256-entry LUT.
vtterminal - Python API: init(framebuf), printChar(char_code), read(). Full VT100 emulator with cursor, scrolling, attributes, 8-color support, cursor blink timer.
Firmware Build
The Dockerfile at MicroPython/firmware/Dockerfile defines the build environment.
# Build Docker image (one-time)
docker build -t picocalc-build MicroPython/firmware/
# Compile firmware
docker run --rm \
-v $(pwd)/MicroPython:/picocalc \
-v $(pwd)/MicroPython/firmware:/out \
picocalc-build \
bash -c "make BOARD=RPI_PICO2_W USER_C_MODULES=/picocalc/micropython.cmake -j\$(nproc) && \
cp build-RPI_PICO2_W/firmware.uf2 /out/picocalc_micropython_pico2w.uf2"
Critical build requirements:
- MUST specify
BOARD=RPI_PICO2_W(default isRPI_PICOfor RP2040, wrong chip) - Dockerfile MUST pin MicroPython to a known-good commit (v1.25.0-preview, commit
f187c77da). MicroPython master (v1.28.0+) has USB-CDC regression on RP2350 — device fails to enumerate over USB. - Dockerfile MUST init
lib/cyw43-driversubmodule inside pico-sdk (required for WiFi/USB on Pico 2W) - Run
make BOARD=RPI_PICO2_W submodulesbefore build to ensure all board deps are present - Known-good .uf2 is ~2.1MB. If build produces ~1.7MB, CYW43 stack is missing.
picocalcdisplay API (updated)
init(framebuf, color_type, auto_refresh), update(), beginDraw(), startAutoUpdate(), stopAutoUpdate(), setLUT(buf), drawTxt6x8(text, x, y, color).
beginDraw()— setsskipUpdate=true, blocks Core 1 from pushing framebuffer. Call beforefill(0)to prevent flicker.update()/show()— in auto mode, setsskipUpdate=falseso Core 1 pushes the complete frame on next cycle.
Critical Constraints
multicore_lockout_victim_init()on Core 1 prevents flash write crashes- Cancel repeating timers before re-adding (timer slot leak on soft reset)
- DMA for SPI transfers (double-buffered line approach)
- Framebuffer shared between cores — use
beginDraw()/show()to coordinate skipUpdatevolatile bool prevents Core 1 from pushing partial frames during fill(0)CORE1_STACK_SIZEis 1024 uint32_t words (4KB allocated, but SDK param is byte count — currently passes 1024 meaning only 1KB used)- pico-sdk:
hardware/spi.h,hardware/dma.h,hardware/gpio.h,pico/multicore.h - Function macros: OBJ_0 through OBJ_3 for fixed args, OBJ_VAR_BETWEEN for variable
Additional Resources
- references/cmake-pattern.md - CMake setup details
- references/micropython-c-api.md - Complete C API reference
- references/existing-modules.md - picocalcdisplay + vtterminal internals