FreeRTOS for the Arduino PORTENTA Pro C33

FreeRTOS is integrated into the Arduino IDE and ready for the PORTENTA Pro devices

Arduino recently introduced its professional line of microcontrollers including the PORTENTA C33 as shown in Figure 1. This device features a Renesas Arm Cortex M33 microcontroller. This exciting addition to the Arduino product line features IoT peripherals and related modules such as Bluetooth and Ethernet connectivity.

The increased hardware capabilities are accompanied by increased software complexity. One solution is to use FreeRTOS. You may be pleasantly surprised to learn that FreeRTOS is built into the IDE. In fact, you can find a FreeRTOS template alongside the other familiar Arduino examples in the IDE’s File tab.

What is the purpose of this article?

This engineering brief introduces FreeRTOS through an Arduino lens. It begins with an introduction of FreeRTOS and then presents a code template that provides clear tab-based separation of the various operating systems tasks. You will find that the Arduino setup( ) and loop( ) constructs provides a natural framework to understand and apply the FreeRTOS environment.

Figure 1: The Arduino PORTENTA C33 is a new Arduino Product that offers out-of-the-box FreeRTOS capability. The C33 is pictured on the PORTENTA breakout board.

What is the Arduino Pro PORTENTA family of products?

The short answer is that the PORTENTA family is a natural extension Arduino’s modular microcontroller product line providing solutions for the advanced needs of engineers and technologists. The turnkey products feature high-end 32-bit microcontrollers. All are able to run an operating system such as FreeRTOS with the most powerful multicore PORTENTA X8 device running Linux (Yocto).

To better understand the Arduino Pro line it is useful to reflect on Arduino history and its impact on education. As described in this article, the Arduino community is considerably larger than the parent Arduino company. The new Pro line is a tacit recognition that this tremendous user base can be leveraged by maintaining the Arduino like feel while programming microcontrollers with additional processing power.

The power and cost savings of the Pro line is revealed by a simple thought experiment. Ask yourself how long it would take to design a custom Printed Circuit Card (PCB) featuring a modern Ball Grid Array (BGA) microcontroller, associated power supplies, along with a Bluetooth transceiver. It’s a non-trivial exercise where the high one-off development cost must be amortized with either high cost for the end product or a high production run over which to distribute the development cost. Consequently, devices such as the Arduino Pro are ideal for development and production runs up to perhaps 1000 end units.

What is a microcontroller-based operating system?

A microcontroller-based operating system is high-level software used to schedule and coordinate multiple software resources. A related primitive is the Arduino Interrupt Service Routine (ISR). Recall that an ISR involves two sections of code including the main loop and the ISR itself. An example is the attachInterrupt( ) function called with a change-on-pin event. In this case, the main loop code will be interrupted to service the higher priority interrupt. A full description is beyond the scope of this article, however, for a single-core machine, we note that one and only one process is active at any given time. There is also a concept of context preservation allowing the microcontroller to return to where it left off.

A fully featured microcontroller Operating System (OS) is considerably more complex. Instead of two semi-coordinated asynchronous software sections, we now have multiple code sections within the larger microcontroller project. All microcontroller OSs are designed to prioritize and then select which task will be active at any given time. The concept of context becomes very important as the OS activates and deactivate the various tasks.

Many microcontroller operating systems use a hardware-based ISR. For example, the OS may “tick” every millisecond. At each tick, the OS will evaluate and then swap tasks based on the programmer’s chosen priority for each task. The result is a microcontroller that appears to be simultaneously performing all tasks.

Why use a microcontroller operating system?

From the programmer’s perspective, the OS can simplify the code making it easier to read, troubleshoot, and modify. Perhaps the greatest benefit is the imposed structure used to coordinate activities between tasks.

Let’s start by recognizing that a microcontroller-based OS is not strictly necessary. With enough time and effort, a programmer can code highly complex microcontroller behaviors. This would certainly include ISR based operations as the ISR is a fundamental building block and greatest strength of the microcontroller. In the same breath, we recognize that complex hand-coded microcontroller programs start to resemble OS-based structures. This is especially true as we time share and synchronize the various program tasks. In many cases, the programmer may be time and money ahead by using widely known structures provided by an OS such as FreeRTOS. You may also save time as FreeRTOS has been around for 2 decades, long enough to discover and fix many bugs – bugs that you would need to fix in your one-off microcontroller code.

