Here’s a comprehensive breakdown of all practical methods to blink an LED on an ESP32, categorized by execution model, with framework context, key APIs, and use-case guidance.
Blocking Methods #
| Method | Framework | Key API | Notes |
|---|---|---|---|
| Simple Delay | Arduino / ESP-IDF | delay(500) or vTaskDelay(pdMS_TO_TICKS(500)) | Blocks the CPU. Fine for single-task sketches. |
| Deep Sleep Wake | ESP-IDF / Arduino | esp_sleep_enable_timer_wakeup(), esp_deep_sleep_start() | Not for continuous blinking. Used for ultra-low-power periodic tasks (e.g., sensor + LED indicator). |
Non-Blocking / Polling Methods #
| Method | Framework | Key API | Notes |
|---|---|---|---|
| millis() State Machine | Arduino | if (millis() - lastToggle >= 500) { toggle(); } | Standard non-blocking pattern. Allows concurrent loops (WiFi, sensors, etc.). |
| esp_timer_get_time() | ESP-IDF | uint64_t now = esp_timer_get_time(); | Microsecond-precision version of millis(). Used in same state-machine pattern. |
Hardware Timer & Interrupt Methods #
| Method | Framework | Key API | Notes |
|---|---|---|---|
| Hardware Timer + ISR | ESP-IDF / Arduino | timer_create(), timer_arm(), attachInterrupt() | Uses ESP32’s 4 hardware timers. ISR toggles GPIO. Must be short & marked IRAM_ATTR. |
| LEDC Peripheral (Hardware PWM) | ESP-IDF / Arduino | ledcSetup(), ledcAttachPin(), ledcWriteTone() | Built-in PWM controller. Can generate exact square waves without CPU. Best for precise 1 Hz. |
| RMT Peripheral | ESP-IDF | rmt_init(), rmt_write_sample() | Remote control peripheral. Overkill for blinking but can output arbitrary pulse trains via DMA. |
FreeRTOS Task & Synchronization Methods #
(ESP32 runs FreeRTOS natively in both Arduino & ESP-IDF)
| Method | Framework | Key API | Notes |
|---|---|---|---|
| Dedicated RTOS Task | ESP-IDF / Arduino | xTaskCreate(), vTaskDelay() | Runs in its own thread. Clean separation for complex systems. |
| FreeRTOS Software Timer | ESP-IDF | xTimerCreate(), xTimerStart() | Callback-based periodic execution. Lightweight, runs in timer daemon task. |
| Event Group / Queue Trigger | ESP-IDF | xEventGroupWaitBits(), xQueueSend() | Task waits for event/message, then toggles LED. Useful for state-driven blinking (e.g., blink on WiFi connect). |
| Semaphore/Binary Mutex | ESP-IDF | xSemaphoreGive(), xSemaphoreTake() | One task signals, another blinks. Rarely needed for single LED but valid in multi-threaded designs. |
Framework/Environment Alternatives #
| Environment | Method | Example |
|---|---|---|
| MicroPython | utime.sleep(), machine.Timer, asyncio | timer = machine.Timer(); timer.init(period=500, mode=machine.Timer.PERIODIC, callback=lambda t: led.toggle()) |
| Zephyr RTOS | k_sleep(), k_timer_start(), workqueues | k_timer_init(&blink_timer, blink_handler, NULL); k_timer_start(&blink_timer, K_MSEC(500), K_NO_WAIT); |
| Bare-Metal / Direct Registers | GPIO + Timer registers | Write to GPIO_OUT_W1TS/W1TC & configure TIMGn timers manually. No RTOS overhead. |
Quick Comparison #
| Method | CPU Load | Precision | Complexity | Best For |
|---|---|---|---|---|
| delay() / vTaskDelay() | High (blocks) | ±1 ms | ⭐ | Simple sketches, prototyping |
| millis() state machine | Low | ±1 ms | ⭐⭐ | Multi-task Arduino apps |
| Hardware Timer + ISR | Very Low | ±1 μs | ⭐⭐⭐ | Real-time requirements |
| LEDC (Hardware PWM) | Zero (after setup) | Exact Hz | ⭐⭐ | Precise 1 Hz, power efficiency |
| FreeRTOS Task | Low-Medium | OS-dependent | ⭐⭐⭐ | Modular firmware |
| Software Timer (xTimer) | Low | ±1 tick | ⭐⭐ | Lightweight periodic callbacks |
| MicroPython machine.Timer | Low | ±1 ms | ⭐ | Rapid prototyping, IoT scripts |