FreeRTOS
FreeRTOS helps organize embedded work into tasks that can wait for events and communicate through kernel primitives. The design problem remains yours: which work must finish when, who owns each resource, and what happens when the system cannot keep up?
Begin with the MCU port and board support package that match the actual processor and toolchain. Then describe task deadlines, shared peripherals, interrupt interactions, and memory budgets. A kernel can schedule eligible work; it cannot make an oversized workload meet a deadline by enthusiasm alone.
TL;DR
- Choose a supported port and verify the vendor SDK’s API differences.
- Use blocking waits for events instead of unnecessary busy polling.
- Define queue capacity, payload ownership, and overload policy together.
- Keep task, interrupt, and software-timer contexts distinct.
- Check allocation failures and measure stack and timing behavior on the target.
Quick Example
This integration sketch creates a queue of four copied uint32_t values and a consumer task. It assumes an already configured upstream FreeRTOS MCU project with dynamic allocation enabled. Call app_setup once before the application starts its scheduler; stop startup through the board’s approved failure path if it returns pdFAIL.
The submit function is task-context only and returns failure to its caller when it cannot enqueue. The 256 stack-depth argument is illustrative upstream stack words, not a measured budget; some vendor SDKs use different units. The block was source-reviewed, not compiled or run. It does not include a board port, scheduler startup, ISR integration, or a safe public concurrent accessor for last_sample; volatile alone is not synchronization.
Core Concepts
Tasks and waits
A task needs a defined lifetime, stack, priority, and waiting behavior. Upstream task API comments describe stack depth in words; stack high-water measurements also have documented units. Check the actual SDK rather than assuming a byte count. Upstream task interface
Queue ownership
FreeRTOS queues copy a configured number of bytes. Copying a pointer copies its address, not the pointed-to storage. The task-context queue API and ISR-specific alternatives have different constraints. Queue interface comments
Context boundaries
The legacy FreeRTOS tutorial explains that software timer callbacks share the timer daemon task and must avoid blocking that task. This is a conceptual reference, not a current vendor-port configuration guide. Timer and interrupt chapters
Assign the primitive to the communication problem
Choose from the information being transferred. A count, a single event bit, and a sequence of sensor samples are different requirements. Combining them into one generic signal can lose information or create unnecessary storage.
For queued pointers, document who allocates storage, when ownership transfers, and who releases it if sending fails. A pointer to a producer’s temporary stack object can become invalid before the consumer runs. Copying a small value avoids that specific lifetime problem, but large payloads need a deliberate buffering design.
Specify overload behavior before choosing capacity. Can you drop the oldest sample, reject the newest request, block for a bounded interval, or enter a fault state? Each option changes the application’s meaning. A larger queue can absorb a burst while also increasing the age of work waiting to run.
Budget time and memory on the target
Write a simple workload table: trigger, required completion time, expected work, worst credible blocking, and shared dependencies. Priorities should follow that analysis. Raising every important task’s priority only rearranges who waits.
Measure under representative interrupt load and peripheral behavior. A test with an idle bus can miss contention, slow-device recovery, and bursts of incoming data. Include the failure path, because error logging or retries can consume more time than ordinary processing.
Define allocation policy for startup and runtime. Check every creation result and decide what the device should do if a required object cannot be created. Static allocation can make storage ownership more explicit, while dynamic allocation needs a suitable heap implementation and lifecycle discipline. Neither choice proves adequate total RAM.
Treat stack observations as evidence from the exercised paths. A comfortable high-water mark during a shallow test does not establish headroom for the deepest error or library path. Verify overflow detection and fault reporting appropriate to the board, then inspect the cases that drive peak usage.
Integrate interrupts and shared peripherals
Keep interrupt work bounded and hand longer processing to an appropriate task when the design permits it. Verify which interrupt priorities may call the chosen port’s kernel API and whether a woken task requires a port-specific yield operation. The ordinary submit function above is not an ISR entry point.
Give each peripheral an ownership strategy. One task can own a device and receive requests, or several tasks can share it under a reviewed synchronization protocol. Include timeout and cancellation behavior so a stalled device cannot hold unrelated work indefinitely.
For ESP32 projects, distinguish upstream FreeRTOS assumptions from the exact ESP-IDF release and its scheduling, multicore, and stack-size conventions. Do not transplant a single-core priority argument into a different runtime without checking its integration documentation.
Start the pilot with one producer, one consumer, and an observable bounded operation. Exercise queue-full, task-creation failure, missing input, and device timeout before expanding the task graph. Hardware acceptance should demonstrate the application deadline and recovery behavior, not only a task trace.
Best Practices
Make ownership visible
Record who owns queue payloads and peripherals at each point. Ensure failed sends leave ownership unambiguous.
Test overload and recovery
Demonstrate the selected full-queue policy and recovery from stalled work on the actual target. Record data age as well as throughput.
Review the exact port
Use the kernel, vendor SDK, compiler, and board configuration as one tested combination. Repeat timing checks when any of them changes.
Common Mistakes
Bad: Queueing a pointer transfers its allocation automatically.
Correct: Specify the payload lifetime and release responsibility, including unsuccessful enqueue.
Bad: Volatile makes shared state safe across tasks.
Correct: Use synchronization appropriate to the access pattern and target memory model.
Bad: A high priority guarantees a deadline.
Correct: Account for execution, interrupts, blocking, contention, and overload on the real target.
FAQ
Is the example a complete firmware project?
No. It is a reviewed queue/task sketch requiring a configured port, board startup, allocation policy, and target testing.
Can software timer callbacks wait on a full queue?
Avoid blocking the shared timer daemon. Use a nonblocking operation with an explicit failure policy or hand work to a task.
How large should a queue be?
Derive it from burst size, consumer progress, acceptable waiting age, and overload behavior. Verify that model under target load.
Related Topics
- Rtos — Real-time scheduling and deadline foundations.
- ESP32 — Vendor platform integration and device context.
- Embedded Iot — Embedded system design and lifecycle topics.