深度解密 ESP32 SPI DMA 架構|實現零等待數據傳輸


在現代嵌入式系統開發中,高效的外設通信是實現高性能應用的關鍵。ESP32 作為一款功能強大的 WiFi/藍牙雙模芯片,其 SPI 接口配合 DMA (直接記憶體存取) 技術可以實現高速數據傳輸而不佔用 CPU 資源。本文將詳細介紹如何在 VSCode 開發環境下,使用 ESP-IDF 框架配置 SPI 與 DMA 進行高效數據傳輸的全過程。

SPI DMA

什麼是 SPI DMA?

SPI (Serial Peripheral Interface) 是一種同步串行通信接口,廣泛用於微控制器與外設之間的短距離通信。

DMA (Direct Memory Access) 是一種允許外設直接與記憶體交換數據而不需要 CPU 介入的技術,可以顯著提高系統性能並降低 CPU 負載。

SPI DMA 結合了兩者的優勢,通過 DMA 控制器來處理 SPI 數據傳輸,使 CPU 能夠專注於其他任務,特別適合高帶寬或實時性要求高的應用場景。

原理快速理解

SPI DMA 的工作原理可以簡化為以下幾個步驟:

  1. CPU 初始化 SPI 和 DMA 控制器
  2. CPU 設置數據緩衝區和傳輸參數
  3. DMA 控制器自動從記憶體讀取數據並通過 SPI 發送
  4. 傳輸完成後,DMA 觸發中斷通知 CPU
  5. CPU 處理後續操作或準備下一次傳輸

這種方式避免了 CPU 在數據傳輸過程中不斷介入,大大提高了系統效率。

開發環境

在開始編程之前,請確保已完成以下準備工作:

專案結構

典型的 ESP-IDF 項目結構如下:

spi_dma_demo/
├── CMakeLists.txt
├── main/
│   ├── CMakeLists.txt
│   └── spi_dma_demo.c
├── sdkconfig
└── README.md

程式碼

以下是完整的 SPI DMA 實現代碼:

#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TAG "SPI_DMA_DEMO"

// SPI pin configuration
#define PIN_NUM_MISO 12
#define PIN_NUM_MOSI 11
#define PIN_NUM_CLK  13
#define PIN_NUM_CS   10

// SPI settings
#define SPI_HOST    SPI2_HOST
#define DMA_CHAN    1
#define SPI_FREQ_HZ 10 * 1000 * 1000  // 10MHz

spi_device_handle_t spi;

void init_spi_with_dma() {
    // 1. Configure the SPI bus
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096,  // Max transfer size for DMA
    };

    // 2. Initialize the SPI bus
    ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, DMA_CHAN));

    // 3. Attach a device to the SPI bus
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = SPI_FREQ_HZ,
        .mode = 0,  // SPI mode 0
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 7,  // Transaction queue size
        .pre_cb = NULL,   // Pre-transfer callback (optional)
        .post_cb = NULL,  // Post-transfer callback (optional)
    };

    ESP_ERROR_CHECK(spi_bus_add_device(SPI_HOST, &devcfg, &spi));
}

void spi_dma_transfer(const uint8_t *data, size_t len) {
    // Create a transaction descriptor
    spi_transaction_t t = {
        .length = len * 8,  // Data length in bits
        .tx_buffer = data,
        .user = NULL
    };

    // Queue the transaction
    ESP_ERROR_CHECK(spi_device_queue_trans(spi, &t, portMAX_DELAY));

    // Wait for the transaction to complete
    spi_transaction_t *rtrans;
    ESP_ERROR_CHECK(spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY));

    ESP_LOGI(TAG, "DMA transfer completed, size: %d bytes", len);
}

void app_main() {
    // Initialize SPI with DMA
    init_spi_with_dma();

    // Prepare test data
    uint8_t test_data[256];
    for (int i = 0; i < sizeof(test_data); i++) {
        test_data[i] = i & 0xFF;
    }

    while (1) {
        // Perform DMA transfer
        spi_dma_transfer(test_data, sizeof(test_data));

        // Delay for 1 second
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

程式碼解說

SPI 初始化部分

  • SPI Bus 配置 (spi_bus_config_t)
  • SPI Bus 初始化(spi_bus_initialize)
  • SPI Bus 配置 (spi_bus_config_t)
  • SPI Bus 初始化 (spi_bus_initialize)
  • SPI Bus 添加 (spi_bus_add_device)

DMA 傳輸部分

  • 傳輸描述符 (spi_transaction_t) : 指定數據長度 (以位為單位) 和 設置發送緩衝區。
  • 隊列傳輸 (spi_device_queue_trans):將傳輸請求加入隊列中。
  • 獲取結果 (spi_device_get_trans_result) : 等待傳輸完成並可獲得傳輸狀態。

編譯和燒錄

完成程式碼後,您可以使用 ESP-IDF 提供的命令進行編譯、燒錄和監控。

在 VS Code 的左下角 ESP-IDF 工具列:

  • 點選 Build project
  • 點選 Flash device
  • 點選 Monitor device

成功運行後,串口監視器將顯示如下輸出:

I (285) SPI_DMA_DEMO: DMA transfer completed, size: 256 bytes
I (1285) SPI_DMA_DEMO: DMA transfer completed, size: 256 bytes
I (2285) SPI_DMA_DEMO: DMA transfer completed, size: 256 bytes
...

結論

透過本文的實作與解析,我們深入探討了如何運用 ESP32 的 SPI DMA 技術 來突破傳統數據傳輸的效能限制。這種「繞過 CPU 直接存取」的設計,不僅大幅降低了系統負擔,更為高速數據傳輸開創了新的可能性。

在 VSCode + ESP-IDF 的開發環境下,我們從底層暫存器配置到實際應用層面,一步步實現了高效的 SPI 通信方案。這項技術特別適合需要即時處理大量數據的應用場景,如工業感測器採集、高速記憶體讀寫,或是流暢的圖形顯示控制等。

掌握了 SPI DMA 的核心原理後,建議可以進一步嘗試結合雙緩衝技術來優化即時性,或是在 FreeRTOS 的多任務環境中測試其並行處理能力,這些進階應用都能讓你的 ESP32 專案發揮更極致的效能。