keepcalculator

06 Numbers and Powers

ESP32 Dual Core Scheduling: How to Use Both Cores

Card No. 06·49044 · Updated · Standard formula, shown below

This guide explains how to use both CPU cores of the ESP32 in your Arduino or ESP-IDF project. You’ll learn how to pin tasks to a specific core, how to communicate between cores safely, and how to avoid the common pitfalls that cause crashes or slowdowns. By the end, you’ll have a working template for dual‑core code that you can adapt to your own IoT project.

What You’ll Need

  • An ESP32 development board (any model with dual cores, e.g., ESP32-WROOM-32)
  • Arduino IDE or ESP-IDF installed and configured for ESP32
  • USB cable for programming and serial monitor
  • Basic familiarity with Arduino sketches or ESP-IDF components

Step-by-Step

  1. Identify the cores. The ESP32 has two Xtensa LX6 cores: Core 0 (often called the “protocol core” or “pro CPU”) and Core 1 (the “application core” or “app CPU”). In Arduino, Core 1 is used by default for loop(), while Core 0 handles Wi‑Fi and Bluetooth stacks. You can run your own tasks on either core, but be careful not to starve the networking stack.
  2. Include the necessary header. In Arduino, add #include "esp32-hal.h" at the top of your sketch. In ESP-IDF, you’ll use #include "freertos/FreeRTOS.h" and #include "freertos/task.h".
  3. Write a task function. A task is just a function that runs forever (or until deleted). It must have the signature void taskFunction(void *parameter). Inside, you typically have a while(1) loop with a delay or a vTaskDelay call to yield the CPU.
  4. Create the task and pin it to a core. In Arduino, use xTaskCreatePinnedToCore(taskFunction, "TaskName", stackSize, NULL, priority, &taskHandle, coreNumber). The last argument is the core: 0 or 1. In ESP-IDF, the same function is available, or you can use xTaskCreate and let FreeRTOS decide.
  5. Set the stack size and priority. A typical stack size for a simple task is 2048 or 4096 bytes. Priority ranges from 0 (lowest) to 24 (highest) in ESP-IDF; in Arduino, the default is 1. If your task does heavy math or uses large local arrays, increase the stack size to avoid a crash.
  6. Add a delay inside the task. Without a delay() (Arduino) or vTaskDelay() (FreeRTOS), the task will hog the core and block lower‑priority tasks. Use delay(10) or vTaskDelay(pdMS_TO_TICKS(10)) to yield.
  7. Communicate between cores safely. Use FreeRTOS queues, semaphores, or mutexes to pass data between tasks. Avoid sharing global variables without protection; a simple volatile flag is okay for booleans, but for anything larger use a queue or a mutex.
  8. Test with a simple example. Write a sketch that prints “Core 0” from a task pinned to Core 0 and “Core 1” from a task pinned to Core 1. Open the serial monitor and confirm both messages appear.
  9. Move your time‑critical code to Core 1. Core 1 is less likely to be interrupted by Wi‑Fi or Bluetooth. If your IoT device does sensor reading, PID control, or display updates, pin those tasks to Core 1. Leave Core 0 for the networking stack and background tasks.

Common Mistakes

  • Not yielding the CPU. A task without a delay or vTaskDelay will starve other tasks on the same core, causing watchdog timeouts and reboots.
  • Sharing variables without synchronization. Two tasks writing to the same global variable can corrupt data. Use a mutex or a queue.
  • Using delay() inside a FreeRTOS task. In ESP-IDF, delay() is not available; use vTaskDelay. In Arduino, delay() works but it’s better to use vTaskDelay for consistency.
  • Setting stack size too small. If your task uses printf or large local arrays, increase the stack size to 4096 or more. A crash with “Guru Meditation Error” often means stack overflow.
  • Pinning too many tasks to Core 1. Core 1 is not a magic fast core; it shares the same clock. If you overload it, you’ll see delays. Balance the load.

Tips

  • Use getCoreID() inside a task to print which core it’s running on – helpful for debugging.
  • For time‑critical loops, use vTaskDelayUntil() to maintain a precise period, rather than vTaskDelay() which drifts.
  • If you only need two tasks, consider using loopTask on Core 1 and a single pinned task on Core 0. That’s often enough.
  • In Arduino, you can also set the CPU frequency with setCpuFrequencyMhz() to save power, but that affects both cores.
  • Use portMUX_TYPE and portENTER_CRITICAL() for very short critical sections, but prefer queues for longer data transfers.

Troubleshooting

  • “Guru Meditation Error: Core 0 panic’d” – Usually a stack overflow in a task on Core 0. Increase the stack size in xTaskCreatePinnedToCore.
  • Wi‑Fi disconnects or slow response – You may be starving Core 0. Move your tasks to Core 1, or reduce the priority of your Core 0 task.
  • Serial monitor shows only one core’s output – Check that both tasks are created correctly and that the core number is 0 or 1. Also ensure the task function has a delay.
  • Task not running at all – Verify the task handle is not NULL, and that you didn’t accidentally delete the task immediately after creation.
  • Random reboots – Watchdog timeouts often happen when a task blocks forever. Add a delay or yield in every loop.

Frequently Asked Questions

Q: Can I run both cores at different frequencies?
A: No, both cores share the same clock and therefore run at the same frequency.

Q: Which core should I use for my main loop?
A: In Arduino, loop() runs on Core 1 by default. Keep it there unless you have a specific reason to move it.

Q: How do I pass data from one core to another?
A: Use a FreeRTOS queue. Create a queue with xQueueCreate(), send with xQueueSend(), and receive with xQueueReceive().

Q: Is it safe to use Serial.print() from both cores?
A: Yes, but the output may interleave. Use a mutex around the print statement if you need clean lines.

Q: Can I run a task on both cores simultaneously?
A: No, a single task runs on one core at a time. To use both cores, create two separate tasks (or use FreeRTOS’s symmetric multiprocessing features, which are not enabled by default).

Q: What is the maximum number of tasks I can create?
A: It depends on RAM. Each task needs its own stack, so the limit is a few hundred on a typical ESP32 with 320KB RAM.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →