A first look at the ESP-IDF v5.0-beta1 and C++20
For my current projects, I have been using the ESP-IDF v4.3.2
. And to be honest, I don’t update the IDF frequently since I fear breaking something.
Recently, I discovered that Espressif had published the first beta of the ESP IDF v5, which now supports C++20 language features thanks to the switch to GCC 11. Since I have always liked to use the most up-to-date C++ versions, I wanted to try it out.
Installing the beta alongside the old installation
If you don’t want to break your existing projects, you can install the new version alongside the old one. My current version resides in ~/esp/esp-idf/
, so I went into the esp
folder to clone the beta version.
git clone -b v5.0-beta1 --recursive https://github.com/espressif/esp-idf.git esp-idf-v5.0-beta1
Afterwards, run the install script to set up all the required dependencies.
. esp-idf-v5.0-beta1/install.sh esp32
Once the install script finishes successfully, everything should be ready to go. In case you run into any issues, check out the official guide.
Verifying the installation
Now you could try to build one of your existing projects, but I choose to create a fresh one for testing purposes. Therefore, I copied the Hello World
example and opened the directory in VS Code.
cp -r /Users/julian.schroden/esp/esp-idf-v5.0-beta1/examples/get-started/hello_world idf-v5-test
To make the IDF available to the path, open a new terminal window in VS Code and run the following command. Ensure to reuse this terminal instance for the upcoming idf.py
commands.
. /Users/julian.schroden/esp/esp-idf-v5.0-beta1/export.sh
Next up, verify that the active IDF Version is correct by running echo $IDF_PATH
, which should output the path to ESP IDF v5. Then, set the target to esp32
and start a build.
# Set the target
idf.py set-target esp32
# Build the project
idf.py build
Verifying C++20 features are available
C++20 introduced many exciting new language features, like coroutines, concepts and modules. But some of these features are still not fully implemented by the compiler or supported by the build tools (e. g modules). Let’s check out concepts
and using enum
to verify that C++20 language features are usable.
For more information on compiler support, check out this page on cppreference.com.
Concepts
Concepts can be used to define constraints on template arguments. Let’s imagine we are writing a program which should control a motor. Using concepts, we can define the interface a motor type needs to comply with by specifying requirements.
#include <concepts>
template <typename T>
concept Motor = requires(T motor, uint8_t speed) {
motor.rotateLeft(speed);
motor.rotateRight(speed);
motor.stop();
{ motor.getSpeed() } -> std::same_as<uint8_t>;
};
If you want to learn more about concepts and how to define them, check out this post on cppstories.com.
But for now, let’s write a dummy class which satisfies the Motor
concept and use it in app_main
.
class DCMotor {
private:
uint8_t speed = 0;
public:
void rotateLeft(uint8_t speed) {
this->speed = speed;
printf("Starting to rotate left at the speed: %d\n", speed);
}
void rotateRight(uint8_t speed) {
this->speed = speed;
printf("Starting to rotate right at the speed: %d\n", speed);
}
void stop() {
this->speed = 0;
printf("Stop rotating");
}
uint8_t getSpeed() {
return speed;
}
};
void app_main(void) {
// ...
Motor auto motor = DCMotor();
motor.rotateLeft(100);
motor.stop();
// ...
}
Here we are using the Motor
concept to add a constraint on the auto keyword.
To see the power of concepts in action, remove any method of the DCMotor
class and try to compile the code. The following compiler output was printed out after I removed the getSpeed()
method.
hello_world_main.cpp:69:30: note: constraints not satisfied
hello_world_main.cpp:29:9: required for the satisfaction of 'Motor<auto [requires ::Motor<<placeholder>, >]>' [with auto [requires ::Motor<<placeholder>, >] = DCMotor]
hello_world_main.cpp:29:17: in requirements with 'T motor', 'uint8_t speed' [with T = DCMotor]
hello_world_main.cpp:33:19: note: the required expression 'motor.getSpeed()' is invalid
33 | { motor.getSpeed() } -> std::same_as<uint8_t>;
| ~~~~~~~~~~~~~~^~
As you can see, the compiler printed out a very descriptive error message.
Using enum
Another welcome addition to C++20 is bringing enum members into scope, which improves readability, especially in long switch case statements.
namespace gpio {
enum class Mode {
disabled,
input,
output,
outputOpenDrain,
inputOutput,
inputOutputOpenDrain,
};
};
void printGpioMode(gpio::Mode mode) {
switch (mode) {
using enum gpio::Mode;
case disabled:
printf("disabled\n");
break;
case input:
printf("input\n");
break;
// ...
}
}
Instead of repeating gpio::Mode
for each entry over and over, the entries are directly accessible.
C++20 language features finally arrived
With the switch to GCC 11, promising new language features are now available when working with the ESP IDF. Over the last year, I have been reading many articles on C++20 features, and it is great we can finally explore them in the microcontroller world.
After the first successful experiments with IDF v5 beta and C++20, I cannot wait to update to a stable release once available.