FreeRTOS Reference
This page is a quick reference for using FreeRTOS on the Zynq in EMBS. FreeRTOS is a lightweight real-time operating system that provides multitasking, scheduling, and inter-task communication primitives. For full details, consult the FreeRTOS Reference Manual (PDF).
Note
Setting up FreeRTOS on the Zynq is covered in Practical 5. This page assumes you already have a working FreeRTOS project.
Task Management
Creating Tasks
Tasks are created with xTaskCreate. Each task is an independent function that runs concurrently with other tasks under the FreeRTOS scheduler.
#include "FreeRTOS.h"
#include "task.h"
#define THREAD_STACKSIZE 1024
TaskHandle_t myTaskHandle;
void my_task(void *params) {
while (1) {
xil_printf("Task running\r\n");
vTaskDelay(1000 / portTICK_RATE_MS); // Delay 1 second
}
}
int main() {
xTaskCreate(
my_task, // Task function
"my_task", // Name (for debugging only)
THREAD_STACKSIZE, // Stack size in words
NULL, // Parameters passed to task function
DEFAULT_THREAD_PRIO,// Priority
&myTaskHandle // Handle (can be NULL if not needed)
);
vTaskStartScheduler();
return 0;
}The parameters to xTaskCreate are:
| Parameter | Description |
|---|---|
pvTaskCode | Pointer to the task function. Must have signature void func(void *params). |
pcName | A descriptive name for the task (used by debuggers, not by FreeRTOS itself). |
usStackDepth | Stack size in words (not bytes). On the Zynq ARM, 1 word = 4 bytes. |
pvParameters | A pointer passed as the argument to the task function. Use to pass configuration data. |
uxPriority | Task priority. Higher number = higher priority. |
pxCreatedTask | Optional handle to the created task. Pass NULL if you don’t need it. |
Deleting Tasks
vTaskDelete(myTaskHandle); // Delete a specific task
vTaskDelete(NULL); // Delete the calling task (self-deletion)
Self-deletion is commonly used for setup tasks that create other tasks and then remove themselves, as seen in the EMBS network startup code.
Task Priorities
DEFAULT_THREAD_PRIOis defined in the BSP and is a sensible default priority.configMAX_PRIORITIESdefines the maximum number of priority levels (set inFreeRTOSConfig.h).- Higher number = higher priority. A task with priority 3 will preempt a task with priority 2.
- Tasks at the same priority are scheduled round-robin.
Important
Avoid setting all tasks to the same priority unless you specifically want round-robin scheduling. If a high-priority task never blocks, lower-priority tasks will starve.
Stack Sizes
The THREAD_STACKSIZE define (typically 1024 words = 4096 bytes) is used throughout the EMBS examples. This is usually sufficient for simple tasks.
Guidelines for sizing stacks:
- Each local variable, function call, and interrupt frame consumes stack space.
- Tasks that use
printf/xil_printfor deep call chains need larger stacks. - If a task crashes or produces corrupted output, try doubling its stack size.
Important
Stack overflow is one of the most common causes of mysterious crashes in FreeRTOS. If your system is crashing and you can’t work out why, try increasing the stack size first.
Delays
// Delay for a fixed number of milliseconds (relative delay)
vTaskDelay(500 / portTICK_RATE_MS); // Delay 500ms
// Delay until an absolute time (for periodic tasks)
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
vTaskDelayUntil(&xLastWakeTime, 1000 / portTICK_RATE_MS); // Every 1 second
// Do periodic work here
}vTaskDelaydelays for a duration relative to when it is called. Timing will drift if the task body takes variable time.vTaskDelayUntildelays until an absolute tick count, giving precise periodic execution.- Always use
portTICK_RATE_MSto convert milliseconds to ticks.
Starting the Scheduler
vTaskStartScheduler(); // Start FreeRTOS - this never returns
Once vTaskStartScheduler() is called, FreeRTOS takes over and your tasks begin executing. Code after vTaskStartScheduler() will only run if vTaskEndScheduler() is called (which is rare in embedded systems).
Inter-Task Communication
Queues
Queues are the primary mechanism for passing data between tasks. They are thread-safe and can be used to send messages of any fixed size.
// Adapted from www.freertos.org/Documentation/02-Kernel/04-API-references/06-Queues/03-xQueueSend
#include "FreeRTOS.h"
#include "queue.h"
#include <string.h>
struct AMessage
{
char ucMessageID;
char ucData[ 20 ];
} xMessage;
unsigned long ulVar = 10UL;
void vATask( void *pvParameters )
{
QueueHandle_t xQueue1, xQueue2;
struct AMessage *pxMessage;
/* Create a queue capable of containing 10 unsigned long values. */
xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );
/* Create a queue capable of containing 10 pointers to AMessage structures.
These should be passed by pointer as they contain a lot of data. */
xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );
/* ... */
if( xQueue1 != 0 )
{
/* Send an unsigned long. Wait for 10 ticks for space to become
available if necessary. */
if( xQueueSend( xQueue1,
( void * ) &ulVar,
( TickType_t ) 10 ) != pdPASS )
{
/* Failed to post the message, even after 10 ticks. */
}
}
if( xQueue2 != 0 )
{
/* Send a pointer to a struct AMessage object. Don't block if the
queue is already full. */
pxMessage = & xMessage;
xQueueSend( xQueue2, ( void * ) &pxMessage, ( TickType_t ) 0 );
}
/* ... Rest of task code. */
}Key queue functions:
| Function | Description |
|---|---|
xQueueCreate(length, itemSize) | Create a queue. Returns NULL on failure. |
xQueueSend(queue, &item, timeout) | Send an item to the back of the queue. Blocks up to timeout ticks. |
xQueueSendFromISR(queue, &item, NULL) | Non-blocking send for use in interrupt/callback context, i.e. if we are coming from udp_get_handler(). |
xQueueReceive(queue, &item, timeout) | Receive an item. Use portMAX_DELAY to block indefinitely. |
Semaphores
Binary semaphores are useful for signalling between an interrupt handler and a task.
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t dataSemaphore;
void setup(void) {
dataSemaphore = xSemaphoreCreateBinary();
}
// Called from an interrupt or callback
void data_ready_isr(void) {
xSemaphoreGiveFromISR(dataSemaphore, NULL); // Signal that data is ready
}
void worker_task(void *p) {
while (1) {
// Block until semaphore is given
if (xSemaphoreTake(dataSemaphore, portMAX_DELAY) == pdTRUE) {
xil_printf("Data ready - processing\r\n");
// Process the data
}
}
}Note
A binary semaphore starts in the “empty” state. The first xSemaphoreTake will block until xSemaphoreGive is called.
Mutexes
Mutexes protect shared resources from concurrent access by multiple tasks.
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t uartMutex;
void setup(void) {
uartMutex = xSemaphoreCreateMutex();
}
void task_a(void *p) {
while (1) {
xSemaphoreTake(uartMutex, portMAX_DELAY);
xil_printf("Task A output\r\n"); // Protected access
xSemaphoreGive(uartMutex);
vTaskDelay(100 / portTICK_RATE_MS);
}
}
void task_b(void *p) {
while (1) {
xSemaphoreTake(uartMutex, portMAX_DELAY);
xil_printf("Task B output\r\n"); // Protected access
xSemaphoreGive(uartMutex);
vTaskDelay(200 / portTICK_RATE_MS);
}
}Mutexes vs binary semaphores:
- Mutexes include priority inheritance: if a high-priority task is waiting on a mutex held by a low-priority task, the low-priority task temporarily inherits the higher priority. This prevents priority inversion.
- Use mutexes for protecting shared resources. Use semaphores for signalling between tasks or from interrupts.
Common Pitfalls on the Zynq
Stack Overflow
Symptoms: Random crashes, corrupted output, funky behaviour.
Solutions:
- Increase
THREAD_STACKSIZEfor the affected task. - Enable stack overflow detection in
FreeRTOSConfig.hby settingconfigCHECK_FOR_STACK_OVERFLOWto 1 or 2. - Avoid large local arrays in functions. You can use
staticor global buffers instead.
Blocking in Interrupt/Callback Context
The udp_get_handler callback runs in the context of the lwIP network thread, not as a regular task. You must not call blocking FreeRTOS functions from within it.
Do not use in udp_get_handler:
vTaskDelayxQueueSend(usexQueueSendFromISRinstead)xSemaphoreTake- Any function that may block
Instead: Use the FromISR variants of FreeRTOS functions, or write data to a global buffer and signal a task to process it.
Priority Inversion
If a low-priority task holds a resource (mutex) that a high-priority task needs, and a medium-priority task preempts the low-priority one, the high-priority task is effectively blocked by the medium-priority task. FreeRTOS mutexes (created with xSemaphoreCreateMutex) include priority inheritance to mitigate this. Always use mutexes rather than binary semaphores for resource protection.
Printf/xil_printf Thread Safety
printf and xil_printf are not thread-safe. If multiple tasks call them concurrently, output can be interleaved or corrupted. Protect print calls with a mutex if multiple tasks need to print, or designate a single task for serial output.
lwIP Integration
When using lwIP with FreeRTOS on the Zynq:
- SOCKET API mode is required. Set
api_modetoSOCKET_APIin the BSP settings. - DHCP timers must be called periodically. The EMBS network code handles this in
network_threadby callingdhcp_fine_tmr()anddhcp_coarse_tmr()at appropriate intervals. - The network thread must run continuously so do not delete it.
- Always free
pbufstructures after processing received packets.
Useful Links
- FreeRTOS Reference Manual (PDF) - Complete API reference
- FreeRTOS Queue Tutorial - Detailed queue usage examples
- Xilinx FreeRTOS BSP Documentation - Zynq-specific BSP configuration