Single Multi Core Comparision Report Final
Single Multi Core Comparision Report Final
On
CERTIFICATE
This is to certify that the Major Project entitled “Parameters Estimation and
Performance Analysis of a Single-core processor and Multi-core
processor using FreeRTOS” is submitted by
Project Guide
CERTIFICATE
This is to certify that the Major Project entitled “Parameters Estimation and
Performance Analysis of a Single-core processor and Multi-core
processor using FreeRTOS” is submitted by
Head of Department
ACKNOWLEDGEMENT
(i)
DECLARATION
(ii)
TABLE OF CONTENTS
5 FreeRTOS – A Detailed 24
Study
6 ESP32- A Feature-rich 42
MCU Board ported with
FreeRTOS
7 Setting up ESP-IDF 49
References 92
(iii)
LIST OF FIGURES
Fig 3.1 RTOS Architecture
Fig 3.2 RTOS within the embedded system abstraction layers
Fig 3.3 RTOS task states with pre-emptive scheduling
Fig 3.4Pre-emptive Scheduling
Fig 3.5Non-pre-emptive scheduling
Fig 4.1 Asymmetric Multi-core System
Fig 4.2 Symmetric Multi-core System
Fig 5.1 Multi-tasking (v/s) Concurrency
Fig 5.2 Context Switching
Fig 5.3 scheduler task
Fig 5.4 FreeRTOS kernel
Fig 5.5 Writing to and reading from a queue. In this example the queue was created
to hold 5 items, and the queue never becomes full.
Fig 5.6 Use case of a matrix
Fig 5.7 A binary semaphore is equivalent to a queue which can contain one
element
Fig 6.1 ESP-32 Board
Fig 6.2 ESP-32 Structure
Fig 6.3 Esp-32 Pin-Diagram
Fig 7.1 Development of applications for ESP32
Fig 7.2 Project configuration - Home window
Fig 8.1 Folder organisation of the project
Fig 8.2 Folder organisation of project after building the project
Fig 8.3 Example of Task List
Fig 8.4 Example of Run Time Stats
Fig 8.5 Adding tools to the path
Fig 8.6 Configuration Window
Fig 8.7 Serial Flasher Config Window
Fig 8.8 Component Config Window
Fig 8.9 Process of building the project
Fig 8.10 Flashing binaries to the device
Fig 9.1 Result obtained for single-core processor
Fig 9.2 Result obtained for single-core processor
Fig 9.3 Result obtained for dual-core processor
Fig 9.4 Result obtained for dual-core processor
(iv)
LIST OF TABLES
(v)
ABSTRACT
(vi)
1. INTRODUCTION
When we hear the word operating system, the primary thought that strikes our
mind is that of the operating systems used in laptops & computers. Generally, we
use operating systems like windows XP, Linux, Ubuntu, Windows 7,8.8.1, and 10.
In the smart phones, the operating systems are like KitKat, Jellybean,
Marshmallow, and Nougat. In a digital electronic device, there is some sort of
operating system which is developed by the microcontroller program. There are
different types of operating systems to develop for the microcontroller, but our area
of interest over here is a real-time operating system (RTOS).
Why an RTOS? The obvious choice of an RTOS comes from the wide benefits it
offers. The real difference between an RTOS and a general-purpose OS is that with
an RTOS the designers have taken care to ensure that the response times are
known. This is not as simple as it may sound. Modern general-purpose operating
system kernels are very large, with several million lines of code. It can be difficult
to trace through them to find all the possible sources of delay in response. An
RTOS tends to be much smaller than a general-purpose OS making guarantying
the response time more practical. It is intended to serve real-time applications that
process data as it comes in, typically without buffer delays. At the beginning of
this documentation, we shall briefly scrutinize the advantages and classifications of
an RTOS.
We propose to run the applications on a microcontroller device using an RTOS, to
schedule tasks across its cores. A wide variety of RTOS are available in the market
today. The choice for our project is FreeRTOS. We shall also have an introspection
into its features and what makes it reliable for the project.
In this project, we intend to run one or more applications on the chosen
microcontroller, ESP32- a series of low-cost, low-power system on a chip
microcontroller with integrated Wi-Fi and dual-mode Bluetooth. ESP32 is a go-to
chip for making several devices as they’re small, powerful, have a ton of onboard
features, and they’re relatively easy to program, offering lesser challenges
compared to its vast advantages. We shall also dive into its features and try and
understand the pros and cons of the device. The ESP32 comes with a light
operating system – FreeRTOS. The methods to program this chip don’t replace the
FreeRTOS firmware, but rather deploy applications for it to run.
A further peek into this project shows us its aim at running an application on the
single-core processor and the dual-core processor and thereby, analysing and
comparing the results. The real time statistics are assessed and the performance
shall be analysed for both the scenarios.
Page | 1
1.1 Project Scope:
It is possible to re-design the processing algorithm to improve processing speed.
Real time based system performance is improved comparing to non-real-time
based system. Further real time system performance can be improved by
implementing different RTOS algorithm. Also, same design can be implement
using different RTOS operating system.
Moreover, RTOS offers wide areas of benefits. Embedded systems are highly
customized, developed and programmed as per user requirements. Embedded
systems will play an important role in Internet of Things (IoT) due to their unique
characteristics and features such as real time computing, low power consumption,
low maintenance and high availability are becoming the key enabler of IoT. Major
players in embedded system hardware and software developments are aiming to
bring these transformations into their products to take advantage of growing IoT
market. The areas that are going to transform are Real Time Operating Systems
(RTOS) and microprocessors and microcontrollers, followed by memory footprints
and networking, open source communities and developers. Hence, the study of
RTOS provides us with scientifically stimulating opportunities.
2. MOTIVATION AND PROBLEM STATEMENT
2.1 Motivation:
Necessity is the mother of invention and embedded systems are inventions that
were fuelled by the idea of making pre-programs to perform a dedicated narrow
range of functions as part of large systems. Embedded systems are being based
more on instruction-oriented design but not on design-oriented instructions.
Embedded systems and real-time operating systems (RTOS) are fast achieving
ubiquity, blurring the lines between science fiction and hard reality. In general, an
RTOS has features like multitasking, process threads that can be prioritized, and a
sufficient number of interrupt levels. An embedded system is any device controlled
by instructions stored on a chip. These devices are usually controlled by a
microprocessor that executes the instructions stored. With these systems being
widely used in devising many tools, we concern ourselves with running
applications on it that uses the benefits of RTOS.
Among the various RTOS available in the market, one is FreeRTOS. With an
available kernel for it, software developers gain great advantages. Application code
can be developed to be portable and flexible. FreeRTOS is a class of RTOS that is
designed to be small enough to run on a microcontroller – although its use is not
limited to microcontroller applications. Utilizing its benefits and analyzing the
performance is a benchmark initiative.
2.2 Problem Statement:
The objective of the project lies at running an application on a single-core and on
the dual-core of the microcontroller, ESP-32 and thereupon, studying the results. A
comparison of the results shall be drawn to understand which is more
advantageous. The application to be developed is a calculator which performs
addition, subtraction, multiplication, division, square, cosine, sine, natural
logarithm functions and conversion of temperature from one unit to other. The real-
time statistics, response time and CPU utilization shall be determined for the
application. The project also focusses on building the necessary foundations such
as, the study of the real-time operating system and its usage in the embedded field.
For the project, the choice of the real-time operating system is the FreeRTOS, and
a detailed explanation of why and how it plays a crucial role in the project is given
in the further chapters. We shall also inspect the advantages and limitations of
FreeRTOS. Subsequently, it includes the installation of ESP-IDF in the Linux
environment wherein we shall run the required codes.
3. REAL-TIME OPERATING SYSTEM
3.1 Introduction:
Beginning with the basic functionality of an Operating System, it hides the difficult
computations performed by hardware, which the software does on the back end.
We are presented a computer screen that we can work on and all other details that
is the communication between software and hardware is hidden form us. So, an
operating system is a type of software which communicates between application
software and hardware. Moving forward, we shall now discuss what is a real-time
operating system and what makes it more reliable.
An operating system has to provide 3 essential things:
1. Task Scheduling:
The scheduler determines which task to run and when a task will run.
2. Task Dispatching:
The dispatcher handles the necessary operations to get a task ready to go.
3. Inter-task Communication:
This is the mechanism that handles how data and information is
exchanged between tasks and processes on the same machine or from
other machines.
These 3 essential things are what makes up the smallest portion of an OS called 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.
A real time operating system is just a special purpose operating system. The 'real
time' part of the name does not mean that the system responds quickly, it just
means that there are rigid time requirements that must be met. If these time
requirements are not met, the results can become inaccurate or unreliable.
Now, let us compare a general purpose operating system and real time operating
system:
RTOS GPOS
RTOS has unfair scheduling i.e. GPOS has fair scheduling i.e. it can be
scheduling is based on priority. adjusted dynamically for optimized
throughput.
It works under worst case assumptions. It optimizes for the average case.
From the above table it is evident that the need to use an RTOS is when there is a
need to monitor and control physical processes in a timely manner. The constraints
one has to deal with when using RTOS are tight scheduling, predictability, and
robustness.
As part of the embedded system abstraction layers, an RTOS is placed above the
low-level device drives and below the user application. The RTOS does not
provide low-level drivers for microcontroller peripherals. Some RTOS may
contain middleware software such as networking, file systems, etc.
Fig 3.2 RTOS within the embedded system abstraction layers
3.3.1 Tasks:
A piece of code performing a specific function is usually called a task. A task can
be also referred to as a thread, process, activity, etc. in different operating systems.
An operating system is expected to execute many different tasks at once – reading
inputs, outputting data, reacting to events, etc. A single microprocessor, however,
can execute code from only one task at a time. Achieving the required multitasking
is possible using a mechanism known as time multiplexing. Each task that the
operating system should execute is given a time window in which it can utilize the
CPU, and then the execution of another task proceeds according to a predefined
scheduling algorithm. If the timing requirements of all the tasks are fulfilled, we
can say we have concurrent like executions of multiple tasks without the use of
separate microprocessors for each task.
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.
Running State: The task’s code is currently being executed by the CPU.
Ready State: The task is ready to be put into the running state. In the ready
state, the task does not consume any CPU cycles.
Blocked State: The task is in this state when it waits for the occurrence of
some event. In this state, the task does not consume any CPU cycles.
Suspended State: Tasks in the suspended state are not active anymore.
Some RTOS implementations may contain additional task states or have a different
naming convention than the ones listed above, but they are all following the same
logic.
Creating a task
Deleting a task
Changing the priority of the task
Changing state of the task
3.3.2. Scheduler:
The scheduler is an integral part of every RTOS. It controls which task should be
executed at any given point in time. The scheduler may use various types of
algorithms for performing the scheduling of the tasks. Almost all of these
algorithms can be classified into two main types:
Pre-emptive Scheduling: This algorithm allows the interruption of a
currently running task, so another one with higher priority can be run.
Non-pre-emptive Scheduling (Co-operative Scheduling): Once a task is
started it cannot be interrupted, it will run until it decides that it should
release the CPU to another task.
Messaging provides a means of communication with other system and between the
tasks. The messaging services include the following.
Semaphores
Event flags
Mailboxes
Pipes
Message queues
Semaphores are used to synchronize access to shared resources, such as common
data areas. Event flags are used to synchronize the inter-task activities. Mailboxes,
pipes, and message queues are used to send messages among tasks.
The most important part of the operating system is the Kernel. Tasks are relived of
monitoring the hardware. It’s the responsibility of the kernel to manage and
allocate the resources. As tasks cannot acquire CPU attention all the time, the
kernel must also provide some more services. These include the following.
Memory management
I/O system management
System protection
Networking
Command interruption
Time services
Device management services
3.4 Features of an RTOS:
Selecting a real-time operating system (RTOS) involves these five key features as
must-haves.
3.4.1. Reliability
Any RTOS must be reliable. This means that it operates for a reasonably long time
without human interference. Reliability also means the configuration to enable the
system to choose the right or most profitable action for current operations. For
example, a system for the telecom or banking industries needs to consider the cost
of terminating or engaging certain actions, compared to a phone or calculator
whose cost is limited to an individual, game, app function, etc.
3.4.2. Predictability
A system must execute actions within a known time frame and produce known
results. Such results are determined by the procedures or operations taking place.
The determination is by targets set during production or procedural planning.
3.4.3. Performance
Real-time operating systems are designed to make work easier. Every system must
solve a problem or reduce the workload. As such, the developer should provide a
system that is easily assimilated with existing software and hardware as well as
aligned to the goals of the organization.
3.4.4. Manageability
This means a system whose veracity or bulkiness is manageable. The software and
hardware required to operate the RTOS must be of reasonable size. Technicians
should also be easy to find and orient. The idea is to reduce the cost of
implementation.
3.4.5. Scalability
The needs of any production or event environment change with time. This means
that a system may require an upgrade or downgrade. Such provisions must be
made during design and installation of any RTOS.
Scheduling is the process of deciding which task should be executed at any point in
time based on a predefined algorithm. The logic for the scheduling is implemented
in a functional unit called the scheduler. The scheduling process is not present only
in RTOS, it can be found in one form or another even in simple “bare-bone”
applications.
There are many scheduling algorithms that can be used for scheduling task
execution on a CPU. They can be classified into two main types: pre-emptive
scheduling algorithms and non-pre-emptive scheduling algorithms.
As an example, let’s have three tasks called Task 1, Task 2 and Task 3. Task 1 has
the lowest priority and Task 3 has the highest priority. Their arrival times and
execute times are listed in the table below.
Task1 10 50
Task 2 40 50
Task 3 40 40
In the table 3.1, we can see that Task 1 is the first to start executing, as it is the first
one to arrive (at t = 10 μs). Task 2 arrives at t = 40μs and since it has a higher
priority, the scheduler interrupts the execution of Task 1 and puts Task 2 into
running state. Task 3 which has the highest priority arrives at t = 60 μs. At this
moment Task 2 is interrupted and Task 3 is put into running state. As it is the
highest priority task it runs until it completes at t = 100 μs. Then Task 2 resumes
its operation as the current highest priority task. Task 1 is the last to complete is
operation.
3.5.2.2 Non-pre-emptive Scheduling (Co-Operative Scheduling):
In non-pre-emptive scheduling, the scheduler has more restricted control over the
tasks. It can only start a task and then it has to wait for the task to finish or for the
task to voluntarily return the control. A running task cannot be stopped by the
scheduler.
The non-pre-emptive scheduling can simplify the synchronization of the tasks, but
that is at the cost of increased response times to events. This reduces its practical
use in complex real-time systems.
We will now introduce some of the most popular scheduling algorithms that are
used in CPU scheduling. Not all of them are suitable for use in real-time embedded
systems. Currently, the most used algorithms in practical RTOS are round-robin
scheduling, and pre-emptive priority scheduling. In an attempt to present the right
explanation, the description and implementation of a few examples in simple C-
language are given.
In the shortest job first scheduling algorithm, the scheduler must obtain
information about the execution time of each task and it then schedules the one
with the shortest execution time to run next.
A disadvantage of this algorithm is that it requires the total execution time of a task
to be known before it is run.
Priority scheduling is one of the most popular scheduling algorithms. Each task is
assigned a priority level. The basic principle is that the task with the highest
priority will be given the opportunity to use the CPU.
Of course, not all tasks can have unique priority levels and there will always be
tasks that have the same priority. Different approaches can be used for handling the
scheduling of those tasks (e.g. FCFS scheduling or round-robin scheduling).
RTOS is thereby, a preferred choice for many embedded device manufacturers and
embedded programmers. There are many RTOS flavours available. FreeRTOS is
widely followed by practitioners. Further from here, a detailed study on FreeRTOS
and its features is done to analyse what makes it the right choice for the project.
Every process in a computer system requires some amount of time for its
execution. This time is both the CPU time and the I/O time. The CPU time is the
time taken by CPU to execute the process. While the I/O time is the time taken by
the process to perform some I/O operation. In general, we ignore the I/O time and
we consider only the CPU time for a process. So, burst time is the total time
taken by the process for its execution on the CPU.
Exit time is the time when a process completes its execution and exit from the
system.
Response time is the time spent when the process is in the ready state and gets the
CPU for the first time. For example, here we are using the First Come First Serve
CPU scheduling algorithm for the below 3 processes:
P1: 0 ms
P2: 7 ms because the process P2 have to wait for 8 ms during the execution
of P1 and then after it will get the CPU for the first time. Also, the arrival
time of P2 is 1 ms. So, the response time will be 8-1 = 7 ms.
P3: 13 ms because the process P3 have to wait for the execution of P1 and
P2 i.e. after 8+7 = 15 ms, the CPU will be allocated to the process P3 for
the first time. Also, the arrival of P3 is 2 ms. So, the response time for P3
will be 15-2 = 13 ms.
Response time = Time at which the process gets the CPU for the first time -
Arrival time
3.7.5 Waiting Time:
Waiting time is the total time spent by the process in the ready state waiting for
CPU. For example, consider the arrival time of all the below 3 processes to be 0
ms, 0 ms, and 2 ms and we are using the First Come First Serve scheduling
algorithm.
P1: 0 ms
P2: 8 ms because P2 have to wait for the complete execution of P1 and
arrival time of P2 is 0 ms.
P3: 13 ms because P3 will be executed after P1 and P2 i.e. after 8+7 = 15
ms and the arrival time of P3 is 2 ms. So, the waiting time of P3 will be: 15-
2 = 13 ms.
Waiting time = Turnaround time - Burst time
In the above example, the processes have to wait only once. But in many other
scheduling algorithms, the CPU may be allocated to the process for some time
and then the process will be moved to the waiting state and again after some time,
the process will get the CPU and so on.
There is a difference between waiting time and response time. Response time is
the time spent between the ready state and getting the CPU for the first time. But
the waiting time is the total time taken by the process in the ready state. Let's take
an example of a round-robin scheduling algorithm. The time quantum is 2 ms.
Process Arrival time (in ms) Burst time (in ms)
P1 0 4
P2 0 6
Table 3.6 Example for Waiting time determination
P1 P2 P1 P2 P2
0ms 2ms 2ms 4ms 4ms 6ms 6ms 8ms 8ms 10ms
Table 3.7 Gnatt Chart
In the above example, the response time of the process P2 is 2 ms because after 2
ms, the CPU is allocated to P2 and the waiting time of the process P2 is 4 ms i.e
turnaround time - burst time (10 - 6 = 4 ms).
Turnaround time is the total amount of time spent by the process from coming in
the ready state for the first time to its completion.
or
For example, if we take the First Come First Serve scheduling algorithm, and the
order of arrival of processes is P1, P2, P3 and each process is taking 2, 5, 10
seconds. Then the turnaround time of P1 is 2 seconds because when it comes at
0th second, then the CPU is allocated to it and so the waiting time of P1 is 0 sec
and the turnaround time will be the Burst time only i.e. 2 seconds. The
turnaround time of P2 is 7 seconds because the process P2 have to wait for 2
seconds for the execution of P1 and hence the waiting time of P2 will be 2
seconds. After 2 seconds, the CPU will be given to P2 and P2 will execute its
task. So, the turnaround time will be 2+5 = 7 seconds. Similarly, the turnaround
time for P3 will be 17 seconds because the waiting time of P3 is 2+5 = 7 seconds
and the burst time of P3 is 10 seconds. So, turnaround time of P3 is 7+10 = 17
seconds.
Different CPU scheduling algorithms produce different turnaround time for the
same set of processes. This is because the waiting time of processes differ when
we change the CPU scheduling algorithm.
3.7.7 Throughput:
Performance is the first thing one thinks about when considering the use of a multi-
core device, but, just as important especially for battery-operated embedded
systems, multiple cores allow you to run your product slower and thus reduce the
power consumption with basically the same performance as a single-core system
running at a much higher frequency.
An AMP system has multiple CPUs, each of which may be a different architecture
(but can be the same – i.e. may be either heterogeneous or homogeneous multi-
core). Each has its own address space (though some or all of the memory may be
shared with other cores), and each may or may not run an OS (and the OSes need
not be the same). Some kind of communication facility between the CPUs is
provided for each.
Fig 4.1 Asymmetric Multi-core System
Advantages:
Disadvantages:
Like AMP, an SMP system has multiple CPUs, but in this case each has exactly
the same architecture – i.e. it must be a homogeneous multi-core design. CPUs
share memory space (or at least some of it). Normally a single OS is used that runs
on all the CPUs, dividing work between them – another significant difference from
AMP. Some kind of communication facility between the CPUs is provided – this is
normally through shared memory, but accessed through the API of the OS.
Advantages:
Having multiple cores doesn’t render obsolete the need for an RTOS kernel. A
kernel is still highly desirable to make optimum use of both cores. In fact, both
cores run their own instance of the same or possibly different kernels to manage
the multiple tasks.
A kernel allows a designer to determine CPU use on a per-task basis and thus
better optimize the design by load balancing the tasks between cores. This load
balancing would typically be done at design time as opposed to run time. In other
words, having an RTOS kernel makes it easier to move functionality between
cores.
Ideally, both cores would be identical. This would make development slightly
easier since you would not have to deal with the slightly different cores. Also, it’s
quite unlikely that these devices will be limited to only two cores. AMP is very
scalable and in the coming years, we should see MCUs with many cores showing
up on the market.
SMT (simultaneous multi threading) technology is used in the dual core processors
so that the software can recognize the multi threading. A dual processor system has
two separate physical computer processors located on the same circuit board and it
is considered as a subset of a larger set of symmetrical processor systems.
A dual core processor design can provide each physical processor its own die
cache or may provide a single die cache which is shared by two processors. The
memory performance of the dual core processors can be viewed by the random
latency and by the sequential access.
Advantages:
The dual core processors can execute the multitasking operations and the user can
do the other work in the background simultaneously. Dual core CPUs have high
performance in terms of cache operations than single core processors..These CPUs
can deliver a result with high processing speeds and minimal risk of errors
The appearance of dual-core processors and solutions with additional new features
once again indicate that the clock frequency race is no longer acute. I believe that
in the near future we will not select processors according to their clock rates but
will take into account the whole lot of various characteristics, including not only
the working frequency, but also the number of processor cores, cache memory size,
additional technologies support and maybe more.
As the time passes and the OS and software developers adapt more to the new
working conditions, the multi-core processor architectures can lead the industry to
the new performance levels.
5. FREERTOS – A Detailed Study
allocate only;
allocate and free with a very simple, fast, algorithm;
a more complex but fast allocate and free algorithm with memory coalescence;
an alternative to the more complex scheme that includes memory coalescence
that allows a heap to be broken across multiple memory areas.
and C library allocate and free with some mutual exclusion protection.
Here are some reasons why FreeRTOS is a good choice for our application:
Consider a Simple Digital Watch. Its 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. A
thread is basically a step by step process.
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
Now let’s add more functionality to our digital watch, let’s put in a pedometer, an
altimeter and a temperature sensor. Now the tasks that it needs to compute
parallelly includes
Let’s 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.
This example gives us a brief idea of the places where simple embedded operating
systems like FreeRTOS fit in.
Tasks
Multitasking
Context switching
Schedulers
Kernels
Inter-task communications
Interrupts
We have previously looked into a few concepts like Tasks, Schedulers in the
previous chapter, Real-Time Operating System.
5.2.1 Tasks:
Any created task should never end before it is destroyed. It is common for task’s
code to be wrapped in an infinite loop, or to invoke vTaskDestroy(NULL) before it
reaches its final brace. As any code in infinite loop can fail and exit, it is safer even
for a repetitive task, to invoke vTaskDelete() before its final brace.
When a task is deleted, it is responsibility of idle task to free all allocated memory
to this task by kernel. Notice that all memory dynamically allocated must be
manually freed.
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. Ready state, Blocked state or Suspended
state. They have been discussed in detail in the previous chapter, Real-Time
Operating System.
The use of a multitasking operating system can simplify the design of what would
otherwise be a complex software application:
Multitasking Vs Concurrency
A conventional processor can only execute a single task at a time – but by rapidly
switching between tasks a multitasking operating system can make it appear as if
each task is executing concurrently. This is depicted by the diagram below which
shows the execution pattern of three tasks with respect to time. The task names are
colour coded and written down the left hand. Time moves from left to right, with
the coloured lines showing which task is executing at any particular time. The
upper diagram demonstrates the perceived concurrent execution pattern, and the
lower the actual multitasking execution pattern.
A task is a sequential piece of code – it does not know when it is going to get
suspended (swapped out or switched out) or resumed (swapped in or switched in)
by the kernel and does not even know when this has happened. Consider the
example of a task being suspended immediately before executing an instruction
that sums the values contained within two processor registers. While the task is
suspended other tasks will execute and may modify the processor register values.
Upon resumption the task will not know that the processor registers have been
altered – if it used the modified values the summation would result in an incorrect
value.
To prevent this type of error it is essential that upon resumption a task has a
context identical to that immediately prior to its suspension. The operating system
kernel is responsible for ensuring this is the case – and does so by saving the
context of a task as it is suspended. When the task is resumed its saved context is
restored by the operating system kernel prior to its execution. The process of
saving the context of a task being suspended and restoring the context of a task
being resumed is called context switching.
5.2.4 Schedulers:
Tasks are made to change from running state to one of the inactive states by
the scheduler by the process of pre-empting.
A currently running task is stopped without any notice, in favour 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.
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.
If both the 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.
5.2.5 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.
As seen in the figure 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.
There are several options available for tasks to communicate with each other
through the kernel of FreeRTOS like queues, mutex, semaphores and notifications.
Queues:
Queues are the primary form of inter-task communications. They can be used to
send messages between tasks, and between interrupts and tasks. In most cases they
are used as thread safe FIFO (First In First Out) buffers with new data being sent to
the back of the queue, although data can also be sent to the front.
Fig 5.5 Writing to and reading from a queue. In this example the queue was created
to hold 5 items, and the queue never becomes full.
The FreeRTOS queue usage model manages to combine simplicity with flexibility
– attributes that are normally mutually exclusive. Messages are sent through
queues by copy, meaning the data (which can be a pointer to larger buffers) is itself
copied into the queue rather than the queue always storing just a reference to the
data. This is the best approach because:
Using queues that pass data by copy does not prevent queues from being
used to pass data by reference. When the size of a message reaches a point
where it is not practical to copy the entire message into the queue byte for
byte, define the queue to hold pointers and copy just a pointer to the
message into the queue instead. This is exactly how
the FreeRTOS+UDP implementation passes large network buffers around
the FreeRTOS IP stack.
The kernel takes complete responsibility for allocating the memory used as
the queue storage area.
Variable sized messages can be sent by defining queues to hold structures
that contain a member that points to the queued message, and another
member that holds the size of the queued message.
A single queue can be used to receive different message types, and messages
from multiple locations, by defining the queue to hold a structure that has a
member that holds the message type, and another member that holds the
message data (or a pointer to the message data). How the data is interpreted
depends on the message type. This is exactly how the task that manages
the FreeRTOS+UDP IP stack is able to use a single queue to receive
notifications of ARP timer events, packets being received from the Ethernet
hardware, packets being received from the application, network down
events, etc.
The implementation is naturally suited for use in a memory protected
environment. A task that is restricted to a protected memory area can pass
data to a task that is restricted to a different protected memory area because
invoking the RTOS by calling the queue send function will raise the
microcontroller privilege level. The queue storage area is only accessed by
the RTOS (with full privileges).
A separate API is provided for use inside of an interrupt. Separating the API
used from an RTOS task from that used from an interrupt service routine
means the implementation of the RTOS API functions do not carry the
overhead of checking their call context each time they execute. Using a
separate interrupt API also means, in most cases, creating RTOS aware
interrupt service routines is simpler for end users than when compared to
alternative RTOS products.
In every way, the API is simpler.
When a task attempts to read from an empty queue the task will be placed into the
Blocked state (so it is not consuming any CPU time and other tasks can run) until
either data becomes available on the queue, or the block time expires.
When a task attempts to write to a full queue the task will be placed into the
Blocked state (so it is not consuming any CPU time and other tasks can run) until
either space becomes available in the queue, or the block time expires.
If more than one task block on the same queue then the task with the highest
priority will be the task that is unblocked first.
See the Queue Management section of the user documentation for a list of queue
related API functions. Searching the files in
the FreeRTOS/Demo/Common/Minimal directory will reveal multiple examples of
their usage.
Note that interrupts must NOT use API functions that do not end in “FromISR”.
xQueueHandlexQueueCreate( unsignedportBASE_TYPEuxQueueLength,
unsigned portBASE_TYPEuxItemSize
);
uxQueueLenght gives the number of elements this queue will be able to
handle at any given time.
xQueueCreatereturnsNULLifthequeuewasnotcreatedduetolackof memory
available; if not, the returned value should be kept to handle the newly
created queue.
Mutex:
Mutex is used for controlling access to the shared resource. It is used to avoid
extended priority inversion using priority inheritance technique.
Priority inheritance can be implemented in two ways: changing the priority of the
task trying to access the mutex
1. to the priority equal to the priority of the task acquiring the mutex or
2. to the higher priority than the priority of the task acquiring the
mutex so that the task trying to access the mutex will immediately get the
mutex
Semaphore:
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.
As an example, we can consider an elevator, which can support only four 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 signalling 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.
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 a key is
needed, it is asked for, from the kernel and if one is available, it will be given.
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.
Creation of Semiphore:
Taking a Semiphore:
portBASE_TYPExSemaphoreTake( xSemaphoreHandlexSemaphore,
portTickTypexTicksToWait );
Giving a Semiphore:
portBASE_TYPExSemaphoreGive( xSemaphoreHandlexSemaphore );
The semaphore is not available, so the task is blocked, wating for the semaphore.
xSemaphoreTake()
xGiveSemaphoreFromISR() xSemaphoreTake()
Which unblocks the task
xGiveSemaphoreFromISR() xSemaphoreTake()
xSemaphoreTake()
Another interrupt occurs and gives another semaphore. In the meanwhile, the task is processing
the first interrupt.
xGiveSemaphoreFromISR() xSemaphoreTake()
When the task has finished to preccess its first work, it waits for another semaphore and gets it
directly, since an interrupt occurred. The task is now able to process the second interrupt.
xSemaphoreTake()
5.2.7 Interrupts:
Interrupts behave in a way similar to the task switching mechanism available in the
scheduler so that the currently running code is pre-empted if an interrupt occurs,
One of our tasks in this project is to determine the execution time and CPU
Utilization for the applications.
The execution time or CPU time of a given task is defined as the time spent by the
system executing that task, including the time spent executing run-time or system
services on its behalf. The execution time is implementation defined. It is
implementation defined which task, if any, is charged the execution time is
consumed by interrupt handlers and run-time services on behalf of the system.
CPU utilization is the sum of work handled by a Central Processing Unit. It is also
used to estimate system performance. CPU utilization can vary according to the
type and amount of computing task because some tasks require heavy CPU time
while others require less CPU time. Process time is another name for CPU time
and is the amount of time used by a CPU for processing instruction of an operating
system or a computer program. CPU time is quantified in clock ticks or
seconds. CPU utilization shows the burden on a processor in terms of percentage
that indicates if any changes are to be made in the system otherwise it may get
exhausted of capacity.
6. ESP32- A Feature-rich MCU Board ported with
FreeRTOS
The ESP32 is a low-cost system-on-chip (SoC) series created by Espressif
Systems. It is an improvement on the popular ESP8266 that is widely used in IoT
projects. The ESP32 has both Wi-Fi and Bluetooth capabilities, which make it an
all-rounded chip for the development of IoT projects and embedded systems in
general. ESP32 has become the go-to chip for making several devices. They’re
small, powerful, have a ton of onboard features, and they’re relatively easy to
program. Powered by 40 nm technology, ESP32 provides a robust, highly
integrated platform, which helps meet the continuous demands for efficient power
usage, compact design, security, high performance, and reliability.
There are totally 39 digital Pins on the ESP32 out of which 34 can be used as
GPIO and the remaining are input only pins. The device supports 18-channels for
12-bit ADC and 2-channel for 8-bit DAC. IT also has 16 channels for PWM signal
generation and 10 GPIO pins supports capacitive touch features. The ESP32 has
multiplexing feature, this enables the programmer to configure any GPIO pin for
PWM or other serial communication though program. The ESP32 supports 3 SPI
Interface, 3 UART interface, 2 I2C interface, 2 I2S interface and also supports
CAN protocol.
Pin Pin Name Details
Category
Analog Pins ADC1_0 to ADC1_5 Used to measure analog voltage in the range
and ADC2_0 to of 0-3.3V.
ADC2_9
12-bit 18 Channel ADC
DAC pins DAC1 and DAC2 Used for Digital to analog Conversion
RTC GPIO RTCIO0 to RTCIO17 These 18 GPIO pins can be used to wake up
pins the ESP32 from deep sleep mode.
On comparing ESP32 with Arduino, we find that both are advantageous and
functional on its own. In terms of power and features obviously the dual
cored microprocessor powered ESP32 will surely take down the
microcontroller powered Arduino UNO. The ESP32 has built in Bluetooth
and Wi-Fi with good number of GPIO pins and communication protocols
for a very cheap price. The Arduino might look a bit handicapped when
competing with ESP32 but it has a large number of shields in the market
which can be readily used, also advanced Arduino boards like Yun has good
processing power as well.
The ESP32 operates on 3.3V and can be programmed with ESP-IDF or with
Arduino IDE; the Arduino operates at 5V and is known for its easy to use
Arduino IDE and strong community support. So, to conclude, if one has
prior experience with programming and the project really requires some
heavy processing with IoT capabilities then ESP32 can be preferred over
Arduino.
Both the ESP32 and ESP8266 are Wi-Fi development boards from Espressif
systems. They can be programmed using the ESP-IDF or the Arduino IDE.
The main difference would be that ESP8266 does not have an in-built
Bluetooth module and also does not feature CAN protocol and has no
SRAM. The ESP8266 is inferior in terms of performance compared with the
ESP32. Hence, ESP32 remains as the go-to chip for the project.
6.5 Powering the board:
There are totally three ways topower the ESP32 board.
Micro USB Jack: Connect the mini USB jack to a phone charger or computer
through a cable and it will draw power required for the board to function.
5V Pin: The 5V pin can be supplied with a Regulated 5V, this voltage will again be
regulated to 3.3V through the on-board voltage regulator.
3.3V Pin: If we have a regulated 3.3V supply then we can directly provide this to
the 3.3V pin of the ESP32.
There are several use cases for wanting to multitask on a microcontroller. For
instance: you might have a microcontroller that reads a temperature sensor, shows
it on an LCD, and send it to the cloud. One can do all three synchronously, one
after the other. But what if one is using an e-ink display that takes a few seconds to
refresh?Luckily the implementation for the ESP32 includes the possibility to
schedule tasks with FreeRTOS. These can run on a single core; many cores and
one can even define which is more important and should get preferential treatment.
6.7 Programming:
Before programming this chip, it’s crucial to understand that, unlike other
embedded systems, the ESP32 comes with a light operating system - FreeRTOS.
The following methods to program this chip don’t replace the FreeRTOS firmware,
but rather deploy applications for it to run.
There are currently two methods to program the ESP32: the ESP-IDF and the
ESP32 Arduino Core.Using the Arduino IDE is easier. On the other hand, using
ESP-IDF gives more power. If we need more control over the hardware, like power
management, wireless power configuration or CPU core management, we’ll need
the ESP-IDF. Hence, we shall opt for ESP-IDF. Expressif included FreeRTOS in
its latest version ESP – IDF. In the subsequent chapters, we shall look into the
setting up of ESP-IDF environment., to be used in the project.
6.8 ESP-IDF RTOS:
The vanilla FreeRTOS is designed to run on a single core. However, the ESP32 is
dual core containing a Protocol CPU (known as CPU 0 or PRO_CPU) and an
Application CPU (known as CPU 1 or APP_CPU). The two cores are identical in
practice and share the same memory. This allows the two cores to run tasks
interchangeably between them.
Given the specifications and features of the ESP32 board, one can see that it is
efficient, lighter and commercially reliable. The advantage that it comes with
FreeRTOS plays a crucial role, and thus, floats out to be the right choice for the
project.
7. SETTING UP ESP-IDF
The native software development framework for the ESP-32 is called the Espressif
IoT Development Framework (ESP-IDF). It provides maximum functionality and
compatibility. It is a bit hard core than some of the other programming options but
it gives a better understanding of what is happening under the hood.
Hardware:
An ESP32 board.
USB cable – USB A / micro USB B.
Computer running Windows, Linux, or mac
OS. Software:
https://dl.espressif.com/dl/esp-idf-tools-setup-2.3.exe
The installer includes the cross-compilers, OpenOCD, CMake and Ninja build tool.
The installer can also download and run installers for Python 3.7 and Git For
Windows if they are not already installed on the computer.
The installer also offers to download one of the ESP-IDF release versions.
For the remaining Getting Started steps, we’re going to use the Windows
Command Prompt.
ESP-IDF Tools Installer creates a shortcut in the Start menu to launch the ESP-IDF
Command Prompt. This shortcut launches the Command Prompt (cmd.exe) and
runs export.bat script to set up the environment variables
( PATH , IDF_PATH and others). Inside this command prompt, all the installed
tools are available.
Note that this shortcut is specific to the ESP-IDF directory selected in the ESP-IDF
Tools Installer. If you have multiple ESP-IDF directories on the computer (for
example, to work with different versions of ESP-IDF), you have two options to use
them:
1. Create a copy of the shortcut created by the ESP-IDF Tools Installer, and
change the working directory of the new shortcut to the ESP-IDF directory you
wish to use.
2. Alternatively, run cmd.exe , then change to the ESP-IDF directory you wish to
use, and run export.bat . Note that unlike the previous option, this way requires
Python and Git to be present in PATH . If you get errors related to Python or
Git not being found, use the first option.
To build applications for the ESP32, we need the software libraries provided by
Espressif in ESP-IDF repository.
To get ESP-IDF, navigate to the installation directory and clone the repository
with git clone . In addition to installing the tools, ESP-IDF Tools Installer for
Windows introduced in Step 1 can also download a copy of ESP-IDF.
In order to install the tools without the help of ESP-IDF Tools Installer, open the
Command Prompt and follow these steps:
cd%userprofile%\esp\esp-idf install.bat
The scripts introduced in this step install compilation tools required by ESP-IDF
%USERPROFILE%\.espressif
inside the user home directory:on Windows and on Linux . To install the tools into
$HOME/.espressif
a different directory, set the
IDF_TOOLS_PATH
environment variablebefore running the installation scripts. Make sure that the
user account has sufficient permissions to read and write this path.
If changing the IDF_TOOLS_PATH , make sure it is set to the same value every
time the Install script install.bat , install.ps1
or install.sh ) and an Export script
( export.bat
(, export.ps1 or export.sh ) are executed.
The installed tools are not yet added to the PATH environment variable. To make
the tools usable from the command line, some environment variables must be set.
ESP-IDF provides another script which does that.
%userprofile%\esp\esp-idf\export.bat
On Linux, in the terminal where you are going to use ESP-IDF, run:
. $HOME/esp/esp-idf/export.sh
Step 5: Start a Project:
We are now ready to prepare the first application for ESP32.
cd%userprofile%\esp
xcopy /e /i %IDF_PATH%\examples\get-started\hello_world hello_world
On Linux,
cd ~/esp
cp -r $IDF_PATH/examples/get-started/hello_world .
Step 7: Configure:
Navigate to the hello_world directory from Step 5. Start a Project, set ESP32 chip
as the target and run the project configuration utility menuconfig.
cd %userprofile%\esp\hello_world
idf.py set-target esp32
idf.py menuconfig
Setting the target with idf.py set-target {IDF_TARGET} should be done once,
after opening a new project. If the project contains some existing builds and
configuration, they will be cleared and initialized.
On Linux,
cd ~/esp/hello_world
idf.py set-target esp32
idf.py menuconfig
idf.py build
This command will compile the application and all ESP-IDF components, then it
will generate the bootloader, partition table, and application binaries.
$ idf.py build
Running cmake in directory /path/to/hello_world/build
Executing "cmake -G Ninja --warn-uninitialized /path/to/hello_world"... Warn
about uninitialized values.
-- Found Git: /usr/bin/git (found version "2.17.0")
-- Building empty aws_iot component due to configuration
-- Component names: ...
-- Component paths: ...
When flashing, we will see the output log similar to the following:
...
esptool.py --chip esp32 -p /dev/ttyUSB0 -b 460800 --before=default_reset --
after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB
0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin
0x10000 hello-world.bin
esptool.py v3.0-dev
Serial port
/dev/ttyUSB0
Connecting......._
Chip is ESP32D0WDQ6 (revision 0)
Features: WiFi, BT, Dual Core, Coding Scheme None
Crystal is 40MHz
MAC: 24:0a:c4:05:b9:14
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Compressed 3072 bytes to 103...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.0 seconds (effective
5962.8 kbit/s)...
Hash of data verified.
Compressed 26096 bytes to 15408...
Writing at 0x00001000... (100 %)
Wrote 26096 bytes (15408 compressed) at 0x00001000 in 0.4 seconds (effective
546.7 kbit/s)...
Hash of data verified.
Compressed 147104 bytes to 77364...
Writing at 0x00010000... (20 %)
Writing at 0x00014000... (40 %)
Writing at 0x00018000... (60 %)
Writing at 0x0001c000... (80 %)
Writing at 0x00020000... (100 %)
Wrote 147104 bytes (77364 compressed) at 0x00010000 in 1.9 seconds (effective
615.5 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
Done
If there are no issues by the end of the flash process, the board will reboot and start
up the “hello_world” application.
...
Hello world!
Restarting in 10 seconds...
This is esp32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 2MB
external flash
Restarting in 9
seconds... Restarting in
8 seconds... Restarting
in 7 seconds...
To exit IDF monitor use the shortcut Ctrl+] .
1.
2. +--esp-idf
3. |
4. |
5. +-- components
6. |
7. +-- docs
8. |
9. +-- examples
10. |
11. +-- make
12. |
13. +-- tools
The components directory holds all the 'C' code for the ESP32. It contains all the
'components' that make up the ESP32. It includes Drivers for numerous
peripherals, the bootloader, bt(bluetooth), freeRTOS etc. If we expand
the components the tree looks like so:
1. +---components
2. |+---app_update
3. ||| component.mk
4. ||| esp_ota_ops.c
5. |||
6. || \---include
7. || esp_ota_ops.h
8. ||
9. |+---bootloader
10.||| component.mk
11.||| README.rst
12. |||
13.||+---include
14.||| esp_image_format.h
15.||| esp_secure_boot.h
16. |||
17.||+---include_priv
18.||| bootloader_flash.h
19. |||
20. || \---src
21.|| bootloader_flash.c
22.|| esp_image_format.c
23.|| secure_boot.c
24.|| secure_boot_signatures.c
25. ||
26. |+---bt
27. ||| bt.c
28...
29. ||
30.|+---driver
31.||| component.mk
32.||| gpio.c
33...
34. ||
35. |+---esp32
36. ||| abi.cpp
37...
38. ||
39.|+---esptool_py
40.|||Kconfig.projbuild
41...
42. ||
43.|+---ethernet
44.||| component.mk
45...
46. ||
47. |+---expat
48...
49. ||
50.|+---freertos
51.||| component.mk
52...
53. ||
54. |+---
idf_test 55...
56. ||
57. |+---newlib
58...
59. ||
60. |+---
nghttp 61...
62. ||
63.|+---nvs_flash
64.|||.gitignore
65. ||
66...
67. ||
68. |+---
openssl 69. ||
70...
71. ||
72. |+---
partition_table 73...
74. ||
75. |+---
spi_flash 76. ||
77...
78. ||
79. |+---
tcpip_adapter 80...
81. ||
82. |+---ulp
83. ||
84...
85. ||
86. |+---vfs
87. ||
88...
89. |+---
wpa_supplicant 90. ||
91.| \---xtensa-debug-module
92.|| component.mk
93...
94.|
95.|
96.+--- docs
1. #include<stdio.h>
2. #include"freertos/FreeRTOS.h"
3. #include"freertos/task.h"
4. #include"esp_system.h"
1. void app_main()
2. {
3. nvs_flash_init();
4. xTaskCreate(&hello_task,"hello_task",2048, NULL,5, NULL);
5. }
1. void hello_task(void*pvParameter)
2. {
3. printf("Hello world!\n");
4. for(int i =10; i >=0; i--){
5. printf("Restarting in %d seconds...\n", i);
6. vTaskDelay(1000/
portTICK_RATE_MS); 7. }
8. printf("Restarting now.\n");
9. fflush(stdout);
10. esp_restart();
11.}
Now, let’s look at the complete code.
1. #include<stdio.h>
2. #include"freertos/FreeRTOS.h"
3. #include"freertos/task.h"
4. #include"esp_system.h"
5.
6. void hello_task(void*pvParameter)
7. {
8. printf("Hello world!\n");
9. for(int i =10; i >=0; i--){
10. printf("Restarting in %d seconds...\n", i);
11. vTaskDelay(1000/ portTICK_RATE_MS);
12.}
13. printf("Restarting now.\n");
14. fflush(stdout);
15. esp_restart();
16.}
17.
18. void app_main()
19.{
20. nvs_flash_init();
21. xTaskCreate(&hello_task,"hello_task",2048, NULL,5, NULL);
22.}
1.#include<stdio.h>
2.#include"freertos/FreeRTOS.h"
3.#include"freertos/task.h"
4.#include"esp_system.h"
5.#include"driver/gpio.h"
6.
7.#define BLINK_GPIO 13
8.void hello_task(void*pvParameter)
9.{
10.
11. while(1)
12. {
13. printf("Hello world!\n");
14. vTaskDelay(100/ portTICK_RATE_MS);
15. }
16.}
17.
18. void blinky(void*pvParameter)
19.{
20.
21. gpio_pad_select_gpio(BLINK_GPIO);
22./* Set the GPIO as a push/pull output */
23. gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
24.while(1){
25./* Blink off (output low) */
26. gpio_set_level(BLINK_GPIO,0);
27. vTaskDelay(1000/ portTICK_RATE_MS);
28./* Blink on (output high) */
29. gpio_set_level(BLINK_GPIO,1);
30. vTaskDelay(1000/ portTICK_RATE_MS);
31.}
32.}
33.
34.
35. void app_main()
36.{
37. nvs_flash_init();
38. xTaskCreate(&hello_task,"hello_task",2048, NULL,5, NULL);
39. xTaskCreate(&blinky,"blinky",512,NULL,5,NULL );
40.}
8. PROJECT DEVELOPMENT AND
IMPLEMENTATION
8.1 DEVELOPMENT:
Before getting in to the program development, let us take a look at the folder where
the source code is to be stored and additional documents are required for the
program to function properly.
The folder is as shown below:
From the above picture, one can see that the program folder consists of a main
folder, cmakeLists.txt, makefile, README.md, sdkconfig.defaults.
1.CMakeLists.txt file contains a set of directives and instructions describing the
project's source files and targets (executable, library, or both).
2.A makefile is a special file, containing shell commands, that you create and
name makefile (or Makefile depending upon the system). While in the directory
containing this makefile, you will type make and the commands in the makefile
will be executed.
3.A README file contains information about other files in a directory or archive
of computer software. A form of documentation, it is usually a simple plain text
file called READ.ME, README.TXT, README.md.
5.The main folder contains the source code for the project.
After building the project, the project folder will be as shown below:
Fig 8.2 Folder organisation of project after building the project
A new folder called as build has been created which stores all the details related to
building the project.
An additional sdkconfig.old document is created which contains the default
configuration details and the sdkconfig folder is updated to the recent
developments.
8.1.1 PROGRAM:
Since we are now familiar with the structure of the program as seen in the Hello
World task earlier, let us proceed to the program development.
The source code of the project for the single-core processor is given below:
#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#define pi 3.14159265
while(1) {
long c;
c=a+b;
printf("sum of %d and %d is %ld\n",a,b,c);
a+=1;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void sub(void *pvParameter)
{
int a=6,b=3;
while(1) {
int c;
c=a-b;
printf("subtraction of %d and %d is %d\n",a,b,c);
a+=2;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
double x,rescos,ressine,restan,val;
x=30;
while(1)
{ val=pi/180.0;
rescos=cos(x*val);
ressine=sin(x*val);
restan=tan(x*val);
printf("The cosine of %lf degrees is %lf\n",x,rescos);
printf("The sine of %lf degrees is %lf\n",x,ressine);
printf("The tangent of %lf degrees is %lf\n",x,restan);
x+=15;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compadd(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=a1+a2;
b3=b1+b2;
printf("The sum of %d+i%d and %d+i%d is %d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=1;
a2+=2;
b1+=-1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compsub(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=a1-a2;
b3=b1-b2;
printf("The difference of %d+i%d and %d+i%d is %d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=3;
a2+=1;
b1+=2;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compmul(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=(a1*a2-b1*b2);
b3=(a1*b2+a2*b1);
printf("The multiplication of %d+i%d and %d+i%d is
%d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=3;
a2+=1;
b1+=2;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void temp(void *pvParameter){
float c=23;
while(1)
{
float f,k;
f=((c*1.8)+32.0);
k=c+273.15;
printf("The farenheit temperature for given %f celcius is %f farenheit\n",c,f);
printf("The kelvin temperature for given %f celcius is %f kelvin\n",c,k);
c=c+1.5;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void app_main()
{
xTaskCreatePinnedToCore(&division, "division", 2048, NULL, 10, NULL, 0);
xTaskCreatePinnedToCore(&mul, "mul", 2048,NULL,8,NULL ,0);
xTaskCreatePinnedToCore(&add, "add", 2048,NULL,6,NULL,0);
xTaskCreatePinnedToCore(&sub, "sub", 8192,NULL,5,NULL,0 );
xTaskCreatePinnedToCore(&square, "square", 4096,NULL,3,NULL,0 );
xTaskCreatePinnedToCore(&sqart, "sqart", 16384,NULL,15,NULL,0);
xTaskCreatePinnedToCore(&logarithimic, "logarithimic", 4096,NULL,2,NULL,0
);
xTaskCreatePinnedToCore(&trig, "trig", 8192,NULL,2,NULL,0 );
xTaskCreatePinnedToCore(&temp, "temp", 2048,NULL,2,NULL,0 );
xTaskCreatePinnedToCore(&compadd, "compadd", 2048,NULL,3,NULL,0 );
xTaskCreatePinnedToCore(&compsub, "compsub", 2048,NULL,4,NULL,0 );
xTaskCreatePinnedToCore(&compmul, "compmul", 2048,NULL,5,NULL,0 );
The source code of the project for the dual-core processor is given below:
#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#define pi 3.14159265
while(1) {
long c;
c=a*b;
printf("Multiplication of %d and %d is %ld\n",a,b,c);
a+=1;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void add(void *pvParameter)
{
int a=6,b=3;
while(1) {
long c;
c=a+b;
printf("sum of %d and %d is %ld\n",a,b,c);
a+=1;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void sub(void *pvParameter)
{
int a=6,b=3;
while(1) {
int c;
c=a-b;
printf("subtraction of %d and %d is %d\n",a,b,c);
a+=2;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
double x,rescos,ressine,restan,val;
x=30;
while(1)
{ val=pi/180.0;
rescos=cos(x*val);
ressine=sin(x*val);
restan=tan(x*val);
printf("The cosine of %lf degrees is %lf\n",x,rescos);
printf("The sine of %lf degrees is %lf\n",x,ressine);
printf("The tangent of %lf degrees is %lf\n",x,restan);
x+=15;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compadd(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=a1+a2;
b3=b1+b2;
printf("The sum of %d+i%d and %d+i%d is %d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=1;
a2+=2;
b1+=-1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compsub(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=a1-a2;
b3=b1-b2;
printf("The difference of %d+i%d and %d+i%d is %d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=3;
a2+=1;
b1+=2;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void compmul(void *pvParameter){
int a1=2,b1=4,a2=3,b2=1;
int a3,b3;
while(1)
{
a3=(a1*a2-b1*b2);
b3=(a1*b2+a2*b1);
printf("The multiplication of %d+i%d and %d+i%d is
%d+i%d\n",a1,b1,a2,b2,a3,b3);
a1+=3;
a2+=1;
b1+=2;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void temp(void *pvParameter){
float c=23;
while(1)
{
float f,k;
f=((c*1.8)+32.0);
k=c+273.15;
printf("The farenheit temperature for given %f celcius is %f farenheit\n",c,f);
printf("The kelvin temperature for given %f celcius is %f kelvin\n",c,k);
c=c+1.5;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
void app_main()
{
xTaskCreatePinnedToCore(&division, "division", 2048, NULL, 10, NULL, 1);
xTaskCreatePinnedToCore(&mul, "mul", 2048,NULL,8,NULL ,0);
xTaskCreatePinnedToCore(&add, "add", 2048,NULL,6,NULL,1);
xTaskCreatePinnedToCore(&sub, "sub", 8192,NULL,5,NULL,0 );
xTaskCreatePinnedToCore(&square, "square", 4096,NULL,3,NULL,0 );
xTaskCreatePinnedToCore(&sqart, "sqart", 16384,NULL,15,NULL,1 );
xTaskCreatePinnedToCore(&logarithimic, "logarithimic", 4096,NULL,2,NULL,0
);
xTaskCreatePinnedToCore(&trig, "trig", 8192,NULL,2,NULL,1 );
xTaskCreatePinnedToCore(&temp, "temp", 2048,NULL,2,NULL,0 );
xTaskCreatePinnedToCore(&compadd, "compadd", 2048,NULL,3,NULL,1 );
xTaskCreatePinnedToCore(&compsub, "compsub", 2048,NULL,4,NULL,0 );
xTaskCreatePinnedToCore(&compmul, "compmul", 2048,NULL,5,NULL,1 );}
8.1.2THE INCLUDES:
As the program requires usage of more than one component, we include the
corresponding .h files in the header section.
stdio.h,math.h the C libraries are included so as to facilitate the
input/output and mathematical operations function smoothly.
freertos/FREERTOS.h facilitates the inclusion of this set's configuration
required to run freeRTOS on ESP32.
freertos/task.h facilitates the inclusion of multitasking functionality.
esp_system.h configures the peripherals in the ESP System which is similar
to system initialization.
8.1.3 MAIN FUNCTION:
The program execution starts with the app_main(), just like good old main(). This
is the first function that gets called automatically.
Example:
void app_main()
{
xTaskCreate():
Each task requires RAM that is used to hold the task state, and used by the task as
its stack. If a task is created using xTaskCreate() then the required RAM is
automatically allocated from the FreeRTOS heap.
Parameters:
pvTaskCode: Pointer to the task entry function i.e; the address of the
task.Tasks are normally implemented as an infinite loop, and must never
attempt to return or exit from their implementing function. Tasks can
however delete themselves.
pcName: A descriptive name for the task. This is mainly used to facilitate
debugging, but can also be used to obtain a task handle. The maximum
length of a task’s name is set using the configMAX_TASK_NAME_LEN
parameter in FreeRTOSConfig.h.
usStackDepth: The number of words (not bytes!) to allocate for use as the
task’s stack.
pvParameters: A value that will passed into the created task as the task’s
parameter. If pvParameters is set to the address of a variable then the
variable must still exist when the created task executes – so it is not valid to
pass the address of a stack variable.
Returns:
If the task was created successfully then pdPASS/pdTRUE is returned.
Otherwise errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned.
xTaskCreatePinnedToCore():
This function is similar to xTaskCreate, but allows setting task affinity in SMP
system.
xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode,
const char * const pcName,
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
const BaseType_t xCoreID );
Parameters:
pvTaskCode: Pointer to the task entry function simply, the address of the
task. Tasks are normally implemented as an infinite loop, and must never
attempt to return or exit from their implementing function. Tasks can
however delete themselves.
pcName: A descriptive name for the task. This is mainly used to facilitate
debugging, but can also be used to obtain a task handle. The maximum
length of a task’s name is set using the configMAX_TASK_NAME_LEN
parameter in FreeRTOSConfig.h.
usStackDepth: The number of words (not bytes!) to allocate for use as the
task’s stack.
pvParameters: A value that will passed into the created task as the task’s
parameter. If pvParameters is set to the address of a variable then the
variable must still exist when the created task executes – so it is not valid to
pass the address of a stack variable.
while(1) {
long c;
c=a+b;
printf("sum of %d and %d is %ld\n",a,b,c);
a+=1;
b+=1;
vTaskDelay(200 / portTICK_RATE_MS);
}
}
Here, the functionality of the task is written in the while loop that run infinitely as
a task in the freertos should run infinitely and it should never attempt to return or
exit from the implementing function.
Now, in order to facilitate the execution of other tasks, we introduce a delay in the
task through vTaskDelay.
vTaskDelay:
void vTaskDelay( const TickType_t xTicksToDelay );
vTaskDelay() specifies a time at which the task wishes to unblock relative to the
time at which vTaskDelay() is called. For example, specifying a block period of
100 ticks will cause the task to unblock 100 ticks after vTaskDelay() is called.
Parameters:
xTicksToDelay: The amount of time, in tick periods, that the calling task
should block.
Example usage:
for( ;; )
{
/* Simply toggle the LED every 500ms, blocking between each toggle. */
vToggleLED();
vTaskDelay(500 / portTICK_PERIOD_MS;);
}
}
In order to get the information about the tasks running and the run time statistics of
the program, two APIs are used. Their functionality is described below:
vTaskList():
In the ASCII table the following letters are used to denote the state of a task:
‘B’ – Blocked
‘R’ – Ready
‘D’ – Deleted (waiting clean up)
‘S’ – Suspended, or Blocked without a timeout
Parameters:
pcWriteBuffer : A buffer into which the above mentioned details will be
written, in ASCII form. This buffer is assumed to be large enough to
contain the generated report. Approximately 40 bytes per task should be
sufficient.
vTaskGetRunTimeStats():
Parameters:
pcWriteBuffer: A buffer into which the execution times will be written,
in ASCII form. This buffer is assumed to be large enough to contain the
generated report. Approximately 40 bytes per task should be sufficient.
8.2 IMPLEMENTATION:
The following are the steps to be followed for implementing the project in a
LINUX based system:
1) Adding tools to the environment
Before proceeding to compiling and building the project ensure that all the tools
must be added to the path so as to function. Hence, navigate to the folder where the
ESP-IDF software is stored and then type the command. ./export.sh. This will
ensure that all the tools are added to the path for smooth functioning of the build
process.
Fig 8.5 Adding tools to the path
Now, navigate to the folder where the project is stored through the terminal and
type make menuconfig. The following window appears:
Now, go to freeRTOS under the component config and set the options as shown
below. These are set to enable the APIs vTaskList and vTaskGetRunTimeStats that
are used in the project and also to to do various other funcyions like to limit the
size of the task name, tick rate etc.
After setting the options as per requirement, press S to save the choices and press
Esc to exit from the configuration window.
In the above figure, we can see that the expected values of the outcome match with
the obtained results. It shows the list of all the tasks performed.
CPU Utilization:
CPU Utilization is the sum of work handled by a Central Processing Unit.
CPU Utilization = 100- (%time for which the CPU is idle)
From the above results we know that, CPU is idle for 49% for IDLE1 and 22% for
IDLE2.
Hence, CPU Utilization = 100 – (49+22) = 29%
The second application is implementing a calculator on a dual-core processor.
Fig 9.3 Result obtained for dual-core processor
In the above figure, we can see that the expected values of the outcome match with
the obtained results. It shows the list of all the tasks performed.
For core-1,
Execution time(te) =1201577+27131+1646545+39032+28717+108791+18142
+9588
= 3079523 microseconds
Execution time(te) = 3.08 seconds
Since, the core-1 has a higher execution time compared to core-0, the execution
time of the dual-core processor as a whole is taken to be 3.08 seconds.
CPU Utilization:
CPU Utilization is the sum of work handled by a Central Processing Unit.
CPU Utilization = 100- (%time for which the CPU is idle)
The CPU utilization of a dual-core processor is given by the average of the CPU
utilization times of both the cores.
Core -0
CPU Utilization = (100-45)% = 55%
Core -1
CPU Utilization = (100-26)% = 74%
Hence, for the dual-core processor,
CPU Utilization=( 55+74)/2 = 64.5%
From the results shown above and all the discussion, it is evident that the dual core
processor totally outplays the single core processor. All the limitations pertaining
to the single core processor have been overcome by the dual core processor. The
execution time of the dual core processor is almost half of the single core processor
which reflects the efficient and quick response time of the dual core processor.
Hence, all these results validate the usage of dual core processors in most of the
modern applications.
From this project we conclude that RTOS is very important to handle critical time
systems, which become useless after missing the deadline. Also, we have seen the
capabilities of the ESP32 board, and up to what extent a single-core processor is
reliable. A dual core processor executes all the tasks much faster. It is perfect to
run multiple processors at one time. If a single core processor is assigned with two
difference things, it cannot do them both simultaneously. It switches to all tasks
one by one but a multiple core processor can do both operations at the same time.
Having a dual core processor means one has the power of two computers in one.
But a dual core processor is much cheaper than buying two computers and than
combining them to build a dual-processor unit. The disadvantage is that, a lot of
power is wasted which of course quickly drains the battery. For simpler
applications, the disadvantage overpowers its advantages.
Depending on the needs required to meet the project, one can choose to whether to
go for the single-core or the dual-core processor. From this project, along with
pointing the difference between the single and dual core systems, it is possible to
conclude that there is a very rich field involving RTOS applications for embedded
tasks.
10.1Future Scope
Microprocessors have found their way in almost every electronic device that is
available today. Microprocessors and microcontrollers enable the modern world to
produce devices that comprehend the environment and react to situations
producing a much desired effect. The operating speed of modern day computers
has increased over a thousand times in the past twenty years.
It’s not hard to predict that next-generation processors will have smaller details,
more transistors on each square millimeter of silicon, and do more clever things,
such as powering down under-used sections even between keystrokes. They will
also have many cores or CPUs.
Even now, Intel has given hardware and software developers a peek at a prototype
80-core processor with one teraflop of computational ability. The company,
however, has set no launch date. Nvidia Corp. sells a Quadro Plex graphic
processor sporting 128 cores. And Cisco Systems Inc., sensing a coming surge in
network traffic, is toying with a 188-core router.
References
(1) https://www.freertos.org/FreeRTOS-quick-start-guide.html
(2) https://www.freertos.org/a00019.html
(3) https://www.freertos.org/a00021.html#vTaskList
(4) https://www.freertos.org/a00021.html#vTaskGetRunTimeStats
(5) https://www.freertos.org/RTOS-message-buffer-API.html
(6) ESP-IDF Programming Guide — ESP-IDF Programming Guide documentation
(7) https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-
setup.html
(8) https://docs.aws.amazon.com/freertos/latest/userguide/what-is-freertos.html
(9) https://www.espressif.com/en/products/socs/esp32/overview
(10) Using the FreeRTOS Real Time Kernel: A Practical Guide Richard Barry