FreeRTOS to Zephyr Porting Example - FreeRTOS to Zephyr Porting Example - WP560

FreeRTOS to Zephyr Porting Overview (WP560)

Document_ID
WP560
Release_Date
2025-02-11
Revision
1.0 English

The FreeRTOS application that is being ported is an example from the AMD Vitis™ Unified IDE. You can find it on the Embedded Software GitHub repository or recreate it in the Vitis Unified IDE by selecting File > New Component > From Examples > FreeRTOS Hello World. The Zephyr port of this application is built from scratch. To set up the Zephyr environment, follow the instructions on the Zephyr Getting Started Guide.

Before porting the demonstration application, review the differences in how FreeRTOS and a Zephyr project are configured. For this application, FreeRTOS has the option to be configured to have configSUPPORT_STATIC_ALLOCATION set to 0 or 1. If this parameter is enabled, the tasks, timers, and queues can be called using a special API to have their memory assigned statically. As mentioned in Table 1, the Zephyr project offers the same functionality without having to set a configuration parameter.

To start with, FreeRTOS and Zephyr applications require a different set of included files. The FreeRTOS architecture requires an include for each component (timer, task, queue) as well as some AMD-specific includes. Notably, xparameters.h is needed to pass hardware information to the application. Conversely, the Zephyr project includes most of what you might need in a single file called kernel.h. Hardware information is passed on to a Zephyr application through Devicetrees.

Table 1. Comparison of FreeRTOS and Zephyr Applications Includes
FreeRTOS Code Zephyr Code
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
/* Xilinx includes. */
#include "xil_printf.h"
#include "xparameters.h"
/* Zephyr includes */
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

The next part of the application defines some constants. Notably, FreeRTOS timers have a timer ID while Zephyr timers do not. The Zephyr project also offers various ways to generate timeout delays in increments of hours, minutes, seconds, milliseconds, microseconds, and nanoseconds. Because you are using seconds for this example, you can use 1 and 10 rather than defining them in microseconds like in the FreeRTOS application.

Table 2. Comparison of FreeRTOS and Zephyr Constants
FreeRTOS Code Zephyr Code
/* FreeRTOS Constants. */
#define TIMER_ID 1
#define DELAY_10_SECONDS 10000UL
#define DELAY_1_SECOND 1000UL
#define TIMER_CHECK_THRESHOLD 9
/* Zephyr constants */
#define DELAY_10_SECONDS 10
#define DELAY_1_SECOND 1
#define TIMER_CHECK_THRESHOLD 9

In this section, you are declaring the necessary structures and parameters to create your tasks/threads, timer, and queue later in the code. In the Zephyr example, even though the threads are going to be defined dynamically, the memory is statically allocated.

Table 3. Comparison of FreeRTOS and Zephyr Parameters
FreeRTOS Code Zephyr Code
/* The Tx and Rx tasks */
static void prvTxTask( void *pvParameters );
static void prvRxTask( void *pvParameters );
 
/* Timer callback function */
static void vTimerCallback( TimerHandle_t pxTimer );

/* The queue used by the Tx and Rx tasks. */
static TaskHandle_t xTxTask;
static TaskHandle_t xRxTask;
static QueueHandle_t xQueue = NULL;
static TimerHandle_t xTimer = NULL;
char HWstring[15] = "Hello World";
long RxtaskCntr = 0;
/* tx and rx thread functions */
static void prv_tx(void *dummy1, void *dummy2, void *dummy3);
static void prv_rx(void *dummy1, void *dummy2, void *dummy3);
 
/* timer expiry (callback) function */
static void my_expiry_function(struct k_timer *timer_id);
 
/* defining thread-related constants */
#define STACKSIZE 1024
#define PRIORITY 7
 
/* the Tx and Rx threads */
static struct k_thread prv_tx_data;
static struct k_thread prv_rx_data;
 
/* define thread stack area of size STACKSIZE */
K_THREAD_STACK_DEFINE(prv_tx_stack_area, STACKSIZE);
K_THREAD_STACK_DEFINE(prv_rx_stack_area, STACKSIZE);
 
/* the queue used by the Tx and Rx threads */
char my_msgq_buffer[sizeof(hw_string)];
struct k_msgq my_msgq;
struct k_timer my_timer;
char hw_string[15] = "Hello World";
long rx_task_cntr = 0;

The next section is the main() function. There are a few important differences.

  • Priorities in FreeRTOS are structured such that the higher the number the higher the priority of the thread. In the Zephyr project, the opposite is the case and negative numbers can be used as a thread’s priority (this is a special priority type which makes the thread a cooperative thread).
  • The FreeRTOS application makes use of a macro called configASSERT(). This macro is typically used in FreeRTOS applications to help debug during development. The Zephyr project also offers debug functionality through various configuration options, which are beyond the scope of this exercise.
  • FreeRTOS applications use an API called vTaskStartScheduler() to start the scheduler. Conversely, the Zephyr project does not use a dedicated API call to start its scheduler. During Zephyr's initialization, scheduling is enabled and spawns system and idle threads. The C main() function is called in the context of system threads.
