ESP32-C3 Super Mini · board_v3 ·
CONFIG_PIN_BUZZER and CONFIG_PIN_BATTERY use GPIO 4. PWM output corrupts ADC reads. Move buzzer to GPIO 3.CONFIG_NEOPIXELS_NB_LEDS is 4. Memory corruption → random crashes.addLeds<WS2812, 10> with hardcoded pin & 16 LEDs, conflicting with main init. Remove or gate behind a flag.while(millis()) loop freezes BLE, sensors, motors. Replace with ledcWriteTone() + timer callback.NUM_LEDS, LED_PIN locally — use config.h values.interval IS a valid global declared in 02_data.h and defined in 02_data.cpp as const int32_t interval = 500. Audit was wrong — not a bug."wdy_wifi_01" / password "12345678" in #else branch. Move to secrets.h (gitignored).LED_PIN_1=2, LED_PIN_2=6 shadow config.h values. Use config defines only.DEF_SERIAL_DEBUG. The #ifdef never matches — error message is dead code.millis()-based timing like neopixels_waving_french_flag() already does.String(...) + "V (" allocates on heap. Use sprintf() — already used elsewhere in same function.const CRGB BLUE(...), RED(...) collide with FastLED built-ins. Add a prefix.extern for leds[]. Verify single definition with consistent size.FastLED.delay() which is blocking. Convert to non-blocking pattern.// ????????????? around a commented return;. Remove or document intent.sin8(wave + section * 85), 128 +, brightness %=150. Extract as named constants.g_ prefix used sometimes, not others. dutyCycle is global without prefix.Guru Meditation Error: Core 0 panic'ed (Instruction access fault). Infinite reboot loop. MEPC: 0x3fc9027c points to RAM (not executable flash).addr2line on the crash addresses pointed to ESP32RMTController::interruptHandler at FastLED/src/platforms/esp/32/rmt_4/idf4_rmt_impl.cpp:827, calling vPortEnterCritical repeatedly with 192-byte stack frames. The decrementing T4 counter (9→4) is the recursion depth.espressif32 ~6.4.0 has a known race on ESP32-C3 (only 2 RMT channels): the ISR doesn't reliably clear its interrupt flag before iret, so it immediately re-fires and overflows the stack within ~9 calls.idf4_rmt_impl.cpp), the ISR handler that fires at the end of each LED frame didn't reliably clear the RMT interrupt status register before executing iret (interrupt return). On ESP32-C3's RISC-V core, the hardware re-checks the pending interrupt line during iret itself — so the ISR re-entered immediately. Each re-entry consumed ~192 bytes of stack (one vPortEnterCritical frame), and with ~9 recursive calls the stack pointer hit non-executable RAM → MCAUSE=0x01 Instruction access fault.espressif32 @ 6.7.0 bundles IDF 5.1 — pinning to that version is the fix.
leds[] array via duplicate FastLED.addLeds() with hardcoded NUM_LEDS=16 in demo files. That WAS a latent bug (would crash if the dead code ran), but wasn't the trigger — sizing the array to 256 didn't stop the crash, and the crash address only shifted (to 0x3fc9027c) because the larger global pushed RAM layout around.MCAUSE=0x01 confirmed execution of non-executable address.
platform = espressif32 @ 6.7.0 (ships ESP-IDF 5.1 with the rewritten RMT driver that fixes the ISR re-entry race).fastled/FastLED @ ^3.7.0 for reproducibility.FASTLED_RMT_MAX_CHANNELS=1 (C3 has only 2) and FASTLED_RMT_BUILTIN_DRIVER=1 (use IDF's tested driver instead of FastLED's custom one).
leds[] to CONFIG_NEOPIXELS_MAX_LEDS (256).FastLED.addLeds() from demo + french flag files.NUM_LEDS, LED_PIN, BRIGHTNESS redefinitions.leds[16+pos2], leds[24], etc.) in #if NB_LEDS >= 32 guards — uncommenting them on a small strip will no longer crash.
pio device monitor launched via launch.sh option 8 ignores Ctrl+C in MSYS2/MinGW terminals. The monitor hangs and cannot be exited normally.Ctrl+] — PlatformIO's alternate quit shortcutCtrl+T then Q — PlatformIO's menu quit sequencemonitor_flags = --raw to platformio.ini or configure a different exit key. Alternatively, wrap the monitor call with proper signal forwarding in launch.sh.platformio.ini had unpinned platform = espressif32 and lib_deps with no version constraints. Different machines / different times = different ESP-IDF, different FastLED, different bugs. Direct cause of why BUG-001's RMT crash showed up.platform = espressif32 @ 6.7.0, fastled/FastLED @ ^3.7.0, qualified all Adafruit libs with author prefix, added monitor_speed = 115200, moved FastLED build flags into build_flags.
No PRO license dialog and refuses to connect:
RemoteXY library jumped from 3.1.14 → 4.1.10 on a clean build, and that 4.x had introduced the PRO enforcement. Pinned to ~3.1.14, wiped the cache, reflashed.RemoteXY.RemoteXY_CONF[] blob over BLE; the app parses it, counts elements + variables, and if either exceeds the free quota it short-circuits the connection regardless of which library version generated the blob. Downgrading the firmware library has no effect because the gatekeeper isn't on the device.RemoteXY_CONF[] in the editor with only 5 variables (joystick_x, joystick_y, button_01, distance graph, circularBar battery). Replace the blob + struct in src/03_remotexy.cpp. Compute speed locally from joystick_y, drive buzzer locally from button_01.RemoteXY (latest). Awaiting decision on which fix path to take.pio run --target upload fails immediately with:
pio device list clearly shows the port:
upload_flags = --connect-attempts 10 — wrong syntax, esptool rejected.upload_port = COM8, let auto-detect find it.pio device list right before flashing.
cmd.exe instead of MSYS2 — rules out MSYS2 Python COM access.pio device list AFTER the BOOT+RESET sequence.upload_protocol = esp-builtin (uses built-in USB JTAG, bypasses CDC reset entirely). Requires OpenOCD path setup.mode COM8 in CMD to verify nothing else is holding the port (RemoteXY mobile app can do this via BLE-bridge).
"Allow this site to know your location?" dialog every time the launcher opens. Three buttons: Allow this visit · Allow once · Never. Hits all 5 pages (index, start-here, flash, monitor, audit) because they share wdiy-script.js.initNightMode() in 02_web/assets/wdiy-script.js (lines 928–944) called navigator.geolocation.getCurrentPosition() to compute precise local sunset, so it could auto-switch the theme to dark after dusk. The simple version (new Date().getHours() >= 21) ran first; the geolocation block ran on top to refine. Net effect: every page load triggered the OS-level location prompt for a feature 99% of users never notice.lab-dark if the user hasn't picked a theme.calcSunset() kept in source as dead code (API compat) but no longer called.
navigator.*, fetch() to external hosts, or localStorage writes before deploying.index.html the splash screen (logo + "ESP32-C3 ROBOT APP" title + tap-to-skip) appeared beside the main app on the left half of the page, not as a full-screen overlay. It also never auto-dismissed.id="splash" but the CSS targeted the .splash class. Selector never matched → no position: fixed → splash inherited display: flex from body and rendered as a sibling flex item next to the app. Even worse, dismissSplash() adds a hidden class but .splash.hidden never matched either, so the dismiss had no visual effect.initSplash() calls setTimeout(dismissSplash, 2500) and dismissSplash() calls setTimeout(() => s.remove(), 600). After ~3.1 s the element is removed from the DOM regardless of CSS. So on most loads we never noticed the misplacement — until the user happened to look at index.html within the first 3 s.wdiy-template.css:
.splash-inner, .splash-logo, .splash-title, .splash-sub, .splash-hint) stayed as-is — they always matched correctly because those elements have proper class= attributes.
id vs class audit: pull every id="..." in the markup and check the CSS targets each one consistently. Mixed-mode templates (id on outer container, class on children) are a common source of phantom selectors..app-footer on start-here.html. Only one was animated; the other was a static duplicate.initPixelPet() in wdiy-script.js always created and inserted a new <div id="pixelPet"> regardless of whether one already existed. Any double-init scenario (hot-reload, theme toggle re-running init, etc.) appended another one instead of reusing.initPixelPet():
lab-light, solarized, bot-pop) — and later kapow, dino — multiple visual issues:
.m-cell) and stat blocks rendered with a dirty grey wash instead of clean white panels.bot-pop background.opacity: .45.dino: Lilita One chunky display font applied to every heading (mission summaries, card titles, sidebar labels) — squashed letterforms, hard to read at 14 px.kapow: same issue with Bangers font cascading to all UI labels.bot-pop: initial Bangers heading font for everything → comic-book lettering on UI chrome.start-here.html used background: rgba(0,0,0,.22) for cards, statbar, <pre>, file tree, pipeline, bootreset. Designed for dark themes only. On light themes that 22% black wash turned crisp panels into wet concrete..app-footer { opacity: .45 } and .bismillah { opacity: .45 }. On dark backgrounds .45 reads as muted-but-visible. On bright backgrounds it reads as not-there.--font-h: 'Bangers' or 'Lilita One' as the heading font, and the template applies var(--font-h) to every heading-class element: h1, .card-title, details.collapsible > summary, .sidebar-title, .help-tab, .section-title. Display fonts are designed for hero moments at large sizes — not 14 px UI labels with text-transform: uppercase already on top.
.light-theme override block in start-here.html's inline <style>:
.m-term) was deliberately not overridden — its dark CRT aesthetic is intentional even on light themes.wdiy-template.css for kapow and dino:
h1 + splash title (where they look heroic), Fredoka 700 takes over for sub-headings (where it reads chunky-but-legible).bot-pop's --font-h from Bangers → 'Fredoka', 'Nunito' outright. Bangers turned out to only fit kapow's comic-book aesthetic.
rgba(0,0,0,.X) backgrounds for component fills — use var(--panel) + a .light-theme override if needed.h1, splash title, banners — never cascade to UI labels.02_hardware/v3/.GPIO 1 = AIN1 + LEDs D5/D6 + J2GPIO 2 = AIN2GPIO 3 = PWMA or J3 (via JP2 solder jumper)GPIO 6 = BIN1 + J4 servoGPIO 7 = BIN2GPIO 10 = PWMB + LEDs D7/D8 + J5 servoGPIO 0 = SW1 push-button (10 kΩ pull-up R2). GPIO 4 = buzzer with transistor stage (R7/R8/R9). GPIO 8/9 = OLED I²C with 10 kΩ pull-ups (R10/R11). GPIO 20/21 = expansion (J6, J7).
00_config.h still says CONFIG_PIN_BATTERY = 4, the firmware is reading random ADC noise — bug remains, but it's a firmware problem not a wiring problem.05_buzzer.cpp needs to be on GPIO 4 (matches schematic) — NOT GPIO 3 as the earlier audit C1 fix proposed. GPIO 3 is now PWMA for motor A.01_src/00_config.h against the new pin map. Update any CONFIG_PIN_* defines that conflict.00_config.h defines, and the schematic. Whichever you write first, validate against the other before publishing. The earlier audit was firmware-only and made guesses about the hardware that turned out wrong on at least 5 GPIOs.esp32c3-lab sister project, clicking Connect in any lab page opened the Chrome BLE picker and immediately showed "Aucun appareil compatible détecté" ("No compatible device detected") — even with the ESP32-C3 powered, advertising, and within range. Multiple browser refreshes, BT toggles, and re-pair attempts didn't help.6e400001-…ca9e) is the documented pattern — that's what maqueen-lab uses, and the firmware did add the UUID to the advertisement via adv->addServiceUUID(NUS_SERVICE_UUID).
esp32c3-lab = 11 bytes wrapped in length+type = 13 bytes) plus standard flags (3 bytes) plus the 128-bit NUS UUID (16 bytes wrapped = 18 bytes) plus advertising-data overhead all add up, you exceed 31 bytes.requestDevice() matches its filters against the primary advertisement only, not the scan response. The browser sees the device's name, sees no NUS UUID in the advertisement payload, and rejects it during filter evaluation. Picker shows nothing.
BBC micro:bit [xxxxx] (~22 bytes including formatting) leaves enough room for the NUS UUID in the primary advertisement. The Workshop-DIY team's chosen device name was just short enough to squeeze in. ESP32-C3 + the chosen esp32c3-lab name overflows the budget.optionalServices ensures NUS is still accessible after pairing, regardless of which filter triggered the match.
namePrefix fallback.c3lab = 5 bytes) so UUID + name both fit in advertisement. Less user-friendly.build_flags_extra is not a real PlatformIO option. Originally invented by me to "append" without overwriting the board defaults, but the real way to do that is just put everything in build_flags (it's additive to the board's defaults already).build_flags_extra + build_unflags dance into a single build_flags list.
src_dir is src (relative to the directory containing platformio.ini). The sketch was at firmware/esp32c3-lab.ino, so PIO scanned firmware/src/, found nothing, refused to build.src_dir = . — keep .ino at firmware/; PIO would also scan README.md etc.firmware/src/esp32c3-lab.ino — works for PIO but Arduino IDE complains "folder name must match .ino name".firmware/esp32c3-lab/esp32c3-lab.ino + set src_dir = esp32c3-lab — works for both. Folder name matches sketch name (Arduino IDE happy); PIO follows src_dir.arduino-esp32 2.0.16 (shipped by espressif32 @ 6.7.0), the esp32-c3-devkitm-1 board profile has an inconsistent macro state when ARDUINO_USB_CDC_ON_BOOT=1 is set without a paired ARDUINO_USB_MODE=0:
esp32-c3-devkitm-1 profile, ARDUINO_USB_MODE is undefined → preprocessor treats it as 0 → the HWCDC block should fire and define Serial. But due to a header-ordering / macro-evaluation quirk in 2.0.16, the #define Serial USBSerial doesn't propagate to the framework's own HardwareSerial.cpp compilation unit — only to user code.Serial.* calls from the .ino. This stopped user-code from referencing Serial, but the framework's own HardwareSerial.cpp:60 calls Serial.available() in serialEventRun() — that file still failed to compile.-DARDUINO_USB_CDC_ON_BOOT=1 flag entirely. The board's default (CDC OFF) makes the framework declare Serial as HardwareSerial(0) via the standard SOC_RX0 path. Framework compiles cleanly. User code doesn't reference Serial anyway. esptool upload still works via the chip's built-in USB JTAG/Serial peripheral — that path is independent of Arduino's HWCDC class.-DARDUINO_USB_CDC_ON_BOOT=1 with -DARDUINO_USB_MODE=0 — would in theory fire the HWCDC declaration cleanly. Untested; CDC-off default is simpler.arduino-esp32 3.x (via espressif32 @ 53.x) — fixes the Serial issue but breaks NimBLE-Arduino 1.x compatibility (NimBLE 2.x has API breakage).USBSerial.begin() directly — would work for user code but not the framework's HardwareSerial.cpp internal call.platformio.ini directive against the official docs — typos / made-up options only emit warnings, not errors.firmware/<sketch-name>/<sketch-name>.ino + src_dir = <sketch-name> pattern is the cleanest way to satisfy both PlatformIO and Arduino IDE simultaneously. Recommend for any hybrid-build project.Serial on ESP32-C3 with arduino-esp32 2.0.x. Either use USBSerial directly, or drop the dependency entirely (the BLE log can substitute for diagnostics).pio run -v shows the exact compiler invocation including which -D macros are defined. Helps disambiguate "is this flag actually being applied?" questions.