>> skills/rtos-patterns

stars: 0
forks: 0
watches: 0
last updated: 2026-03-21 16:24:14

RTOS Patterns for STM32

Provide RTOS integration guidance for STM32 using FreeRTOS (native API and CMSIS-RTOS v2 wrapper).

FreeRTOS + STM32 Integration

HAL Timebase Conflict

STM32 HAL uses SysTick as its timebase by default. FreeRTOS also needs SysTick for its scheduler tick. This creates a conflict.

Solution: Configure HAL to use a different timer (e.g., TIM6 or TIM7) for its timebase:

  • In STM32CubeMX: Pinout & Configuration -> SYS -> Timebase Source -> TIM6
  • Manually: Implement HAL_InitTick() and HAL_GetTick() using a basic timer

FreeRTOS keeps SysTick for its scheduler tick.

FreeRTOSConfig.h Key Parameters

Extract from documentation or configure based on the target MCU:

#define configCPU_CLOCK_HZ              (SystemCoreClock)  // From RCC config
#define configTICK_RATE_HZ              ((TickType_t)1000) // 1ms tick
#define configMAX_PRIORITIES            (7)                // 0-6 priority levels
#define configMINIMAL_STACK_SIZE        ((uint16_t)128)    // Words (512 bytes)
#define configTOTAL_HEAP_SIZE           ((size_t)15360)    // Bytes, adjust to SRAM
#define configUSE_PREEMPTION            1
#define configUSE_MUTEXES               1
#define configUSE_COUNTING_SEMAPHORES   1
#define configUSE_QUEUE_SETS            0

/* Cortex-M interrupt priority configuration - CRITICAL */
#define configPRIO_BITS                 4  // Check RM for __NVIC_PRIO_BITS
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY      15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY  5

Critical: Interrupt Priority Boundary

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY defines the boundary:

  • Interrupts with priority >= this value (lower urgency) CAN use FreeRTOS *FromISR() APIs
  • Interrupts with priority < this value (higher urgency) MUST NOT call any FreeRTOS API
  • This is the most common source of hard faults in FreeRTOS + STM32 projects

Check the reference manual NVIC section for the number of priority bits (__NVIC_PRIO_BITS).

Task Patterns

Task Creation

// Static allocation (recommended - deterministic)
StaticTask_t xTaskBuffer;
StackType_t xStack[256];
xTaskCreateStatic(vTaskFunction, "Name", 256, NULL, 2, xStack, &xTaskBuffer);

// Dynamic allocation
xTaskCreate(vTaskFunction, "Name", 256, NULL, 2, &xHandle);

Stack Sizing Guidelines

  • Minimal task (no printf, no float): 128 words (512 bytes)
  • Task with printf/sprintf: 256+ words (uses significant stack)
  • Task with floating-point: 256+ words (FPU context saving)
  • Task with deep call chains: analyze worst-case depth
  • Use uxTaskGetStackHighWaterMark() to check actual usage

Priority Assignment

  • Priority 0: Idle task (reserved by FreeRTOS)
  • Priority 1: Low-priority background tasks
  • Priority 2-3: Normal application tasks
  • Priority 4-5: Time-sensitive tasks
  • Priority 6 (configMAX_PRIORITIES-1): Highest priority, use sparingly

Synchronization Primitives

Binary Semaphore (Event Signaling)

Use for ISR-to-task signaling (deferred interrupt processing):

SemaphoreHandle_t xSem = xSemaphoreCreateBinary();
// In ISR:  xSemaphoreGiveFromISR(xSem, &xHigherPrioTaskWoken);
// In task: xSemaphoreTake(xSem, portMAX_DELAY);

Mutex (Resource Protection)

Use for mutual exclusion with priority inheritance:

SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
xSemaphoreTake(xMutex, portMAX_DELAY);
// ... access shared resource ...
xSemaphoreGive(xMutex);

Never use a mutex from an ISR.

Queue (Data Transfer)

Use for passing data between tasks or from ISR to task:

QueueHandle_t xQueue = xQueueCreate(10, sizeof(uint32_t));
// Producer: xQueueSend(xQueue, &data, portMAX_DELAY);
// Consumer: xQueueReceive(xQueue, &data, portMAX_DELAY);
// From ISR: xQueueSendFromISR(xQueue, &data, &xHigherPrioTaskWoken);

Task Notifications (Lightweight)

Faster than semaphores/queues for simple signaling:

// Signal:  xTaskNotifyGive(xTaskHandle);
// Wait:    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// From ISR: vTaskNotifyGiveFromISR(xTaskHandle, &xHigherPrioTaskWoken);

ISR Safety Rules

  1. Only use *FromISR() API variants in interrupt handlers
  2. Never use blocking calls in ISRs (no xSemaphoreTake, no xQueueReceive, no vTaskDelay)
  3. Always check and yield: After *FromISR() calls, check xHigherPriorityTaskWoken and call portYIELD_FROM_ISR() if set
  4. Respect priority boundary: Only ISRs with priority >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY may call FreeRTOS APIs

Deferred Interrupt Processing Pattern

void USART2_IRQHandler(void) {
    BaseType_t xHigherPrioTaskWoken = pdFALSE;
    // Minimal ISR work: clear flag, grab data
    uint8_t data = USART2->DR;
    xQueueSendFromISR(xRxQueue, &data, &xHigherPrioTaskWoken);
    portYIELD_FROM_ISR(xHigherPrioTaskWoken);
}

void vRxTask(void *pvParameters) {
    uint8_t data;
    for (;;) {
        xQueueReceive(xRxQueue, &data, portMAX_DELAY);
        // Process data here (heavy work in task context)
    }
}

Heap Management

SchemeDescriptionUse Case
heap_1Allocate only, no freeStatic systems, tasks never deleted
heap_2Best fit, no coalescingFixed-size allocations
heap_3Wraps malloc/freeWhen libc malloc is available
heap_4First fit, coalescingRecommended for most projects
heap_5Like heap_4, multiple regionsSpanning multiple SRAM regions

heap_5 for Multi-Region SRAM

HeapRegion_t xHeapRegions[] = {
    { (uint8_t *)0x20000000, 0x10000 },  // SRAM1: 64KB
    { (uint8_t *)0x2001C000, 0x4000  },  // SRAM2: 16KB
    { NULL, 0 }  // Terminator
};
vPortDefineHeapRegions(xHeapRegions);

Where to Find RTOS-Related Info in Docs

  • RM NVIC section: __NVIC_PRIO_BITS value for configPRIO_BITS
  • RM SysTick section: SysTick configuration (FreeRTOS manages this)
  • AN5365: ST application note on FreeRTOS with STM32 (check docs/application-notes/)
  • Datasheet: Total SRAM size for configTOTAL_HEAP_SIZE calculation

Additional Resources

  • references/rtos-integration-guide.md - Complete FreeRTOS+STM32 setup walkthrough, HAL timebase workaround details, ISR-to-task communication patterns
    Good AI Tools