Seri Protokoller
TEKNİK REHBER SERİ PROTOKOLLER SPI 2026

SPI protokolü
ve spidev.

Dört tel, full-duplex, gigabit hızlara çıkabilen senkron bus. /dev/spidevX.Y üzerinden C ioctl veya Python spidev kütüphanesiyle doğrudan kontrol.

00 SPI protokolü — temel kavramlar

SPI (Serial Peripheral Interface) — Motorola'nın tasarladığı, dört telli, full-duplex senkron seri protokol.

  Master                          Slave 0 (CS0)
  ┌──────────┐    MOSI ──────────►┌──────────┐
  │          │    MISO ◄──────────│          │
  │          │    SCK  ──────────►│          │
  │          │    CS0  ──────────►└──────────┘
  │          │
  │          │    MOSI ──────────►┌──────────┐
  │          │    MISO ◄──────────│  Slave 1 │
  │          │    SCK  ──────────►│  (CS1)   │
  └──────────┘    CS1  ──────────►└──────────┘
    
MOSIMaster Out Slave In — master'dan slave'e veri hattı.
MISOMaster In Slave Out — slave'den master'a veri hattı.
SCKSerial Clock — master tarafından üretilir.
CS/SSChip Select / Slave Select — düşük (active-low) olduğunda slave seçilir.

CPOL ve CPHA — dört çalışma modu

SPI'nın hangi saat kenarında veri örneklendiği ve saat boştayken seviyesi CPOL ve CPHA parametreleriyle belirlenir:

ModCPOLCPHASaat boştaykenÖrnekleme kenarıCihaz örnekleri
000DüşükYükselen kenarW25Qxx NOR Flash, SD kart, MAX7219
101DüşükDüşen kenarMCP3208 ADC
210YüksekDüşen kenarNadir kullanılır
311YüksekYükselen kenarBMI160, bazı TFT driverları

Full-duplex transfer

SPI gerçek anlamda full-duplex çalışır: MOSI ve MISO eş zamanlı aktiftir. Her SCK pulsunda master bir bit gönderirken slave de bir bit gönderir. Bu nedenle SPI işlemleri "transfer" olarak adlandırılır — okuma veya yazma değil. Slave'e komut gönderirken MISO üzerindeki veri anlamsızdır; komuttan sonra gelen yanıtta ise MOSI'ye gönderilen dummy baytlar anlamsızdır.

Multi-slave — daisy chain

Her slave için ayrı CS hattı gerekir. Daisy-chain bağlantısında MOSI→Slave1→MISO→Slave2 zinciri kurulabilir fakat bu konfigürasyon pratikte nadiren kullanılır.

01 Linux spidev driver

spidev, SPI controller'ını userspace'e character device olarak açan generic Linux driver'ıdır.

kernel config
CONFIG_SPI=y
CONFIG_SPI_SPIDEV=y            # /dev/spidevX.Y erişimi için
CONFIG_SPI_BCM2835=y           # Raspberry Pi SPI controller
CONFIG_SPI_IMX=y               # NXP i.MX SPI controller
CONFIG_SPI_OMAP2_MCSPI=y       # TI OMAP/AM335x

Device Tree binding

spidev driver'ı otomatik yüklemek için device tree'de compatible değeri olarak ROHM DH2228FV veya benzer "dummy" bir değer kullanılır. Bu, gerçek bir cihaz driver'ı olmayan kullanım içindir.

device-tree.dts
&spi0 {
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;

    /* CS0: W25Q32 NOR Flash */
    flash@0 {
        compatible = "rohm,dh2228fv";  /* spidev için dummy ID */
        reg = <0>;                         /* CS0 */
        spi-max-frequency = <10000000>;    /* 10 MHz */
        spi-cpol;                         /* CPOL=1 ise ekle */
        spi-cpha;                         /* CPHA=1 ise ekle */
    };

    /* CS1: başka bir slave */
    sensor@1 {
        compatible = "rohm,dh2228fv";
        reg = <1>;
        spi-max-frequency = <1000000>;
    };
};
bash — yükleme ve kontrol
# spidev modülünü yükle (=m ise):
sudo modprobe spidev

