FreeRTOS Tutorial: Create a Hello World Application with ESP32
To help others as they make their way through this process, we decided to document and release our findings as a FreeRTOS tutorial for developers. Here is how to create a Hello World application.
Updated August 20, 2021
While building our latest IoT product, our development team found that documentation for FreeRTOS was dated and often flat-out wrong. To help others as they make their way through this process, we decided to document and release our findings as a FreeRTOS tutorial for developers. Here is how to create a Hello World application.
You can always view our open-source IoT project here on Github.
Prerequisites
You have followed and completed the instructions laid out in FreeRTOS tutorial #2 and confirmed that you are able to talk, build and flash the ESP32.
You have your machine (virtual or actual) on and with the ESP32 connected, and your device can connect to the ESP32 via Putty or some other shell.
Where to Start
Navigate to your FreeRTOS download directory.
- Using ls the only directory you should see is FreeRTOS
To compile a program (hello world or otherwise) there are a couple of mandatory steps.
- First we need to make a CMakeLists.txt file, this explains to the cross compiler where to find libraries, what chip it is compiling on, and how/where to generate the binary executables. For now enter touch CMakeLists.txt this will make the file but with nothing in it.
- We also need a src folder. In the same directory as your CMakeLists.txt file enter mkdir src
- Inside src we need to make a main.c file. This is where we will create our hello world program.
The above is a sample CMakeLists.txt file that will work for hello world and most applications. If you want to add extra files, libraries, and dependencies you can add them after the last line add_subdirectory(FreeRTOS). Check the specific documentation, and setup here and scroll down to Add Custom Components to ESP-IDF
- Line 1 is mandatory and forces the compiler to use a minimum version of CMake
- Lines 3 - 11 try to figure out which type of ESP32 board you are working with (ESP32s2 or ESP32). Depending on which tool chains it finds it will set the SOC_NAME variable to be either esp32s2 or esp32
- Line 12 is how we set the name of the project. This is custom and up to the user to decide
- Line 15 - 19 tell the tool chain what to link against and where to find the main.c executable.
- Line 30 set which directories to include
- Lines 33 - 37 This line helps us figure out which tool chains to use esp32_devkitc or esp32_saola_1
- Line 38 we are adding/compiling the entire FreeRTOS OS along with all its libraries and files. This is for the most part also a mandatory line.
- With all this we now have a configured CMakeList.txt be sure to save before closing vim or the IDE.
Creating the Hello_World application
Setting up file
Applications in FreeRTOS are very similar to a typical C application.
- There is a main function.
- You can design functions and modularize your code.
- You can include certain libraries into your namespace and use them.
- In freeRTOS instead of having an int main() function we use void app_main() this is essentially your main function and anything you would like to be executed should go here.
- In terms of libraries we will need the C standard library, FreeRTOS library, esp_systems library, and task library are what we need. It should look like this.
Implementing Interface
Along with the app_main() we also need to implement these 5 functions.
- vApplicationTickHook()
- vApplicationIdleHook()
- vApplicationDaemonTaskStartupHook()
- vApplicationGetIdleTaskMemory()
- vApplicationGetTimerTaskMemory()
The reason being is that inside the aws Free_RTOS kernel configs Amazon has enabled/enforced some “conditions” to be set to true (1). Depending on what gets set to 1 this enforces the user to implement their own versions of these functions and the program will not compile unless they are implemented. Other versions of FreeRTOS might have these dependencies disabled and depending on the online documentation you may see different and more concise versions of hello world.
While this might be slightly annoying for a simple hello world project, for people who like tinkering with OSs and implementing their own task handler this can be fun/useful.
If you have no idea how to implement these functions, most if not all of the freertos demos implement them and you can use those implementations.
What your current implementation should look like so far:
Creating Hello World Task
Now with app_main declared and the 5 functions implemented it is time to write a hello task.
Actions are executed in the FreeRTOS in the form of tasks. Tasks are functions or behaviors you want your microcontroller to perform. This could be:
- Reading sensor data
- Printing to screen
- Sending data over wifi
- Computation
- Etc. more on tasks here
So why Tasks and how do they work in FreeRTOS?
Your ESP32 comes with a dual core processor. Meaning it can execute 2 tasks at once. Since this is a very barebone OS we as developers need to tell FreeRTOS which tasks take priority over others.
Let us say we have 3 Tasks. Task 1 reads data from sensor 1 every second. Task 2 reads and adjusts an output signal based on the sensor readings from Task 1. And Task 3 sends the collected sensor data to some online database every 30 seconds.
We can only run 2 Tasks at any given time. In this scenario, it makes sense to give Tasks 1 and 2 a higher priority than task 3 since we are reading and adjusting every second. While only sending data every 30 seconds. If we give Task 3 a higher or equal priority to tasks 1 and 2 we would be wasting clock cycles doing nothing and effectively wasting resources and power.
How do we make a task?
Tasks are essentially the same as functions in C. They are of type void and take one parameter of **void *** which are optional parameters you can pass if your tasks need or depend on something external.
Our hello world task will look like this
From here the code looks very similar in C. We will try to print hello world infinitely every second. It should look something like this.
- vTaskDelay() is FreeRTOS’ way of delaying execution for a second
- pdMS_TO_TICKS is how we convert from ticks to milliseconds.
With this, our hello world task is implemented.
Back in main we need to tell FreeRTOS which tasks we want to execute. We do use by using the xTaskCreate() function.
This creates the task and hands it over to the scheduler so that it can be executed onto a processor. It should look something like this:
- The first parameter is the task name we created earlier
- The second parameter is a description of the task
- The third parameter is how much memory do we want to allow this task to use
- The fourth parameter is a pointer to the parameters you want to pass into the task
- The fifth parameter is the priority of the task (the lower the number the higher the priority)
- The sixth parameter is optional and is used to pass back a handle if we want to reference the task
With this we can now run our main.c.
Flashing and building
In order to execute your implementation make sure you are in the same directory as the CMakeLists.txt file that you created earlier. If this is a new terminal make sure you run:
- source FreeRTOS/vendors/espressif/esp-idf/export.sh
Then run cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=freertos/tools/cmake/toolchains/xtensa-esp32.cmake -GNinja
- If you moved your tool chain to a different folder you need to update -DCMAKE_TOOLCHAIN_FILE to point to the directory where xtensa-esp32.cmak is residing.
- This will build the build directory that will hold and generate the binaries that will run on your ESP32
Finally, run cmake --build build (if your build directory is named something different like my_build then the command should look like cmake --build my_build).
If there are no errors and it compiles, you can use idf.py flash to flash the code onto the board (remember to hold the reset button for a second while it is trying to connect).
You can use idf.py monitor or putty to see the terminal print Hello world every second.