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 ──────────►└──────────┘
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:
| Mod | CPOL | CPHA | Saat boştayken | Örnekleme kenarı | Cihaz örnekleri |
|---|---|---|---|---|---|
| 0 | 0 | 0 | Düşük | Yükselen kenar | W25Qxx NOR Flash, SD kart, MAX7219 |
| 1 | 0 | 1 | Düşük | Düşen kenar | MCP3208 ADC |
| 2 | 1 | 0 | Yüksek | Düşen kenar | Nadir kullanılır |
| 3 | 1 | 1 | Yüksek | Yükselen kenar | BMI160, 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.
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.
&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>;
};
};
# 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
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.
# 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.
#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;
}
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:
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");
04 Python ile SPI: spidev kütüphanesi
Python spidev modülü, Linux spidev ioctl API'sini Python'a saran C extension'dır.
pip3 install spidev
# veya:
sudo apt install python3-spidev
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()
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
| Komut | Hex | Açıklama |
|---|---|---|
| JEDEC ID | 0x9F | Manufacturer ID (1B) + Memory Type (1B) + Capacity (1B) |
| Read Status Reg 1 | 0x05 | WIP[0], WEL[1], BP[4:2], TB[5], SEC[6], SRP[7] |
| Write Enable | 0x06 | WEL bitini set et (yazma/silme öncesi gerekli) |
| Read Data | 0x03 | 3 byte adres + istenen sayıda bayt oku (max SCK: 50 MHz) |
| Fast Read | 0x0B | 3 byte adres + 1 dummy byte + oku (max SCK: 104 MHz) |
| Page Program | 0x02 | 256 byte'a kadar yaz (önce WREN, sayfayı silmeden) |
| Sector Erase | 0x20 | 4 KB sektörü sil (0xFF'e doldurur) |
| Chip Erase | 0xC7 | Tüm flash'ı sil (~30 sn) |
| Power Down | 0xB9 | Düşük güç moduna gir |
| Release Power Down | 0xAB | Güç modundan çık |
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
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()
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ı
# 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.
| Belirti | Olası neden | Çözüm |
|---|---|---|
| MISO sürekli 0xFF | Yanlış CPOL/CPHA modu | Datasheet'ten modu doğrula, 4 modu dene |
| MISO sürekli 0x00 | MISO hattı GND'ye kısa, cihaz beslenmiyor | Bağlantıları kontrol et |
| İlk bayt doğru, sonrakiler bozuk | CS timing yetersiz, saat fazla hızlı | Hızı düşür, cs_change ve delay_usecs ayarla |
| open() ENOENT hatası | /dev/spidevX.Y yok | CONFIG_SPI_SPIDEV=y ve device tree binding kontrol et |
| open() EACCES hatası | Kullanıcı spi grubunda değil | sudo usermod -aG spi $USER + oturumu yenile |
| ioctl EINVAL | Desteklenmeyen hız veya mod | SoC'nin maks. hızını ve desteklenen modları kontrol et |
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.