FreeRTOS skill for Codex

Task tables, not priority guesswork. Codex starts every FreeRTOS job by tabulating tasks with their priority, stack, blocking call and deadline, which exposes most starvation and deadlock bugs at a glance. It enables asserts and overflow hooks, uses task notifications and stream buffers from ISRs, checks Cortex-M interrupt priorities against configMAX_SYSCALL_INTERRUPT_PRIORITY, and sizes memory from measured watermarks.

  • Embedded & IoT
  • $freertos
  • 172 lines
  • 2 files
  • ~1,750 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- freertos

Installs to ~/.agents/skills/freertos/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $freertos, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and debugs FreeRTOS applications on Cortex-M, ESP32 and RISC-V - task priorities and stack sizing, queues, task notifications, mutexes versus semaphores, FromISR APIs and interrupt priority limits, software timers, heap schemes, and FreeRTOSConfig.h checks. Use when tasks starve or deadlock, a HardFault or configASSERT fires inside the kernel, stacks overflow, an ISR must wake a task, or when laying out the task structure for new firmware.

Use it when

  • Tasks starve, deadlock or miss deadlines
  • A HardFault or assert fires inside the FreeRTOS kernel
  • An interrupt needs to hand work to a task
  • Planning task structure and memory for new RTOS firmware

Use something else when

  • The project is built on Zephyr's kernel: use zephyr-rtos
  • The bug is in register access or shared-variable semantics without an RTOS: use embedded-c

How to check the result

Run the worst-case load with asserts and hooks enabled, then record each task's stack high-water mark and the minimum-ever free heap and confirm 20 % headroom.

The mistake it prevents

Calling a FromISR API from an interrupt whose priority is more urgent than configMAX_SYSCALL_INTERRUPT_PRIORITY corrupts kernel lists and crashes much later.

~/.agents/skills/freertos/SKILL.md

--- name: freertos description: Designs and debugs FreeRTOS applications on Cortex-M, ESP32 and RISC-V - task priorities and stack sizing, queues, task notifications, mutexes versus… license: MIT ---

Done well means: every task has a written reason for its priority and a measured stack high-water mark, ISRs only signal and never wait, shared data has exactly one owner or one lock, and configASSERT plus overflow and malloc-failure hooks are on in every debug build.

Inspect first

  1. Read AGENTS.md, then FreeRTOSConfig.h (or sdkconfig on ESP-IDF, where it is generated) and note: configTICK_RATE_HZ, configMAX_PRIORITIES, configCHECK_FOR_STACK_OVERFLOW, configUSE_MALLOC_FAILED_HOOK, configASSERT, configSUPPORT_STATIC_ALLOCATION, configMAX_SYSCALL_INTERRUPT_PRIORITY.
  2. Find which heap file is linked (heap_1.c … heap_5.c) and configTOTAL_HEAP_SIZE.
  3. List every xTaskCreate* call: name, stack, priority, what it blocks on. Draw that table before changing anything; most bugs are visible in it.

Units trap

PortusStackDepth in xTaskCreate means
Vanilla FreeRTOS (STM32, NXP, RP2040 SDK port)Words (StackType_t), so 256 = 1 KB on 32-bit
ESP-IDFBytes, so 256 is far too small

Debug configuration (minimum)

#define configASSERT(x)  do { if (!(x)) { taskDISABLE_INTERRUPTS(); for (;;); } } while (0)
#define configCHECK_FOR_STACK_OVERFLOW  2
#define configUSE_MALLOC_FAILED_HOOK    1
#define configUSE_TRACE_FACILITY        1
#define configRECORD_STACK_HIGH_ADDRESS 1
void vApplicationStackOverflowHook(TaskHandle_t task, char *name) {
    (void)task;
    log_panic("stack overflow: %s", name);   // keep it minimal; the stack is already bad
    for (;;) {}
}
void vApplicationMallocFailedHook(void) { log_panic("malloc failed"); for (;;) {} }

Halt-on-assert is right in development; in production log the location and reset through the watchdog instead.

Priority design

  • Higher number means higher priority. Idle is 0. Keep configMAX_PRIORITIES small (5-7) so the plan stays readable.
  • Assign by deadline, shortest deadline highest (rate monotonic), not by “importance”.
  • Every task above idle must block (queue, notification, delay) or lower tasks starve.
  • The timer daemon runs callbacks at configTIMER_TASK_PRIORITY; slow callbacks delay all timers. Keep them short or forward work to a task.
Prio  Task        Blocks on                 Deadline
4     motor_ctl   notification from TIM ISR 1 ms
3     comms_rx    UART DMA idle notification 5 ms
2     sensor      vTaskDelayUntil 100 ms    100 ms
1     logger      queue (depth 16)          best effort
0     idle        -                         -