# Mevcut spidev cihazlarını listele:
ls /dev/spidev*
# /dev/spidev0.0  /dev/spidev0.1

# Raspberry Pi'da spidev aktifleştirme:
sudo raspi-config
# Interfacing Options → SPI → Enable
# VEYA /boot/config.txt'e ekle:
# dtparam=spi=on

# Kullanıcıyı spi grubuna ekle:
sudo usermod -aG spi $USER
NOT

Adlandırma: /dev/spidev0.0 → bus 0, CS 0. /dev/spidev0.1 → bus 0, CS 1. /dev/spidev1.0 → bus 1, CS 0. Bus ve CS numaraları device tree'deki reg değerine karşılık gelir.

02 /dev/spidev arayüzü

spidev character device'ı standard open/close/read/write/ioctl arayüzü sunar. Major number 153'tür.

bash
# Karakter cihaz bilgisi:
ls -la /dev/spidev0.0
# crw-rw---- 1 root spi 153, 0 Jan  1 00:00 /dev/spidev0.0
#               ↑                ↑
#               group=spi        major=153, minor=0

# sysfs üzerinden SPI cihaz bilgisi:
ls /sys/class/spidev/
# spidev0.0  spidev0.1

cat /sys/class/spidev/spidev0.0/device/modalias
# spi:dh2228fv

# Basit test — düşük seviye okuma (sadece MISO'dan veri alır):
sudo dd if=/dev/spidev0.0 bs=4 count=1 2>/dev/null | xxd
# 00000000: ff ff ff ff  ....  (CS düşürülmeden okunur, faydasız)

Raw read/write sistem çağrıları CS'yi kontrol etmez ve transfer parametrelerini dikkate almaz. Her zaman ioctl ile SPI_IOC_MESSAGE(n) kullan.

03 C ile SPI: ioctl API

Linux SPI ioctl API, <linux/spi/spidev.h> başlık dosyasında tanımlanır. Tam kontrolü C kodu ile sağlar.

spi_example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_DEVICE   "/dev/spidev0.0"
#define SPI_SPEED_HZ  10000000UL    /* 10 MHz */
#define SPI_BITS_PER_WORD  8

int spi_init(int *fd_out)
{
    int fd = open(SPI_DEVICE, O_RDWR);
    if (fd < 0) { perror("open spidev"); return -1; }

    uint8_t  mode  = SPI_MODE_0;  /* CPOL=0, CPHA=0 */
    uint8_t  bits  = SPI_BITS_PER_WORD;
    uint32_t speed = SPI_SPEED_HZ;

    if (ioctl(fd, SPI_IOC_WR_MODE,          &mode)  < 0) goto err;
    if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits)  < 0) goto err;
    if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ,  &speed) < 0) goto err;

    *fd_out = fd;
    return 0;
err:
    perror("ioctl"); close(fd); return -1;
}

int spi_transfer(int fd,
                 const uint8_t *tx, uint8_t *rx, size_t len)
{
    struct spi_ioc_transfer tr = {
        .tx_buf        = (unsigned long)tx,
        .rx_buf        = (unsigned long)rx,
        .len           = len,
        .speed_hz      = SPI_SPEED_HZ,
        .delay_usecs   = 0,
        .bits_per_word = SPI_BITS_PER_WORD,
        .cs_change     = 0,
    };
    return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}

int main(void)
{
    int fd;
    if (spi_init(&fd) < 0) return 1;

    /* W25Q32 JEDEC ID oku: 0x9F komutu, 3 dummy bayt */
    uint8_t tx[4] = { 0x9F, 0x00, 0x00, 0x00 };
    uint8_t rx[4] = { 0 };

    if (spi_transfer(fd, tx, rx, 4) < 0) {
        perror("spi_transfer"); close(fd); return 1;
    }

    printf("Manufacturer : 0x%02X\n", rx[1]);  /* 0xEF = Winbond */
    printf("Memory type  : 0x%02X\n", rx[2]);  /* 0x40 = W25Q  */
    printf("Capacity     : 0x%02X\n", rx[3]);  /* 0x16 = 32Mbit */

    close(fd);
    return 0;
}
bash — derleme ve çalıştırma
gcc -Wall -o spi_example spi_example.c
sudo ./spi_example
# Manufacturer : 0xEF
# Memory type  : 0x40
# Capacity     : 0x16

