Mastering C++ Design Patterns for I2C Management on ESP32


This blog introduces how to use ESP-IDF on the ESP32 platform, in conjunction with C++ design patterns, to architect a robust and scalable I2C bus management system. By applying design patterns such as the Factory Pattern and Observer Pattern, we can simplify the management of multiple I2C buses and devices. Additionally, design patterns are leveraged to ensure efficient and modular logging of system statuses and data operations using esp_log.

In the world of IoT and embedded systems, I2C (Inter-Integrated Circuit) is a widely used communication protocol. When a system contains multiple I2C buses, each with several devices, managing the read/write operations of these buses and devices can become quite complex.

Design Patterns

Design Goals and Architecture

The goal is to create a flexible and scalable I2C bus management system on the ESP32 using design patterns to meet the following requirements:

  • Multiple I2C Buses: Support for multiple I2C buses.
  • Multiple I2C Devices: Each bus can accommodate multiple I2C devices.
  • Scanning Functionality: The ability to scan the I2C bus for device addresses, useful for verifying component connections.
  • Data Read/Write Functionality: Ability to perform data read/write operations on specific devices.
  • Observable Data Changes: Support for the Observer Pattern, where devices notify observers when data changes.

Development Environment Setup

Design Patterns Used

  • Factory Pattern: Dynamically manage the initialization and instantiation of multiple I2C buses.
  • Observer Pattern: Allows devices on the I2C bus to receive notifications when data changes.

Programming Methodology

The following steps will guide you through using ESP32’s ESP-IDF and C++ design patterns to set up multiple I2C buses, manage devices, and use esp_log for logging I2C operations, errors, and data changes. This also allows easy tracking and debugging.

Defining the I2C Bus Manager Class (I2CBusManager)

I2CBusManager is a class responsible for managing a single I2C bus, including initialization, data read/write operations, device scanning, and notifying registered observers (devices on the bus).

Using the I2CManagerFactory to Manage Multiple I2C Buses

I2CManagerFactory uses the Factory Pattern to manage the initialization and instantiation of I2C buses, ensuring that each bus is initialized only once.

Defining the I2CDeviceObserver (Device Observer)

The Observer Pattern allows devices on the I2C bus to be notified when data is updated.

Main Program

Finally, integrate all these components in the main program, scan each I2C bus, and verify connected devices.

Output

After uploading the code to the ESP32 development board, you can view the log output using the ESP-IDF plugin tool. Below is an example of the expected log output, demonstrating the use of design patterns in the I2C bus management system:

Explanation

I2C BUS Initialization: This shows that the I2C buses, I2C_NUM_0 and I2C_NUM_1, have been initialized with their respective SDA and SCL pins.

I2C BUS Scan: During the scan of the I2C_NUM_0 bus, an I2C device at address 0x48 was detected.

Data Write and Read: Data has been successfully written to the device at address 0x48 on I2C_NUM_0, and data was successfully read from the device.

Observer Receiving Data: After the data is successfully read, the SensorDevice device is notified, and it shows that the data was received.

These log messages clearly show how the I2C bus initialization process, scan results, data read/write operations, and the notification of device data changes are implemented using C++ design patterns. It provides visibility into whether operations on the I2C buses were successful and how device data changes are communicated.

Conclusion

This blog shows how to fully leverage the ESP32’s capabilities by applying design patterns such as the Factory PatternObserver Pattern, and other object-oriented design patterns to build a flexible and scalable I2C bus management system. By incorporating these design patterns, we ensure that each step of the process—initialization, device management, and data communication—follows a structured approach, while design patterns like the Observer Pattern facilitate effective data notification and updates. Additionally, using esp_log allows us to log every operation for better debugging, traceability, and system monitoring.