FreeRTOS is a popular free and open-source real-time operating system (RTOS) for embedded platforms. It has its own scheduler, tasks, and synchronisation primitives. Writing concurrent applications for FreeRTOS requires to learn the environment and make use of the provided tools.

In some cases, a developper might want to port an existing concurrent application written with POSIX interfaces (such as pthreads, semaphores, message queues, etc.). Usually there are two approaches here :

  1. Convert everything to FreeRTOS primitives.
  2. Write an OS abstraction layer (OSAL) that will use either FreeRTOS or POSIX primitives and rewrite application using the OSAL.

However, there is a third option, FreeRTOS provides a POSIX threading wrapper (https://github.com/FreeRTOS/Lab-Project-FreeRTOS-POSIX). The FreeRTOS+POSIX project implements a small subset of the POSIX threading API features so not everything is supported. Nevertheless, this can save time when porting a POSIX application. Another benefit is that developers more familiar with POSIX than FreeRTOS may have an easier time porting their application.

In our case we were interested in FreeRTOS+POSIX because we develop some of our firmwares on desktop computers (e.g., running GNU/Linux) in an emulated environment (QEMU). Where our firmware interfaces to QEMU but runs as a regular userspace application. This has the major benefit of not requiring development boards, gives more visibility (debugging), and allows access to many libraries for quick prototyping.

During a firmware project we were interested in adding asynchronous tasks. On the firmware running on the desktop this could be done easily by using POSIX threads. However, on the real hardware (A Xilinx Zynq platform) our firmware was running bare metal. Adding asynchronous tasks could be done with bare metal multiprocessing but this would be a major hassle. Therefore, the project was migrated to FreeRTOS on the embedded side to have access to tasks and scheduling.

Adding the asynchronous tasks could now be done either by

  1. Rewriting the firmware with FreeRTOS primitives
  2. Adding an OSAL to work both with POSIX and FreeRTOS
  3. Adding the FreeRTOS+POSIX layer and keep the firmware as is

The path of least effort seemed to be number 3 and worked out well in our case.

FreeRTOS+POSIX

While it seems a promising solution, there is not much information available online to do this (it has been discussed on the Xilinx forums without much success, thread1, thread2). This is the main reason I chose to write this guide.

Below is a step-by-step guide on how to add FreeRTOS+POSIX to a Xilinx Vitis FreeRTOS project and a simple message queue application with 3 tasks/threads communicating will be used for illustration purposes (The Xilinx FreeRTOS “Hello World” example application). The steps below target a Zynq platform but also apply for Microblaze softcore processors and the ZynqUltraScale+ MPSoCs family.

Creating the Platform and Hello World project

This section goes over how to create a platform and example project in the Xilinx Vitis IDE.

Platform

Create a platform that will provide the build support package (BSP). You may already have a platform, you can also reuse it. But we will modify the BSP in the following steps.

I created A “Zynq7000” platform based on the zc706 board default XSA. The operating system is chosen to be FreeRTOS 10.

Application

Let’s create a new application project (File/New/Application Project…) and select the platform created above (or choose another existing platform).

I’ll call the application “posix_app”.

Select the FreeRTOS 10 Domain.

And let’s choose the FreeRTOS Hello World application, this will give us a base application to modify.

Once the application project is created we can compile and run it. I’ll run it on the real board.

The application will run two tasks, a Tx task that will send a message to a queue every second and a Rx task that will read the queue and print the messages. Source code is visible here.

The serial interface shows the expected messages. After 10 seconds a timer expires and the callback function will check if at least 9 messages have been sent and print a relevant message. Then it deletes the Rx and Tx tasks.

Adding FreeRTOS+POSIX

Before we rewrite the hello world project with the POSIX API we need to add the FreeRTOS+POSIX files to the project and configure FreeRTOS. This is the more obscure part, some documentation is available here and in the GitHub project but there is no straightforward guide for this… (especially not for Zynq).

so here it is !

Summary

  1. Clone the FreeRTOS+POSIX project
  2. Add include directories to project
  3. Configure FreeRTOS to work with the FreeRTOS+POSIX project
  4. Test the build

1. Clone the project

The sources for FreeRTOS+POSIX are here available on GitHub https://github.com/FreeRTOS/Lab-Project-FreeRTOS-POSIX so I chose to clone them in the application project (one may choose to clone them in the platform BSP). If your project is git based I suggest adding it as a submodule.

The folder now appears in the Vitis Explorer

2. Adding include directories

If you try to build at this stage it will fail miserably. There are a few things that need to be done. First the toolchain must be made aware of the new include directories. Select the project properties.

Under C/C++ Build, Settings, Tool Settings, Directories add…

Choose from workspace, so paths will be relative to the workspace and project, then select the following directories :

  • Lab-Project-FreeRTOS-POSIX/include
  • Lab-Project-FreeRTOS-POSIX/include/private
  • Lab-Project-FreeRTOS-POSIX/FreeRTOS-plus-POSIX/include
  • Lab-Project-FreeRTOS-POSIX/FreeRTOS-plus-POSIX/portable
  • Lab-Project-FreeRTOS-POSIX/FreeRTOS-plus-POSIX/portable/pc/windows

The last one is chosen because there is no special include for the Zynq platform and the include file for Windows is (almost) empty (uses the defaults) so we choose that directory (because the file will still be needed).

If you want to include the POSIX include files directly e.g., #include “pthread.h” you should also include

  • Lab-Project-FreeRTOS-POSIX/include/FreeRTOS_POSIX

Here, I chose not to include it and include these files specifying the directory explicitly e.g., #include “FreeRTOS_POSIX/pthread.h” in to avoid confusion with the system headers and the risk of including the wrong headers.

Check that all the directories are added as shown below.

3. Configure FreeRTOS

As per the documentation the FreeRTOS+POSIX project has some dependencies.

  • configUSE_POSIX_ERRNO must be set to 1
  • configUSE_APPLICATION_TASK_TAG must be set to 1

There is another dependency that is not documented but the code will not compile without it.

  • configSUPPORT_STATIC_ALLOCATION must be set to 1

Because the code in the FreeRTOS+POSIX project uses static allocation functions (e.g., xSemaphoreCreateCountingStatic()). Note that static and dynamic allocation can both be used at the same time in a project (e.g., xCreateTask() and xCreateTaskStatic() will both be available).

This can be changed in the FreeRTOSConfig.h file. Be mindful of the path because several copies of this file exist in the BSP so check that you are editing the correct one (see path in screenshot). You can also use “Open Declaration (F3)” to go to the symbol from a reference in a compiled project. Note that “#define configUSE_POSIX_ERRNO” is not present in the file and must be added (the other options only require to change 0 to 1).

The configSUPPORT_STATIC_ALLOCATION requires the user to provide static allocation callback functions as described in the documentation.

For this I created a file called “static_allocation.c” in the same directory. I added the include “FreeRTOS.h” directive and copy-pasted the functions from the documentation in the file.

I chose to add this in the BSP, because it is related to the configSUPPORT_STATIC_ALLOCATION option, but this file can also be added in the application project instead of the BSP. The code could also be directly copied in the freertos_hello_world.c file). Save the file and now we should be able to build the project