What are the downsides of using a microcontroller operating system?

Let’s also recognize that an OS adds overhead to a project in terms of software, hardware, and time. The OS is a piece of software that runs on the microcontroller. Like any piece of software, the OS consumes precious microcontroller resources as it selects and coordinates the operation of your tasks. At a base level, the OS is always loaded into memory. It consumes CPU cycles every time it “ticks.”

To use or not to use an OS is not a trivial question. In addition to the software, hardware, and time consideration, we must also consider energy. In some respects, an OS is parasitic in nature. A poorly implemented OS will certainly consume energy resulting is shortened battery life for portable products. There is also something to be said for an optimizes hand-coded program that speeds most or its time with the microcontroller in sleep mode.

Tech Tip: A common question involves selecting the ideal microcontroller. This is especially true for the beginner who faces a daunting list of microcontrollers with a tremendous variability of hardware resources. A pragmatic answer suggests that there is no such thing as a perfect microcontroller. The best we can do is optimize a microcontroller to a specific project. Consequently, there will always be tremendous variability in microcontrollers.

Selection of an OS is yet another layer to the microcontroller selection process. As a rule, the OS is viable for projects with complexity and performance requirements that demand a 32-bit microcontroller. However, don’t rule out 8-bit devices. Lightweight OSs have been available for devices such as the 8051 since its introduction over 3 decades ago.

What is FreeRTOS?

FreeRTOS is an operating system specifically designed for resource constrained microcontrollers. It’s written in C and designed to be compiled along with the remainder of your program. In many respects, it is like any other Arduino library as it becomes an integral part of your compiled program.

To better understand the OS, let’s explore using Arduino example. Recall that every Arduino program features a setup( ) and a loop ( ) function. Your program may also feature one or more ISRs. In all cases, one and only one program section may be active at any given time. For example, we know that setup( ) runs once when the Arduino is powered on. We know that loop( ) runs forever. We also know that the ISR will operate when its associated event occurs.

This is a question of attention where the microcontroller can only focus its watchful eye (program counter) on a single section of code. For clarity in transition, let’s call each of these code section a task. We would therefor say that the microcontroller can focus on a run a single task. It is up to the programmer to coordinate the tasks and to safely share data between tasks. For more information about sharing data, please see this article with its focus on atomic data transfers. The article presents a flag and mailbox synchronization scheme which is primitive technique that suggests the elegant options found in a tremendously more capable OS.

Tech Tip: The Arduino loop( ) function should not be confused with the main( ) found in a traditional C program. Recall that main( ) is an anchor describing the beginning point for your C program. Your Arduino program has a main( ) function but it is hidden by layers of Arduino abstraction. In reality setup( ) and loop( ) are called from a lower and hidden main( ). Every experienced Arduino programmer knows this to be true as the static keyword must be used to preserve variable across loop( ) calls.

FreeRTOS expands the Arduino workspace to include many setup and loop functions

To better understand the power of FreeRTOS, let’s expand our view of the Arduino. Instead of a single setup( ) and loop ( ) function, imagine we are free to establish many of these setup / loop constructs. For convenience, let’s once again change our definition of an Arduino FreeRTOS “task” to identify any code section with its own setup and a loop. We can program tasks to blink an LED, send serial data, control a motor, etc. In each case the tasks operate independently of each other.

This independence is a key attribute of the OS. To better understand consider a simple example. Suppose we wish to blink an LED. Looking all the way back to your first Arduino program you may remember code that looked like this:

void loop( ) {
  digitalWrite(LED_BUILTIN, HIGH); 
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);   
  delay(1000);                     
}

Later you learned that this type of code was to be avoided at all costs because of the blocking action of the delay( ) function. You learned that the microcontroller does nothing; the microcontroller is blind until the blocking function is complete. Consequently, we like the delay( ) function because it is easy to read. We avoided it because it blocks other microcontroller tasks.

Not anymore.

With FreeRTOS the blink task is an independent entity. For clarity we can use a vTaskDelay( ) function without impacting any of the other independent tasks. For example, here is a representative FreeRTOS task to blink a LED:

