/**
* ESP32 Critical Section Demo
*
* Demonstrate how priority inversion can be avoided through the use of
* critical sections guard.
*
* Date: April 19, 2025
* Author: Mike
* License: 0BSD
*/
// You'll likely need this on vanilla FreeRTOS
//#include semphr.h
// Use only core 1 for demo purposes
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Settings
TickType_t cs_wait = 250; // Time spent in critical section (ms)
TickType_t med_wait = 5000; // Time medium task spends working (ms)
// Globals
// Use critical section guard instead of semaphore.
static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
//*****************************************************************************
// Tasks
// Task L (low priority)
void doTaskL(void *parameters) {
TickType_t timestamp;
// Do forever
while (1) {
// Take lock
Serial.println("Task L trying to take lock...");
timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
// xSemaphoreTake(lock, portMAX_DELAY);
portENTER_CRITICAL(&spinlock);
// Say how long we spend waiting for a lock
Serial.print("Task L got lock. Spent ");
Serial.print((xTaskGetTickCount() * portTICK_PERIOD_MS) - timestamp);
Serial.println(" ms waiting for lock. Doing some work...");
// Hog the processor for a while doing nothing
timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
while ( (xTaskGetTickCount() * portTICK_PERIOD_MS) - timestamp < cs_wait);
// Release lock
Serial.println("Task L releasing lock.");
// xSemaphoreGive(lock);
portEXIT_CRITICAL(&spinlock);
// Go to sleep
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
// Task M (medium priority)
void doTaskM(void *parameters) {
TickType_t timestamp;
// Do forever
while (1) {
// Hog the processor for a while doing nothing
Serial.println("Task M doing some work...");
timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
while ( (xTaskGetTickCount() * portTICK_PERIOD_MS) - timestamp < med_wait);
// Go to sleep
Serial.println("Task M done!");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
// Task H (high priority)
void doTaskH(void *parameters) {
TickType_t timestamp;
// Do forever
while (1) {
// Take lock
Serial.println("Task H trying to take lock...");
timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
// xSemaphoreTake(lock, portMAX_DELAY);
portENTER_CRITICAL(&spinlock);
// Say how long we spend waiting for a lock
Serial.print("Task H got lock. Spent ");
Serial.print((xTaskGetTickCount() * portTICK_PERIOD_MS) - timestamp);
Serial.println(" ms waiting for lock. Doing some work...");
// Hog the processor for a while doing nothing
timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
while ( (xTaskGetTickCount() * portTICK_PERIOD_MS) - timestamp < cs_wait);
// Release lock
Serial.println("Task H releasing lock.");
// xSemaphoreGive(lock);
portEXIT_CRITICAL(&spinlock);
// Go to sleep
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
//*****************************************************************************
// Main (runs as its own task with priority 1 on core 1)
void setup() {
// Configure Serial
Serial.begin(115200);
// Wait a moment to start (so we don't miss Serial output)
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Priority Inversion Demo---");
// The order of starting the tasks matters to force priority inversion
// Start Task L (low priority)
xTaskCreatePinnedToCore(doTaskL,
"Task L",
1024,
NULL,
1,
NULL,
app_cpu);
// Introduce a delay to force priority inversion
vTaskDelay(1 / portTICK_PERIOD_MS);
// Start Task H (high priority)
xTaskCreatePinnedToCore(doTaskH,
"Task H",
1024,
NULL,
3,
NULL,
app_cpu);
// Start Task M (medium priority)
xTaskCreatePinnedToCore(doTaskM,
"Task M",
1024,
NULL,
2,
NULL,
app_cpu);
// Delete "setup and loop" task
vTaskDelete(NULL);
}
void loop() {
// Execution should never get here
}