Table 4. Comparison of FreeRTOS and Zephyr main() Functions
FreeRTOS Code Zephyr Code
int main( void )
{
const TickType_t x10seconds = pdMS_TO_TICKS( DELAY_10_SECONDS );
 
xil_printf( "Hello from Freertos example main\r\n" );
 
/* Creating tasks. */
xTaskCreate( prvTxTask, 
 ( const char * ) "Tx",
 configMINIMAL_STACK_SIZE, 
 NULL, 
 tskIDLE_PRIORITY,
 &xTxTask );

xTaskCreate( prvRxTask,
 ( const char * ) "GB",
 configMINIMAL_STACK_SIZE,
 NULL,
 tskIDLE_PRIORITY + 1,
 &xRxTask );
 
/* Creating queue */
xQueue = xQueueCreate( 1,
 sizeof( HWstring ) ); 
/* Check the queue was created. */
configASSERT( xQueue );
 
/* Create timer. */
xTimer = xTimerCreate( (const char *) "Timer",
 x10seconds,
 pdFALSE,
 (void *) TIMER_ID,
 vTimerCallback);
 
/* Check the timer was created. */
configASSERT( xTimer );
 
/* Start timer with a block time of 0 ticks. */
xTimerStart( xTimer, 0 );
 
/* Start the tasks and timer running. */
vTaskStartScheduler();
 
/* If this executes an error has occurred. */
for( ;; );
} 
int main(void)
{
const k_timeout_t ten_seconds = K_SECONDS(DELAY_10_SECONDS);
 
printk("Hello from Zephyr example main\r\n");
 
/* creating threads */
k_thread_create(&prv_tx_data,
 prv_tx_stack_area,
 K_THREAD_STACK_SIZEOF(prv_tx_stac
 k_area),
 prv_tx,
 NULL,
 NULL,
 NULL,
 PRIORITY,
 0,
 K_FOREVER);
 
k_thread_create(&prv_rx_data,
 prv_rx_stack_area,
 K_THREAD_STACK_SIZEOF(prv_rx_stac
 k_area),
 prv_rx,
 NULL,
 NULL,
 NULL,
 PRIORITY-1,
 0,
 K_FOREVER);
 
/* initialize message queue */
k_msgq_init(&my_msgq,
 my_msgq_buffer, 
 sizeof(hw_string),
 1);
 
/* initialize timer */
k_timer_init(&my_timer, my_expiry_function, NULL);
 
/* start timer with no block time */
k_timer_start(&my_timer, ten_seconds, K_NO_WAIT);
 
/* start the threads */
k_thread_start(&prv_tx_data);
k_thread_start(&prv_rx_data);
}

In the next section, aside from syntactic differences, the TX and RX functions have the same implementation in both FreeRTOS and the Zephyr example.

Table 5. Comparison of FreeRTOS and Zephyr TX Functions
FreeRTOS Code Zephyr Code

static void prvTxTask( void *pvParameters )
{ 
const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
 
for( ;; )
 {
 /* Delay for 1 second. */
 vTaskDelay( x1second );
 
 /* Send the next value on the queue. */
 xQueueSend( xQueue,
 HWstring,
 0UL );
 }
}

static void prv_tx(void *dummy1, void *dummy2, void *dummy3)
{
k_timeout_t sleep_time = K_SECONDS(DELAY_1_SECOND);
 
for(;;)
 {
 /* delay for 1 second */
 k_sleep(sleep_time);
 
 /* send the next value on the queue */
 k_msgq_put(&my_msgq,
 &hw_string,
 K_NO_WAIT);
 }
}
Table 6. Comparison of FreeRTOS and Zephyr RX Functions
FreeRTOS Code Zephyr Code

static void prvRxTask( void *pvParameters )
{
char Recdstring[15] = "";
 
for( ;; )
 {
 /* Wait without timeout for data. */
 xQueueReceive( xQueue, 
 Recdstring,
 portMAX_DELAY );
 
 /* Print the received data. */
 xil_printf( "Rx task received string from Tx 
 task: %s\r\n", Recdstring );
 
 /* Increment counter. */
 RxtaskCntr++;
 }
}

static void prv_rx(void *dummy1, void *dummy2, void *dummy3)							
{
char recdstring[15] = "";
 
for(;;)
 {
 /* wait without timeout for data */
 k_msgq_get(&my_msgq,
 &recdstring,
 K_FOREVER);
 
 /* print the received data */
 printk("Rx task received string from Tx task: 
 %s\r\n", recdstring);
 
 /* increment counter */ 
 rx_task_cntr++;
 }
}

Finally, the timer callback/expiry functions are also very similar. The only major differences come from the fact that the FreeRTOS application uses a TIMER_ID parameter and makes use of the configASSERT() macro.

Table 7. Comparison of FreeRTOS and Zephyr Timer Functions
FreeRTOS Code Zephyr Code

static void vTimerCallback( TimerHandle_t pxTimer )
{
long lTimerId;
configASSERT( pxTimer );
 
lTimerId = ( long ) pvTimerGetTimerID( pxTimer );
 
if (lTimerId != TIMER_ID)
 {
 xil_printf("FreeRTOS Hello World Example 
 FAILED");
 }
 
if (RxtaskCntr >= TIMER_CHECK_THRESHOLD)
 {
 xil_printf("Successfully ran FreeRTOS Hello 
 World Example");
 }
 else
 {
 xil_printf("FreeRTOS Hello World Example 
 FAILED");
 }
vTaskDelete( xRxTask );
vTaskDelete( xTxTask );
}

static void my_expiry_function(struct k_timer *timer_id)
{
 
if (rx_task_cntr >= TIMER_CHECK_THRESHOLD)
 {
 printk("Successfully ran Zephyr Hello World 
 Example\r\n");
 }
 else
 {
 printk("Zephyr Hello World Example FAILED\r\n");
 }
  
k_thread_abort(&prv_tx_data);
k_thread_abort(&prv_rx_data); 
}