4. Build the project

The project now builds without errors.

The Hello World project with POSIX threads, timers, and messaging queues

Now that we integrated the FreeRTOS+POSIX code and configured FreeRTOS let’s rewrite the example with the POSIX API. The resulting code is shown below. The main differences are

  • POSIX Threads (pthreads) are used instead of tasks
  • POSIX Message queues are used instead of FreeRTOS queues
  • POSIX Timers are used instead of FreeRTOS timers
  • The scheduler is running before launching the threads
  • A main FreeRTOS task is launched as an entry point, this task should not not return, (here it is deleted before completion)
  • A “stop” message is used to tell the Rx thread to stop
  • The queue is closed resulting in the Tx thread to stop
  • Both threads are joined (where in FreeRTOS the tasks are deleted)

Running the example (on the ZC706 board) we observe the desired behavior :

The code of the POSIX version is listed below, and available download here (with both original FreeRTOS and FreeRTOS+POSIX versions) :

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h" /* To start the scheduler */
/* POSIX includes */
#include "FreeRTOS_POSIX/pthread.h"
#include "FreeRTOS_POSIX/mqueue.h"
#include "FreeRTOS_POSIX/time.h"
#include "FreeRTOS_POSIX/fcntl.h"
#include "FreeRTOS_POSIX/unistd.h"
/* Xilinx includes. */
#include "xil_printf.h"
#include "xparameters.h"

#define DELAY_10_SECONDS	10000UL
#define DELAY_1_SECOND		1000UL
#define TIMER_CHECK_THRESHOLD	9
/*-----------------------------------------------------------*/

static void *prvTxTask( void *pvParameters );
static void *prvRxTask( void *pvParameters );
static void vTimerCallback( union sigval pxTimer );
/*-----------------------------------------------------------*/

