使用 C++ Try-Catch 和 ESP32 | 掌握強大 SPI Scan 技術
本篇介紹如何在 ESP32 的 C++ 開發環境中,透過 try-catch 機制實現 SPI 設備掃描。C++ 的異常處理不僅讓開發者能有效捕捉並處理錯誤,還可以讓程式更具健壯性。我們會逐步介紹如何配置開發環境、實作 SPI 掃描器、以及如何使用 std::exception 類別來處理並回報潛在的 SPI 錯誤。
內容
簡介
C++ 的 try-catch 機制是一種異常處理方式,允許開發者捕捉在程式運行時發生的錯誤並做出反應,這在處理不穩定硬體時尤其有用。透過設計一個 SPIScanner 類別,我們可以使用 try-catch 來保證 SPI 設備在初始化或通訊時的安全性。如果通訊失敗,異常處理機制可以防止程式崩潰,並提供錯誤資訊給開發者或使用者。SPI 掃描器會初始化 SPI 總線、執行設備掃描,並將結果回報。
開發環境設置
1. 從官方網站安裝 Visual Studio Code。
2. 安裝 ESP-IDF 插件。
為 SPI Scan 項目設置 ESP32
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.
檔案結構
my_project/
├── CMakeLists.txt # 構建配置
├── main/
│ ├── CMakeLists.txt # 主組件的 CMake 配置
│ └── main.cpp # LED 燈帶類和應用邏輯
└── sdkconfig # 項目配置
什麼是 C++ 的 Try-Catch 機制
C++ 的異常處理機制提供了一種管理程式執行階段錯誤的方式。透過使用 try 和 catch 區塊,開發者可以在 try 區塊中執行可能引發錯誤的程式碼,並在 catch 區塊中擷取和處理這些錯誤。這種機制使得程式更加健壯,並能避免因未處理的錯誤而導致的崩潰。
Try-Catch 的基本結構
在 C++ 中,try-catch 語句用於捕捉和處理異常,這使得程式能在出現錯誤時穩定運行並妥善處理。下面是這段 try-catch 語句的詳解:
try {
// Code that might throw an exception
} catch (const std::exception& e) {
// Handle the exception
ESP_LOGE("Error", "Caught exception: %s", e.what());
}
1. 當 try 區塊內的程式碼拋出異常時,程式會跳至對應的 catch 區塊執行異常處理。
2. catch (const std::exception& e) 表示捕捉所有 std::exception 類型及其派生類型的異常。
3. e.what() 是一個成員函數,返回關於異常的簡短描述,有助於開發人員了解異常的具體情況。
什麼是 std::exception?
std::exception 是 C++ 標準庫中所有異常類的基類。它提供了基本的異常處理功能,允許用戶定義自己的異常類並繼承自 std::exception。所有自定義異常類都可以利用 std::exception 提供的接口,使得異常處理更加一致。
std::exception 的主要功能
1. what() 方法: std::exception 提供了一個虛函數 what(),用於返回描述異常的 C 風格字符串。用戶可以重寫該方法以提供更具體的異常信息。
2. 類型安全: 使用 std::exception 及其子類可以確保異常處理的類型安全,因為 catch 語句可以捕獲所有類型的異常。
自定義異常類
以下是個自定義異常類的示例,繼承自 std::exception:
#include <exception>
#include <string>
class MyException : public std::exception {
private:
std::string msg;
public:
MyException(const std::string& message) : msg(message) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
在這個例子中,MyException 繼承自 std::exception,並提供了自定義的錯誤消息。
什麼是 SPI(串行外設接口)
SPI(Serial Peripheral Interface)是一種同步串行通信協議,廣泛用於微控制器與各種外部設備(如傳感器、存儲器和顯示器)之間的通信。SPI 通過四個基本信號線來實現數據傳輸:
1. MOSI(Master Out Slave In): 主設備輸出,外設輸入的數據線。
2. MISO(Master In Slave Out): 主設備輸入,外設輸出的數據線。
3. SCLK(Serial Clock): 由主設備生成的時鐘信號。
4. CS(Chip Select): 用於選擇具體的外設。
SPI 的主要優點包括高速傳輸和全雙工通信能力,但它需要更多的引腳和更複雜的時序控制。
代碼實現
下面是一個使用 C++ 異常處理機制的 ESP32 SPI 掃描器示例代碼。該代碼使用 ESP-IDF 庫進行 SPI 通信,並通過自定義異常類來處理可能出現的錯誤。
// main.cpp
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h" // Include ESP log header
// Define a tag for logging
static const char *TAG = "SPIScanner"; // Tag for log messages
// Custom exception class for handling SPI errors
class SPIException : public std::exception {
private:
std::string msg; // Message describing the exception
public:
// Constructor that initializes the exception message
SPIException(const std::string& message) : msg(message) {}
// Override the what() method to return the exception message
const char* what() const noexcept override {
return msg.c_str();
}
};
// Class responsible for scanning SPI devices
class SPIScanner {
private:
spi_device_handle_t spi; // Handle for the SPI device
// Initialize the SPI bus
void initSPI(int mosi, int miso, int sclk, int cs) {
// Configuration structure for the SPI bus
spi_bus_config_t buscfg = {
.mosi_io_num = mosi, // Master Out Slave In pin
.miso_io_num = miso, // Master In Slave Out pin
.sclk_io_num = sclk, // Serial Clock pin
.quadwp_io_num = -1, // Not used in this configuration
.quadhd_io_num = -1, // Not used in this configuration
.max_transfer_sz = 0, // Maximum transfer size
.flags = SPICOMMON_BUSFLAG_MASTER // Set as master
};
// Configuration structure for the SPI device
spi_device_interface_config_t devcfg = {
.mode = 0, // SPI mode (clock polarity and phase)
.clock_speed_hz = 1000000, // Clock speed in Hz
.spics_io_num = cs, // Chip Select pin
.queue_size = 7, // Transaction queue size
};
// Initialize the SPI bus
esp_err_t ret = spi_bus_initialize(HSPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
throw SPIException("Failed to initialize SPI bus"); // Throw an exception if initialization fails
}
// Add the SPI device to the bus
ret = spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
if (ret != ESP_OK) {
throw SPIException("Failed to add device to SPI bus"); // Throw an exception if adding the device fails
}
}
public:
// Constructor for SPIScanner that initializes SPI
SPIScanner(int mosi, int miso, int sclk, int cs) {
try {
initSPI(mosi, miso, sclk, cs); // Call the initialization method
ESP_LOGI(TAG, "SPI Scanner initialized successfully"); // Log successful initialization
} catch (const SPIException& e) {
ESP_LOGE(TAG, "SPI initialization failed: %s", e.what()); // Log initialization failure
throw; // Rethrow the exception
}
}
// Destructor to clean up SPI device
~SPIScanner() {
spi_bus_remove_device(spi); // Remove the device from the bus
spi_bus_free(HSPI_HOST); // Free the SPI bus resources
}
// Method to scan for SPI devices
void scan() {
uint8_t tx_data = 0xFF; // Data to transmit (dummy data)
uint8_t rx_data = 0; // Variable to store received data
// Transaction structure for the SPI communication
spi_transaction_t t = {
.length = 8, // Transaction length in bits
.tx_buffer = &tx_data, // Pointer to the transmit buffer
.rx_buffer = &rx_data // Pointer to the receive buffer
};
ESP_LOGI(TAG, "Starting SPI scan..."); // Log the start of the scan
// Transmit the data and receive the response
esp_err_t ret = spi_device_transmit(spi, &t);
if (ret != ESP_OK) {
throw SPIException("SPI transmission failed"); // Throw an exception if transmission fails
}
ESP_LOGI(TAG, "Received data: 0x%02X", rx_data); // Log the received data
}
};
// ESP-IDF entry point for the application
extern "C" void app_main(void) {
try {
SPIScanner scanner(13, 12, 14, 15); // Create an instance of SPIScanner with specified GPIO pins
while(1) {
try {
scanner.scan(); // Scan for SPI devices
} catch (const SPIException& e) {
ESP_LOGW(TAG, "Scan iteration failed: %s, Continuing...", e.what()); // Log scan failure
}
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait for 1 second before the next scan
}
} catch (const SPIException& e) {
ESP_LOGE(TAG, "Fatal error: %s, System halted.", e.what()); // Log fatal errors
}
}
配置 ESP32 開發環境
在開始編寫程式之前,請確保已經設定好 ESP32 的開發環境。我們將使用 VS Code 搭配 ESP-IDF 插件進行開發。
配置 sdkconfig
若要在 ESP32 開發中啟用 C++ 的異常處理功能,你需要在 SDK 配置中開啟 C++ 異常處理。在 VS Code 中,開啟 ESP-IDF 插件提供的 SDK Configuration Editor,找到並啟用以下選項:
Compiler options: 勾選 "Enable C++ exceptions"
我們還需要在 CMakeLists.txt 中加入編譯選項來啟用異常處理和設定 C++ 標準
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
....
編譯與燒錄
在 VSCode 視窗最下方中找到 "ESP32-IDF : Build, Flash and Monitor" ICON 執行和燒錄。
程式運行效果
運行程式後,您將在終端機中看到以下輸出:
I (123) [SPIScanner]: SPI Scanner initialized successfully
I (223) [SPIScanner]: Starting SPI scan...
I (223) [SPIScanner]: Received data: 0xAA
I (1233) [SPIScanner]: Starting SPI scan...
I (1233) [SPIScanner]: Received data: 0xBB
I (2233) [SPIScanner]: Starting SPI scan...
W (2233) [SPIScanner]: Scan iteration failed: SPI transmission failed, Continuing...
I (3233) [SPIScanner]: Starting SPI scan...
I (3233) [SPIScanner]: Received data: 0xCC
結論
在 C++ 中,利用 try-catch 機制對 SPI 通信中的錯誤進行有效管理對於提升應用程式的健壯性至關重要。這種方法不僅提高了代碼的可讀性,還確保意外問題能夠得到優雅處理,防止應用程式崩潰。
在我們實現的 SPIScanner 類中,try-catch 區塊用於捕獲設備掃描過程中的異常,同時記錄信息豐富的錯誤訊息,並允許掃描過程繼續進行。這確保了所有潛在地址都被檢查,從而最大限度地提高了檢測可用設備的機會。
希望這篇文章能幫助你理解如何在 SPI 專案中有效利用 try-catch。運用這些技術來構建更具韌性的應用程式,提升你的物聯網開發之旅。祝你編碼愉快!