FreeRTOS, Everything You Need To Know..!

When I started out learning about FreeRTOS, I was not able to find good short resources to read, and hence I had to spend weeks reading a 400 page eBook, see a bunch of webinars and videos, then cross-reference to some other resources when concepts were not clear, before I was finally able to understand what FreeRTOS is, how it works and how to use it in applications. So I decided to write this guide to summarise all the information I have learned over my research in an easy to understand manner so that you can get started with FreeRTOS in a much shorter time-frame. So let’s begin by having a look at the 2 line explanation of FreeRTOS. 

What is FreeRTOS? FreeRTOS stands for Free Real-Time Operating System. It is an open-source operating system targeted on embedded applications that run on a microcontroller and need real-time event processing.

Let’s begin our journey into the FreeRTOS world by first looking at what FreeRTOS is and what does Real-Time mean.

FreeRTOS

What does “Free” in FreeRTOS stand for? FreeRTOS is both free for commercial use and the source code is available for everyone to see as it is open-source, same as Linux, which is another famous open-source operating system. The difference being the fact that while Linux is completely developed and maintained by the online community, freeRTOS was founded by Richard Barry and it was further developed, maintained and quality controlled by an organization named Real Time Engineers Ltd.

Since it is free to use, open-source and professionally maintained by an organization, it is considered to be the best operating system for embedded and IoT applications.

Before we can look at the “RTOS” part of the name, let’s take a look at real-time systems.

What are real-time systems? Real-time systems act predictable (or deterministic) to events. Their main characteristic is the fact that they will react to events within the specified time constraints.

Let’s take a simple example of a computer keyboard to understand what real-time systems are. When you are typing something, you expect the letter to appear on the screen as soon as you type it. If the word comes in even with a 2-second delay then it is going to contribute to a bad user-experience. So there is a time constraint of a few milliseconds here, which cannot be perceived by normal human senses so that the end-user will feel like the letter you just pressed magically appeared on the screen. 

They are of 2 types of real-time systems namely hard and soft real-time systems.

Hard real-time systems are those that have a hard deadline, in other words, a late answer is a wrong answer. 

For example, let’s consider an embedded device installed on a Mars-rover which is responsible for collision avoidance so that it does not bump into some boulders. In this case, if the rover is heading for an obstacle and the embedded system does not react in a timely manner and pull the brakes, then it can result in damage to the rover which can, in turn, result in the catastrophic failure of the entire mission.

Soft real-time systems have a deadline that is not as firm as hard real-time systems. In other words, a late answer is an inaccurate answer but still useful. 

The keyboard example we talked about in the beginning is a good example as even if the letters are delayed by a few more milliseconds it is still acceptable performance to the end-user.

Before we can learn about RTOS let’s take a moment to cover one more idea, that is multitasking.

What is multitasking? Multitasking means running many tasks/threads/processes on a processor simultaneously at the same time. Operating Systems, in general, are used to support multitasking.

For example, consider a device

  • that can sense the temperature in your room. 
  • that can update the readings over wifi to your smartphone.
  • simultaneously it controls an LED indicator that blinks once every 2 seconds if the temperature is above 20 degrees and blinks every second if the temperature is below 20 degrees.

It has a single processor that needs to do all these three tasks at the same time, in other words, it needs to do some multitasking. But a processor can only handle one task at a time. To give us the illusion that all three processes are running at the same time, operating systems use a scheduler to let the 3 processes to share the processor time. So sensor reading is taken in the first 5 milliseconds, in the next 10 milliseconds it is sent to your smartphone and then in the next 5 milliseconds the LED status is updated. This is done over and over again to make the end-user think that all 3 processes are running parallelly even though they are running one after the other in a pseudo-parallel manner.

We use this multitasking feature every day whenever we use our smartphone to listen to music and use it to do some other activity at the same time. (Like I am doing right now, as I am listening to music while I am writing this article!)  These processes are called threads in the PC world and Tasks in RTOS world but they refer to the same idea. 

Now that we have covered what real-time systems are and what does multi-tasking mean, let’s learn about what an RTOS is.

What is RTOS? RTOS stands for Real-Time Operating System. This type of operating system is specially designed for use in real-time systems. The main characteristic of this system is to execute code within the specified deadline. 