static pthread_t xTxTask;
static pthread_attr_t xTxAttr;
static pthread_t xRxTask;
static pthread_attr_t xRxAttr;
static mqd_t xQueue = NULL;
static struct mq_attr xQueueAttr = {0,};
const char *xQueueName = "/posix_mq";
static timer_t xTimer = NULL;
static struct sigevent xSev = {0,};
static struct itimerspec xTrigger = {0,};
char HWstring[15] = "Hello World";
long RxtaskCntr = 0;

static void prvMainPosixTask( void *opaque );
/*-----------------------------------------------------------*/

/* FreeRTOS Main entry point with FreeRTOS functions called */
int main( void )
{
    /* Start the task to run POSIX hello world */
    xTaskCreate( prvMainPosixTask,
                 "posix",
                 configMINIMAL_STACK_SIZE,
                 NULL,
				 tskIDLE_PRIORITY,
                 NULL );

	/* Start the tasks and timer running. */
	vTaskStartScheduler();

	/* If all is well, the scheduler will now be running, and the following line
	will never be reached.  If the following line does execute, then there was
	insufficient FreeRTOS heap memory available for the idle and/or timer tasks
	to be created.  See the memory management section on the FreeRTOS web site
	for more details. */
	for( ;; );
	return 0;
}

/* Main with POSIX functions */
void prvMainPosixTask( void * opaque )
{
	int iret = 0;

	xil_printf( "Hello from Freertos example main\r\n" );

	/* Create the queue before launching the tasks because the scheduler is already running */
	xQueueAttr.mq_maxmsg = 1; /* There is only one space in the queue. */
	xQueueAttr.mq_msgsize = sizeof( HWstring ); /* Each space in the queue is large enough to hold the string. */

	xQueue = mq_open(	xQueueName,			/* Message queue name must start with / */
						O_RDWR | O_CREAT,	/* Read and Write. Create queue if it doesn't exist. */
						0,					/* the "mode" parameter is unsupported by FreeRTOS+POSIX */
						&xQueueAttr );		/* The attributes for the queue */

	/* Check the queue was created. */
	configASSERT( xQueue );

	/* Create the two tasks.  The Tx task is given a lower priority than the
	Rx task, so the Rx task will leave the Blocked state and pre-empt the Tx
	task as soon as the Tx task places an item in the queue. */
	iret = pthread_attr_init( &xTxAttr );
	configASSERT( !iret );
	iret = pthread_attr_init( &xRxAttr );
	configASSERT( !iret );

	struct sched_param xTxSchedParam = {.sched_priority = sched_get_priority_min(0),}; /* The task runs at the idle priority */
	struct sched_param xRxSchedParam = {.sched_priority = sched_get_priority_min(0) + 1,}; /* The task runs at a higher priority */

	iret = pthread_attr_setschedparam(	&xTxAttr,
										&xTxSchedParam );
	configASSERT( !iret );
	iret = pthread_attr_setschedparam(	&xRxAttr,
										&xRxSchedParam );
	configASSERT( !iret );

	iret = pthread_create(	&xTxTask,
							&xTxAttr,	/* The thread attributes. */
							prvTxTask,	/* The function that implements the thread. */
							NULL );		/* The task parameter is not used, so set to NULL. */
	configASSERT( !iret );

	iret = pthread_create(	&xRxTask,
							&xRxAttr,	/* The thread attributes. */
							prvRxTask,	/* The function that implements the thread. */
							NULL );		/* The task parameter is not used, so set to NULL. */
	configASSERT( !iret );

	/* Create a timer with a timer expiry of 10 seconds. The timer would expire
	 after 10 seconds and the timer call back would get called. In the timer call back
	 checks are done to ensure that the tasks have been running properly till then.
	 The threads are joined in the timer call back and a message is printed to convey that
	 the example has run successfully.
	 The timer expiry is set to 10 seconds and the timer set to not auto reload. */
	xSev.sigev_notify = SIGEV_THREAD; /* Must be SIGEV_THREAD since signals are currently not supported */
	xSev.sigev_notify_function = vTimerCallback; /* Callback function */
	xSev.sigev_value.sival_ptr = &xTimer; /* Timer ID */

	iret = timer_create(	0,			/* the "clock_id" parameter is ignored as this function uses the FreeRTOS tick count as its clock */
							&xSev,
							&xTimer );

	/* Check the timer was created. */
	configASSERT( !iret );
	configASSERT( xTimer );

	/* start the timer with a block time of 0 ticks. This means as soon
	   as the schedule starts the timer will start running and will expire after
	   10 seconds */
	xTrigger.it_value.tv_sec = 10;	/* Set the expiration 10 seconds in the future */

	iret = timer_settime(	xTimer,		/* The timer to be armed */
							0,			/* No flags */
							&xTrigger,	/* Trigger in 10 seconds */
							NULL );		/* No old value needed */
	configASSERT( !iret );

	/* This task was created with the native xTaskCreate() API function, so
	must not run off the end of its implementing thread. */
	vTaskDelete( NULL );
}


