Helper library for ESP32 Arduino to run FreeRTOS tasks easily. Call begin() once to prepare Low / Normal / High priority tasks on each core (0/1). Implement the provided hook functions in your sketch to run code periodically; if you leave a hook undefined, the task exits immediately so it doesn’t waste resources.
Defaults:
periodMs = 1stackSize = ARDUINO_LOOP_STACK_SIZE(ESP32 Arduino’s default: 8192 words ≈ 32 KB; FreeRTOS stack sizes are in words). If you need more, passbegin(stackBytes).- Setting
periodMs = 0runs as fast as possible but can starve lower-priority tasks; keep it ≥1. - Priorities must stay within FreeRTOS limits (
0–24, higher is higher). For beginners, stick to ~1–4.- Default priorities: Low=1, Normal=2, High=3 (Core1 Low matches the Arduino
loop()priority ~1; avoid long non-delaywork there).
- Default priorities: Low=1, Normal=2, High=3 (Core1 Low matches the Arduino
ESP32AutoTask(this repo): DefineLoopCore*_*( )hooks and they auto-run with minimal setup. Best for quick sketches and a handful of simple periodic tasks.- ESP32TaskKit: C++ task helpers with explicit creation/config objects (priority, stack, period, core pinning, suspend/resume). Choose this when you need more tasks, per-task settings, or lifecycle control beyond the fixed hook set.
- ESP32SyncKit: C++ wrappers for FreeRTOS Queue / Notify / Semaphore / Mutex. Pair it with AutoTask or TaskKit whenever tasks or ISRs need to pass data, events, or share resources safely.
- Arduino’s
loop()runs as a task pinned to Core 1 (priority ~1, stack 8192 words ≈ 32 KB). Wi‑Fi/BT system tasks mainly occupy Core 0 at higher priority. - This library offers Low / Normal / High hooks on both Core 0 and Core 1, with default priorities
1 / 2 / 3. Use Normal for work similar toloop()but a bit higher priority, High for heavier/urgent work, Low for light background chores. Core1 Low is the same priority asloop()(~1); long, non-yielding work there will slowloop(), so keep it short and include delays. - On single-core ESP32 parts (ESP32-SOLO / ESP32-C3 / C2 / C6 / S2, etc.), Core1 hooks also run on Core0 (only the ordering is separated).
- Include
ESP32AutoTask.h. - Call
AutoTask.begin()insetup(). - Define the hook functions you need (e.g.,
LoopCore0_Low,LoopCore1_Normal).
#include <ESP32AutoTask.h>
void setup() {
ESP32AutoTask::AutoTask.begin(); // Use defaults
}
// Define only the hooks you need
void LoopCore0_Low() {
// Core0, low priority loop
}
void LoopCore1_Normal() {
// Core1, normal priority loop
}
void LoopCore1_High() {
// Core1, high priority loop
}- Each core exposes Low / Normal / High hooks.
- If a hook is not defined, the underlying task calls
vTaskDelete(NULL)and exits, so there is no resident overhead.
If you suspect the default stack is too small, override the common stack size (applies to all tasks) via the argument:
void setup() {
ESP32AutoTask::AutoTask.begin(/*stackBytes=*/16384); // Override only the stack size
}- Beginners can stick with
begin(). Increase the number only when you see stack-related issues.
Pass a Config when you need to adjust priority, stack size, or period per hook.
void setup() {
ESP32AutoTask::Config cfg;
cfg.core0.low = {1, ARDUINO_LOOP_STACK_SIZE, 1};
cfg.core0.normal = {2, ARDUINO_LOOP_STACK_SIZE, 1};
cfg.core0.high = {3, ARDUINO_LOOP_STACK_SIZE, 1};
cfg.core1.low = {1, ARDUINO_LOOP_STACK_SIZE, 1};
cfg.core1.normal = {2, ARDUINO_LOOP_STACK_SIZE, 1};
cfg.core1.high = {3, ARDUINO_LOOP_STACK_SIZE, 1};
ESP32AutoTask::AutoTask.begin(cfg); // Use fully specified parameters
}Common tweak: keep defaults but bump a single value.
ESP32AutoTask::Config cfg; // Initialized with defaults
cfg.core1.high.priority = 4; // Raise only Core1 high-priority hook
ESP32AutoTask::AutoTask.begin(cfg);- What multicore means: ESP32 typically has two cores (Core0/Core1); the OS schedules tasks across them.
- Core0 and Wi‑Fi/BT: When wireless is enabled, Core0 runs high-priority system tasks, so user tasks on Core0 may jitter. Without wireless, Core0 is lighter and can host heavier work.
- Core1 and
loop(): Arduinoloop()runs on Core1 at priority ~1. When placing user tasks on Core1, choose priorities relative toloop(). - Same priority: Tasks on the same core and priority time-slice cooperatively. If you need distinct responsiveness, offset priorities instead of keeping them equal.
- Interrupts: They outrank tasks but should stay tiny. Do heavy work in tasks; send an event via notification/queue from the ISR.
- High-priority task etiquette: Keep runtime short; heavier work should run at lower priority. Always yield with
delay/vTaskDelayafter work so lower-priority tasks can run; skipping waits can cause WDT/panics. - I2C/shared buses: Concurrent access from multiple tasks can panic/reset. Funnel I2C/SPI/Serial access through a dedicated task and send requests via queue/notification.
- Task sync basics: Use notifications/queues/mutexes for sharing state. Notifications signal single events, queues pass data in order, mutexes guard shared resources. A dedicated task receiving I2C/SPI requests via queue avoids bus conflicts.
- Time accuracy: Task timing is not exact. Use hardware timers (interrupts) plus a notification to the task when you need tighter timing.
vTaskDelayUntilhelps reduce drift compared with plain delays. - Why notifications: A high-priority task can block on a notification; when an interrupt sends the notification, that task resumes immediately, giving near–real-time response.
- Task handles: After
begin(), task handles are available asESP32AutoTask::handleCore*(Low/Normal/High for Core0/Core1) if you need them for monitoring or notifications. - WDT: Hogging the CPU too long trips the watchdog. Split heavy work and insert
delay/vTaskDelay, or lower the priority to let others run. - Panic checklist: (1) Stack shortage → try
begin(stackBytes), (2) missingdelayin loops, (3) heavy work at same priority, (4) simultaneous access to shared resources. - Serial/logging: Heavy
Serial.printin high-priority tasks can block others. Buffer and flush from a lower-priority or dedicated logging task. - When you need more control: If this helper feels limiting, switch to raw FreeRTOS APIs (
xTaskCreatePinnedToCore, notifications, queues, mutexes) directly. - Extra tips: Avoid long blocking I/O at high priority; if stack seems tight, bump
begin(stackBytes); keep WDT in mind and insert delays. - FreeRTOS tick timing: ESP32 Arduino uses a 1 ms tick, scheduling each core from highest priority downward every tick; calling
delay/vTaskDelayyields the CPU. If a task never yields, same-core tasks of equal or lower priority cannot run. ESP-IDF defaults to a 10 ms tick, sovTaskDelay(1)waits 10 ms there. - If using raw FreeRTOS APIs (ESP-IDF style): Use
vTaskDelay/vTaskDelayUntilwithpdMS_TO_TICKSfor timing. Example:vTaskDelay(pdMS_TO_TICKS(1000))waits 100 ticks (1000 ms on Arduino’s 1 ms tick). IDF’s default tick is 10 ms, sopdMS_TO_TICKS(1)becomes 10 ms there.
Design notes and rationale: SPEC.md (English). Japanese version: SPEC.ja.md.
MIT License (see LICENSE).