What differentiates RTOS from other traditional operating systems? The difference is in the implementation of Schedulers. Here the algorithm used for scheduling allows the application programmer to prioritize tasks, set a minimum time limit to respond to an event so that a deterministic real-time system can be designed. 

FreeRTOS place among other embedded software.

Now that we understand what FreeRTOS is, lets next try and understand its place among other design-patterns and operating systems by taking a look at some examples.

Complexity level#0

Digital watch
Digital watch

Consider a Simple Digital Watch. It’s only functionality is that it tells time. Such a product can be developed using a simple 8-bit microcontroller and an operating system is of very little use here as it only runs a single thread continuously. We will have a look at the concept of threads in a coming section, for now, you just need to know that a thread is a step by step process.

Complexity level#1

Sports watch
Sports watch

Now let’s add a little bit more complexity, Let’s add a stopwatch to the timer, and an alarm clock and a count-down timer. Now the system has to run multiple tasks at the same time. These tasks include

  • it has to keep track of the time,
  • if a stopwatch is running it has to run count the number of seconds that have expired since the start button has been clicked and
  • if it’s time to ring the alarm to wake you up it needs to start beeping.

For such an application there are effective software design patterns that can be used so that interrupts and loops can be used to implement this without an operating system on a microcontroller.

Yes, using an operating system can certainly help ease the development process as it will take care of task priorities and the timing needs. But these devices are usually resource-constrained in terms of memory and processing power as an attempt to keep the cost low to keep up with the competition.

The software complexity without an operating system is still considered manageable and hence they are usually built without an OS.

Complexity level #2

Fitness monitor
Fitness monitor

Now let’s add more functionality to our digital watch, lets put in a pedometer, an altimeter and a temperature sensor. Now the tasks that it needs to compute parallelly includes

  • Keeping track of time
  • Stopwatch
  • Alarm clock
  • Pedometer signal processing 
  • Temperature sensing
  • Altitude sensing
  • Display information on an LCD/LED screen

So, on one side it has to do the job of a normal digital watch, on the other hand it needs to do some smart features by taking in raw accelerometer and gyroscope values from an IMU sensor and do some signal processing to calculate your steps (you can have a look at another article about IMU sensors here), continuously read the temperature values from the temperature sensor, read altitude measurements and display it all on your screen. 

Even this application can be designed without an operating system, by just architecting some good design patterns, but usually, this kind of system can be benefited by adding a simple embedded operating system as the software complexity without an operating system is usually considered to be not very easy to develop and maintain. Also, such a system are usually built using 32bit microcontrollers with enough resources to accommodate an operating system and this makes engineers of such products to go for an operating system based software. This is where simple real-time operating systems like FreeRTOS comes in.

Let’s go a bit further and look at a more complex device to see where the benefits of FreeRTOS ends.

Complexity level #3

Smartwatch
Smartwatch

Lets now add-in a few more features, let’s add in a heart rate monitor, a sleep monitor, a Bluetooth communication system to communicate with your phone, an mp3 player to stream music to your Bluetooth earphones and a notification system to make your smartwatch vibrate every time you receive a call on your paired smartphone. Essentially all the features of smartwatches these days.

Now the system is not simple enough to be made using simple embedded operating systems like FreeRTOS. It is still possible to do it but then the added complexity is not easy for the development and maintenance of the system. Now we will probably need a much more sophisticated Operating system like Embedded Linux to accomplish our tasks.

Need even more functionality/complexity? Then it’s essentially a smartphone! Even more? Then we enter the PC territory!

I hope you got the point and understood the places where simple embedded operating systems like FreeRTOS fit in! 

Next, let’s take a look at some of the basic operating system concepts that you need to understand to make effective use of FreeRTOS.

Operating System Concepts

There are 7 concepts and associated terminology you must master to start developing using FreeRTOS, they include 

  • Tasks
  • Multitasking
  • Context switching
  • Schedulers
  • Kernels
  • Interrupts and
  • Intertask communications

Tasks/Processes

In the examples above of using a digital watch, each separate activity/feature is a Process. 

Process is a single line of actions, that are executed one after the other