#include "TaskBlink.h"

void blink_thread_func(void *pvParameters) {

  /* setup( ) */

  pinMode(LEDR, OUTPUT);

 /* loop( ) */

  for (;;) {
    digitalWrite(LEDR, LOW);          // Note that the PORTENTA C33 LED is active low
    xSemaphoreGive(xSemaphoreLEDOn);
    vTaskDelay(100);
    digitalWrite(LEDR, HIGH);
    xSemaphoreGive(xSemaphoreLEDOff);
    vTaskDelay(900);
  }
}

What are the components of a FreeRTOS task?

The previous code listing encapsulates a simple FreeRTOS task. It’s a self-contained piece of code that contains the equivalent of setup and loop functionality. The pinMode( ) function is performed on the first iteration of the task effectively performing the setup. The task then enters an infinite loop with the various statements contained within the never ending for( ; ; ). Normally, such an infinite loop is undesirable as the microcontroller is blocked from performing other actions outside of an ISR. That is not the case in this situation. Instead, the OS is the puppet master controlling the task.

Earlier we described an OS that ticks as a millisecond rate. This ticking analogy represents a structured decision-making moment for the OS. It can keep the active task, or it can swap the task for a higher priority task as defined by the programmer’s given prioritization. While an individual task may appear to run continuously within the for( ; ; ) loop, it only runs when allowed by the OS. In this example, when it’s time to service our blink code, the OS loads the context into the microcontroller’s machinery. At other times the context for other higher priority tasks is loaded.

Note that the Arduino delay( ) has been replaced by FreeRTOS vTaskDelay( ). By default as defined within #include <Arduino_FreeRTOS.h>, both delay( ) and vTaskDelay( ) operate at a millisecond level. Both functions will work for our simple LED blink delay. The FreeRTOS version is preferred as it provides the OS with a hint as to when the next activation cycle is required. Since the OS is aware of its own ticking, it will immediately suspend the current task and swap in the context for the next task on the priority list. The taskYIELD( ) function is a similar CPU relinquishing (context swapping) command.

Tech Tip: The term context is the fundamental action performed by an OS that allow multitasking. Each task requires specific hardware to do its job including memory (local and stack), program counter, and status register. Collectively these hardware locations embody the context of the task. The OS is responsible for preserving and restoring the context for each task under its direction. For example, this is necessary when the OS preempts a low priority task in favor of a high priority task. Note that is takes time for the OS to switch context. In some respects, this is like a person attempting to multitask. It takes time to remember where you were before you can resume the old activity.

How is the task linked to the OS?

The task is anchored into the greater FreeRTOS OS using the xTaskCreate( ) function. This critical code is called as part of the tradition Arduino setup( ) allowing it to run once and only once when the Arduino is powered on or reset.

Note that the xTaskCreate( ) function instantiates a task. Consequently there is a one to one relationship between any given task the xTackCreate( ) function. We also establish the working space reserved for the task along with the all-important priority. In this example, the code requirement is minimal and the priority for a blinking an LED is low.

  xTaskCreate(
    blinkTask,          /* Function that implements the task. */
    "Blink Task",       /* Text name for the task. */
    32,                 /* Stack size in words, not bytes. */
    NULL,               /* Parameter passed into the task. */
    2,                  /* Priority at which the task is created. */
    &xHandle            /* Used to pass out the created task's handle. */
  );

  if( xReturned == pdPASS ){
    vTaskDelete( xHandle );
  }

Tech Tip: The stack is a dedicated location for each task to store its variables. Beware as incorrect stack allocation of inverted priorities will crash the OS. Please see the FreeRTOS documentation for techniques to detect the actual usage.

To recover from a crash be sure to double click the PORTENTA’s reset button. You should be greeted by a green LED slowly cycling in brightness.

Template for Arduino FreeRTOS

To get started with FreeRTOS and Arduino, please download and review the example code as we continue the conversation.

Download the code here: FreeRTOSExample.zip (4.5 KB)