Çoklu transfer — SPI_IOC_MESSAGE(n)

CS'yi yükseltmeden birden fazla transfer yapabilmek için spi_ioc_transfer dizisi kullanılır:

multi_transfer.c (kısmi)
struct spi_ioc_transfer transfers[2];
memset(transfers, 0, sizeof(transfers));

/* Transfer 1: komut + adres gönder */
uint8_t cmd[4] = { 0x03, 0x00, 0x00, 0x00 };  /* Read Data + adres 0x000000 */
transfers[0].tx_buf        = (unsigned long)cmd;
transfers[0].rx_buf        = 0;
transfers[0].len           = 4;
transfers[0].cs_change     = 0;  /* CS yükseltme — transfer 2'ye geç */

/* Transfer 2: 256 bayt oku */
uint8_t data[256];
uint8_t dummy[256];
memset(dummy, 0xFF, 256);
transfers[1].tx_buf        = (unsigned long)dummy;
transfers[1].rx_buf        = (unsigned long)data;
transfers[1].len           = 256;
transfers[1].cs_change     = 1;  /* işlem bitti, CS yükselt */

int ret = ioctl(fd, SPI_IOC_MESSAGE(2), transfers);
if (ret < 0) perror("SPI_IOC_MESSAGE");
SPI_IOC_WR_MODESPI mod yaz (SPI_MODE_0..3). SPI_MODE_3 için SPI_CPOL|SPI_CPHA kullan.
SPI_IOC_RD_MODEMevcut modu oku.
SPI_IOC_WR_MAX_SPEED_HZMaksimum SCK frekansını Hz cinsinden ayarla.
SPI_IOC_WR_BITS_PER_WORDWord uzunluğunu ayarla (genellikle 8).
SPI_IOC_MESSAGE(n)n adet spi_ioc_transfer yapısından oluşan dizi ile transfer başlat.

04 Python ile SPI: spidev kütüphanesi

Python spidev modülü, Linux spidev ioctl API'sini Python'a saran C extension'dır.

bash — kurulum
pip3 install spidev
# veya:
sudo apt install python3-spidev
spi_python.py
import spidev

# Bus 0, CS 0 aç:
spi = spidev.SpiDev()
spi.open(0, 0)                  # (bus, device/CS)

# SPI parametreleri ayarla:
spi.max_speed_hz = 10_000_000   # 10 MHz
spi.mode = 0                    # CPOL=0, CPHA=0
spi.bits_per_word = 8
spi.lsbfirst = False            # MSB first

try:
    # W25Q32 JEDEC ID oku:
    resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
    print(f"JEDEC ID: {[hex(b) for b in resp]}")
    # [0xff, 0xef, 0x40, 0x16]

    # Adres 0x000000'dan 16 bayt oku:
    # Read Data komutu (0x03) + 3 byte adres + 16 dummy
    cmd = [0x03, 0x00, 0x00, 0x00] + [0x00] * 16
    result = spi.xfer2(cmd)
    data = result[4:]            # ilk 4 bayt komut/adres echo
    print(f"Data: {[hex(b) for b in data]}")

    # writebytes2 — sadece yaz, okuma yok (tx_buf optimize):
    spi.writebytes2([0x06])      # Write Enable (WREN)

    # xfer — xfer2'den farkı: CS transferler arası yükselir
    # xfer2 — tek CS assertion ile tüm baytları gönderir (tercih et)

finally:
    spi.close()