In FreeRTOS these processes are called Tasks. Each such task has its own stack in combination with another block of memory to store information about itself known as a Task Control Block which is used by FreeRTOS kernel to manage the task. (more about kernels in a bit)

In FreeRTOS, these tasks are implemented as C functions with a never-ending loop. 

Multitasking and state changes

As we saw earlier, multitasking is the way of sharing the processor’s time, so that a number of tasks can execute pseudo-parallelly by taking turns to use the CPU. At any given point in time, only one process can be present in the running state. Other processes are usually in one of the following 3 states 

  1. Ready state
  2. Blocked state
  3. Suspended state
tasks changing states
tasks changing states

In the ready state, the task is ready to run but the CPU is currently being occupied by some other task. In our smart-watch example, you can think of the stop-watch as a task that is in the running stage, while the regular clock function is in the ready state.  

In the blocked state, the task is waiting for some data or an event to occur. A good example is the alarm task on our smartwatch example. It will be in kept blocked state till the time arrives for the alarm to ring.

Tasks in the suspended state are not active anymore. Let’s take the same alarm task for example, it will in a suspended state once you turn it off. 

Tasks are made to change from running state to one of the inactive states by the scheduler by the process of preempting.

What is preempting? A currently running task is stopped without any notice, in favor of a higher priority task, and its contents and status information are stored in memory so that when it runs again, it can start from where it left off.  

What is Scheduler? It is a task from the FreeRTOS operating system, whose task is to manage the state of the other tasks. 

It is the most important part of any real-time operating system. Its duty is to make sure no lower priority tasks can be in running state while a higher priority task is in the ready state so that the timing requirements can be met. 

But as we just saw, the scheduler is also a task, which means it can only take a limited amount of processor’s time, else it will do more harm than good as the actual application needs to run on top of the operating system.

So the scheduler usually runs once every fixed duration of time, say for example once every 10 milliseconds. The scheduler occupies the CPU, say for 0.5ms and during this time, it will see the manage the tasks so that only the highest priority task in the ready state gets the next slot in time.

scheduler task
scheduler task

Here the green task is the scheduler. It starts at 0ms and runs till 0.5ms and chooses Task1 to run till 10ms. Then at 10ms the scheduler comes back again and this time picks another task to run from the ready queue which is Task 2 and now Task 2 runs till 20ms.

If 2 tasks of the same priority are in the ready state, then the scheduler will choose the one that hasn’t had a chance to run for the longest period of time and give it the next 9.5ms period to use the processor. Once that’s done the scheduler comes in again and gives the CPU to the other same priority task. Thus both these tasks can take turns running of using the processor.

As an application designer, it is our job to architect our application with proper priority levels, so that the most important tasks that are ready to run are always getting executed when needed, but at the same time, we do not let the lower priority tasks starve too much for CPU time.

Let’s take another example from space exploration. Say we are sending a rocket to collect data about some outer planets in our solar system.  As it travels, one of its tasks is to continuously click pictures and send it to us. Another task is to avoid asteroids as it travels so that it does not hit anything and get destroyed in the process, so it also has the scan the field ahead to see there are rocks floating around. 

Okay, what if there is an asteroid dead ahead? Then it has to take evasive maneuvers by changing its direction.  

So far we have 3 tasks

  1. Continuously click and send pictures
  2. Continuously scan the path for asteroids
  3. If asteroids are found on the path, take evasive maneuvers

Out of these 3, task #3 is the highest priority, task#2 is the next and task#1 is the lowest priority. Since we need the rocket to be safe, we don’t care if it is not able to send pictures for the 10mins it spends on escaping from the asteroid since these missions usually last for decades and loss of 10min data means nothing compared to the safety of the satellite.

In FreeRTOS the higher the number, the higher the priority. So a priority table can look like this. 

TaskPriority number
Evasive maneuvers10
Scan path8
Click and send pics7

Basically what this means is, we are saying the system to drop everything and run if an asteroid approaches it!

Kernel

What is the kernel? The kernel is considered to be the core of an operating system. Its main task is to manage the hardware and the processes running on it.

The scheduler we saw above is the most important part of a kernel. Other important parts include inter-task communications and synchronizations. 

kernel in a operating system

