Skip to content

Commit ff789fd

Browse files
Copilotsofthack007
andauthored
docs: fix ADC bit-width macro for IDF v5 and add IDLE task explanation in esp-idf.instructions.md
Agent-Logs-Url: https://github.com/MoonModules/WLED-MM/sessions/d96831e9-2088-4a18-8b07-040fd66cd133 Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
1 parent e42d1eb commit ff789fd

2 files changed

Lines changed: 26 additions & 4 deletions

File tree

.github/cpp.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ On ESP32, `delay(ms)` calls `vTaskDelay(ms / portTICK_PERIOD_MS)`, which **suspe
464464
Even in stock arduino-esp32, `yield()` calls `vTaskDelay(0)`, which only switches to tasks at equal or higher priority — the IDLE task (priority 0) is never reached.
465465
**Do not use `yield()` to pace ESP32 tasks or assume it feeds any watchdog**.
466466
467-
**Custom `xTaskCreate()` tasks must call `delay(1)` in their loop, not `yield()`.** Without a real blocking call, the IDLE task is starved and the IDLE watchdog may panic:
467+
**Custom `xTaskCreate()` tasks must call `delay(1)` in their loop, not `yield()`.** Without a real blocking call, the IDLE task is starved. The IDLE watchdog panic is the first visible symptom — but the damage starts earlier: deleted task memory leaks, software timers stop firing, light sleep is disabled, and Wi-Fi/BT idle hooks don't run. See `esp-idf.instructions.md` for a full explanation of what IDLE does.
468468
469469
```cpp
470470
// WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog.

.github/esp-idf.instructions.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ WLED validates at compile time that exactly one target is defined and that it is
6060
| `SOC_I2S_SUPPORTS_ADC` | `bool` | `usermods/audioreactive/audio_source.h` | I2S ADC sampling mode (ESP32 only) |
6161
| `SOC_I2S_SUPPORTS_APLL` | `bool` | `usermods/audioreactive/audio_source.h` | Audio PLL for precise sample rates |
6262
| `SOC_I2S_SUPPORTS_PDM_RX` | `bool` | `usermods/audioreactive/audio_source.h` | PDM microphone input |
63-
| `SOC_ADC_MAX_BITWIDTH` | `int` | `util.cpp` | ADC resolution (12 or 13 bits) |
63+
| `SOC_ADC_MAX_BITWIDTH` | `int` | `util.cpp` | ADC resolution (12 or 13 bits). Renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5 |
6464
| `SOC_ADC_CHANNEL_NUM(unit)` | `int` | `pin_manager.cpp` | ADC channels per unit |
6565
| `SOC_UART_NUM` | `int` | `dmx_input.cpp` | Number of UART peripherals |
6666
| `SOC_DRAM_LOW` / `SOC_DRAM_HIGH` | `addr` | `util.cpp` | DRAM address boundaries for validation |
@@ -588,16 +588,27 @@ ADC is one of the most fragmented APIs across IDF versions:
588588

589589
### Bit width portability
590590

591-
Not all chips have 12-bit ADC. Use `SOC_ADC_MAX_BITWIDTH` to adapt:
591+
Not all chips have 12-bit ADC. `SOC_ADC_MAX_BITWIDTH` reports the maximum resolution (12 or 13 bits). Note that in IDF v5, this macro was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH`. Write version-aware guards:
592592

593593
```cpp
594-
#if SOC_ADC_MAX_BITWIDTH == 13
594+
// IDF v4: SOC_ADC_MAX_BITWIDTH IDF v5: CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
595+
#if defined(CONFIG_SOC_ADC_RTC_MAX_BITWIDTH) // IDF v5+
596+
#define MY_ADC_MAX_BITWIDTH CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
597+
#elif defined(SOC_ADC_MAX_BITWIDTH) // IDF v4
598+
#define MY_ADC_MAX_BITWIDTH SOC_ADC_MAX_BITWIDTH
599+
#else
600+
#define MY_ADC_MAX_BITWIDTH 12 // safe fallback
601+
#endif
602+
603+
#if MY_ADC_MAX_BITWIDTH == 13
595604
adc1_config_width(ADC_WIDTH_BIT_13); // ESP32-S2
596605
#else
597606
adc1_config_width(ADC_WIDTH_BIT_12); // ESP32, S3, C3, etc.
598607
#endif
599608
```
600609

610+
WLED-MM's `util.cpp` uses the IDF v4 form (`SOC_ADC_MAX_BITWIDTH`) — this will need updating when the codebase migrates to IDF v5.
611+
601612
---
602613

603614
## RMT Best Practices
@@ -699,6 +710,17 @@ FreeRTOS on ESP32 is **preemptive** — all tasks are scheduled by priority rega
699710

700711
**`yield()` does not yield to IDLE.** Any task that loops with only `yield()` calls will starve the IDLE task, causing the IDLE watchdog to fire. Always use `delay(1)` (or a blocking FreeRTOS call) in tight task loops. Note: WLED-MM redefines `yield()` as an empty macro on ESP32 WLEDMM_FASTPATH builds — see `cpp.instructions.md`.
701712

713+
#### Why the IDLE task is not optional
714+
715+
The FreeRTOS IDLE task (one per core on dual-core ESP32/S3) is not idle in the casual sense — it performs essential system housekeeping:
716+
717+
- **Frees deleted task memory**: when a task calls `vTaskDelete()`, the IDLE task reclaims its TCB and stack. Without IDLE running, deleted tasks leak memory permanently.
718+
- **Runs FreeRTOS software timers**: the timer daemon relies on IDLE-priority execution. Many ESP-IDF components (Wi-Fi, BLE, NVS) schedule work via software timers.
719+
- **Implements tickless idle / light sleep**: on battery-powered devices, IDLE is the entry point for low-power sleep. A permanently starved IDLE task disables light sleep entirely.
720+
- **Runs registered idle hooks**: ESP-IDF components register callbacks via `esp_register_freertos_idle_hook()` (e.g., Wi-Fi background maintenance, Bluetooth housekeeping). These only fire when IDLE runs.
721+
722+
In short: **starving IDLE corrupts memory cleanup, breaks software timers, disables low-power sleep, and prevents Wi-Fi/BT maintenance.** The IDLE watchdog panic is a symptom — the real damage happens before the watchdog fires.
723+
702724
### Watchdog management
703725

704726
Long-running operations may trigger the task watchdog. Feed it explicitly:

0 commit comments

Comments
 (0)