使用 C++ Design Patterns 和 ESP32 實現多組 I2C 設備管理
本文將介紹如何在 ESP32 上使用 ESP-IDF,配合 C++ 設計模式 (Design Patterns) 來設計穩定的 I2C BUS 管理架構,並使用 esp_log 進行系統狀態與數據操作的日誌記錄。
在物聯網和嵌入式系統中,I2C (Inter-Integrated Circuit) 是一種廣泛應用的通訊協定。當我們的系統中有多組 I2C bus 並且每個 bus 上接有多個 I2C 裝置時,管理這些總線與裝置的讀寫操作可能會變得相當複雜。
內容
設計目標與架構
目標是在 ESP32 上利用設計模式 (Design Patterns) 構建一個靈活且可擴展的 I2C BUS 管理系統,達到以下需求:
- 多組 I2C BUS:支援多組 I2C BUS。
- 多個 I2C 裝置:每組 BUS 上可接多個 I2C 裝置。
- 掃描功能:可以掃描 I2C BUS 上所有裝置的地址,便於確認元件連接情況。
- 資料讀寫功能:可向特定裝置進行資料讀寫。
- 可觀察數據變化:支援裝置觀察者模式 (Observer Pattern),當數據更新時通知觀察者。
開發環境設置
- 從官方網站安裝 Visual Studio Code。
- 安裝 ESP-IDF 插件。
使用的設計模式 (Design Patterns)
- Factory 工廠設計模式:動態管理多組 I2C BUS 的初始化和實例化。
- Observer 觀察者設計模式:允許 I2C BUS 上的裝置作為觀察者,在數據變動時接收通知。
程式設計方式
以下步驟將詳細說明如何使用 ESP32 的 ESP-IDF 和 C++ 設計模式 (Design Patterns) 來架設多組 I2C BUS,並進行裝置管理。並以 esp_log 日誌管理來記錄 I2C 操作與錯誤狀態和觀察設計模式 (Design Patterns),便於開發者追蹤和除錯。
定義 I2C BUS 的管理類別 I2CBusManager
I2CBusManager
是一個專門用來管理單一 I2C BUS 的類別,負責該 BUS 的初始化、數據讀寫、裝置掃描以及通知所有註冊的觀察者(即在該 BUS 上的裝置)。
#include "driver/i2c.h"
#include "esp_log.h"
#include <vector>
class I2CDeviceObserver {
public:
virtual void onDataReceived(uint8_t* data, size_t length) = 0;
};
class I2CBusManager {
private:
i2c_port_t i2c_port;
int sda_pin;
int scl_pin;
std::vector<I2CDeviceObserver*> observers;
public:
I2CBusManager(i2c_port_t port, int sda, int scl, uint32_t clk_speed)
: i2c_port(port), sda_pin(sda), scl_pin(scl) {
i2c_config_t config;
config.mode = I2C_MODE_MASTER;
config.sda_io_num = sda_pin;
config.scl_io_num = scl_pin;
config.sda_pullup_en = GPIO_PULLUP_ENABLE;
config.scl_pullup_en = GPIO_PULLUP_ENABLE;
config.master.clk_speed = clk_speed;
ESP_ERROR_CHECK(i2c_param_config(i2c_port, &config));
ESP_ERROR_CHECK(i2c_driver_install(i2c_port, config.mode, 0, 0, 0));
ESP_LOGI("I2CBusManager", "I2C bus initialized on I2C_NUM_%d with SDA:%d, SCL:%d", i2c_port, sda_pin, scl_pin);
}
void scanBus() {
ESP_LOGI("I2CBusManager", "Starting I2C bus scan on I2C_NUM_%d...", i2c_port);
for (uint8_t address = 0x03; address < 0x78; ++address) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 10 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
ESP_LOGI("I2CBusManager", "Device found at address 0x%X on I2C_NUM_%d", address, i2c_port);
}
}
ESP_LOGI("I2CBusManager", "I2C bus scan completed on I2C_NUM_%d", i2c_port);
}
void addObserver(I2CDeviceObserver* observer) {
observers.push_back(observer);
}
void notifyObservers(uint8_t* data, size_t length) {
for (auto observer : observers) {
observer->onDataReceived(data, length);
}
}
void writeToDevice(uint8_t address, uint8_t* data, size_t length) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
i2c_master_write(cmd, data, length, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
ESP_LOGI("I2CBusManager", "Data written to device at 0x%X on I2C_NUM_%d", address, i2c_port);
} else {
ESP_LOGE("I2CBusManager", "Failed to write to device at 0x%X on I2C_NUM_%d", address, i2c_port);
}
}
void readFromDevice(uint8_t address, uint8_t* data, size_t length) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, data, length, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
ESP_LOGI("I2CBusManager", "Data read from device at 0x%X on I2C_NUM_%d", address, i2c_port);
notifyObservers(data, length);
} else {
ESP_LOGE("I2CBusManager", "Failed to read from device at 0x%X on I2C_NUM_%d", address, i2c_port);
}
}
};
使用 I2CManagerFactory
動態管理多組 I2C BUS
I2CManagerFactory
使用工廠模式管理 I2C BUS 的初始化和實例化,確保每組 BUS 只被初始化一次。
#include <map>
class I2CManagerFactory {
private:
std::map<i2c_port_t, I2CBusManager*> i2c_buses;
public:
I2CBusManager* getI2CBusManager(i2c_port_t port, int sda, int scl, uint32_t clk_speed = 100000) {
if (i2c_buses.find(port) == i2c_buses.end()) {
i2c_buses[port] = new I2CBusManager(port, sda, scl, clk_speed);
ESP_LOGI("I2CManagerFactory", "Created new I2CBusManager for I2C_NUM_%d", port);
} else {
ESP_LOGW("I2CManagerFactory", "I2C_NUM_%d already initialized", port);
}
return i2c_buses[port];
}
~I2CManagerFactory() {
for (auto& pair : i2c_buses) {
delete pair.second;
}
}
};
定義裝置觀察者 I2CDeviceObserver
這個觀察者模式允許特定的 I2C 裝置在數據更新時接收通知。
class SensorDevice : public I2CDeviceObserver {
public:
void onDataReceived(uint8_t* data, size_t length) override {
// Handle the received data
ESP_LOGI("SensorDevice", "Received data of length %d", length);
}
};
主程式範例
最後,將這些組件放在主程式中進行整合,並掃描每組 I2C BUS 以確認連接的裝置。
extern "C" void app_main() {
I2CManagerFactory factory;
// Initialize two I2C buses
I2CBusManager* bus0 = factory.getI2CBusManager(I2C_NUM_0, GPIO_NUM_21, GPIO_NUM_22);
I2CBusManager* bus1 = factory.getI2CBusManager(I2C_NUM_1, GPIO_NUM_18, GPIO_NUM_19);
// Perform scanning
bus0->scanBus();
bus1->scanBus();
// Create and register a device observer
SensorDevice sensor;
bus0->addObserver(&sensor);
// Example of writing and reading data
uint8_t data_to_write[2] = {0x01, 0x02};
bus0->writeToDevice(0x48, data_to_write, 2);
uint8_t data_to_read[2];
bus0->readFromDevice(0x48, data_to_read, 2);
}
輸出
在 main.cpp 文件中執行此程式碼並成功上傳至 ESP32 開發板後,您可以透過 ESP-IDF 插件工具來檢視日誌輸出。以下是預期的日誌輸出範例:
I (100) I2CBusManager: I2C bus initialized on I2C_NUM_0 with SDA:21, SCL:22
I (110) I2CBusManager: I2C bus initialized on I2C_NUM_1 with SDA:18, SCL:19
I (120) I2CBusManager: Starting I2C bus scan on I2C_NUM_0...
I (150) I2CBusManager: Device found at address 0x48 on I2C_NUM_0
I (160) I2CBusManager: I2C bus scan completed on I2C_NUM_0
I (170) I2CBusManager: Starting I2C bus scan on I2C_NUM_1...
I (200) I2CBusManager: I2C bus scan completed on I2C_NUM_1
W (210) I2CManagerFactory: I2C_NUM_0 already initialized
I (220) I2CBusManager: Data written to device at 0x48 on I2C_NUM_0
I (230) I2CBusManager: Data read from device at 0x48 on I2C_NUM_0
I (240) SensorDevice: Received data of length 2
解說
I2C BUS 初始化:這顯示 I2C BUS I2C_NUM_0
和 I2C_NUM_1
已分別使用 SDA 和 SCL 引腳初始化。
I2C BUS 掃描:在掃描 I2C_NUM_0
BUS 時,偵測到位於 0x48
的 I2C 裝置。
資料寫入與讀取:已成功寫入資料至 I2C_NUM_0
上的 0x48
裝置並成功從裝置讀取數據。
觀察者接收資料:當讀取資料成功後並通知了 SensorDevice
裝置和顯示接收到數據。
這些日誌訊息可以清楚了解我們以 C++ 設計模式 (Design Patterns) 來實現 I2C BUS 的初始化過程、掃描結果、資料讀取與寫入等操作是否成功,並觀察裝置數據變化的通知情況。
總結
在 ESP32 上使用 C++ 設計模式 (Design Patterns) 建立多組 I2C BUS 管理的架構,並透過 esp_log
實現完善的日誌記錄。通過將 I2CBusManager
、I2CManagerFactory
和觀察者模式相結合,可以輕鬆管理多組 I2C BUS 及其上多個 I2C 裝置的讀寫操作,並動態處理數據變化通知。