Learn C++ Inheritance with ESP32 | A Powerful Task Setup
Introduction
Inheritance is a powerful feature of C++ that significantly enhances the capabilities of object-oriented programming (OOP). When combined with the ESP32, a microcontroller that boasts robust processing power and support for FreeRTOS, it becomes an ideal choice for developing multitasking applications. By utilizing the ESP-IDF framework, developers can unlock the full potential of C++ through inheritance. In this blog, we will explore how to establish a flexible and reusable task management system in C++ for the ESP32, leveraging inheritance to simplify the management of multiple tasks in embedded applications.
Contents
Why Use Inheritance in C++ on the ESP32?
Inheritance is a key feature of OOP that allows classes to derive properties and methods from other classes. By implementing a base class that encapsulates common functionality, we can reduce code duplication and simplify complex, multi-task systems. When applied to FreeRTOS on the ESP32, inheritance allows us to create versatile task classes that streamline the process of creating, managing, and reusing multiple tasks.
Development Environment Setup
1. Install Visual Studio Code from the official website.
2. Install the ESP-IDF plugin.
Creating a New Project
1. In VS Code, press Ctrl (Cmd) + Shift + P, and enter ESP-IDF: New Project.
2. Choose a template project (e.g., blink example), set the project name (e.g., my_project), and choose a storage path.
3. The system will generate a new ESP-IDF project with a basic CMake file and example code.
Project File Structure
my_project/
├── CMakeLists.txt # Build configuration
├── main/
│ ├── CMakeLists.txt # Main component's CMake configuration
│ └── main.cpp # UART class and application logic
└── sdkconfig # Project configuration
Benefits of Using Inheritance for Tasks
Code Reuse: Common functionality is defined once in the base class and inherited by all task classes, reducing code duplication.
Modularity: Each task is encapsulated in its own class, making it easy to manage, extend, and modify tasks individually.
Scalability: New tasks can be easily added by simply inheriting from the base task class and defining specific functionality.
Simplicity: Task management is abstracted, allowing developers to focus on the task logic instead of the boilerplate FreeRTOS setup.
Setting Up the Base Task Class
Our base task class will encapsulate the necessary FreeRTOS task setup. This class will provide:
1. A constructor to initialize task properties.
2. A start method to create and start the task.
3. A pure virtual function, run(), that derived classes will override to define specific task behaviors.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
class Task {
public:
Task(const char* name, uint32_t stackSize, UBaseType_t priority)
: taskName(name), taskStackSize(stackSize), taskPriority(priority), taskHandle(nullptr) {}
void start() {
xTaskCreate(taskFunctionWrapper, taskName, taskStackSize, this, taskPriority, &taskHandle);
}
protected:
virtual void run() = 0;
private:
static void taskFunctionWrapper(void* parameter) {
Task* task = static_cast<Task*>(parameter);
task->run();
}
const char* taskName;
uint32_t taskStackSize;
UBaseType_t taskPriority;
TaskHandle_t taskHandle;
};
Creating Specialized Tasks with Inheritance
Let's build a reusable, task-based system for the ESP32 using this base class. We'll create three specific task classes:
1. CounterTask: This task increments and prints a counter every second.
2. TemperatureTask: This task simulates a temperature reading every 5 seconds.
3. HumidityTask: This task simulates a humidity reading every 3 seconds.
Each of these tasks will inherit from Task and implement its unique functionality in run().
Bringing It All Together in app_main
To start all tasks, we’ll create instances of each derived task class that utilizes Inheritance from the base task class and call their start() method, as shown below. The base class manages all the task management details, while the derived classes implement their specific logic through Inheritance.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "string.h"
// Base class Task, encapsulating FreeRTOS task functionality
class Task {
public:
Task(const char* name, uint32_t stackSize, UBaseType_t priority)
: taskName(name), taskStackSize(stackSize), taskPriority(priority), taskHandle(nullptr) {}
void start() {
xTaskCreate(taskFunctionWrapper, taskName, taskStackSize, this, taskPriority, &taskHandle);
}
protected:
virtual void run() = 0;
private:
static void taskFunctionWrapper(void* parameter) {
Task* task = static_cast<Task*>(parameter);
task->run();
}
const char* taskName;
uint32_t taskStackSize;
UBaseType_t taskPriority;
TaskHandle_t taskHandle;
};
// Counter Task
class CounterTask : public Task {
public:
CounterTask() : Task("Counter Task", 2048, 3), counter(0) {}
protected:
void run() override {
while (true) {
counter++;
ESP_LOGI("COUNTER_TASK", "Counter value: %d", counter);
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay for 1 second
}
}
private:
int counter;
};
// Temperature Measurement Task
class TemperatureTask : public Task {
public:
TemperatureTask() : Task("Temperature Task", 2048, 3) {}
protected:
void run() override {
while (true) {
float temperature = getTemperature();
ESP_LOGI("TEMP_TASK", "Temperature: %.2f °C", temperature);
vTaskDelay(5000 / portTICK_PERIOD_MS); // Delay for 5 seconds
}
}
private:
float getTemperature() {
return 25.0 + (rand() % 100) / 10.0; // Simulate temperature
}
};
// Humidity Measurement Task
class HumidityTask : public Task {
public:
HumidityTask() : Task("Humidity Task", 2048, 3) {}
protected:
void run() override {
while (true) {
float humidity = getHumidity();
ESP_LOGI("HUMIDITY_TASK", "Humidity: %.2f %%", humidity);
vTaskDelay(3000 / portTICK_PERIOD_MS); // Delay for 3 seconds
}
}
private:
float getHumidity() {
return 50.0 + (rand() % 100) / 10.0; // Simulate humidity
}
};
extern "C" void app_main() {
// Create task instances and start them
CounterTask counterTask;
TemperatureTask temperatureTask;
HumidityTask humidityTask;
counterTask.start();
temperatureTask.start();
humidityTask.start();
}
Explanation
Base Class Task: Encapsulates the FreeRTOS task creation and management logic. Each specific task class must implement the run() method.
Counter Task CounterTask: Increments a counter every second and prints its current value.
Temperature Task TemperatureTask: Simulates temperature readings every 5 seconds and prints them.
Humidity Task HumidityTask: Simulates humidity readings every 3 seconds and prints them.
Compiling and Flashing
In the VSCode window, find the ESP32-IDF : Build, Flash and Monitor icon to compile and flash the program.
Program Output
Once the program runs, you should see the following output in the terminal:
I (324) COUNTER_TASK: Counter value: 1
I (1324) COUNTER_TASK: Counter value: 2
I (2324) COUNTER_TASK: Counter value: 3
I (3324) COUNTER_TASK: Counter value: 4
I (4324) COUNTER_TASK: Counter value: 5
I (5324) TEMP_TASK: Temperature: 25.60 °C
I (5324) HUMIDITY_TASK: Humidity: 50.30 %
I (6324) COUNTER_TASK: Counter value: 6
I (7324) TEMP_TASK: Temperature: 26.10 °C
I (7324) HUMIDITY_TASK: Humidity: 52.10 %
I (8324) COUNTER_TASK: Counter value: 7
I (9324) TEMP_TASK: Temperature: 25.80 °C
I (9324) HUMIDITY_TASK: Humidity: 53.90 %
I (10324) COUNTER_TASK: Counter value: 8
I (11324) TEMP_TASK: Temperature: 27.30 °C
I (11324) HUMIDITY_TASK: Humidity: 51.50 %
Conclusion
Using inheritance to build a task system on the ESP32 with FreeRTOS and C++ allows for a clean, organized, and scalable way to manage tasks. By defining a reusable base class, we simplify task creation and management, making the codebase more readable, maintainable, and scalable. Whether you're developing a simple application or a complex, multitasking embedded system, this approach enables you to efficiently handle multiple tasks on the ESP32.
With a well-structured task system in place, you’re set to explore more advanced features and expand your ESP32 projects with confidence. Happy coding!