Post

Post #1.2: Implementing OOP Principles in Embedded Systems

Welcome to Post #1.2 of Mastering Object-Oriented Programming (OOP), C++, and Embedded Systems Series!

This series will take you through the journey of mastering OOP and C++ concepts and applying them in embedded systems development. Each topic is split into two posts: the first part focuses on core C++ concepts, while the second part shows how to apply those concepts to embedded systems using the ESP32-S3 microcontroller.

Note: This series is based on my personal learning path. If you’re completely new to OOP, you may find some parts challenging, but if you have a basic understanding of OOP, you’ll find useful applications of these principles in embedded systems.

Check out the full series here.

In this post, we’ll cover setting up a project in PlatformIO with Visual Studio Code, creating basic classes, and applying OOP concepts to control an RGB LED on an ESP32 board. You can find Post #1.1 here.

Creating a New Project in PlatformIO with Visual Studio Code

  1. Open PlatformIO in Visual Studio Code:
    • Launch Visual Studio Code.
    • Click on the PlatformIO icon in the left sidebar.
    • Select “Create New Project” under PROJECT TASKS or click the home icon at the bottom left to open “PIO Home.”
  2. Start a New Project:
    • Click the “New Project” button on the PlatformIO Home screen.
  3. Configure the New Project:
    • Name: Enter a name for your project (e.g., day1_oop_esp).
    • Board: Choose “Espressif ESP32-S3-DevkitC-1-N8”.
    • Framework: Select “Arduino”.
    • Location: Choose a folder to save the project files or leave it as the default.
  4. Create the Project:
    • Click “Finish”. PlatformIO will create a project with the required folder structure and configuration files. This may take some time, especially if it’s your first time using PlatformIO.
  5. Open the Main Project File:
    • PlatformIO will open the project folder in the VS Code workspace.
    • Navigate to the src folder and open main.cpp to start writing your code.
    • The platformio.ini file is also essential for including libraries or configuring hardware settings.
  6. Upload Your Code or Use the Serial Monitor:
    • To upload code, click the right arrow button at the bottom left or go to PROJECT TASKS and select Upload under General.
    • To open the serial monitor, click the plug icon or select Monitor under General.

Bonus: Install Required Drivers
ESP32 boards usually require CP210x (Silicon Labs) or CH340 (WCH) USB-to-serial drivers:

  • Windows: Check “Device Manager” under Ports (COM & LPT) for “Silicon Labs CP210x USB to UART Bridge” or “USB-SERIAL CH340.”
  • Linux: Use dmesg | grep tty to check for connected devices.

These steps help you set up a new project environment with all the necessary files and dependencies to start developing and experimenting with embedded systems!

RGB LED Control with OOP Concepts

To get started with controlling an RGB LED using Object-Oriented Programming (OOP) principles, we need to set up our environment correctly. Here’s the configuration for the platformio.ini file:

1
2
3
4
5
6
7
8
9
10
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino

monitor_speed = 115200
lib_deps = 
    adafruit/Adafruit NeoPixel

build_flags = -frtti

The -frtti flag is essential for enabling dynamic casting in C++. We are using the Adafruit NeoPixel library to control the RGB LED. Although the RGB LED on the ESP32 board can function as a regular LED when using LED_BUILTIN, it can be controlled in more sophisticated ways through the library.

Let’s dive into a basic example where we control the LED using OOP principles.

Step 1: Basic LED Class with Constructors and Destructors

We’ll start by defining a base class to manage basic LED operations:

The ESP32 board has an RGB LED that can be controlled similarly to a regular LED using digitalWrite(). By encapsulating this functionality within a class, we gain the flexibility to extend its capabilities for more advanced features.

mLED.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <Arduino.h>

// Base class for a basic LED
class mLED {
private:
    int pin;  // GPIO pin number for the LED

public:
    // Constructor: Initializes the LED class
    mLED() {
        Serial.println("mLED class: mLED() constructor");
    }

    // Destructor: Cleanup if necessary (not required in this simple example)
    ~mLED() {
        Serial.println("mLED class: mLED() destructor");
    }

    // Initialize the LED
    void begin(int ledPin) {
        pin = ledPin;
        pinMode(pin, OUTPUT);
        Serial.println("mLED class: begin() method");
    }

    // Method to turn the LED on
    virtual void turnOn() {
        digitalWrite(pin, HIGH);
        Serial.println("mLED class: turnOn() method");
    }

    // Method to turn the LED off
    virtual void turnOff() {
        digitalWrite(pin, LOW);
        Serial.println("mLED class: turnOff() method");
    }
};
  • Encapsulation: The mLED class encapsulates the LED’s pin number and provides methods to control it (begin, turnOn, turnOff). This hides the implementation details and simplifies the interface.
  • Constructors and Destructors: The constructor initializes the class, while the destructor is available for any necessary cleanup (though it’s not necessary in this case).

Step 2: Inheritance - Creating an RGB LED Class

Next, we’ll create an RGBLED class that inherits from mLED and adds functionality for controlling an RGB LED.

RGBLED.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <Adafruit_NeoPixel.h>
#include "mLED.h"

#define PIN 48
#define NUMPIXELS 1
#define DELAYVAL 10

