Tuesday, 14 October 2025

FreeRTOS Scheduling: Start, Preempt, Yield (and What’s Happening in RAM)

Your tasks are in Ready; the scheduler decides who runs. Here’s the practical path to start it, pick a policy (pre-emptive vs co-operative), and understand the heap/TCB story behind xTaskCreate().


✅ Start the Scheduler

vTaskStartScheduler() launches the kernel; it never returns unless heap is insufficient to create the Idle (and timer) task.

configASSERT(xTaskCreate(Task1,"T1",256,NULL,2,NULL)==pdPASS); configASSERT(xTaskCreate(Task2,"T2",256,NULL,2,NULL)==pdPASS); vTaskStartScheduler(); // returns only on failure

⚙️ Scheduling Policies

  • Pre-emptive (configUSE_PREEMPTION 1): the running task can be replaced on ticks or when a higher-priority task becomes ready.

  • Co-operative (configUSE_PREEMPTION 0): tasks voluntarily give up the CPU with taskYIELD() (no forced pre-emption).

// FreeRTOSConfig.h #define configUSE_PREEMPTION 1 #define configTICK_RATE_HZ 1000 // 1 ms tick

🔁 Round-Robin & ⏫ Priority-Based

  • Round-robin: equal-priority ready tasks share the CPU in time slices tied to the RTOS tick.

  • Priority-based: the highest priority ready task runs; when it blocks, the next highest takes over; if a higher priority task unblocks, it pre-empts immediately.


🤝 Co-operative Example (clean prints)

static void Task1(void* pv){ for(;;){ puts("T1"); taskYIELD(); } } static void Task2(void* pv){ for(;;){ puts("T2"); taskYIELD(); } }

If your output looks garbled under pre-emption, protect the shared sink (UART/ITM) with a mutex.


🧠 Behind the Scenes: Heap, TCB, Stacks

  • xTaskCreate() dynamically allocates the TCB and the task’s private stack from the RTOS heap and then links the TCB into the Ready list.

  • Heap size is set by configTOTAL_HEAP_SIZE; many projects use heap4.c (malloc/free over a single heap array).

  • On Cortex-M, task stacks are tracked with PSP (process stack pointer); the TCB stores TopOfStack.

// FreeRTOSConfig.h #define configTOTAL_HEAP_SIZE (75*1024) // tune to your MCU RAM

⚡ Pro Tips

  • Keep for(;;){ … vTaskDelay(...) } or block on queues; avoid busy loops.

  • Few priority levels → fewer context switches (less RAM/time overhead).

  • Always check xTaskCreate() and enable overflow/malloc-fail hooks.

  • Put app code inside USER CODE BEGIN/END in Cube projects to survive regen.

🎯 Conclusion

Choose pre-emptive for responsiveness or co-operative for deterministic prints, start the scheduler, and size the heap/stack sanely, your tasks will play nicely and your logs will, too.


Written By: Musaab Taha


This article was improved with the assistance of AI.

No comments:

Post a Comment