/*-----------------------------------------------------------*/
static void *prvTxTask( void *pvParameters )
{
	int iret = 0;

	for( ;; )
	{
		/* Delay for 1 second. */
		sleep(1);

		/* Send the next value on the queue. */
		iret = mq_send(	xQueue,				/* The queue being written to. */
						HWstring,			/* The address of the data being sent. */
						sizeof( HWstring ),	/* The size of the data being sent. */
						0 );				/* The message priority. */
		
		if (iret) {
			xil_printf( "Tx task could not send message to queue\r\n" );
			break;
		}
	}

	return NULL;
}

/*-----------------------------------------------------------*/
static void *prvRxTask( void *pvParameters )
{
	char Recdstring[15] = "";
	int size = 0;

	for( ;; )
	{
		/* Block to wait for data arriving on the queue. */
		size = mq_receive(	xQueue,					/* The queue being read. */
							Recdstring,				/* Data is read into this address. */
							sizeof( Recdstring ),	/* Size of destination buffer. */
							NULL );					/* The message priority. */

		if (size <= 0) {
			xil_printf( "Rx task could not receive message from queue\r\n" );
			break;
		}

		if( strncmp( Recdstring, "stop", 15 ) == 0 ) {
			xil_printf( "Rx task was told to stop receiving messages\r\n" );
			break;
		}

		/* Print the received data. */
		xil_printf( "Rx task received string from Tx task: %s\r\n", Recdstring );
		RxtaskCntr++;
	}

	return NULL;
}

/*-----------------------------------------------------------*/
static void vTimerCallback( union sigval pxTimer )
{
	int iret = 0;
	configASSERT( pxTimer.sival_ptr );

	/* Cannot check the timer ID the same way as FreeRTOS, because the ID is assigned by timer_create() */

	/* If the RxtaskCntr is updated every time the Rx task is called. The
	 Rx task is called every time the Tx task sends a message. The Tx task
	 sends a message every 1 second.
	 The timer expires after 10 seconds. We expect the RxtaskCntr to at least
	 have a value of 9 (TIMER_CHECK_THRESHOLD) when the timer expires. */
	if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
		xil_printf("Successfully ran FreeRTOS Hello World Example\r\n");
	} else {
		xil_printf("FreeRTOS Hello World Example FAILED\r\n");
	}

	/* Join the threads */

	/* Send a message to the Rx thread so it can finish gracefully */
	iret = mq_send(	xQueue,				/* The queue being written to. */
					"stop", 			/* The address of the data being sent. */
					sizeof( HWstring ),	/* The size of the data being sent. */
					0 );				/* The message priority. */
	configASSERT( !iret );
	pthread_join( xRxTask, NULL );
	xil_printf("Rx thread joined\r\n");

	/* Closing the message queue will make the Tx thread stop (but not wake the blocked Rx task) */
	mq_close( xQueue );
	mq_unlink( xQueueName );
	pthread_join( xTxTask, NULL );
	xil_printf("Tx thread joined\r\n");
}

Conclusion

Thanks to FreeRTOS+POSIX it is possible to run code that relies on the POSIX threading API directly on FreeRTOS. This was helpful to us because now we can run the same firmware code both on a desktop computer and on the embedded platform. It is true that not all POSIX features are supported but most of the basic use cases are covered.

The documentation inside the header files in https://github.com/FreeRTOS/Lab-Project-FreeRTOS-POSIX/tree/master/include/FreeRTOS_POSIX gives a good overview of what is supported and what is not (and for the curious the .c files have the implementation). From the example above we can already see that we have threading, timers, message queues, and of course there are semaphores and condition variables.

For us, the possibility alone to run asynchronous tasks in our firmware both on GNU/Linux and on FreeRTOS relying on the same code is a major benefit ! Removing the need maintain two different code bases or rewriting our firmware with an OSAL.

References