xfer(data)Her bayt arasında CS geçici olarak yükselir. Çoğu cihaz için yanlış davranış.
xfer2(data)Tüm baytları tek CS assertion ile gönderir. Genellikle bu kullanılmalıdır.
xfer3(data)xfer2 gibi ama büyük transferleri parçalamaz (spidev >= 3.5).
writebytes(data)Sadece yazar, okuma arabelleği oluşturmaz.
readbytes(n)n adet 0xFF göndererek n bayt okur.

05 Pratik: W25Q32 NOR Flash okuma

Winbond W25Q32 — 32 Mbit (4 MB) NOR Flash. SPI Mode 0 veya Mode 3. Maks. 104 MHz saat hızı. Bootloader veya filesytem depolama olarak yaygın kullanılır.

Temel komutlar

KomutHexAçıklama
JEDEC ID0x9FManufacturer ID (1B) + Memory Type (1B) + Capacity (1B)
Read Status Reg 10x05WIP[0], WEL[1], BP[4:2], TB[5], SEC[6], SRP[7]
Write Enable0x06WEL bitini set et (yazma/silme öncesi gerekli)
Read Data0x033 byte adres + istenen sayıda bayt oku (max SCK: 50 MHz)
Fast Read0x0B3 byte adres + 1 dummy byte + oku (max SCK: 104 MHz)
Page Program0x02256 byte'a kadar yaz (önce WREN, sayfayı silmeden)
Sector Erase0x204 KB sektörü sil (0xFF'e doldurur)
Chip Erase0xC7Tüm flash'ı sil (~30 sn)
Power Down0xB9Düşük güç moduna gir
Release Power Down0xABGüç modundan çık
w25q32_read.py
import spidev
import sys

def w25q32_jedec_id(spi):
    resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
    mfr  = resp[1]
    mtype = resp[2]
    cap  = resp[3]
    print(f"JEDEC: Mfr=0x{mfr:02X} Type=0x{mtype:02X} Cap=0x{cap:02X}")
    cap_mb = (1 << (cap - 0x10))
    print(f"       → Winbond W25Q{cap_mb}FV ({cap_mb} Mbit)")

def w25q32_wait_ready(spi):
    while True:
        sr = spi.xfer2([0x05, 0x00])[1]
        if not (sr & 0x01):   # WIP=0: işlem tamamlandı
            break

def w25q32_read(spi, addr, length):
    """Fast Read: 3-byte adres + 1 dummy byte + length byte oku."""
    cmd = [
        0x0B,
        (addr >> 16) & 0xFF,
        (addr >>  8) & 0xFF,
         addr        & 0xFF,
        0x00          # dummy byte
    ] + [0x00] * length
    resp = spi.xfer2(cmd)
    return bytes(resp[5:])   # ilk 5 bayt komut+adres+dummy echo

def w25q32_read_status(spi):
    sr1 = spi.xfer2([0x05, 0x00])[1]
    sr2 = spi.xfer2([0x35, 0x00])[1]
    print(f"SR1=0x{sr1:02X} (WIP={sr1&1} WEL={(sr1>>1)&1})")
    print(f"SR2=0x{sr2:02X}")

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 10_000_000
spi.mode = 0

try:
    w25q32_jedec_id(spi)
    w25q32_read_status(spi)
    data = w25q32_read(spi, 0x000000, 16)
    print(f"İlk 16 bayt: {data.hex(' ')}")

    # Sayfanın başına bakıyoruz: silerek 0xFF dolmuş mu?
    if all(b == 0xFF for b in data):
        print("Sektör silinmiş (0xFF)")
    else:
        print("Sektörde veri mevcut")
finally:
    spi.close()

06 SPI ile LCD/TFT sürme

TFT LCD'ler (ILI9341, ST7789 vb.) SPI üzerinden çalışır ve komut/veri ayrımı için ekstra bir DC (Data/Command) pini kullanır.

  SoC                          TFT LCD
  ┌────────────┐    MOSI ──────►┌──────────┐
  │            │    SCK  ──────►│ ILI9341  │
  │            │    CS   ──────►│          │
  │            │    DC   ──────►│ D/CX pin │
  │            │    RST  ──────►│          │
  └────────────┘                └──────────┘
    DC=0 → Command frame
    DC=1 → Data frame
    
tft_init.py (ILI9341 temel init)
import spidev
import gpiod
import time

SPI_BUS = 0
SPI_CS  = 0
DC_CHIP = "gpiochip0"
DC_LINE = 24   # GPIO24 → DC pin
RST_LINE = 25  # GPIO25 → RST pin

# GPIO kurulumu (DC ve RST):
chip = gpiod.Chip(DC_CHIP)
dc_line  = chip.get_line(DC_LINE)
rst_line = chip.get_line(RST_LINE)
dc_line.request(consumer="tft", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0])
rst_line.request(consumer="tft", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[1])

