ESP32 Part 3 - Advanced

E15. FreeRTOS Tasks and Dual-Core Basics

Run two jobs at the same time: a task blinks one LED while loop() blinks another at a different speed.

Estimated time 3 hours

Learning outcomes

  • Say what a task is: a second loop that runs at the same time as loop()
  • Start a task with xTaskCreate
  • Use delay() in a task without stopping loop()
  • Choose between millis() and a task

Parts and preparation

ESP32 DevKit, USB cable, two LEDs with 220-330 ohm resistors on GPIO 2 and GPIO 4 (or the on-board LED plus one external LED).

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

What you will do

On the Uno, delay() stops everything, so you learned millis() to do two things at once. The ESP32 has another way: tasks.

The sketch starts a task that blinks the LED on GPIO 4 once a second, while loop() blinks the LED on GPIO 2 four times faster. Both use plain delay().

Core 0 worker task and core 1 loop LED blink on dual-core ESP32
loop() and a task take turns so quickly that both seem to run at once.

What is a task?

The ESP32 runs a small operating system called FreeRTOS. It shares the processor between tasks, switching between them many times a second. Your loop() is already one task.

A task you write looks like a function with a while (true) loop inside, so it never ends. When a task calls delay(), only that task waits - the others keep running. A classic ESP32 also has two processor cores, so two tasks can really run at the same moment.

Part of the callMeaning
blinkTaskThe function the task runs
"blink"A name for the task (any short text)
2048Memory for the task - raise it if the board keeps restarting
NULL, 1, NULLCopy as they are (no input, normal priority, no handle)

Task or millis()?

millis() in one loop() is still a good choice for simple jobs, and you already know it. Tasks help when one job is slow - such as waiting for a website - and another must keep going.

Be careful when two tasks use the same variable or the same Serial port at once. Bigger projects pass data between tasks with a FreeRTOS queue - a topic for after this course.

Wiring and safe build sequence

  1. GPIO 2 -> 220-330 ohm -> LED -> GND (or the on-board LED)
  2. GPIO 4 -> 220-330 ohm -> LED -> GND
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.

Worked sketch: Two LEDs, two speeds

Download .ino

What this sketch is for: The GPIO 2 LED blinks fast and the GPIO 4 LED blinks slowly, and neither one waits for the other.

const int fastLed = 2;                    // blinked by loop()
const int slowLed = 4;                    // blinked by the task

void blinkTask(void* parameter) {         // the task (copy this first line as it is)
  pinMode(slowLed, OUTPUT);               // the slow LED pin is an output
  while (true) {                          // a task never ends, so it loops for ever
    digitalWrite(slowLed, HIGH);          // slow LED on
    delay(1000);                          // wait 1 second - only this task waits
    digitalWrite(slowLed, LOW);           // slow LED off
    delay(1000);                          // wait 1 second
  }
}

void setup() {                            // runs once
  pinMode(fastLed, OUTPUT);               // the fast LED pin is an output
  xTaskCreate(blinkTask, "blink", 2048, NULL, 1, NULL); // start the task running
}

void loop() {                             // loop() is a task too
  digitalWrite(fastLed, HIGH);            // fast LED on
  delay(250);                             // wait a quarter of a second
  digitalWrite(fastLed, LOW);             // fast LED off
  delay(250);                             // wait a quarter of a second
}

How the code works

  1. void* parameter is how FreeRTOS writes 'this task takes no input that matters' - copy it as it is.
  2. while (true) keeps the task alive. A task function must never reach its closing bracket.
  3. Try it the Uno way: move the slow LED code into loop(). The two LEDs then take turns instead of blinking independently.

Test and record evidence

Expected result: The GPIO 2 LED blinks about twice a second while the GPIO 4 LED blinks once every 2 seconds, each at its own steady speed.

Practical evidence checklist

Common faults and checks
  • Slow LED never lights: check its wiring on GPIO 4, and that xTaskCreate is inside setup().
  • Board keeps restarting: raise 2048 to 4096.
  • Only one LED on your board: use the on-board LED for GPIO 2 and add one external LED on GPIO 4.
Challenge (extension - optional): Add a second task that prints "tick" on Serial every 3 seconds (add Serial.begin(115200) to setup()).

Check your understanding

Q1. What is a task?

Show answer

A function that runs at the same time as loop(), shared out by FreeRTOS.

Q2. What operating system runs under ESP32 sketches?

Show answer

FreeRTOS.

Q3. Which command starts a task?

Show answer

xTaskCreate.

Q4. When a task calls delay(), what happens to loop()?

Show answer

Nothing - loop() keeps running. Only the task waits.

Q5. Why does a task use while (true)?

Show answer

So it never ends.

Q6. What should you try if the board keeps restarting?

Show answer

Give the task more memory: raise 2048 to 4096.

Q7. When is millis() in loop() still a good choice?

Show answer

For simple jobs that do not have to wait for something slow.