esp32-expert · diff
git:20260708.586577e to git:20260725.6c1b23d
63 added, 170 removed. Audit A to A.
---
name: esp32-expert
- description: This skill should be used when the user is writing, reviewing, debugging, or architecting C++ firmware for ESP32 and variants (ESP32-S2, S3, C3, C5, C6, H2, P4) using ESP-IDF or PlatformIO. Provides expert critique covering FreeRTOS task design, memory management (IRAM/DRAM/PSRAM), peripheral drivers (I2C/SPI/UART/GPIO), build systems (CMake/platformio.ini), power management, OTA updates, and embedded C++ best practices. Use when the user asks "review my ESP32 code", "fix FreeRTOS crash", "optimize memory usage", "debug I2C issue", "set up PlatformIO project", "review my CMakeLists", "search datasheet for this chip", "why is my task crashing", "configure deep sleep", or "help with ESP-IDF component structure".
+ description: >-
+ This skill should be used for ESP32-specific hardware and runtime work in
+ ESP-IDF, Arduino-ESP32, or PlatformIO projects: target/build detection,
+ FreeRTOS tasks and ISRs, memory/DMA, peripherals, power, networking/security,
+ OTA, crash diagnosis, and unattended reliability. Trigger on "debug this
+ ESP32 crash", "review this ESP32 FreeRTOS code", "my Arduino ESP32 sketch
+ reboots", "why does ESP32 I2C time out", "optimize ESP32 memory", or "check
+ this ESP32 firmware before deployment". Not for generic C++, ESP8266, or
+ non-ESP32 targets.
---
- # ESP32 C++ Expert
-
- Think like a senior firmware engineer reviewing code destined for thousands of deployed devices that must run unattended for years. Assess code for correctness, safety, performance, FreeRTOS patterns, and hardware interaction quality. Every finding explains WHY it matters -- what crash it prevents, what field failure it avoids, what resource leak it reveals.
-
- **CRITICAL**: Embedded firmware has no second chances. There is no user to click "restart." There is no log viewer in the field. A deployed device with a heap fragmentation bug will crash at 3AM after 47 days of uptime, and nobody will know why.
-
- ## First: Detect the Project Framework
-
- Before giving ANY advice, determine the build system. Check for these files in the project root:
-
- | File Found | Framework | Implications |
- |---|---|---|
- | `platformio.ini` | **PlatformIO** | Check `framework = ` line (arduino, espidf, or both) |
- | `CMakeLists.txt` with `include($ENV{IDF_PATH}/...` | **ESP-IDF native** | Use `idf.py` commands, component architecture |
- | `CMakeLists.txt` with `idf_component_register` | **ESP-IDF component** | Part of larger IDF project |
- | `sdkconfig` or `sdkconfig.defaults` | **ESP-IDF** | Menuconfig-based configuration |
- | Both `platformio.ini` AND `sdkconfig` | **PlatformIO + ESP-IDF framework** | PlatformIO wrapping IDF |
-
- Adapt ALL guidance to the detected framework. Never suggest `idf.py` commands to a PlatformIO project or `pio` commands to a native IDF project.
-
- ## Second: Identify the Target Variant
-
- Check `sdkconfig`, `platformio.ini`, or `CMakeLists.txt` for the target chip:
-
- - **ESP32** (Xtensa dual-core 240MHz) -- original, most common
- - **ESP32-S2** (Xtensa single-core 240MHz) -- USB OTG, no Bluetooth
- - **ESP32-S3** (Xtensa dual-core 240MHz) -- AI/vector instructions, USB OTG
- - **ESP32-C3** (RISC-V single-core 160MHz) -- BLE only, WiFi, low cost
- - **ESP32-C5** (RISC-V single-core 240MHz) -- first dual-band WiFi 6 (2.4 + 5 GHz), BLE, Thread/Zigbee
- - **ESP32-C6** (RISC-V single-core 160MHz) -- WiFi 6 (2.4 GHz only), Thread/Zigbee, BLE
- - **ESP32-H2** (RISC-V single-core 96MHz) -- Thread/Zigbee, BLE, NO WiFi
- - **ESP32-P4** (RISC-V dual-core 400MHz) -- NO wireless, MIPI-DSI/CSI, H.264, 768KB SRAM
-
- Newer variants ship continuously (C61, H21, ...). If the project targets a chip not listed here, do not guess its capabilities from the family name -- fetch its datasheet and the ESP-IDF support matrix first.
-
- Variant matters for: available peripherals, core count (SMP vs single), memory layout, wireless capabilities, instruction set (Xtensa vs RISC-V).
-
- ## Zoom out before you edit
-
- Firmware sessions that skip this step produce split-brain code (a second ring buffer, a second retry helper, a second pin table) and orphans. Non-negotiable sequence for any change:
-
- 1. **Before adding a function, struct, or helper: search for an existing one** (`rg -i` the concept, not just your candidate name). A second implementation of the same logic is a drift bug on a timer.
- 2. **Read the whole file and the task/ISR context around the target lines** -- in firmware, the bug you were shown is usually a symptom of the concurrency shape around it.
- 3. **After the change, grep the symbols you replaced** and delete anything now unreferenced in the same change.
- 4. **State in one sentence where the change sits** (which task, which ISR path, which layer below). If you cannot, read more before editing.
- 5. **Report honestly.** If you could not flash and test on hardware, say "compiles, untested on target" -- never imply a runtime check you didn't do. Verification means command output (build log, monitor output), not the sentence "verified."
-
- ## How to Think About Embedded Problems
-
- Before fixing any issue, identify which layer it belongs to:
-
- - **Layer 1 -- Hardware Constraints (WHERE):** Which chip? Memory layout? Available peripherals? Pin assignments? Read the datasheet. Search online for the datasheet if unfamiliar with the device.
- - **Layer 2 -- RTOS Design (HOW):** Task priorities, stack sizes, synchronization, ISR design. Check against FreeRTOS rules.
- - **Layer 3 -- Application Logic (WHAT):** Protocol implementation, state machines, data flow, error recovery.
- - **Layer 4 -- C++ Correctness (WHY):** Language-level issues that cause UB, leaks, or crashes.
-
- When a crash or hang appears, reframe it as a design question:
-
- | Symptom | Don't Just Say | Ask Instead |
- |---|---|---|
- | Stack overflow | "Increase stack size" | Why is the task using so much stack? Is it allocating large buffers locally? |
- | Guru Meditation Error | "Check the backtrace" | Which memory region was accessed? Is the pointer from a freed task? |
- | Task watchdog timeout | "Feed the watchdog" | Why is this task blocked? Is it waiting on a resource another task holds? |
- | I2C timeout | "Increase timeout" | Is the bus stuck? Are pullups correct? Is another task accessing the bus? |
- | Heap exhaustion | "Increase heap" | Who is allocating and not freeing? Is fragmentation the real issue? |
- | WiFi disconnect loop | "Add retry" | Is the event handler re-entrant? Is the task stack large enough for TLS? |
-
- ## Review Process
-
- When critiquing ESP32 C++ code, work through these in order. Consult the reference file for each domain.
-
- 1. **Framework & Variant** -- Detect ESP-IDF vs PlatformIO, target chip. Adapt all advice.
- 2. **FreeRTOS Correctness** → *Consult [FreeRTOS reference](references/freertos.md) for task design, priorities, stack sizing, and ISR rules.*
- 3. **Concurrency & Deadlocks** → *Consult [concurrency reference](references/concurrency.md) for primitive selection (when to use / NOT use each), deadlock prevention, race conditions, and SMP rules.*
- 4. **Memory Safety** → *Consult [memory reference](references/memory.md) for IRAM/DRAM/PSRAM placement, heap fragmentation, ETL containers, and memory pool patterns.*
- 5. **C++ Guidelines** → *Consult [C++ guidelines](references/cpp-guidelines.md) for modern C++ on embedded, RAII, constexpr, and Core Guidelines rules.*
- 6. **Embedded C++ Patterns** → *Consult [embedded C++ reference](references/embedded-cpp.md) for CRTP, state machines, type-safe hardware access, and callback patterns.*
- 7. **Peripheral Drivers** → *Consult [peripherals reference](references/peripherals.md) for I2C/SPI/UART/GPIO/ADC patterns, bus recovery, and datasheet lookup protocol.*
- 8. **Build System** → *Consult [build system reference](references/build-system.md) for CMake, platformio.ini, sdkconfig management, and component architecture.*
- 9. **Networking** → *Consult [networking reference](references/networking.md) for WiFi event handling, socket lifecycle, BLE, MQTT, and reconnection strategies.*
- 10. **Security** → *Consult [security reference](references/security.md) for secure boot, flash encryption, TLS, credential storage, and real ESP32 CVEs.*
- 11. **Power Management** → *Consult [power reference](references/power.md) for sleep modes, wake sources, battery design, and ULP coprocessor.*
- 12. **Debugging** → *Consult [debugging reference](references/debugging.md) for crash analysis, JTAG, GDB, logging, heap tracing, and code inspection checklist.*
- 13. **Testing** → *Consult [testing reference](references/testing.md) for host-based tests, HIL, static analysis, and CI/CD patterns.*
- 14. **Design Patterns** → *Consult [design patterns reference](references/design-patterns.md) for event-driven architecture, supervisor pattern, HAL layers, and configuration management.*
- 15. **LVGL** → *Consult [LVGL reference](references/lvgl.md) for thread safety, animation performance, FPS anti-patterns, screen lifecycle, and display tearing fixes.*
- 16. **Libraries** → *Consult [libraries reference](references/libraries.md) for common ESP32 libraries, per-library dos/don'ts, and known bugs.*
-
- ## Thinking Prompts
-
- Before suggesting a fix, work through:
-
- 1. **What crash does this prevent?** If you cannot name a concrete failure mode, the fix may not be worth the complexity.
- 2. **What happens at 3AM in the field?** A watchdog timeout reboots the device. A memory leak grows for weeks. A race condition triggers once per million cycles. Think in terms of deployed devices, not bench testing.
- 3. **Is the hardware doing what you think?** Always verify against the datasheet. Signal timing, voltage levels, and electrical characteristics are not optional reading.
- 4. **Would this survive 10,000 hours?** Embedded systems run continuously. Patterns that work for minutes may fail over months. Memory fragmentation, timer overflow, and resource leaks are time bombs.
-
- ## Project Onboarding
-
- Run `/esp-teach` once per project. It scans the codebase, discovers hardware, searches online for datasheets, asks targeted questions, and persists all context to CLAUDE.md. Future sessions start with full hardware knowledge.
-
- ## Datasheet Guidance
-
- When encountering unfamiliar hardware (sensors, displays, motor drivers, ICs):
-
- 1. **Search online for the datasheet** using the part number (e.g., "BME280 datasheet", "SSD1306 datasheet")
- 2. Key sections to check: electrical characteristics, timing diagrams, register maps, communication protocol details
- 3. Verify voltage compatibility with ESP32 variant (3.3V logic, some peripherals need level shifting)
- 4. Check Espressif's own datasheets for peripheral capabilities: pin multiplexing, DMA channels, clock sources
-
- ## Severity Levels
-
- Label every finding:
-
- - **critical** -- Will crash, corrupt data, or brick device in production. Fix immediately.
- - **important** -- Wrong RTOS pattern, memory issue, or design flaw that causes intermittent failures. Should fix.
- - **warning** -- Suboptimal pattern, missing error handling, or portability issue. Fix before release.
- - **nit** -- Style, naming, minor idiom. Fix if convenient.
- - **praise** -- Highlight well-written embedded code. Reinforce good patterns.
-
- ## Output Format
-
- Group findings by file. For each finding:
- 1. File path and line number
- 2. Severity label
- 3. Category (FreeRTOS / Memory / C++ / Peripheral / Build / Security)
- 4. **WHY it matters** -- the concrete consequence (crash, hang, field failure, security hole)
- 5. Before/after code block when the fix is non-obvious
-
- Skip files with no findings. End with a prioritized summary.
-
- ## Critical Anti-Patterns
-
- See `references/anti-patterns.md` for the full catalog (34 anti-patterns, AP-01 through AP-34). The most dangerous:
-
- **Will Crash (Critical):**
- - Blocking in ISR context (AP-01) -- watchdog reset, missed interrupts
- - Missing `IRAM_ATTR` on ISR (AP-02) -- crash during NVS/OTA flash writes
- - DMA buffer in PSRAM (AP-04) -- silent data corruption
- - ADC2 + WiFi active (AP-23) -- garbage readings or crash (silicon limitation)
- - GPIO strapping pins as I/O (AP-24) -- device fails to boot or bricks
- - Wrong init order (AP-25) -- panic on `esp_wifi_init()`
- - `MALLOC_CAP_32BIT` for floats (AP-27) -- `LoadStoreError` (FPU can't access IRAM)
- - Wake stub calling flash code (AP-30) -- `LoadProhibited` on every deep sleep wake
-
- **Will Fail Eventually (Important):**
- - `malloc`/`new` in loops without free (AP-07) -- heap exhaustion over hours/days
- - Heap fragmentation (AP-15) -- `malloc` returns NULL after weeks despite "free" memory
- - Binary semaphore for resource protection (AP-14) -- priority inversion, high-priority task starved
- - `taskDISABLE_INTERRUPTS` on dual-core (AP-28) -- data race (only protects one core)
- - `vTaskDelay` for periodic tasks (AP-32) -- cumulative timing drift
- - NVS writes every boot (AP-29) -- flash wear-out after months/years
-
- **Concurrency-specific:** → *Consult [concurrency reference](references/concurrency.md) for deadlock prevention strategies, when to use each synchronization primitive, and SMP-specific rules.*
+ # ESP32 Expert
- ---
+ Identify the chip/board, SDK/framework and version, build frontend, and selected
+ configuration independently from `platformio.ini`, `CMakeLists.txt`, `sdkconfig`,
+ and build output. PlatformIO may build Arduino-ESP32 or ESP-IDF; inspect the
+ selected environment's platform, board, framework, and overrides.
+ Chip families differ in cores, radio support, memory, peripherals, DMA, and
+ instruction set. If the target or attached hardware is unfamiliar, read its
+ current datasheet and support matrix instead of inferring capabilities from the
+ family name.
- ## The AI Slop Test
+ ## Opinions worth carrying
- **Critical quality check**: LLMs generate embedded code that compiles but fails in the field. AI models lack hardware awareness -- they don't understand flash cache, DMA constraints, interrupt latency, or RTOS scheduling.
+ - **Reason from field behavior, not compilation.** Ask what happens after weeks
+ of uptime, during OTA, under radio reconnect storms, at low voltage, or when a
+ peripheral holds a bus. Report when hardware validation was not possible.
+ - **Treat context as part of the type.** Task, ISR, timer callback, event-loop,
+ and normal thread contexts have different legal operations. A helper safe in
+ one may deadlock, allocate illegally, or touch flash unsafely in another.
+ - **Bound every resource.** Task stacks, queues, retries, waits, allocations,
+ payloads, and reconnect loops need explicit limits. Unbounded behavior becomes
+ a watchdog reset, heap exhaustion, flash wear, or latency collapse in the
+ field.
+ - **Prefer ownership and message passing over shared mutable state.** Choose
+ FreeRTOS primitives for the access pattern, keep ISR work minimal, and account
+ for SMP when the chip has multiple cores. Do not fix races by disabling
+ interrupts on only one core.
+ - **Design memory placement deliberately.** Internal RAM, IRAM, DRAM, PSRAM,
+ DMA-capable memory, RTC memory, and flash are not interchangeable. Verify the
+ requirements of the peripheral and API before moving buffers.
+ - **Make recovery explicit.** Separate transient faults from invalid
+ configuration and hardware failure. Retries require backoff, a ceiling, and
+ an observable terminal state. A watchdog is a last-resort recovery mechanism,
+ not normal control flow.
- **The test**: Would a senior firmware engineer look at this code and immediately say "an AI wrote this"? If yes, that's the problem.
+ ## Investigate the failure, not its symptom
- → *Consult [AI slop reference](references/ai-slop.md) for the full checklist of AI code tells: framework confusion, missing IRAM_ATTR, hallucinated APIs, outdated training data patterns, and generic C++ that doesn't belong in embedded.*
+ A stack overflow is not automatically solved by increasing the stack. An I2C
+ timeout is not automatically solved by waiting longer. A watchdog event is not
+ automatically solved by feeding it. Trace the hardware state, task ownership,
+ wait graph, memory region, and triggering sequence before changing constants.
- When you spot them, fix them -- don't just flag them. AI slop in firmware ships to devices that run for years without updates.
+ ## Load details only when signaled
- ---
+ | Signal | Reference |
+ |---|---|
+ | Tasks, priorities, ISRs, synchronization | `references/runtime.md` |
+ | Heap, stacks, PSRAM, DMA, placement | `references/memory-and-dma.md` |
+ | I2C, SPI, UART, GPIO, ADC | `references/peripherals.md` |
+ | CMake, PlatformIO, sdkconfig, partitions, panics, JTAG, tracing | `references/build-and-debug.md` |
+ | Wi-Fi, BLE, MQTT, TLS, OTA, credentials | `references/networking-and-security.md` |
+ | Sleep, wake, flash wear, soak behavior | `references/power-and-reliability.md` |
+ | Host tests, HIL, CI, static analysis | `references/testing.md` |
+ | Displays, framebuffers, LVGL | `references/display-lvgl.md` |
- **NEVER**:
- - Suggest a fix without explaining what concrete failure it prevents
- - Assume the developer's hardware matches the most common variant
- - Recommend `ESP_ERROR_CHECK` for runtime paths (use `ESP_RETURN_ON_ERROR`)
- - Suggest `delay()` when `vTaskDelay(pdMS_TO_TICKS())` is correct
- - Ignore the build system -- PlatformIO and ESP-IDF have different conventions
- - Give WiFi/BLE advice without checking which variant supports it (H2 has no WiFi, P4 has no radio)
+ Load a reference when the request, project evidence, or a consequential unknown
+ signals it. For underspecified or high-risk work, surface the few unknowns whose
+ answers could change the architecture or verification approach. Prefer current
+ tool help, datasheets, source, and measured logs over a copied catalogue.
- Remember: Firmware ships once and runs for years. A design review that catches one race condition or one missing `IRAM_ATTR` before deployment is worth more than a month of field debugging. Be thorough, be specific, and always explain why.
+ Explain each finding through its concrete field consequence. Verify with the
+ project's existing build and tests, then distinguish clearly between what was
+ compiled, what was simulated, and what was exercised on target hardware.