As you can see in the pic above, the operating system can have device drivers, file systems, networking stacks like TCP/IP, file systems, etc., The central part of any operating system is its kernel and it consists of the scheduler and inter-task-communication systems.

Inter-Task Communication

Let’s go back to our satellite example. There the “scan path” task must inform the “take evasive maneuver” task if it actually finds some obstacle.  So there is a need for the 2 tasks to share some information with each other. 

There are several options available for tasks to communicate with each other through the kernel of FreeRTOS like queues, mutex, semaphores and notifications. Let’s take a brief look at them.

Queues

To explain queues let’s take another example. Clicking and sending pictures from the satellite can also be implemented as 2 separate tasks. Let’s assume our satellite is going past Saturn and we will be able to get a very good view of it in the next 2 hours. Let’s assume our camera can click 10 pics per second but our radio can only send 1 pic per second. So in that situation, we may need to continuously click pictures and store them in a memory buffer, so that it can send it one by one later when the processor is free. 

This is where queues come in. 

Let’s take a look at the operations of a queue using an illustration.

queue illustration 1
queue illustration 1

So the operation of the queue goes like this

  • the queue is empty at the beginning
  • task one is running and it produces the green ball and puts it in the front of the queue
  • task one produces another ball, this time an orange one and puts that also in the queue
queue illustration 2
queue illustration 2
  • next task 2 starts running and it notices that there are 2 balls in the queue
  • first, it takes the green ball and processes it thereby removing it from the queue
  • so once the green ball is removed, the kernel automatically move the orange ball to the front of the queue
  • once task 2 finishes processing the data in the green ball, it goes ahead and takes the orange ball from the queue
  • then the queue is empty again.

It’s a simple operation no different than standing in a queue to buy something at a shop. It works on a First-In-First-Out basis.

Mutex 

What is Mutex? Mutex stands for MUTual EXclusion. It is a type of variable that controls access to shared resources. 

Let’s learn more about it by looking at an example. 

Let’s take the example of printing something to a shared text file. Consider 2 tasks Task#1 and Task#2. They both are executing pseudo-parallelly and they can both read and write to this text file.

void task1()
{
    while (true)
    {
        print(abc.txt, “1 is the best number\n”);
    }
}

void task2()
{
    while (true)
    {
        print(abc.txt, “2 is bigger than one\n”);
    }
}

void main()
{
    start_scheduler();
    start_task(task1);
    start_task(task2);
}

Task#1 will print the line

“1 is the best number!” and 

Task#2 will print the line

2 is bigger than one

in bold, and they will both print continuously onto the text file. 

Consider both these tasks to be of the same priority.  Now as we saw previously, the scheduler will make sure each will get its turn to run since they are of the same priority.

So if the scheduler decides to switch in the middle of printing as the Task#1 is running you will get something like this 

1 is the best number!

1 is 2 is bigger than one

2 is bigger than one

2 is big1 is the best number! 

1 is the best number!

Which makes no sense. Just imagine 100 Tasks writing to this text file instead of 2, which is very normal for a Windows or Linux Operating systems that run on our computers. Unless we install some policies this multiple writer scenario will probably render the text file corrupted and totally useless! 

So we need some sort of mechanism to control the access to the text file so that if one task starts writing to it, the other tasks that need the write-access must wait before the previous task has given up its write-access.

This is actually simple to do using a simple boolean status flag called file_in_use as seen in the pseudo-code below.

bool file_in_use;

void task1()
{
    while (true)
    {
        if (file_in_use == false) {
            file_in_use = true;
            print(abc.txt, “1 is the best number\n”);
            file_in_use = false;
        }
    }
}

void task2()
{
    while (true)
    {
        if (file_in_use == false) {
            file_in_use = true;
            print(abc.txt, “2 is bigger than one\n”);
            file_in_use = false;
        }
    }
}

void main()
{
    start_scheduler();
    start_task(task1);
    start_task(task2);
}

Now, this status flag is a global variable which makes it easier to maintain the “one at a time” write-access to the abc.txt file.

In other words, a mutex is just a more robust version of our status flag. They act as keys to our shared object. There is only one key, so only one person can access the resource at a time. 

Mutexes do the same job as this status flag, except for the fact that a status flag is a global variable at application level and mutex is implemented as a kernel object.

