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
* Clarified PDM microphone behavior: data unit width is effectively 16-bit in PDM mode
* Updated microsecond timing with Arduino-ESP32 note about direct timer usage
* Added "Precision waiting" subsection: coarse delay then busy-spin for microsecond accuracy
* Expanded error-handling docs with non-aborting check example
* Adjusted logging example presentation for human readers
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
Copy file name to clipboardExpand all lines: .github/esp-idf.instructions.md
+49-4Lines changed: 49 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -499,6 +499,9 @@ The ESP32 has an audio PLL for precise sample rates. Rules:
499
499
500
500
- Not supported on ESP32-C3 (`SOC_I2S_SUPPORTS_PDM_RX` not defined).
501
501
- ESP32-S3 PDM has known issues: sample rate at 50% of expected, very low amplitude.
502
+
-**16-bit data width**: Espressif's IDF documentation states that in PDM mode the data unit width is always 16 bits, regardless of the configured `bits_per_sample`.
503
+
- See [espressif/esp-idf#8660](https://github.com/espressif/esp-idf/issues/8660) for the upstream issue.
504
+
-**Flag `bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT` in PDM mode** — this causes the S3 low-amplitude symptom.
502
505
- No clock pin (`I2S_CKPIN = -1`) triggers PDM mode in WLED-MM.
503
506
504
507
---
@@ -579,13 +582,21 @@ if (!pinManager.allocatePin(myPin, true, PinOwner::UM_MyUsermod)) {
579
582
580
583
### Microsecond timing
581
584
582
-
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`:
585
+
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`.
586
+
<!-- HUMAN_ONLY_START -->
583
587
584
588
```cpp
585
589
#include<esp_timer.h>
586
590
int64_t now_us = esp_timer_get_time(); // monotonic, not affected by NTP
587
591
```
588
592
593
+
> **Note**: In arduino-esp32, both `millis()` and `micros()` are thin wrappers around `esp_timer_get_time()` — they share the same monotonic clock source. Prefer the direct call when you need the full 64-bit value or ISR-safe access without truncation:
@@ -606,6 +617,27 @@ esp_timer_start_periodic(timer, 1000); // 1 ms period
606
617
607
618
Always prefer `ESP_TIMER_TASK` dispatch over `ESP_TIMER_ISR` unless you need ISR-level latency — ISR callbacks have severe restrictions (no logging, no heap allocation, no FreeRTOS API calls).
608
619
620
+
### Precision waiting: coarse delay then spin-poll
621
+
622
+
When waiting for a precise future deadline (e.g., FPS limiting, protocol timing), avoid spinning the entire duration — that wastes CPU and starves other tasks. Instead, yield to FreeRTOS while time allows, then spin only for the final window.
623
+
<!-- HUMAN_ONLY_START -->
624
+
```cpp
625
+
// Wait until 'target_us' (a micros() / esp_timer_get_time() timestamp)
626
+
long time_to_wait = (long)(target_us - micros());
627
+
// Coarse phase: yield to FreeRTOS while we have more than ~2 ms remaining.
628
+
// vTaskDelay(1) suspends the task for one RTOS tick, letting other task run freely.
629
+
while (time_to_wait > 2000) {
630
+
vTaskDelay(1);
631
+
time_to_wait = (long)(target_us - micros());
632
+
}
633
+
// Fine phase: busy-poll the last ≤2 ms for microsecond accuracy.
634
+
// micros() wraps esp_timer_get_time() so this is low-overhead.
> The threshold (2000 µs as an example) should be at least one RTOS tick (default 1 ms on ESP32) plus some margin. A value of 1500–3000 µs works well in practice.
640
+
609
641
---
610
642
611
643
## ADC Best Practices
@@ -672,14 +704,15 @@ RMT drives NeoPixel LED output (via NeoPixelBus) and IR receiver input. Both use
672
704
- New chips (C6, P4) have different RMT channel counts — use `SOC_RMT_TX_CANDIDATES_PER_GROUP` to check availability.
673
705
- The new RMT API requires an "encoder" object (`rmt_encoder_t`) to translate data formats — this is more flexible but requires more setup code.
674
706
707
+
<!-- HUMAN_ONLY_END -->
675
708
---
676
709
677
710
## Espressif Best Practices (from official examples)
678
711
679
712
### Error handling
680
713
681
-
Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code:
682
-
714
+
Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code.
For situations between these two extremes — where you want the `ESP_ERROR_CHECK` formatted log message (file, line, error name) but must not abort — use `ESP_ERROR_CHECK_WITHOUT_ABORT()`.
730
+
731
+
<!-- HUMAN_ONLY_START -->
732
+
```cpp
733
+
// Logs in the same format as ESP_ERROR_CHECK, but returns the error code instead of aborting.
734
+
// Useful for non-fatal driver calls where you want visibility without crashing.
WLED-MM uses its own logging macros — **not**`ESP_LOGx()`. For application-level code, always use the WLED-MM macros defined in `wled.h`:
@@ -706,13 +750,14 @@ All of these wrap `Serial` output through the `DEBUGOUT` / `DEBUGOUTLN` / `DEBUG
706
750
707
751
**Exception — low-level driver code**: When writing code that interacts directly with ESP-IDF APIs (e.g., I2S initialization, RMT setup), use `ESP_LOGx()` macros instead. They support tag-based filtering and compile-time log level control:
708
752
753
+
<!-- HUMAN_ONLY_START -->
709
754
```cpp
710
755
staticconstchar* TAG = "my_module";
711
756
ESP_LOGI(TAG, "Initialized with %d buffers", count);
712
757
ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM");
713
758
ESP_LOGE(TAG, "Failed to allocate %u bytes", size);
0 commit comments