In our last discussion, we explored the basics of FreeRTOS and how it enables multitasking on microcontrollers like the ESP32. Today, we're going further under the hood—examining how the FreeRTOS scheduler works and how tasks are prioritized, delayed, and preempted in real time.
We’ll demonstrate this using a fun, realistic example: a smart irrigation controller running on the ESP32.
🌱 Why FreeRTOS Scheduling Matters
If you’ve ever wondered how multiple things can happen “at the same time” in embedded systems—like reading sensors, blinking LEDs, or responding to inputs—FreeRTOS is the answer. It allows your program to switch between tasks so efficiently that it feels like everything is running in parallel.
⏱️ The FreeRTOS Tick and Scheduler
Here’s what’s really going on under the hood:
-
A special hardware timer (called the tick timer) triggers an interrupt every few milliseconds.
-
This interrupt activates the scheduler, which checks for any task that’s ready to run.
-
The highest-priority task gets to run first.
-
Tasks with the same priority take turns using a method called time slicing.
All of this happens at lightning speed—typically every 1 millisecond!
📋 How FreeRTOS Chooses Tasks
Each task in FreeRTOS is given a priority number. A higher number means higher priority. The scheduler chooses the highest-priority READY task to run.
Tasks can transition between several states:
-
READY: Can run when scheduled
-
RUNNING: Currently executing
-
BLOCKED: Waiting (e.g., due to delay)
-
SUSPENDED: Manually paused
-
DELETED: Terminated by the program
If a high-priority task becomes ready while a lower one is running, preemption occurs—FreeRTOS instantly stops the lower one and gives CPU time to the higher.
🌦️ Example: Smart Irrigation with ESP32
Let’s simulate a smart irrigation controller:
-
Task 1: Watering task — simulates turning on a water pump for 100ms at intervals.
-
Task 2: Soil monitoring task — prints a mock soil moisture level every 50ms.
-
Task 3: Controller task — toggles the watering system on/off every few seconds.
We’ll assign higher priority to the monitoring task, so it can interrupt watering if needed.
The circuit diagram is shown below.
✅ Arduino Code for ESP32
#include <Arduino.h>
// Define GPIO pins for real hardware
#define MOISTURE_SENSOR_PIN 34 // Analog-capable pin (A0 equivalent on ESP32)
#define RELAY_PIN 26 // Digital pin to control relay (pump)
// Task handles
TaskHandle_t ReadTaskHandle;
TaskHandle_t WaterPumpTaskHandle;
// Shared moisture level variable
volatile int moistureLevel = 0;
// Moisture threshold (adjust based on sensor output)
const int dryThreshold = 2000; // Value below which soil is considered dry
void TaskReadSensor(void *pvParameters) {
while (1) {
moistureLevel = analogRead(MOISTURE_SENSOR_PIN);
Serial.print("Soil Moisture Reading: ");
Serial.println(moistureLevel);
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1 second delay
}
}
void TaskControlPump(void *pvParameters) {
while (1) {
if (moistureLevel < dryThreshold) {
Serial.println("Soil is dry. Activating water pump...");
digitalWrite(RELAY_PIN, HIGH); // Turn on relay
vTaskDelay(2000 / portTICK_PERIOD_MS); // Keep pump on for 2 seconds
digitalWrite(RELAY_PIN, LOW); // Turn off relay
Serial.println("Pump deactivated.");
}
vTaskDelay(5000 / portTICK_PERIOD_MS); // Check every 5 seconds
}
}
void TaskLogger(void *pvParameters) {
while (1) {
Serial.println("System running...");
vTaskDelay(3000 / portTICK_PERIOD_MS); // Every 3 seconds
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("ESP32 FreeRTOS Moisture Control");
pinMode(MOISTURE_SENSOR_PIN, INPUT);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW); // Make sure pump is OFF initially
// Create tasks
xTaskCreate(TaskReadSensor, "ReadMoisture", 2048, NULL, 1, &ReadTaskHandle);
xTaskCreate(TaskControlPump, "WaterPump", 2048, NULL, 2, &WaterPumpTaskHandle);
xTaskCreate(TaskLogger, "Logger", 1024, NULL, 1, NULL);
}
void loop() {
// loop() is not used in FreeRTOS, but it still runs as a task
}
Pins Used:
Component | ESP32 GPIO | Description |
---|---|---|
Soil Moisture Sensor | GPIO 34 | ADC input (analogRead) |
Relay Module (Pump) | GPIO 26 | Digital output (HIGH to turn ON) |