Choosing the primitive

NeedUseNot
ISR wakes one known taskDirect task notification (vTaskNotifyGiveFromISR / ulTaskNotifyTake)Binary semaphore (slower, more RAM)
Pass data between tasksQueue of small structs or pointers into a poolGlobal variable plus flag
Byte stream from ISR to one taskStream bufferQueue of single bytes
Protect a shared resourceMutex (priority inheritance)Binary semaphore (no inheritance, priority inversion)
Wait for several conditionsEvent groupPolling flags
Count resourcesCounting semaphore

ISR to task

static TaskHandle_t s_rx_task;

void USART2_IRQHandler(void) {
    BaseType_t woken = pdFALSE;
    clear_irq_flags();
    vTaskNotifyGiveFromISR(s_rx_task, &woken);
    portYIELD_FROM_ISR(woken);              // switch now if the woken task is higher priority
}

static void rx_task(void *arg) {
    for (;;) {
        if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)) == 0) {
            continue;                       // timeout: check link health
        }
        drain_rx_buffer();
    }
}

Hard rules:

  • Only ...FromISR APIs inside interrupts. Never a blocking call, never a mutex.
  • On Cortex-M, any ISR that calls a FreeRTOS API must have a priority numerically equal to or greater than configMAX_SYSCALL_INTERRUPT_PRIORITY (lower urgency). A higher-urgency ISR calling the API corrupts the kernel; configASSERT in vPortValidateInterruptPriority catches it if enabled. On STM32, use NVIC_PRIORITYGROUP_4 so all bits are preemption bits.
  • ISRs above that threshold are allowed but must not touch the kernel at all.

Periodic tasks

static void sensor_task(void *arg) {
    TickType_t last = xTaskGetTickCount();
    for (;;) {
        sample();
        xTaskDelayUntil(&last, pdMS_TO_TICKS(100));   // fixed period, no drift
    }
}

vTaskDelay drifts by the execution time. pdMS_TO_TICKS rounds down; with a 100 Hz tick, pdMS_TO_TICKS(5) is 0, which means “do not block”. Check tick rate before small delays.

Memory

  • Prefer static allocation for tasks, queues and buffers created at boot (xTaskCreateStatic, xQueueCreateStatic); no heap failure possible after start.
  • If heap is used, heap_4 (coalescing) is the default choice; heap_5 when RAM is in several regions. Never free and reallocate in steady state.
  • Measure, then size: run the worst-case path, then uxTaskGetStackHighWaterMark(NULL) per task (words left, minimum ever) and xPortGetMinimumEverFreeHeapSize(). Keep at least 20-25 % stack headroom.
  • printf and snprintf with floats can use 1-2 KB of stack on newlib; tasks that log need more than tasks that do not.

Deadlock and starvation checklist

  • Two mutexes taken in different orders in different tasks? Fix the order globally
  • Mutex held across a blocking call (queue wait, delay, I/O)? Restructure
  • portMAX_DELAY on a queue whose producer can die? Use a timeout and log it
  • A high-priority task polling without blocking? It starves everything below
  • Deleted tasks never cleaned up because idle never runs? Idle needs CPU time to free memory after vTaskDelete

Tooling

  • vTaskList / vTaskGetRunTimeStats (need configUSE_STATS_FORMATTING_FUNCTIONS and a run-time counter) give a one-shot task table over UART.
  • SEGGER SystemView or Percepio Tracealyzer show context switches and priority inversion; use them when timing bugs do not reproduce with logs.
  • Debugger RTOS awareness (OpenOCD -rtos FreeRTOS, J-Link, STM32CubeIDE) shows every task’s stack; check it on a HardFault before reading code.

Verification

  • Task table (name, priority, stack, blocks on, deadline) written in the code or docs
  • All asserts and hooks enabled in debug build; long run without triggering them
  • High-water marks recorded under worst-case load; headroom at least 20 %
  • Minimum-ever free heap recorded; stays constant after boot
  • Interrupt priorities checked against configMAX_SYSCALL_INTERRUPT_PRIORITY

Report

**Kernel:** FreeRTOS V11.1 on STM32F407, 1 kHz tick, heap_4 32 KB
**Root cause:** UART ISR at NVIC prio 2 called xQueueSendFromISR (limit is 5) -> list corruption
**Change:** ISR prio 6; queue of bytes replaced by stream buffer; logger mutex now inherits priority
**Measured:** stack high-water min 31 % (logger), heap min-ever 9.8 KB constant over 6 h

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "FreeRTOS"
  short_description: "Task tables, not priority guesswork"
  default_prompt: "$freertos configASSERT fires in vPortValidateInterruptPriority, what is wrong?"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem