You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .github/cpp.instructions.md
+38Lines changed: 38 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -449,6 +449,44 @@ Always pair every `esp32SemTake` with a matching `esp32SemGive`. Choose a timeou
449
449
450
450
**Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow. For example, `volatile bool` flags like `suspendStripService`, `doInitBusses`, `loadLedmap`, and `OTAisRunning` (declared in `wled.h`) are checked sequentially in the main loop (`wled.cpp`), so they serialize access without requiring a semaphore. Use mutexes when true concurrent access from multiple FreeRTOS tasks is possible and race-conditions can lead to unexpected behaviour. Rely on control-flow ordering when operations are sequenced within the same loop iteration.
451
451
452
+
### `delay()` vs `yield()` in FreeRTOS Tasks
453
+
454
+
On ESP32, `delay(ms)` calls `vTaskDelay(ms / portTICK_PERIOD_MS)`, which **suspends only the calling task**. The FreeRTOS scheduler immediately runs all other ready tasks. This differs from ESP8266, where `delay()` stalled the entire system unless `yield()` was called inside.
455
+
456
+
**`delay()` in `loopTask` is acceptable.** The Arduino `loop()` function runs inside `loopTask`. Calling `delay()` there does not block the network stack, audio FFT, LED DMA, or any other FreeRTOS task.
457
+
458
+
**`yield()` is a no-op in WLED-MM on ESP32.**`WLEDMM_FASTPATH` redefines `yield()` to an empty macro:
459
+
460
+
```cpp
461
+
#defineyield() {} // WLEDMM: yield() is completely unnecessary on ESP32
462
+
```
463
+
464
+
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. Do not use `yield()` to pace ESP32 tasks or assume it feeds any watchdog.
465
+
466
+
**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
+
468
+
```cpp
469
+
// WRONG — IDLE task is never scheduled; yield() is a no-op in WLEDMM_FASTPATH
470
+
void myTask(void*) {
471
+
for (;;) {
472
+
doWork();
473
+
yield();
474
+
}
475
+
}
476
+
477
+
// CORRECT — delay(1) suspends the task for ≥1 ms, IDLE task runs, IDLE watchdog is fed
478
+
void myTask(void*) {
479
+
for (;;) {
480
+
doWork();
481
+
delay(1); // DO NOT REMOVE — lets IDLE(0) run and feeds its watchdog
482
+
}
483
+
}
484
+
```
485
+
486
+
Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed.
487
+
488
+
**Watchdog note.** WLED-MM disables the Task Watchdog by default (`WLED_WATCHDOG_TIMEOUT 0` in `wled.h`). When enabled, `esp_task_wdt_reset()` is called at the end of each `loop()` iteration. Long blocking operations inside `loop()` — such as OTA downloads or slow file I/O — must call `esp_task_wdt_reset()` periodically, or be restructured so the main loop is not blocked for longer than the configured timeout.
489
+
452
490
## General
453
491
454
492
- Follow the existing style in the file you are editing
Copy file name to clipboardExpand all lines: .github/esp-idf.instructions.md
+15Lines changed: 15 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -682,6 +682,21 @@ Guidelines:
682
682
#endif
683
683
```
684
684
685
+
### `delay()`, `yield()`, and the IDLE task
686
+
687
+
FreeRTOS on ESP32 is **preemptive** — all tasks are scheduled by priority regardless of `yield()` calls. This is fundamentally different from ESP8266 cooperative multitasking.
688
+
689
+
| Call | What it does | Reaches IDLE (priority 0)? |
690
+
|---|---|---|
691
+
|`delay(ms)` / `vTaskDelay(ticks)`| Suspends calling task; scheduler runs all other ready tasks | ✅ Yes |
692
+
|`yield()` / `vTaskDelay(0)`| Hint to switch to tasks at **equal or higher** priority only | ❌ No |
693
+
|`taskYIELD()`| Same as `vTaskDelay(0)`| ❌ No |
694
+
| Blocking API (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) | Suspends task until event or timeout; IDLE runs freely | ✅ Yes |
695
+
696
+
**`delay()` in `loopTask` is safe.** Arduino's `loop()` runs inside `loopTask`. Calling `delay()` suspends only `loopTask` — all other FreeRTOS tasks (Wi-Fi stack, audio FFT, LED DMA) continue uninterrupted on either core.
697
+
698
+
**`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`.
699
+
685
700
### Watchdog management
686
701
687
702
Long-running operations may trigger the task watchdog. Feed it explicitly:
0 commit comments