Please understand this code is for demonstration purposes only. It is not optimized and the FreeRTOS configurations such as stack allocation have not been verified. Please accept this code “as is” without “warranty of any kind.” Think of it as a starting point exploring the possibilities of FreeRTOS running on Arduino. Also, note that the code is derived from several examples included with the Arduino IDE including the PORTENTA C33 Arduino_FreeRTOS example and the EthernetC33 DhspChatServer as included with Arduino IDE v2.3.2.

The template is a multi-tab Arduino sketch with each task assigned to its own tab (.cpp and .h pair). The primary Arduino .ino file is used as a launching point for the various tasks. It is also used to hold the necessary global variables including the instantiation handles for each task and signaling OS device such as semaphores. This modular approach should aid in troubleshooting and adding additional modules. It keeps the shared items in a single location. It also helps with focus as operation of each individual task is easily seen.

What operations are performed by the code template?

The software was chosen to explore the real-time capabilities of the Arduino / FreeRTOS combination. Specifically, it explores how the system responds at the millisecond level. It is an experiment used to determine if a timer based ISR is necessary for a future PID application.

The selected FreeRTOS tasks (.cpp and .h pairs) are designed to:

  • blink the RED LED and give semaphores indicating the LED status. This is a basic indicator to show that the microcontroller is active and not in a frozen state.

  • take semaphores and then send the LED status to the Arduino serial monitor.

  • pulse the D6 output high for 1 ms and then low for 9 ms. This task has the highest priority and will be used to determine the real-time suitability of the C33 / FreeRTOS solution. The intention is to use this heartbeat task to hold a future PID control system.

  • send information to a terminal program such as PuTTY using a wired Ethernet interface.

Real-Time Performance

Figure 2 presents the critical measured real-time performance for the PORTENTA C33 under FreeRTOS control. This screen capture from the Digilent Analog Discovery shows the result of this simple task. It’s important to know that this task was given the highest priority and that we had loaded the C33 with other heavy tasks in an attempt to disrupt the normal flow.

  for (; ;) {
    digitalWrite(HEARTBEAT_PIN, HIGH);
    vTaskDelay(1);
    digitalWrite(HEARTBEAT_PIN, LOW);
    vTaskDelay(9);
  }

The measured jitter from Figure 2 suggests a +/-40 us jitter on a 10 ms signal. This indicates that the C33 / FreeRTOS combination can maintain consistent timing which is critical for a control algorithm such as the PID. The +/- 40 us jitter is well within the requirements for our future implementation of a PID controller coupled to a servomotor with a time constant in the 500 ms range. This is good news as it indicates that we can use a high-level Arduino implementation without the need to delve into bare-metal ISR based programming on the C33’s complex Renesas Arm Cortex microcontroller.

Figure 2: Real-time performance of the Arduino PORTENTA C33 showing +/- 40 us jitter from one 10 ms rising edge to another while sending the “quick brown fox” pangram on Ethernet.

What are the next steps to learn about FreeRTOS

We have only scratched the surface when it comes to using FreeRTOS. For example, the demo program features FreeRTOS semaphores where the blink tasks tells the serial task that the LED has changed states. This inter-task communication is one of the fundamental aspects of FreeRTOS as it provides a safe way to communicate between asynchronous tasks.

You could certainly learn more about FreeRTOS at their primary page. You may also be interested in this DigiKey YouTube playlist produced several years ago by @ShawnHymel.

Conclusion

Part of getting started is knowing what is in the realm of the possible. I trust this brief introduction has given you a taste of what is possible with Arduino and FreeRTOS especially when coupled with the higher performance of Arduino Pro line of microcontrollers.

Please leave your feedback in the space below. Suggestions for future topics are encouraged, especially when we consider the abrupt end to this note. Also, we would cherish any success stories if you were able to apply and expand this information.

Best Wishes,

APDahlen

Please follow this link for related Arduino education content.

About the Author

Aaron Dahlen, LCDR USCG (Ret.), serves as an application engineer at DigiKey. He has a unique electronics and automation foundation built over a 27-year military career as a technician and engineer which was further enhanced by 12 years of teaching (interwoven). With an MSEE degree from Minnesota State University, Mankato, Dahlen has taught in an ABET accredited EE program, served as the program coordinator for an EET program, and taught component-level repair to military electronics technicians. Dahlen has returned to his Northern Minnesota home and thoroughly enjoys researching and writing articles such as this.