Using FreeRTOS to Improve Run Time Efficiency
Simple updates to change to an event driven structure
The previous example
demonstrated how simple it was to convert the stand alone USB example into a
new RTOS task for incorporation into the FreeRTOS TCP example. The demonstrated
technique was however rather crude, and viewing the
executing tasks in the FreeRTOS Eclipse State Viewer plug-in reveals one of
the reasons why; one of the TCP related tasks and the Virtual COM / CDC
task are each consuming about 50% of the total CPU time available.
Viewing the RTOS task's run-time statistics in the Eclipse plug-in
Both tasks are polling their respective interfaces without ever entering the
Blocked state (if a task is in the Blocked state then it is not using any
CPU time), so the real time scheduler is sharing processing time between
them. This has a number of disadvantages, including:
CPU time is being wasted because the tasks are running even when there
is nothing to do. The wasted CPU time could be better utilised by
adding more functionality into the application, or the CPU could
simply be placed into a low power mode when there was no real
processing that needed to be performed.
The tasks have to run at the lowest priority, otherwise they will
starve all lower priority tasks of getting any CPU time at all.
The execution pattern and behaviour of the tasks is dependent on the
both the hardware speed and other software that is running on the
As the behaviour of the tasks is dependent on other software that is
running on the same device the interfaces they implement cannot be
classified as exhibiting deterministic behaviour.
The steps on this page show how, through the addition of some simple FreeRTOS
primitives, the exact same TCP and USB functionality can be obtained in a
system in which the TCP and USB tasks consume a tiny fraction of the available
CPU time. The image below shows the task's run time statistics after the
modifications have been made. This time nearly all the CPU time is being
consumed by the Idle task - which is the task that runs when there are no
application tasks that need to run. Implementing an
Idle task hook function
that places the CPU into a low power state will save a lot of power - using
FreeRTOS's tickless idle mode
will save a great deal more power.
After the changes, the application has the same functionality, but
the Idle task is receiving nnn% of the total run time
Two techniques are demonstrated by the workflow steps below. The first
technique is very simple and just places a task into the Blocked state for a
fixed period when it has nothing to do. That is effective at reducing the
amount of CPU time the task uses, but does not make the task truly event
driven. The second technique is more sophisticated as it makes the task
truly event driven and maximises responsiveness to real world events - it
places the task into the Blocked state until it receives notification from
an interrupt that something has happened that requires the task's attention.
Open the freertos_tcpecho.c source file, and search for a
function called vSetupIFTask().
The function contains a while( 1 ) loop that is monitoring the
network's link status, and unless the link status has changed, it has
nothing to do. The link status is not going to change very
often, so does not need to be polled continuously.
Add an 'else' part that contains a call to
vTaskDelay() to the existing
'if (physts & PHY_LINK_CHANGED)' statement. As shown below the vTaskDelay()
call will place the task into the Blocked state for 500ms each
time it determines the link status has not changed.
Delaying the PHY polling task when it has no
actions to perform
In the previous step a simple vTaskDelay() call was used to place
a task into the Blocked state when it had nothing to do. That
technique cannot be used with the USB task because virtual COM / CDC
drivers must be very responsive. This time an RTOS semaphore is going
to be used to unblock the USB task immediately that the USB task has
actions to perform. The USB task is then free to Block on the
RTOS semaphore, knowing it will not miss any important events.
First the RTOS semaphore is declared by adding the following line
somewhere in cdc_main.c:
static xSemaphoreHandle xCDCEventSemaphore;
Next the RTOS semaphore is created by adding the following line at
the start of the task that implements the USB Virtual COM / CDC
functionality (note xSemaphoreCreateBinary() is preferred over
vSemaphoreCreateBinary() from FreeRTOS V8.0.0).
vSemaphoreCreateBinary( xCDCEventSemaphore );
cdc_main.c is now making use of FreeRTOS semaphores, so
the following two lines must also be added to the top of the file:
Creating the RTOS semaphore
Now the RTOS semaphore has been created the implementation of the
virtual COM task is going to be updated to block on the RTOS
semaphore using a call to xSemaphoreTake().
Add the call to xSemaphoreTake() to the top of the while( 1 )
loop within the implementation of cdc_task() (still in the
cdc_main.c source file).
Updating the USB task's implementation so it blocks
on the semaphore
Now the RTOS semaphore has been declared and created, and
the task is blocking on the RTOS semaphore, the final edit is to
ensure the USB CDC interrupt gives the RTOS semaphore by calling
xSemaphoreGiveFromISR() each time it
is called. Giving the RTOS semaphore unblocks the USB task to allow
the task to perform any processing necessitated by the interrupt.
Update the USB_IRQHandler() function in cdc_main.c as per the
Updating the interrupt handler to 'give' the
Finally, verify that the updated application provides the exact
same functionality, but using a fraction of the CPU resources,
by building, downloading and executing the example for one last
Typing text into a Tera Term window that is connected
to the virtual COM/CDC port