Learn C++ Constructors with ESP32 : Powerful UART Setup
Introduction
In this blog post, we will explore how to manage UART communication using C++ constructors within the ESP32 development framework (ESP-IDF). Constructors simplify hardware initialization and provide a clean way to manage code. We will start from the basics, demonstrating the benefits of using constructors, and guide you through a practical example.
Contents
Why Choose C++ and Constructors?
C++ offers powerful object-oriented features, and constructors are a key highlight. In hardware initialization, constructors can automatically execute initialization logic during object creation, requiring no additional configuration steps. This approach is especially convenient for IoT development, allowing communication interfaces like UART to maintain a clean and structured codebase on the ESP32.
C++ Constructors Basics on ESP32
Using a C++ constructor in ESP32 development helps us initialize resources efficiently. For example, we can define a UART class and complete the UART configuration in the constructor. This approach is more intuitive than the traditional C-style initialization in the main function.
ClassName(parameters) : member1(initial_value), member2(initial_value) {
// Additional initialization operations
}
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 # CMake build configuration
├── main/
│ ├── CMakeLists.txt # Main component's CMake configuration
│ └── main.cpp # UART class and application logic
└── sdkconfig # Project configuration generated by menuconfig
Initializing UART with a C++ Constructor
Create a main.cpp file in the main directory and add the following code:
#include <iostream>
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "string.h"
#include "esp_log.h" // Include the ESP-IDF log library
// Define a tag for logging
static const char *TAG = "UART_LOG"; // Tag for identifying log messages
class UART {
private:
uart_port_t uart_num;
public:
// Constructor: Initialize UART port
UART(uart_port_t uart_num, int tx_pin, int rx_pin, int baud_rate) : uart_num(uart_num) {
uart_config_t uart_config = {
.baud_rate = baud_rate,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
// Configure UART parameters
esp_err_t err = uart_param_config(uart_num, &uart_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure UART parameters: %s", esp_err_to_name(err));
}
err = uart_set_pin(uart_num, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set UART pins: %s", esp_err_to_name(err));
}
err = uart_driver_install(uart_num, 1024 * 2, 0, 0, NULL, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to install UART driver: %s", esp_err_to_name(err));
}
}
// Method to send data
void sendData(const char *data) {
int len = uart_write_bytes(uart_num, data, strlen(data));
ESP_LOGI(TAG, "Sent %d bytes: %s", len, data);
}
// Method to receive data
void receiveData() {
uint8_t data[128];
int length = uart_read_bytes(uart_num, data, sizeof(data) - 1, 100 / portTICK_PERIOD_MS);
if (length > 0) {
data[length] = '\0'; // Null-terminate the received data
ESP_LOGI(TAG, "Received: %s", data);
}
}
};
extern "C" void app_main() {
// Create UART object, configure UART1, and specify TX and RX pins
UART uart(UART_NUM_1, GPIO_NUM_17, GPIO_NUM_16, 115200);
// Send data every 2 seconds
while (true) {
uart.sendData("Hello, UART!\n");
uart.receiveData(); // Receive data
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
Explanation:
1. UART Class and Constructor: The UART class is defined in main.cpp, and the constructor initializes the UART parameters.
2. Parameter Configuration: The constructor uses uart_param_config to set UART parameters and uart_set_pin to assign TX and RX pins.
3. Data Transmission and Reception: The sendData method sends strings over UART, and receiveData receives and outputs data.
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 (1234) UART_LOG: Configuring UART parameters...
I (1235) UART_LOG: Setting UART pins...
I (1236) UART_LOG: Installing UART driver...
I (1350) UART_LOG: Sent 14 bytes: Hello, UART!
I (1360) UART_LOG: Received: Some response from the connected device
I (1350) UART_LOG: Sent 14 bytes: Hello, UART!
I (1360) UART_LOG: Received: Another response
Conclusion
Using C++ constructors in ESP32 development offers several advantages:
1. Clearer Code: Placing initialization logic in the constructor makes code structure clearer and more organized.
2. Simplified Resource Management: Resources initialize automatically when objects are created, reducing errors.
3. Improved Code Reusability: Encapsulating UART functionality in a class makes it easier to reuse across projects.
This guide demonstrates how effectively using C++ constructors can simplify and organize your ESP32 UART communication setup. We hope this helps you succeed in your IoT development journey with ESP32! Consider applying this concept to managing other hardware resources in future projects.