Monday, 13 October 2025

FreeRTOS Tasks: Create, Run, and Delete (the Practical Way)

In RTOS land, you split your app into tasks, tiny, schedulable functions that each do one job (e.g., read sensor, refresh display, handle keys). Here’s the clean, code-first way to write handlers, create tasks, pass data, and mind stacks/priorities.


✅ Task Handler (a schedulable function)

A task is just a C function that usually runs forever. If you ever leave the loop, delete yourself first.

#include "FreeRTOS.h" #include "task.h" static void TaskFn(void *pv) { for (;;) { // do work vTaskDelay(pdMS_TO_TICKS(10)); } vTaskDelete(NULL); // if you ever break the loop }

๐Ÿ—️ Create a Task with xTaskCreate()

Pass handler, name, stack depth in words (not bytes), a parameter, priority, and an optional handle.

#include "FreeRTOS.h" #include "task.h" TaskHandle_t t1; BaseType_t ok = xTaskCreate(TaskFn, "Task-1", 200, // stack words (e.g., 200*4 B on Cortex-M) NULL, // pvParameters 2, // priority (0..configMAX_PRIORITIES-1) &t1); // out handle configASSERT(ok == pdPASS);

๐Ÿ“จ Passing Data to a Task

Give each task its own message via pvParameters. Cast back inside.

static void PrinterTask(void *pv) { const char *msg = (const char *)pv; for (;;) { puts(msg); vTaskDelay(pdMS_TO_TICKS(500)); } } xTaskCreate(PrinterTask, "P1", 200, (void*)"Hello from T1", 2, NULL); xTaskCreate(PrinterTask, "P2", 200, (void*)"Hello from T2", 2, NULL);

๐Ÿงต Local vs. static in Task Functions

  • Local (non-static) lives in the task’s private stack → each task instance gets its own copy.

  • static lives in global data → shared across all instances.

static void Work(void *pv) { int counter = 0; // per-task copy (stack) static int shared = 0; // one shared copy (void)pv; for (;;) { counter++; shared++; } }

๐Ÿง  Priorities (who runs first?)

  • Range: 0 .. (configMAX_PRIORITIES - 1) (set in FreeRTOSConfig.h).

  • Higher number = higher urgency.

xTaskCreate(TaskFn, "Low", 200, NULL, 1, NULL); xTaskCreate(TaskFn, "High", 200, NULL, 3, NULL);

๐Ÿ“‹ Minimal Two-Task Demo

static void Sensor(void *pv){ for(;;){ /* read temp */ vTaskDelay(pdMS_TO_TICKS(100)); } } static void Display(void *pv){ for(;;){ /* draw */ vTaskDelay(pdMS_TO_TICKS(50)); } } xTaskCreate(Sensor, "Sensor", 256, NULL, 2, NULL); xTaskCreate(Display, "Display", 256, NULL, 2, NULL); vTaskStartScheduler(); // never returns

⚡ Pro Tips

  • Stack depth is in words, not bytes (configSTACK_DEPTH_TYPE). 200 on Cortex-M ≈ 800 B.

  • Check return: configASSERT(xTaskCreate(...) == pdPASS);

  • Keep handlers inside for(;;); call vTaskDelete(NULL) if you must exit.

  • Be stingy with large local arrays; they burn task stack.

  • Too many priority levels → more context switches → more RAM/time overhead.

  • In STM32Cube projects, put app code inside USER CODE BEGIN/END blocks.


๐ŸŽฏ Conclusion

Treat tasks as small, focused loops. Create them with clear priorities, right-sized stacks, and explicit parameters. Keep handlers lean, delete on exit, and your scheduler will stay happy.

Written By: Musaab Taha


This article was improved with the assistance of AI.

No comments:

Post a Comment