Build and flash
CDC Badge OS builds with PlatformIO on top of the ESP-IDF framework. The single build environment is cdc_badge_usb (platformio.ini).
Prerequisites
Section titled “Prerequisites”The build pulls in git submodules (the WAMR runtime and other vendored components). Initialize them first:
git submodule update --init --recursiveThe pio binary is not on PATH in the standard setup; use its full path under the PlatformIO virtualenv.
Build and flash commands
Section titled “Build and flash commands”# Build firmware~/.platformio/penv/bin/pio run
# Build and flash~/.platformio/penv/bin/pio run -t upload
# Monitor serial output (115200 baud)~/.platformio/penv/bin/pio device monitorThe serial monitor speed is 115200 baud, configured as monitor_speed in platformio.ini.
Whether a manual reset is needed after upload
Section titled “Whether a manual reset is needed after upload”Whether the chip needs a manual reset after pio run -t upload depends on how download mode was entered:
- Via the serial
BOOTLOADERcommand (sendAUTH <pin>thenBOOTLOADERover the CDC port): no reset needed. After each esptool operation the chip auto-reboots into the new firmware and re-enumerates asBadgeV1. - Via manual flash mode (hold BOOT and press RESET): the chip stays in the ROM bootloader after upload, so you must press RESET manually to boot the firmware.
Wait a few seconds for re-enumeration as BadgeV1 before running host-side checks.
Environment configuration
Section titled “Environment configuration”Key settings from platformio.ini:
| Setting | Value |
|---|---|
platform | espressif32 |
framework | espidf |
board | cdc-badge-usb |
board_build.flash_size | 16 MB |
board_build.psram | enabled |
board_build.partitions | partitions.csv |
board_build.sdkconfig_defaults | sdkconfig.defaults |
| C++ standard | -std=gnu++17 (c++2b/c++23 explicitly unflagged) |
Three pre-build scripts run before compilation: a Python dependency installer, a submodule fetcher, and a component manager (tools/pio_python_deps.py, tools/pio_submodules.py, tools/pio_component_manager.py).
The MODULES list and generated registration
Section titled “The MODULES list and generated registration”Feature modules are added in exactly one place: the MODULES list in main/CMakeLists.txt.
set(MODULES mod_2fa mod_fido2 mod_password mod_gpg mod_sao mod_vcard mod_ble_serial mod_nvsedit mod_blehid mod_usbhid mod_otphid mod_vfat)That list does two things during the CMake configure step:
- Each name is added to the
maincomponent’sREQUIRES, so the module component is linked in. - CMake generates
modules_init.gen.hin the build directory. For every entry it emits anextern "C" void <name>_register();declaration and a call inside a generatedmodules_register_all()function.
main.cpp includes the generated header and calls modules_register_all() at boot, followed by ModuleRegistry::instance().runAllInitializers(), then rebuilds the UI menus with ui_on_modules_ready(). There are no manual includes per module.
Adding a module
Section titled “Adding a module”- Create the component under
components/mod_<name>/with its ownCMakeLists.txt. - Export
extern "C" void mod_<name>_register(void);. - Add
mod_<name>to the MODULES list inmain/CMakeLists.txt.
Deleting a module’s folder and removing its line from the MODULES list is enough to remove it cleanly, because the core never references modules directly. See Architecture for the isolation rules and the IModule lifecycle.
Flash and partition layout
Section titled “Flash and partition layout”The flash layout comes from partitions.csv. On the ESP32-S3 the bootloader lives at offset 0x0 and the partition table at 0x8000; the partitions below start after that.
| Name | Type | SubType | Offset | Size |
|---|---|---|---|---|
nvs | data | nvs | 0x9000 | 0x47000 |
app0 | app | factory | 0x50000 | 0xDA0000 (about 13.6 MB) |
plugins | data | fat | 0xDF0000 | 0x200000 (2 MB) |
coredump | data | coredump | 0xFF0000 | 0x10000 (64 KB) |
The plugins FAT partition is mounted at /plugins and holds installed WASM plugins plus the runtime i18n overlay files (/plugins/i18n/lang_<code>.json). The coredump partition stores crash dumps for offline analysis.
Memory model
Section titled “Memory model”Internal SRAM on this build is the bottleneck; PSRAM is the default allocation pool. PSRAM is configured as octal mode at 80 MHz, with BSS allowed in external memory (sdkconfig.defaults):
CONFIG_SPIRAM=yCONFIG_SPIRAM_MODE_OCT=yCONFIG_SPIRAM_SPEED_80M=yCONFIG_SPIRAM_USE_MALLOC=yCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=yGuidelines for new code:
- Reserve internal RAM for task stacks, ISR-touched data, BLE/WiFi-internal buffers, and small static state. Everything else goes to PSRAM.
- Large static buffers use the
EXT_RAM_BSS_ATTRattribute so they land in PSRAM rather than internal BSS. - Runtime allocations larger than a few kilobytes use PSRAM.
cdc::core::psramAlloc<T>(count)(incdc_core/Raii.h) wrapsheap_caps_malloc(..., MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)and returns aPsramUniquePtr<T>that frees through the caps allocator on destruction.