I2C protokolü 400 kHz hız sınırıyla sensör okumak için harika olsa da, renkli TFT/OLED ekranlara (ST7789, ILI9341) piksel basarken, SD kartlara yüksek hızda veri kaydederken veya harici NOR/NAND Flash belleklerle çalışırken onlarca megahertz (MHz) hız gerekir. ESP32’de ESP-IDF SPI veri yolu kullanımı (Serial Peripheral Interface); 80 MHz’e kadar çıkan saat hızları ve CPU’yu meşgul etmeyen Doğrudan Bellek Erişimi (DMA) desteğiyle gömülü sistemlerin en hızlı seri iletişim standardıdır.
Bu rehberde; SPI sinyallerini (MOSI, MISO, SCLK, CS), ESP32’nin SPI kontrolcülerini (SPI2HOST, SPI3HOST), spibusconfigt ve spideviceinterfaceconfig_t yapısını, DMA tabanlı kuyruklu transferleri adım adım öğreneceğiz.
SPI Protokolü ve ESP32 Donanım Blokları
SPI dört temel hattan oluşan tam çift yönlü (full-duplex) senkron bir protokoldür:
- SCLK (Serial Clock): Master tarafından üretilen saat sinyali.
- MOSI (Master Out Slave In): Master’dan köleye veri aktarım hattı.
- MISO (Master In Slave Out): Köleden Master’a veri okuma hattı.
- CS / SS (Chip Select): Birden fazla aygıt bağlandığında o an haberleşilecek köleyi seçen aktif-low hat.
ESP32 içinde 4 adet SPI kontrolcüsü bulunur:
SPI0veSPI1: Dahili flash bellek ve PSRAM için ayrılmıştır; kullanıcı kodlarında kullanılamaz.SPI2_HOST(eski adıyla HSPI): Genel kullanıma tamamen açıktır.SPI3_HOST(eski adıyla VSPI): Genel kullanıma tamamen açıktır.
SPI Modları: CPOL ve CPHA Matrisi
Her SPI köle aygıtı saat sinyalinin yükselen veya düşen kenarında veri örnekler. Bu durum 4 SPI modunu oluşturur:
| SPI Modu | CPOL (Clock Polarity) | CPHA (Clock Phase) | Açıklama |
|---|---|---|---|
| Mod 0 | 0 (Boşta LOW) | 0 (İlk Kenarda Örnekle) | En yaygın mod (Ekranlar, Sensörler) |
| Mod 1 | 0 (Boşta LOW) | 1 (İkinci Kenarda Örnekle) | Bazı ADC entegreleri |
| Mod 2 | 1 (Boşta HIGH) | 0 (İlk Kenarda Örnekle) | Özel bellekler |
| Mod 3 | 1 (Boşta HIGH) | 1 (İkinci Kenarda Örnekle) | SD Kartlar, Flash çipler |
Adım 1: SPI Veri Yolunun Başlatılması (spibusinitialize)
İlk olarak ortak veri yolunu (MOSI, MISO, SCLK) ve DMA kanalını başlatıyoruz:
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 18
#define PIN_NUM_CS 5
static const char *TAG = "SPI_MASTER";
static spi_device_handle_t spi_device_handle;
esp_err_t init_spi_bus(void) {
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 // DMA üzerinden tek seferde taşınabilecek maksimum bayt
};
// SPI2_HOST'u DMA Kanalı (SPI_DMA_CH_AUTO) ile başlat
esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI Veri yolu başlatılamadı: %s", esp_err_to_name(ret));
}
return ret;
}
Adım 2: Aygıt Ekleme (spibusadd_device)
Aynı SPI veri yoluna birden fazla aygıt eklenebilir. Her aygıtın kendi CS pini ve saat frekansı tanımlanır:
esp_err_t add_spi_device(void) {
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 10 * 1000 * 1000, // 10 MHz saat frekansı
.mode = 0, // SPI Mod 0 (CPOL=0, CPHA=0)
.spics_io_num = PIN_NUM_CS, // Donanımsal Chip Select pini
.queue_size = 7, // Arka arkaya sıralanabilecek işlem kuyruğu
.flags = 0,
.pre_cb = NULL, // Transfer öncesi callback (örn: DC pini kontrolü)
.post_cb = NULL
};
return spi_bus_add_device(SPI2_HOST, &devcfg, &spi_device_handle);
}
Adım 3: Veri Gönderme ve Alma (Polling vs DMA Transfer)
Tek bir işlemde veri gönderip aynı anda yanıt okumak için spitransactiont yapısı kullanılır:
// SPI üzerinden veri alışverişi yapan fonksiyon
esp_err_t spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, size_t length) {
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length = length * 8; // Bit cinsinden uzunluk!
t.tx_buffer = tx_data;
t.rx_buffer = rx_data;
// İşlemi gönder ve tamamlanmasını bekle (Polling)
return spi_device_polling_transmit(spi_device_handle, &t);
}
Örnek: Harici SPI Flash Kimliği Okuma (JEDEC ID)
SPI Flash belleklerin (örn: Winbond W25Q64) kimliğini okumak için 0x9F komutu gönderilir ve ardından gelen 3 bayt okunur:
void read_flash_jedec_id(void) {
uint8_t tx_buf[4] = { 0x9F, 0x00, 0x00, 0x00 };
uint8_t rx_buf[4] = { 0 };
esp_err_t ret = spi_transfer(tx_buf, rx_buf, 4);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Flash Üretici ID: 0x%02X, Bellek Tipi: 0x%02X, Kapasite: 0x%02X",
rx_buf[1], rx_buf[2], rx_buf[3]);
} else {
ESP_LOGE(TAG, "SPI okuma başarısız: %s", esp_err_to_name(ret));
}
}
DMA (Direct Memory Access) Performans Sırrı
Ekranlara saniyede 30-60 kare tam ekran resim gönderirken CPU’nun beklemesini engellemek için spidevicequeue_trans kullanılır:
- Grafik tamponu (Frame buffer)
heapcapsmalloc(size, MALLOCCAPDMA)ile DMA uyumlu bellekten tahsis edilir. spidevicequeuetrans(spidevicehandle, &t, portMAXDELAY)ile transfer arka plan donanımına devredilir.- CPU serbest kalır ve bir sonraki karenin çizim hesaplarını yaparken DMA arka planda pikselleri ekrana basar!
Özet
ESP-IDF SPI sürücüsü, yüksek bant genişliği gerektiren grafik ekranlar ve bellekler için rakipsiz bir çözümdür. SPI2HOST veya SPI3HOST ile onlarca megahertz hızlara çıkabilir, DMA entegrasyonuyla işlemcinizi bloklamadan ultra akıcı donanım arayüzleri inşa edebilirsiniz.