Since our file_in_use is a global variable, tomorrow your teammate can write another Task called Task#3 and there is nothing stopping Task#3 from using this global variable which is considered to be bad practice in programming. But mutexes, on the other hand, are kernel objects, and if you need a mutex key to access a resource, the kernel will first check if it has already been taken, if not the access to use the shared resource will be given to you. Once you finish using the shared resource, it’s your duty as the programmer to give the mutex key back to the kernel by “releasing” it so that the other Tasks can go ahead have access to the shared resource. 

Semaphores

Semaphores are just an extended version of mutexes. The shared resource guarded by a mutex has only one key. On the other hand, the resource guarded by semaphores can have multiple keys, so that a number of processes can access that particular resource simultaneously.

Think of an elevator, which can support only 4 people at a time. Here the elevator is the shared resource. If you need access to it, you need to press the button outside the elevator signaling the elevator system that you need to use it. Now if there is space in the elevator once it opens, say only one person is already inside, then you can go in. But if there are already 4 people inside the elevator, then you can either wait and take the next one or if you are friends with stairs like our Kung fu Panda, then you can go ahead and use the stairs!

Semaphores work much the same way. The elevator control system is analogous to the kernel and the number of spaces available at any given time is analogous to the number of free keys available to our shared resource at a given instant. If you need a key, you need to ask the kernel and if one is available, it will be given to you. 

Let’s take the same text file example that we saw in the previous section and see how controlled access to multiple tasks will be useful. If a task needs to write to it, then of course only one should do it at a time, but if a task just needs to read it, then it does not change the contents of the file so in this case, more than one task can read the text file at any given instant.

So here this shared text file will have just 1 mutex key to control the write-access and depending on the use case maybe 5 semaphore keys to control the reading access of the file.

Since mutex and semaphores are so similar to each other, mutexes are also called binary semaphores. Collectively mutexes, semaphores, queues, etc., are called communication objects as they are used to communicate events, statuses and data from one task to another task.

Interrupts

Interrupts behave in a way similar to the task switching mechanism available in the scheduler so that the currently running code is preempted if an interrupt occurs,

Interrupts should not be confused with task switching. 

  • task switching is done by the scheduler through software to make sure that a lower priority task cannot run while another higher priority task is ready to run.
  • Interrupts are produced by hardware events like a user pressing a button.
  • Interrupts are used to capture events from the physical world the device is working in while the task switching is done primarily for multitasking.

I have written more about interrupts in another article you can read about it here in section 8 Interrupt controllers.  In the interest of keeping this article self-contained, I am presenting the same information here.

Interrupt controllers

Interrupt controllers listen to the peripherals for events and reports to the processor once an event occurs.

Lets first talk about what interrupts are by considering some real-life examples of interrupts.

  • You are sleeping and in the morning the alarm you set goes off and wakes you up  (by interrupting your sleep)
  • You are reading a book and your phone rings to alert you of a call (by interrupting your reading session.)

These are 2 examples of day to day interrupts experienced by us humans. 

This concept has been ported to embedded systems using special devices called interrupt controllers. I like thinking of them as event listener peripherals. Their main job is to listen to the events generated by the other peripherals and report them to the processor. 

Examples of events that can produce interrupts include

  • GPIO reads 1 or 0
  • Timer countdown reached 0
  • Serial communication received a packet of data and
  • DMA has completed a transfer 

The particular event that we need to listen to can be programmed by us by registering them to the interrupt controller and unnecessary events can be ignored.

Once a registered event happens, the interrupt controller goes and tells the CPU about it, so that the CPU can take appropriate actions.

For example, consider a simple circuit where one pin is configured as digital input and it is connected to a push-button. Another GPIO pin is configured as a digital output and is connected to an LED as shown in the figure.

Arduino circuit with button and LED
Arduino circuit with button and LED

Here the job of the system is to light up the LED if the button is pressed. We can write code to achieve this functionality in 2 ways.

Polling

This first method is that we continuously see if the button is being pressed and once it is, then we turn ON the LED. This approach of doing things is also called polling. The code snippet below shows how to implement this method.

