Freertos
Freertos
Freertos
loop2 – This is the name of the function you want to run as a parallel task. In our
case we have named the function loop2.
"loop2" – This is a user-readable name for the function. This can be any string you
like. But keep it short and readable.
1000 – This is the stack size (memory size) for the task. The task will use this
amount of memory when it needs to store temporary variables and results. The value
is in number of bytes and 1000 bytes is more than enough for our simple task.
Memory-intensive tasks will require more memory.
NULL – This is a pointer to the parameter that will be passed to the new task. We
are not using it here and therefore it is set to NULL.
0 – This is the priority of the task. We are setting it to 0.
NULL – This is a handle or pointer to the task that we are going to create. This
handle can be used to invoke the task. We don’t need it for our example and
therefore it can be NULL.
0 – This is the ID of the processor core we want our task to run at. ESP32 has two
cores identified as 0 and 1. So we are pinning our task to core 0.
You can see that the function loop2() has a single argument called void*
pvParameters. That argument part is required for any function that is going to be
run as a task. The parameter is a type of void pointer with the name pvParameters.
That name doesn’t have to be the same and it can be any. But for the sake of
clarity, keep the name as is. The return type of a task function should always be
void. Otherwise, it will generate a compilation error. If you are wondering why the
default loop() function does not have any arguments, that is because the default
loop() is automatically encapsulated by a preprocessor (an application that scans
and rearranges your code) before it is compiled.
The core part of an RTOS is a kernel (a supervising program with the highest system
privileges). The kernel takes care of all the dirty work for us, including timing,
queuing, task synchronization, priorities, interrupts, and more. FreeRTOS can work
on both a single-core or multi-core environment. In cases where we have multiple
cores, we can execute tasks in different cores. But if there is only one core,
FreeRTOS slices the processor time to different quanta (a minimum unit of time,
usually a very small time interval) and allows each task to consume the time slices
periodically. That means we can run multiple parallel RTOS tasks on the same core
without any worries, just like how we run loop() and loop2() on the same core.
When you write an Arduino application for ESP32, FreeRTOS is used in the underlying
layers to schedule your tasks without you even knowing. The default loop() function
is run inside a FreeRTOS task called loopTask(). The function
xTaskCreatePinnedToCore() is one of the many methods to create a task in FreeRTOS.
The more generic version of the function is called xTaskCreate(). This function
does not pin the task to any core explicitly but determines it automatically.
Race conditions give rise to weird bugs that may not show up during normal testing.
So whenever you need to share a variable, object, function, or peripheral between
tasks, make sure that only one task accesses the resource at a time. The accessing
task must also leave the resource in a determined state after the operation. But
how do we do it? Read about it in the next section.
by using FreeRTOS, we can make sure that each task of Arduino have a deterministic
execution pattern and every task will meet its execution deadline. In other words,
it is a scheduler that assigns Arduino CPU resources to every task according to a
scheduling algorithm.
Task Synchronization:-
Whenever we need to share a resource or make two tasks/processes communicate with
each other, we need a mechanism to prevent both tasks from accessing and modifying
the resource at the same time. When a task accesses a resource we need to signal
all other tasks that the resource is being used at the moment. This is called task
synchronization. There are two techniques to achieve this; Mutex and Semaphore.
Mutex stands for “Mutual Exclusion”. A mutex is simply a shared variable that is
always accessed through atomic operations. What is an atomic operation you ask?
It’s a way of executing a sequence of instructions without anything interrupting
the operation until it finishes. A mutex variable can remain in two states; locked
and unlocked (0 or 1). You can think of it as a boolean variable. It is used to
protect a common resource from simultaneous access by multiple tasks. A
process/task can acquire a mutex lock by calling a function called acquire(). Once
a lock is acquired the task can continue its operations on the object protected by
the mutex without worrying about other tasks modifying the resource. If any other
tasks try to acquire a mutex lock when it is already in the locked position, the
requesting tasks will be put in a wait state called busy waiting. The processes
that try to get a lock will repeatedly try to acquire it until they succeed. Busy
waiting is also called a spinlock.
The task that initially acquired the lock will release the lock when it finishes
all of its operations. Releasing the mutex lock can be done by release(). Now,
other tasks waiting to get a lock can acquire it this time. Both acquire() and
release() are generic function names used for explaining the concept and the actual
names can be platform-specific.
A semaphore handle can be used to create any type of semaphores or mutex. xMutex is
only a handle initialized with a NULL value. It is not usable yet. We define the
type of mutex in the setup() function using xSemaphoreCreateMutex().