The default task runs automatically when your ESP32 boots, and you can pause it by calling vTaskSuspend() with its handle
When you upload code to an ESP32 using the Arduino IDE, the system creates a default task that runs your loop() function. This task has a handle — a reference number — that the operating system assigns automatically. To pause it, you need to get that handle first, then call the suspend function with it as an argument.
The most straightforward way is to capture the handle in your setup() function using xTaskGetCurrentTaskHandle(), store it in a variable, and then call vTaskSuspend() on that handle whenever you want to pause the loop. When you want the loop to run again, call vTaskResume() with the same handle.
Key Takeaways
- Capture the default task's handle in setup() using xTaskGetCurrentTaskHandle() and store it in a global variable so other functions can access it.
- Call vTaskSuspend(taskHandle) to pause the loop, and vTaskResume(taskHandle) to resume it from wherever it stopped.
- The default task runs at priority 1 by default, so suspending it does not affect other tasks or interrupt handlers running at higher priorities.
- You must include freertos/task.h at the top of your sketch to use the FreeRTOS task functions.
Getting the default task handle in setup()
The ESP32 runs FreeRTOS under the hood, even when you write simple Arduino code. Every task in FreeRTOS needs a handle — a pointer that identifies it uniquely. The default task that runs your loop() function is created automatically, but you have to grab its handle yourself.
At the very top of your sketch, declare a global variable to hold the handle:
TaskHandle_t defaultTaskHandle = NULL;
Then in your setup() function, capture the handle of the currently running task (which is the default task):
void setup() { defaultTaskHandle = xTaskGetCurrentTaskHandle(); Serial.begin(115200); }
This line runs once when the ESP32 boots. The function xTaskGetCurrentTaskHandle() returns a handle to whichever task is running at that moment — which is the default task during setup. Store it in your global variable so you can use it later in loop() or in other functions.
Suspending and resuming the task
Once you have the handle, suspending the task is one line: vTaskSuspend(defaultTaskHandle); When you call this, the loop stops running immediately. The task does not consume CPU time while suspended, and it stays frozen at whatever line it was executing.
To resume it, call vTaskResume(defaultTaskHandle); The task picks up where it left off and continues running the loop normally.
A common pattern is to suspend the loop when you want the ESP32 to do something else — like respond to a button press or run a different task — without the loop interfering. For example:
void loop() { if (buttonPressed) { vTaskSuspend(defaultTaskHandle); handleButtonEvent(); vTaskResume(defaultTaskHandle); } delay(100); }
Note that you cannot suspend a task from within itself and then immediately resume it on the next line — the task is frozen, so the resume line never runs. Instead, suspend the task from a different task or from an interrupt handler.
Why the default task matters
The ESP32 is a dual-core processor running FreeRTOS, which means it can run multiple tasks at the same time. Your Arduino loop() function is not actually a loop in the traditional sense — it is a task that the operating system schedules to run repeatedly. The default task runs at priority 1, which is lower than many system tasks.
When you suspend the default task, other tasks and interrupt handlers keep running. WiFi, Bluetooth, and timer interrupts all continue to work. Only the code in your loop() function pauses. This is useful when you need to stop the main loop temporarily without shutting down the entire system.
If you suspend the task and then try to call Serial functions or access variables that the loop was using, you may see unexpected behavior. Plan your suspension carefully so that other parts of your code do not depend on the loop running.
Including the right header file
To use xTaskGetCurrentTaskHandle(), vTaskSuspend(), and vTaskResume(), you must include the FreeRTOS task header at the top of your sketch:
#include <freertos/task.h>
The Arduino IDE for ESP32 includes FreeRTOS by default, so this header is already available. Without it, the compiler will report that xTaskGetCurrentTaskHandle and the other functions are undefined.
Common mistakes and how to avoid them
One frequent error is trying to suspend the task from within the loop and then resume it on the next line. Once suspended, the task stops executing, so the resume line never runs. Instead, use a separate task or an interrupt handler to control suspension.
Another mistake is forgetting to store the handle in a global variable. If you call xTaskGetCurrentTaskHandle() only in setup() and store it in a local variable, that variable disappears when setup() ends, and you lose the handle. Always use a global TaskHandle_t variable.
A third issue is suspending the task and then expecting Serial output or other operations to continue normally. Serial functions may not work as expected if the task that manages them is suspended. Test your code carefully to see what still works when the default task is paused.
Frequently Asked Questions
What happens to variables in the loop when I suspend the task?
Variables keep their values. The task is frozen in place, so any local variables in the loop remain unchanged. Global variables also keep their values. When you resume the task, it continues from where it stopped, with all variables intact.
Can I suspend the default task from an interrupt handler?
Yes. Interrupt handlers run at a higher priority than tasks, so you can call vTaskSuspendFromISR(defaultTaskHandle) from within an interrupt. Use the ISR version of the function to avoid blocking the interrupt handler itself.
Does suspending the default task affect WiFi or Bluetooth?
No. WiFi and Bluetooth run on separate tasks with higher priorities. Suspending the default task does not stop them. However, if your code was managing WiFi or Bluetooth in the loop, that management pauses until you resume the task.
What is the difference between suspending a task and using delay()?
A delay() pauses the task but keeps it scheduled — the operating system still checks on it regularly. A suspended task is completely frozen and does not consume CPU time. Suspension is more efficient if you need to pause for an extended period.
Can I suspend the task multiple times without resuming it?
Calling vTaskSuspend() multiple times on an already-suspended task has no additional effect. The task remains suspended. You need to call vTaskResume() the same number of times to fully resume it, because FreeRTOS tracks a suspension count.