- In this series, we'll go over general RTOS concepts,
but I'd also like to show you
how you can implement those concepts
using FreeRTOS on an ESP32.
I plan to use the Arduino IDE to keep things simple,
but first, let me show you
how you can download the FreeRTOS source code,
so you can include it in your non-Arduino project.
(upbeat music)
Head to freeRTOS.org, and click download FreeRTOS.
While that's downloading, let's look at the documentation.
Click on Kernel, getting started.
The steps here walk you through building
and running the demo applications
that come with the source code.
If your board is not supported,
check out the create your own FreeRTOS project page.
Including FreeRTOS in your project
is relatively straightforward.
At a minimum, you need to include the C
and header files listed here,
and choose your heap management file.
You do need to assign one of your microcontroller's timers
to be the RTOS tick timer,
so make sure you read this section on doing that.
If you ever need help with FreeRTOS,
I highly recommend clicking on the books link,
and downloading the "Mastering the FreeRTOS
Real-Time Kernel," and the reference manual.
Both are amazingly helpful when navigating FreeRTOS.
When it's done downloading, extract the zip file,
and head into the FreeRTOS directory.
The FreeRTOS library is just a scheduler,
which is great if you have your own device and file drivers.
The FreeRTOS Plus library is the scheduler
and a few drivers, mostly for networking, like TCP and UDP.
You'll want to copy or link
to the source files in your build system.
Take a look in the demo folder,
and pick one of the example projects.
Let's look at a GCC example.
You can see that they all have a config file
that defines some important variables.
You'll want to make sure to have something like this
in your own project.
Feel free to look at main.c
to see how the various library files are included.
The ESP32 does not run Vanilla FreeRTOS.
Rather, it runs a modified version of FreeRTOS
inside its ESP-IDF framework.
Most ESP32s you come across will have a dual-core processor,
and use a symmetric multiprocessing, or SMP architecture.
This is when you have two or more cores
that share memory and other resources.
Most modern desktop or laptop microprocessors
use an SMP architecture to support multiple cores.
Espressif modified FreeRTOS specifically for the ESP32
to support its SMP architecture.
We won't get into multi-core support in this series,
as it goes beyond a simple introduction
to real-time operating systems.
In our demo applications, we'll limit the ESP32
to use only one core for our tasks.
In the ESP32 documentation, you can find a section
that explains the differences
between Vanilla FreeRTOS and ESP-IDF.
Once you are familiar with FreeRTOS,
I recommend coming back and reading through this page
to understand how the scheduler can use the second core
in many ESP32s to truly run concurrent tasks.
While I'll give you examples for ESP-IDF,
they should work in FreeRTOS as well.
I'll try to make notes or comments
where something differs from Vanilla FreeRTOS.
That way, you can take what you've learned from these videos
and apply it to your non-ESP32 project, using FreeRTOS.
I'm using an Adafruit Feather HUZZAH32,
but almost any ESP32 board should work.
If it's supported in Arduino,
it likely runs ESP-IDF out of the box.
All you need to do is plug it in.
Install the Arduino IDE, if you have not already done so.
Go to file, preferences, and click the URL list button
by additional boards manager URLs, here, add
https://dl.espressif.com/dl/package_esp32_index.json
on a new line.
Click okay, and click okay again.
Then go to tools, board, board manager.
Search for ESP32.
Look for the Espressif Systems ESP32 package.
Select the most recent version, and click install.
Wait for that to finish, and click close.
The ESP32 relies on settings in a FreeRTOS config file,
just like we saw from the Vanilla demo examples.
If you'd like to view that file,
head to wherever Arduino keeps your board packages.
In Windows, you can find it under your username, app data,
local, Arduino 15, packages, ESP32, hardware,
ESP32, your package version number, tools,
SDK, include, FreeRTOS, FreeRTOS,
and there you'll find the FreeRTOS config.h file.
That's a good dig to find this file,
but it can be very helpful if you'd like to know
how FreeRTOS is configured for the ESP32.
For example, you can see that we have,
at most, 25 priority levels available to us,
and the smallest task stack size we can set is 768 bytes.
If you ever have any questions about the settings
for some of these defines, refer to this file.
If something has multiple possible settings,
like this check for stack overflow define,
you can always print it to the serial terminal
to figure out what it is.
Let's head back to our code.
At the top, let's define a constant
that constricts the ESP32 to using only one core
when we create tasks.
Normally, you would not want to use this,
so you can unleash the full dual-core power of your ESP32,
but, for learning RTOS concepts, having a second core
can actually make things more difficult.
Let's define an LED pin,
as we're going to create the classic Blinky program.
I'll use the Arduino defined built-in LED pin,
but feel free to use whichever pin
you want to connect an LED to.
A task is simply a function that gets called during setup,
or from another task.
However, we have to tell the scheduler about it
so that it can prioritize and time-slice it appropriately.
For FreeRTOS, the function should return nothing,
and accept one void pointer as a parameter.
This will allow you to pass in arguments if needed.
However, it can sometimes be tricky
if your setup or calling task goes out of scope,
which might remove the memory allocated for the argument.
For now, let's keep it simple,
and assume we won't pass in any arguments.
You can do any setup you might need here, but for this demo,
let's move straight into the while true loop.
If you've ever made a Blinky program with Arduino before,
this should look very familiar.
We turn the LED on for 500 milliseconds,
and then turn it off for 500 milliseconds.
I believe that the Arduino delay function works in ESP32
as a non-blocking weight,
but I would not get in the habit of using it.
Instead, you should use the vTaskDelay function,
which is non-blocking, and works in Vanilla FreeRTOS.
It tells the scheduler to run other tasks
until the specified delay time is up,
and then come back to continue running this task.
Almost all RTOSs you come across are based on a tick timer.
A tick timer is simply
one of the microcontroller's hardware timers
allocated to interrupt the processor at a specific interval.
The interrupt period is known as a tick,
and the scheduler has an opportunity to run each tick
to figure out which task needs to run for that tick.
By default, FreeRTOS sets the tick period to one millisecond
and defines portTICK PERIOD MS to one.
VTaskDelay expects the number of ticks to delay,
not the number of milliseconds.
So, we need to divide our desired milliseconds
by the tick period for the argument.
In setup, we set our LED pin to be an output,
just like we would for any Arduino Blinky program.
We then call the TaskCreate function.
Here, we'll use xTaskCreatePinnedToCore
to tell the scheduler that we want to run our task
in only one of the cores.
This function does not exist in Vanilla Free RTOS,
and you'd want to use xTaskCreate instead.
Note that xTaskCreate does still work in ESP-IDF.
However, by doing so, you tell the scheduler
that it's free to run the task in whatever core it wants.
For the parameters, we need to tell it
what function we want to call for our task,
and give the task a name as a string.
We then set the stack size in bytes.
Note that this would be number of words in Vanilla FreeRTOS.
According to the config file we looked at earlier,
the smallest stack size we can set here is 768 bytes,
which is the minimum required to run an empty task,
and whatever overhead the scheduler needs.
Since this is a basic task,
let's increase that to one kilobyte.
You can use the next parameter
to pass in a pointer to some memory
as an argument to the task function, if you'd like.
We'll leave it as null for now. Next is the task priority.
The higher the number, the higher the priority.
The config max priorities setting
can be found in the config file,
and it's set to 25 by default.
That means we can assign a priority between zero and 24,
with zero being the lowest priority.
If you would like to manage this task from other tasks,
or the main execution loop,
you can set a pointer or handle to this task.
That way, you can check on its status,
watch its memory usage, or end it if necessary.
Finally, for this particular function,
we need to define the CPU core we want the task to run in.
For this series, let's have everything run in core one
of our dual-core processor.
Note that this parameter is not present
in the xTaskCreate function for Vanilla FreeRTOS.
In the Arduino framework for ESP32,
the setup and loop functions exist inside their own task,
separate from the main program entry point.
Because of this, a new task is spawned
as soon as we call xTaskCreate, or xTaskCreatePinnedToCore.
In other systems,
you would need to call the vTaskStartScheduler function
to tell the scheduler to start running.
Only then would new tasks begin executing.
However, that function has already been called for us
prior to the setup function,
so we don't need to worry about it.
We will leave the loop function empty.
Note that the setup and loop functions run as their own task
with priority one in core one of the ESP32.
As a result, this task may override other tasks you create,
if you make them a lower priority.
We'll look at how to handle that in a future video.
In tools, board, select your ESP32 Arduino board,
which is the Adafruit ESP32 Feather for me.
Click upload, and save your work if you'd like.
When it's done, you should see the LED
blinking once per second.
It's not the most exciting outcome,
but we took quite a circuitous route to get here.
For your challenge, I'd like you to make the same LED
blink at different rates.
That means creating another thread, or task,
just like the one we already have,
and then setting the delay to something different.
That should give you a crazy blink pattern
that looks something like this.
My solution to this challenge can be found
in a link in the description,
if you'd like to check it out
and compare it to your own work.
Good luck, and happy hacking.
In the next lecture, we'll talk about prioritization
and context switching when it comes to tasks.
(upbeat music)