spi = spidev.SpiDev()
spi.open(SPI_BUS, SPI_CS)
spi.max_speed_hz = 40_000_000
spi.mode = 0

def send_command(cmd, data=None):
    dc_line.set_value(0)          # DC=0: komut
    spi.xfer2([cmd])
    if data:
        dc_line.set_value(1)      # DC=1: veri
        spi.xfer2(list(data))

# Hardware reset:
rst_line.set_value(0); time.sleep(0.05)
rst_line.set_value(1); time.sleep(0.15)

# ILI9341 başlatma dizisi:
send_command(0x01)         # Software Reset
time.sleep(0.12)
send_command(0x11)         # Sleep Out
time.sleep(0.12)
send_command(0x3A, [0x55]) # COLMOD: 16-bit color (RGB565)
send_command(0x36, [0x48]) # MADCTL: column/row order
send_command(0x29)         # Display On

print("ILI9341 başlatıldı.")
spi.close()
NOT

Linux'ta fbtft kernel driver'ı ILI9341, ST7789, ST7735 ve benzeri LCD'ler için hazır driver sağlar. Device tree üzerinden yapılandırılınca /dev/fb0 olarak görünür ve framebuffer araçlarıyla (fbi, mplayer) kullanılabilir. Doğrudan spidev kullanmak daha düşük gecikme sunar.

07 Troubleshooting

SPI sorunlarının büyük çoğunluğu yanlış CPOL/CPHA, CS timing veya hız kaynaklıdır.

Clock polarity hataları

bash — mod testi
# Python ile mod sırayla dene:
import spidev
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1_000_000   # düşük hızdan başla

for mode in [0, 1, 2, 3]:
    spi.mode = mode
    resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
    print(f"Mode {mode}: {[hex(b) for b in resp]}")
    # Doğru moddaki yanıt anlamlı JEDEC ID verecektir

spi.close()

Oscilloscope ile SPI decode

  • MOSI, MISO, SCK, CS kanallarını bağla.
  • Trigger: CS düşen kenar (falling edge).
  • Sigrok / PulseView → "SPI" decoder → CPOL, CPHA, bit order seç.
  • Her transaction'ı hexadecimal olarak decode eder.
BelirtiOlası nedenÇözüm
MISO sürekli 0xFFYanlış CPOL/CPHA moduDatasheet'ten modu doğrula, 4 modu dene
MISO sürekli 0x00MISO hattı GND'ye kısa, cihaz beslenmiyorBağlantıları kontrol et
İlk bayt doğru, sonrakiler bozukCS timing yetersiz, saat fazla hızlıHızı düşür, cs_change ve delay_usecs ayarla
open() ENOENT hatası/dev/spidevX.Y yokCONFIG_SPI_SPIDEV=y ve device tree binding kontrol et
open() EACCES hatasıKullanıcı spi grubunda değilsudo usermod -aG spi $USER + oturumu yenile
ioctl EINVALDesteklenmeyen hız veya modSoC'nin maks. hızını ve desteklenen modları kontrol et
NOT

Bazı SoC'ler (örn. Raspberry Pi BCM2835) SPI_MODE_1 ve SPI_MODE_2'yi desteklemez veya donanım limitleri nedeniyle yüksek hızlarda timing sorunları yaşar. Güvenli başlangıç hızı: 1 MHz. Cihazın çalıştığından emin olduktan sonra hızı kademeli artır.