int main()
{
    gpio_init(LED, output);
    gpio_init(Button, input);
    while (true)
    {
        if (gpio_read(Button) == 1) {
              gpio_write(LED, 1);
        }
        else {
             gpio_write(LED, 0);
        }
    }
    return 0;
}

This is analogous to look at the clock all night and getting out of the bed as soon as its 6 AM and turns on the light! So we wasted the entire night without sleeping and wasted a lot of energy, which is a very inefficient way of doing it!

Interrupts

Instead, the better way to do it is to go ahead with our sleep and just set an alarm(/interrupt) to wake us up at 6 AM. 

On the microcontrollers, we can accomplish this by setting up the interrupt controller to tell the CPU that the button has been pressed.

The interrupt controller then listens to the button press on the GPIO input pin and once the button is pressed, it wakes up the microcontroller and executes a function that turns the LED on for us. Once the LED is ON, the CPU gets to go back to sleep. (unlike us who need to get to work!)

This method has an energy-efficient implementation since the CPU is sleeping most of the time. 

But you may ask, what about the interrupt controller, won’t is waste energy by seeing if the button is pressed or not 24/7? The answer to that question is yes the interrupt controller will consume some power, but CPU can consume 100’s of times more energy than a typical interrupt controller. 

This mechanism of doing things is called interrupts.

The code snippet shown below shows the implementation of interrupts to achieve the same result. 

void LED_ON_ISR()
{
    gpio_write(LED, 1);
}
 
void LED_OFF_ISR()
{
    gpio_write(LED, 0);
}
 
int main()
{
    gpio_init(LED, output);
    gpio_init(Button, input);
    init_interrupt(Button_pressed, LED_ON_ISR);
    init_interrupt(Button_released, LED_OFF_ISR);
 
    while (true)
    {
        sleep();
    }
    return 0;
}

Here in the main function, as soon as everything is initialized, the CPU goes to sleep and only wakes up when the interrupt controller sees a button press or button release event.

As you can see, the code looks more complicated once interrupts came into the picture, but even setting up your alarm needs extra work, that does not mean it is the best way of getting a good night’s sleep!

So whenever there is an opportunity, its good practice to use interrupts instead of polling. (when laziness to implement more code kicks in, just remember the sleep and alarm analogy!)

Interrupt vector table

In the above example, if button1 is pressed then LED1 must be lit and if button1 is released then LED1 must be turned off. The CPU needs a way to figure out which ISR should be called on button press and release. That is there needs to be a mapping between a given Interrupt request (IRQ) and its Interrupt Service Routine (ISR)

Interrupt vector table

IRQ stands for Interrupt ReQuest and ISR stands for Interrupt Service Routine. These are just technical names, so don’t stress too much over it. Just remember that the interrupt controller waking up the CPU is a ReQuest to service an Interrupt. (Or its an IRQ) , the function that gets executed once IRQ comes is the ISR. 

The interrupt vector table is simply a table with 2 columns, column1 contains the interrupt number (IRQ number) and column2 contains the address of the function to be executed (ISR) if a given interrupt occurs. Once an interrupt comes (the interrupt number comes in with the IRQ), the CPU simply checks this table to get the address of the ISR and then it goes and runs the appropriate code.

In the previous example, we had 2 IRQs: Button_pressed and Button_released, connected to 2 ISRs: LED_ON_ISR and LED_OFF_ISR respectively.

On top of the information presented, in the context of FreeRTOS you must know that

  1. interrupt service routines can preempt even the highest priority tasks
  2. Interrupts can also be given priorities like tasks
  3. Unlike tasks a higher priority number does not always mean higher logical priority, it depends upon the manufacturer of the microcontroller you are using. Sometimes a lower number can mean higher priority. 
  4. Interrupts can also be nested, in other words, a higher priority interrupt can preempt the ISR of a lower priority interrupt.

So now that we have covered all the basics of FreeRTOS, I will stop here.

Hope this article provided some value to you! 

You can email us or contact us through this link if you have any questions or suggestions.

If you liked the post, feel free to share this post with your friends and colleagues!

EI

We’re passionate about inventing embedded devices and we hope you are too! This blog deals with a wide variety of topics from C programming to IOT to networking certifications and more. Hope you enjoy your time spent here and hope you get some value out of our blog!

You may also like...