Day 2 of 5
⏱ ~60 minutes
RTOS in 5 Days — Day 2

Tasks and Scheduling

The scheduler decides which task runs. Today you'll understand priorities, preemption, task states, and how to use FreeRTOS task management APIs.

Task States

A FreeRTOS task is in one of four states: Running: currently executing on the CPU (only one at a time in single-core). Ready: can run, waiting for CPU. Blocked: waiting for an event (timer, queue, semaphore). Suspended: explicitly suspended, not scheduled. The scheduler always runs the highest-priority ready task. Among equal-priority tasks, round-robin scheduling with a configurable time slice (default 1 tick = 1ms).

Priorities and Preemption

FreeRTOS priorities: 0 = lowest (idle task), configMAX_PRIORITIES-1 = highest. When a higher-priority task becomes ready, it immediately preempts the running task. This is preemptive scheduling — it happens mid-instruction-stream, controlled by the PendSV exception. Set priorities by function: safety-critical ISR handlers at highest, sensor reading at medium, logging at low. The idle task (priority 0) runs when nothing else is ready — put garbage collection or power saving here.

Stack Size and Overflow Detection

Each task has a dedicated stack. Stack overflow = corruption of adjacent memory = hard fault or silent data corruption (worse). Configure stack size: estimate maximum call depth × bytes per stack frame + local variables. Use uxTaskGetStackHighWaterMark(NULL) to find the minimum free stack space ever recorded. Enable stack overflow checking in FreeRTOSConfig.h: configCHECK_FOR_STACK_OVERFLOW 2 uses a canary pattern to detect overflow.

c
// Task priorities, states, and stack monitoring
#include "FreeRTOS.h"
#include "task.h"

TaskHandle_t xSensorHandle = NULL;
TaskHandle_t xLogHandle    = NULL;

// High priority: read sensor every 10ms
void vSensorTask(void *pv) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(10);

    while (1) {
        // vTaskDelayUntil gives precise periodic execution
        // Unlike vTaskDelay, it accounts for execution time
        vTaskDelayUntil(&xLastWakeTime, xPeriod);

        float sensor_val = readSensor(); // your sensor code
        // Process sensor_val, post to queue, etc.
    }
}

// Low priority: log stack usage every 5 seconds
void vMonitorTask(void *pv) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(5000));

        // Stack high water mark: min free stack ever
        UBaseType_t sensorHWM  = uxTaskGetStackHighWaterMark(xSensorHandle);
        UBaseType_t logHWM     = uxTaskGetStackHighWaterMark(xLogHandle);
        UBaseType_t monitorHWM = uxTaskGetStackHighWaterMark(NULL); // this task

        printf("Stack HWM (words): sensor=%u log=%u monitor=%u\n",
               sensorHWM, logHWM, monitorHWM);
        printf("Free heap: %u bytes\n", (unsigned)xPortGetFreeHeapSize());
    }
}

// Stack overflow hook (configCHECK_FOR_STACK_OVERFLOW = 2)
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
    printf("STACK OVERFLOW: task '%s'\n", pcTaskName);
    // Log to persistent storage if available, then reset
    NVIC_SystemReset();
}

void vApplicationMallocFailedHook(void) {
    printf("MALLOC FAILED\n");
    NVIC_SystemReset();
}

int main(void) {
    // Higher priority for sensor task (time-critical)
    xTaskCreate(vSensorTask, "SENSOR", 256, NULL, 3, &xSensorHandle);
    xTaskCreate(vMonitorTask, "MON",   512, NULL, 1, NULL);
    vTaskStartScheduler();
    while (1);
}
💡
Use vTaskDelayUntil() instead of vTaskDelay() for periodic tasks. vTaskDelay(10ms) means '10ms after this line'. vTaskDelayUntil() means '10ms after the last wake — regardless of how long execution took'. This prevents drift accumulation over thousands of iterations.
📝 Day 2 Exercise
Tune Task Priorities and Stack Sizes
  1. Create 3 tasks: high-priority sensor (priority 3), medium-priority compute (priority 2), low-priority log (priority 1). Give each a stack size of 128 words initially.
  2. Add the monitor task to print stack HWM every 5 seconds. Let it run for 1 minute.
  3. Look at the HWM values. Which task uses the most stack? Reduce the others' stack sizes to HWM + 32 words as safety margin.
  4. Enable stack overflow checking (configCHECK_FOR_STACK_OVERFLOW=2 in FreeRTOSConfig.h). Intentionally set a task stack too small. Does the hook fire?
  5. Use vTaskSuspend()/vTaskResume() to pause a task from another task. Observe scheduling behavior.

Day 2 Summary

  • 4 task states: Running, Ready, Blocked (waiting for event), Suspended
  • Highest priority ready task always runs; equal priority tasks time-slice (configTICK_RATE_HZ determines slice)
  • vTaskDelayUntil() prevents drift in periodic tasks — use it instead of vTaskDelay() for control loops
  • uxTaskGetStackHighWaterMark() reveals minimum free stack — tune stack sizes based on real measurements
Challenge

Implement a rate-monotonic scheduling analysis for your tasks. Rate-monotonic theory says a set of periodic tasks is schedulable if the sum of (execution_time/period) ≤ n*(2^(1/n) - 1). For 3 tasks: measure actual execution time of each task with DWT cycle counter. Compute utilization. Is your system schedulable? What happens when you add a 4th task?

Finished this lesson?