class RGBLED : public mLED {
private:
    Adafruit_NeoPixel pixels;

public:
    // Constructor: Initialize base class and Adafruit NeoPixel object
    RGBLED() : mLED(), pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800) {
        Serial.println("RGBLED class: RGBLED() constructor");
        pixels.begin();
    }

    // Method to fade between colors
    void fadeColor(int steps = 100) {
        Serial.println("RGBLED class: fadeColor() method");
        int colors[3][3] = {
            {255, 0, 0},   // Red
            {0, 255, 0},   // Green
            {0, 0, 255}    // Blue
        };

        for (int colorIndex = 0; colorIndex < 3; colorIndex++) {
            int nextColorIndex = (colorIndex + 1) % 3; 

            int startR = colors[colorIndex][0];
            int startG = colors[colorIndex][1];
            int startB = colors[colorIndex][2];
            int endR = colors[nextColorIndex][0];
            int endG = colors[nextColorIndex][1];
            int endB = colors[nextColorIndex][2];

            for (int i = 0; i <= steps; i++) {
                int r = startR + (endR - startR) * i / steps;
                int g = startG + (endG - startG) * i / steps;
                int b = startB + (endB - startB) * i / steps;

                pixels.setPixelColor(0, pixels.Color(r, g, b));
                pixels.show();
                delay(DELAYVAL);
            }
        }
    }

    // Override to turn the LED on with a green light
    void turnOn() override {
        Serial.println("RGBLED class: turnOn() method");
        pixels.setPixelColor(0, pixels.Color(0, 255, 0));  // Green color
        pixels.show();
    }

    // Override to turn the LED off with a red light
    void turnOff() override {
        Serial.println("RGBLED class: turnOff() method");
        pixels.setPixelColor(0, pixels.Color(255, 0, 0));  // Red color
        pixels.show();
    }
};
  • Inheritance allows the RGBLED class to reuse methods from the mLED class and add new features, such as color fading.
  • Polymorphism is achieved through method overriding: the turnOn and turnOff methods provide customized behavior for the RGB LED.

Step 3: Polymorphism Example

Polymorphism is a key concept in OOP that allows us to use a base class pointer to refer to objects of derived classes. This enables us to call methods on these objects through the base class pointer, while the actual method that gets executed is determined at runtime based on the type of the object pointed to.

In this example, we demonstrate polymorphism with the mLED and RGBLED classes.

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "RGBLED.h"

mLED* myLed;
RGBLED* rgbPtr;

void setup() {
  Serial.begin(115200);

  // Allocate memory for the base class object
  myLed = new mLED();
  myLed->begin(LED_BUILTIN);

  myLed->turnOn();
  delay(2000);                                       
  myLed->turnOff();
  delay(2000);

  // Deallocate memory for the base class object before reassigning
  delete myLed;  

  // Allocate memory for the derived class object
  myLed = new RGBLED();  // Upcast to the base class pointer

  // Demonstrate polymorphism with upcasting
  myLed->turnOn();  // Calls RGBLED's overridden turnOn() method
  delay(2000);                                       
  myLed->turnOff(); // Calls RGBLED's overridden turnOff() method
  delay(2000);   

  // Downcasting to the derived class pointer
  rgbPtr = dynamic_cast<RGBLED*>(myLed);  // Use dynamic_cast for safe downcasting

  if (!rgbPtr) {
      Serial.println("Downcasting failed, program halt");
      while (true);  // Halt execution if downcasting fails
  }
}

void loop() {
    rgbPtr->fadeColor(); // Call the derived class-specific method
}
  • Polymorphism:
    • Concept: Polymorphism allows us to use a base class pointer (mLED*) to reference objects of derived classes (RGBLED). This enables us to call methods defined in the base class (turnOn, turnOff), but the actual implementation that gets executed is determined by the object’s actual type (RGBLED in this case).
    • Upcasting: When we assign a RGBLED object to a mLED pointer (myLed = new RGBLED();), it’s known as upcasting. Upcasting is safe because a RGBLED is-a mLED, and it ensures that we can call methods defined in the base class, even though the actual object is of the derived class.
    • Dynamic Method Binding: When myLed->turnOn() is called, it executes RGBLED’s turnOn method because RGBLED overrides the base class method. This is due to dynamic binding, which occurs at runtime based on the actual object type.
  • Dynamic Casting:
    • Concept: Dynamic casting is used for safe downcasting, which means converting a base class pointer back to a derived class pointer. It’s useful when you need to call methods that are specific to the derived class.
    • Usage: rgbPtr = dynamic_cast<RGBLED*>(myLed); attempts to cast myLed (which is a mLED* pointing to an RGBLED object) to RGBLED*. If the cast fails (i.e., myLed doesn’t point to an RGBLED object), dynamic_cast returns nullptr, which allows us to check for success or failure.
    • Safety Check: The code checks if rgbPtr is nullptr (i.e., the downcast failed). If it is, it halts execution to prevent undefined behavior, ensuring that further operations on rgbPtr don’t cause issues.
  • Dynamic Memory Allocation:
    • Concept: The new operator is used to allocate memory for objects dynamically at runtime. This allows flexible and efficient management of resources. In the example, memory is allocated for both mLED and RGBLED objects.
    • Resource Management: It’s important to deallocate memory using delete when the object is no longer needed. This avoids memory leaks. In this example, delete myLed; is used to clean up the memory before reassigning myLed to a new object.

The output of the program should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mLED class: mLED() constructor
mLED class: begin() method
mLED class: turnOn() method
mLED class: turnOff() method
mLED class: mLED() destructor
mLED class: mLED() constructor
RGBLED class: RGBLED() constructor
RGBLED class: turnOn() method
RGBLED class: turnOff() method
RGBLED class: fadeColor() method
RGBLED class: fadeColor() method
RGBLED class: fadeColor() method
RGBLED class: fadeColor() method
...
This post is licensed under CC BY 4.0 by the author.