← Back to all posts 260 words

Mastering FreeRTOS Task Scheduling on STM32

•
Habib Rahman
•
2 min read

When transitioning from a simple super-loop (while(1)) architecture to a Real-Time Operating System (RTOS), understanding the scheduler’s behavior is vital for predictable and crash-free execution.

In this article, we dissect how FreeRTOS manages task context switching on ARM Cortex-M4 cores (STM32F4/G4 series) and how to avoid classic concurrency pitfalls.

The Cortex-M PendSV Exception

FreeRTOS relies on the PendSV (Pended Service Call) exception to perform context switches safely. Unlike hardware ISRs that must execute with minimal latency, PendSV is configured with the lowest interrupt priority.

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

static SemaphoreHandle_t xSensorMutex = NULL;

void vSensorTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = pdMS_TO_TICKS(10); // 100 Hz

    for (;;) {
        if (xSemaphoreTake(xSensorMutex, portMAX_DELAY) == pdTRUE) {
            // Read I2C sensor data
            read_imu_registers();
            xSemaphoreGive(xSensorMutex);
        }

        // Deterministic rate execution
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}

Priority Inversion & Mutex Inheritance

Priority inversion occurs when a low-priority task holds a shared resource needed by a high-priority task, while a medium-priority task preempts the low-priority task.

Solution: Priority Inheritance Mutexes

Always use FreeRTOS Mutexes (xSemaphoreCreateMutex()) rather than binary semaphores for shared resources because mutexes implement automatic priority inheritance.

void app_main_init(void) {
    xSensorMutex = xSemaphoreCreateMutex();
    configASSERT(xSensorMutex != NULL);

    xTaskCreate(vSensorTask, "IMU_Task", 256, NULL, 3, NULL);
    xTaskCreate(vTelemetryTask, "Radio_Task", 512, NULL, 2, NULL);
}

Stack Sizing Guidelines

Task NameRecommended Stack (Words)Critical Operations
Sensor_Task256 words (1024 B)I2C/SPI DMA triggers
Network_Task512 words (2048 B)Packet framing & CRC32
CLI_Task192 words (768 B)UART string parsing

Always enable configCHECK_FOR_STACK_OVERFLOW = 2 during development to catch stack overflows before they reach production hardware.

☕

Habib Rahman

@habibrahman

Firmware & Embedded Systems Engineer. Exploring bare-metal C, FreeRTOS, Linux kernel, and hardware telemetry.