| name | freertos-rtos |
| description | FreeRTOS — gestion des tâches, files, sémaphores, timers, scheduling, ports ARM/RISC-V |
| category | edge-ai |
| author | E.V.A |
| version | 1.0.0 |
FreeRTOS — Système d'Exploitation Temps Réel Embarqué
Vue d'ensemble
FreeRTOS est le RTOS open-source le plus déployé au monde sur MCU. Kernel ~6-12 KB, préemptif, priorités fixes, 100% C. Ce guide couvre la configuration avancée, les patterns de synchronisation, l'optimisation mémoire et les ports ARM/RISC-V.
Architecture du Noyau
Composants principaux
FreeRTOS Kernel
├── Tâches (Task) — threads coopératifs/préemptifs
│ ├── États : Ready, Running, Blocked, Suspended
│ └── Context switch via PendSV (ARM) / Machine SWI (RISC-V)
├── Synchronisation
│ ├── Queue — FIFO générique, ISR-safe
│ ├── Semaphore — binaire, compteur, mutex (avec inheritance)
│ └── Event Group — 24 bits de flags
├── Timers
│ └── Software Timer — callback, auto-reload ou one-shot
├── Memory Management
│ └── heap_1..heap_5 — stratégies d'allocation
└── Ports — 40+ architectures (ARM, RISC-V, AVR, PIC, TriCore)
Configuration (FreeRTOSConfig.h)
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ ((unsigned long) 168000000)
#define configTICK_RATE_HZ ((TickType_t) 1000)
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE ((unsigned short) 128)
#define configTOTAL_HEAP_SIZE ((size_t) (64 * 1024))
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 0
#define configUSE_MALLOC_FAILED_HOOK 1
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configASSERT(x) if(!(x)) { taskDISABLE_INTERRUPTS(); while(1); }
Gestion des Tâches
Création
StackType_t task_stack[256];
StaticTask_t task_buf;
void vTaskFunction(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(100));
}
}
TaskHandle_t xTask = xTaskCreateStatic(
vTaskFunction, "TaskName",
256,
NULL,
3,
task_stack,
&task_buf
);
xTaskCreate(vTaskFunction, "TaskName", 256, NULL, 3, NULL);
Transitions d'état
┌──────────┐
│ Running │ ←── Sélectionné par le scheduler
└────┬─────┘
│ Preempted (tick IRQ, priorité plus haute prête)
┌───────┴───────┐
│ │
┌────▼────┐ ┌──────▼───────┐
│ Ready │ │ Blocked │ ← vTaskDelay, queue pend, sem pend
└────▲────┘ └──────▲───────┘
│ │ vTaskResume / Timeout / Data available
└───────────────┘
Priorités et Inheritance
Idle Hook et Stack Overflow
void vApplicationIdleHook(void) {
__asm volatile("WFI");
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
(void) pcTaskName;
taskDISABLE_INTERRUPTS();
while(1);
}
Queues (Files de messages)
Création et Usage
QueueHandle_t xQueue = xQueueCreate(10, sizeof(struct DataPacket));
struct DataPacket pkt = {.id = 42, .value = 3.14f};
xQueueSend(xQueue, &pkt, pdMS_TO_TICKS(100));
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(xQueue, &pkt, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
struct DataPacket rx;
if (xQueueReceive(xQueue, &rx, portMAX_DELAY) == pdPASS) {
process_packet(&rx);
}
Queue Set (Plusieurs files)
QueueSetHandle_t xSet = xQueueCreateSet(5);
xQueueAddToSet(xUART_Queue, xSet);
xQueueAddToSet(xCAN_Queue, xSet);
xQueueAddToSet(xButton_Queue, xSet);
QueueSetMemberHandle_t xMember;
xMember = xQueueSelectFromSet(xSet, portMAX_DELAY);
if (xMember == xUART_Queue) {
xQueueReceive(xUART_Queue, &data, 0);
} else if (xMember == xCAN_Queue) { ... }
Sémaphores
Binaire
SemaphoreHandle_t xSem = xSemaphoreCreateBinary();
xSemaphoreGiveFromISR(xSem, &xHigherPriorityTaskWoken);
xSemaphoreTake(xSem, pdMS_TO_TICKS(1000));
Compteur
SemaphoreHandle_t xCountSem = xSemaphoreCreateCounting(10, 3);
Mutex (avec Priority Inheritance)
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
xSemaphoreTake(xMutex, portMAX_DELAY);
xSemaphoreGive(xMutex);
SemaphoreHandle_t xRecMutex = xSemaphoreCreateRecursiveMutex();
xSemaphoreTakeRecursive(xRecMutex, portMAX_DELAY);
xSemaphoreGiveRecursive(xRecMutex);
Deadlock Prevention
void safe_transfer(SemaphoreHandle_t m1, SemaphoreHandle_t m2) {
if (xSemaphoreTake(m1, pdMS_TO_TICKS(100)) == pdTRUE) {
if (xSemaphoreTake(m2, pdMS_TO_TICKS(100)) == pdTRUE) {
xSemaphoreGive(m2);
}
xSemaphoreGive(m1);
}
}
Event Groups
EventGroupHandle_t xEG = xEventGroupCreate();
xEventGroupSetBits(xEG, BIT_0 | BIT_3);
EventBits_t bits = xEventGroupWaitBits(
xEG,
BIT_0 | BIT_3,
pdTRUE,
pdTRUE,
pdMS_TO_TICKS(1000)
);
Software Timers
TimerHandle_t xTimer = xTimerCreate(
"LED",
pdMS_TO_TICKS(500),
pdTRUE,
NULL,
vTimerCallback
);
void vTimerCallback(TimerHandle_t xTimer) {
toggle_LED();
}
xTimerStart(xTimer, 0);
Gestion Mémoire
Stratégies heap (heap_1..heap_5)
| Stratégie | Allocation | Free | Fragmentation | Cas d'usage |
|---|
| heap_1 | Simple array | NON | N/A | Sécurité critique, pas de delete |
| heap_2 | Best-fit list | OUI | OUI | Anciens projets |
| heap_3 | malloc/free (newlib) | OUI | Selon libc | Avec OS complet |
| heap_4 | Best-fit + coalesce | OUI | NON | Recommandé général |
| heap_5 | heap_4 + régions non-contiguës | OUI | NON | RAM externe + interne |
UBaseType_t free_bytes = xPortGetFreeHeapSize();
UBaseType_t min_free = xPortGetMinimumEverFreeHeapSize();
printf("Heap used: %d bytes\n", configTOTAL_HEAP_SIZE - free_bytes);
Allocation Statique (recommandé critique)
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 0
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
Task Notifications (ultra-rapide)
xTaskNotifyGive(xTaskConsumer);
xTaskNotify(xTaskConsumer, BIT_0, eSetBits);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
uint32_t ulValue;
xTaskNotifyWait(0x00, ULONG_MAX, &ulValue, portMAX_DELAY);
Port ARM Cortex-M
Context Switch via PendSV
; PendSV_Handler — context switch (appelé par yield ou tick IRQ)
; Sauvegarde/Restaure R4-R11, PSP
__asm void xPortPendSVHandler(void) {
mrs r0, psp
isb
ldr r3, =pxCurrentTCB
ldr r2, [r3]
stmdb r0!, {r4-r11} ; Sauvegarder R4-R11
str r0, [r2] ; pxCurrentTCB->pxTopOfStack = PSP
stmdb sp!, {r3, r14}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}
ldr r1, [r3]
ldr r0, [r1] ; Nouveau pxTopOfStack
ldmia r0!, {r4-r11} ; Restaurer R4-R11
msr psp, r0
isb
bx r14
}
Critical Sections
#define taskENTER_CRITICAL() portDISABLE_INTERRUPTS()
#define taskEXIT_CRITICAL() portENABLE_INTERRUPTS()
Port RISC-V
Machine-mode trap handler
.section .text
.globl trap_handler
.type trap_handler, @function
.align 6 # MTVEC aligné 64 bytes
trap_handler:
# Sauvegarder contexte
addi sp, sp, -32*4
sw x1, 1*4(sp) # ra
sw x5, 5*4(sp) # t0
# ... sauvegarder tous les registres (x1-x31)
# Lire MCAUSE pour identifier la source
csrr t0, mcause
li t1, 0x80000000
and t1, t0, t1 # Bit d'interruption
beqz t1, 1f # Exception (pas d'interruption)
# Interruption — appeler vPortSysTickHandler
csrr a0, mepc
call xPortSysTickHandler
j 2f
1: # Exception
call vApplicationExceptionHandler
2: # Restaurer contexte
lw x1, 1*4(sp)
# ... restaurer tous les registres
addi sp, sp, 32*4
mret
Profiling et Debug
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configGENERATE_RUN_TIME_STATS 1
void configureTimerForRunTimeStats(void) {
TIM2->PSC = 0;
TIM2->ARR = 0xFFFF;
TIM2->CR1 |= TIM_CR1_CEN;
}
unsigned long getRunTimeCounterValue(void) {
return TIM2->CNT;
}
TaskStatus_t *pxTaskStatusArray = pvPortMalloc(
uxTaskGetNumberOfTasks() * sizeof(TaskStatus_t)
);
UBaseType_t uxArraySize = uxTaskGetSystemState(
pxTaskStatusArray, uxTaskGetNumberOfTasks(), NULL
);
Trace (SEGGER SystemView / Percepio)
traceTASK_SWITCHED_IN();
vTraceSetQueueName(xQueue, "UART_TX");
vTracePrint(1, "State machine enter RUN");
Pitfalls
- Stack overflow : configCHECK_FOR_STACK_OVERFLOW=2 (registre), mais utiliser aussi des patterns (0xa5a5a5a5)
- ISR blocking : Jamais
xSemaphoreTake() ou xQueueReceive() avec timeout > 0 dans une ISR
- Priorité ISR ARM : configMAX_SYSCALL_INTERRUPT_PRIORITY = éviter de masquer des NMI
- Tickless idle : Le timer doit être configurable pour one-shot — vérifier le port
- Mutex vs Semaphore : Mutex a inheritance — utiliser pour exclusion ; Semaphore pour signalisation
- Task notification overflow : Un appel sans consume peut saturer (max 1 notification)
- heap_4 fragmentation : Malgré coalesce, allocations de tailles très diverses fragmentent
- FreeRTOS+TCP : Nécessite configIP_TASK_STACK_SIZE ~512+ pour TCP stack
Ressources