Project Deck
ANALİZ RAPORU DONANIM · SÜRÜCÜ · PROTOKOL CEF168 2026

cef168 — Canon EF
Lens Kontrol Kartı.

Pinefeat cef168 adaptörünün tam protokol analizi: Raspberry Pi tarafında I²C kernel sürücüsü ve V4L2 arayüzü, UART ASCII seri protokolü, Canon EF mount'ındaki düşük seviye SPI-benzeri hat ve tüm bunları entegre eden device-tree / build altyapısı.

00 Genel Bakış — Sistem Mimarisi ve Dosya Haritası

cef168, Canon EF/EF-S lensi Raspberry Pi kamerasına bağlayan Pinefeat adaptörünün yazılım altyapısıdır. Depo; Linux kernel sürücüsü, kullanıcı alanı kalibrasyon aracı ve device-tree enjeksiyon betiklerinden oluşur.

Neden bu adaptör?

Raspberry Pi kamera modülleri kendi lens motorları olmayan sensörler kullanır. Canon EF lensleri ise elektronik odak ve diyafram kontrolüne sahiptir; ancak protokol Canon'a özeldir. cef168 kartı, Raspberry Pi'nin I²C bus'unu Canon EF mount elektriğine köprüler ve protokol çevirisi yapar.

Üç katmanlı protokol mimarisi

┌───────────────────┐          ┌───────────────────────┐          ┌──────────────┐
│   Raspberry Pi    │          │    cef168 kart        │          │  Canon EF /  │
│                   │          │    (MCU firmware)     │          │  EF-S lens   │
│  libcamera        │          │                       │          │              │
│  rpicam-apps  ────┤ I²C      │  ┌─────────────────┐ │  DCL     │              │
│  V4L2 API     ────┼─────────►│  │ EF Protocol     │─┼─────────►│              │
│               ◄───┤ 0x0d    │  │ Engine (closed) │◄┼──────────│              │
│                   │          │  └─────────────────┘ │  DLC     │              │
│  INDI / serial────┤ UART     │                       │  LCLK    │              │
│  minicom      ────┼─────────►│                       │          │              │
└───────────────────┘ 115200   └───────────────────────┘          └──────────────┘
                                    ↑ bu repoda yok:
                                    Canon EF protokolü firmware içinde

Bu repo yalnızca sol tarafı uygular. Canon'un lens protokolü tersine mühendislik ile elde edilmiş olup kart firmware'inin içindedir.

Desteklenen sensörler

sensors.txt
imx219   # Raspberry Pi Camera Module 2
imx283
imx296
imx477   # Raspberry Pi HQ Camera (en yaygın kullanım)
imx519
imx708   # Raspberry Pi Camera Module 3
ov5647   # Raspberry Pi Camera Module v1
ov9281

Dosya haritası

DosyaTürRol
cef168.cC — KernelI²C client sürücüsü, V4L2 sub-device, CRC-8 I/O, control handler
cef168.hC — BaşlıkKernel + userspace paylaşımlı: opcode sabitler, struct tanımı, CRC polinomu
cef168.dtsiDevice TreeSensor overlay'lerine enjekte edilen lens node snippet'i
calibrate.cppC++ — UserspaceKalibrasyon aracı; V4L2 ve ham I²C arka uçları, PWL üretimi
configure.shBashOrkestratör: sensor seç, overlay indir, yama yap, Makefile oluştur
download.shBashRPi firmware versiyonuna karşılık gelen DTS'leri GitHub'dan çek
vcm.shBash + awkSensor .dts dosyasına VCM node ekle/değiştir, fragment yaz
Makefile.templateMakefileKernel modülü (.ko) + kalibrasyon aracı + DTBO derlemesi
doc/serial.mdMarkdownUART ASCII protokolü tam referans belgesi

Kurulum özeti

bash — tek seferlik kurulum
# 1. Repoyu klonla
git clone https://github.com/pinefeat/cef168.git
cd cef168

# 2. Sensor seç ve overlay'leri hazırla (imx477 = HQ Camera)
./configure.sh    # menüden imx477 seç

# 3. Kernel modülü + kalibrasyon aracı + DTBO derle
make

# 4. Kur (/boot/firmware/overlays'e kopyala, modprobe)
sudo make install

# 5. Yeniden başlat
sudo reboot

01 I²C Protokolü — Adres, Paket Yapısı, CRC-8

cef168'in birincil haberleşme yolu I²C'dir. Raspberry Pi, kamera CSI connector'ının I²C bus'unu hem sensörle hem de cef168 kartıyla paylaşır.

Bus parametreleri

I²C adresi0x0d (7-bit) — Device Tree: reg = <0x0d>;
compatible"pinefeat,cef168" — kernel driver bu string ile eşleşir
Güç3.3 V mantık — vcc-supply = <&vdd_3v3_reg>;
Media graph adıcef168*-000d pattern'i — son 4 karakter adres

Cihazı bul

bash — bus ve sub-device tespiti
# Media device'ı bul (unicam = RPi 4, rp1-cfe = RPi 5):
export DEV_MEDIA=$(v4l2-ctl --list-devices \
  | awk '/unicam|rp1-cfe/ {found=1} found && /\/dev\/media/ {print; exit;}')
echo $DEV_MEDIA
# /dev/media0

# Media pipeline'ını göster:
media-ctl -d $DEV_MEDIA -p | grep entity
# ...  cef168 1-000d (1 pad, 0 link)

# Lens sub-device dosyasını bul:
export DEV_LENS=$(media-ctl -d $DEV_MEDIA -p \
  | awk '/entity.*cef168.*-000d/ {found=1} found && /\/dev\/v4l-subdev/ {print $4; exit;}')
echo $DEV_LENS
# /dev/v4l-subdev2

# Tüm kontrolleri listele:
v4l2-ctl -d $DEV_LENS --list-ctrls-menus
# focus_absolute 0x009a0901 (int) : min=0 max=32767 ...
# iris_absolute 0x009a0903 (int) : min=0 max=32767 ...
# calibrate 0x00980aaa (button) ...

Yazma paketi formatı (host → kart)

Her yazma işlemi 4 baytlık sabit boyutlu bir pakettir. Bayt sırası:

 ┌─────────┬─────────┬─────────┬─────────┐
 │ bayt 0  │ bayt 1  │ bayt 2  │ bayt 3  │
 │ opcode  │ val LSB │ val MSB │  CRC-8  │
 └─────────┴─────────┴─────────┴─────────┘
  komut     ←── little-endian u16 ───→    poly=168,seed=0xFF
cef168.c:64-84 — paket oluşturma ve gönderme
static int cef168_i2c_write(struct cef168_device *cef168_dev, u8 cmd, u16 val)
{
    struct i2c_client *client = v4l2_get_subdevdata(&cef168_dev->sd);
    int retry, ret;

    /* little-endian dönüşümü */
    __le16 le_data = cpu_to_le16(val);
    char tx_data[4] = { cmd,
                        ((u8 *)&le_data)[0],   /* LSB */
                        ((u8 *)&le_data)[1] };  /* MSB */

    /* CRC-8 hesapla ve son bayta yaz */
    tx_data[3] = crc8(cef168_crc8_table, tx_data, 3, CRC8_INIT_VALUE);

    /* en fazla 3 deneme (-EIO veya -EREMOTEIO'da) */
    for (retry = 0; retry < 3; retry++) {
        ret = i2c_master_send(client, tx_data, sizeof(tx_data));
        if (ret == sizeof(tx_data))
            return 0;
        else if (ret != -EIO && ret != -EREMOTEIO)
            break;
    }
    dev_err(&client->dev, "I2C write fail after %d retries, ret=%d\n", retry, ret);
    return -EIO;
}
NEDEN 3 DENEME?

Bazı Canon lensleri (özellikle STM motorlular) motor hareket ederken I²C trafiğine NACK döndürür. Üç deneme, firmware'in meşgul durumu geçmesini beklemeye yetecek kadar zaman tanır; çok daha fazla bekleme libcamera'nın frame zamanlamalarını bozar.

Okuma paketi formatı (kart → host)

Okuma işlemi sizeof(struct cef168_data) bayt döndürür. Son bayt CRC-8'dir; uyuşmazlık durumunda sürücü -EIO döndürür ve hata mesajı log'a yazar.

cef168.c:87-116 — okuma ve CRC doğrulama
static int cef168_i2c_read(struct cef168_device *cef168_dev,
                           struct cef168_data *rx_data)
{
    struct i2c_client *client = v4l2_get_subdevdata(&cef168_dev->sd);

    int ret = i2c_master_recv(client, (char *)rx_data,
                              sizeof(struct cef168_data));
    if (ret != sizeof(struct cef168_data)) {
        dev_err(&client->dev, "I2C read fail, ret=%d\n", ret);
        return -EIO;
    }

    /* CRC-8 doğrula (son bayt hariç tüm struct) */
    u8 computed_crc = crc8(cef168_crc8_table, (const u8 *)rx_data,
                           sizeof(struct cef168_data) - 1, CRC8_INIT_VALUE);
    if (computed_crc != rx_data->crc8) {
        dev_err(&client->dev,
                "CRC mismatch calculated=0x%02X read=0x%02X\n",
                computed_crc, rx_data->crc8);
        return -EIO;
    }

    /* little-endian → host byte order */
    rx_data->moving_time         = le16_to_cpup((__le16 *)&rx_data->moving_time);
    rx_data->focus_position_min  = le16_to_cpup((__le16 *)&rx_data->focus_position_min);
    rx_data->focus_position_max  = le16_to_cpup((__le16 *)&rx_data->focus_position_max);
    rx_data->focus_position_cur  = le16_to_cpup((__le16 *)&rx_data->focus_position_cur);
    rx_data->focus_distance_min  = le16_to_cpup((__le16 *)&rx_data->focus_distance_min);
    rx_data->focus_distance_max  = le16_to_cpup((__le16 *)&rx_data->focus_distance_max);
    return 0;
}

CRC-8 detayları

Polinom168 (0xA8)CEF_CRC8_POLYNOMIAL sabiti; crc8_populate_msb() ile tablo oluşturulur
Başlangıç değeri0xFF — kernel: CRC8_INIT_VALUE, userspace I2CDev: explicit crc = 0xFF
Bit sırasıMSB-first — crc8_populate_msb() çağrısı bu sırayı garantiler
KapsamYazma: bayt 0–2 (3 bayt). Okuma: struct'ın son baytı hariç tamamı.
DOĞRULAMA: kalibrasyon paketi

calibrate.cpp'deki I²C kalibrasyon paketi hardcoded: {0x22, 0x00, 0x00, 0x30}. 0x30'un {0x22, 0x00, 0x00} üzerinden polinom 168 / seed 0xFF ile hesaplanan CRC-8 olduğu elle doğrulanabilir. Bu, tam protokol formatını bağımsız olarak kanıtlar.

02 Komut Opcode'ları — Tam Referans

Yedi opcode, cef168.h'de sabit olarak tanımlanmıştır. Tüm komutlar 4 baytlık yazma paketiyle gönderilir; kart onay vermez (write-only akış).

cef168.h:22-28 — opcode sabitleri
#define INP_CALIBRATE     0x22
#define INP_SET_FOCUS      0x80
#define INP_SET_FOCUS_P    0x81
#define INP_SET_FOCUS_N    0x82
#define INP_SET_APERTURE   0x7A
#define INP_SET_APERTURE_P 0x7B
#define INP_SET_APERTURE_N 0x7C

Opcode tablosu

HexSembolFonksiyonDeğer (u16, LE)Örnek paket
0x22INP_CALIBRATETam odak aralığı kalibrasyonu başlat0 (kullanılmaz)22 00 00 30
0x7AINP_SET_APERTUREMutlak diyafram — f-stop × 100ör. 560 = f/5.67A 38 02 XX
0x7BINP_SET_APERTURE_PDiyaframı N × 0.01 f-stop aç (+)100 = 1 f-stop7B 64 00 XX
0x7CINP_SET_APERTURE_NDiyaframı N × 0.01 f-stop kapat (−)100 = 1 f-stop7C 64 00 XX
0x80INP_SET_FOCUSMutlak odak adımı — kalibrasyon sonrası geçerli0 .. max80 F4 01 XX
0x81INP_SET_FOCUS_POdağı sonsuza doğru N adım kaydır (+)adım sayısı81 64 00 XX
0x82INP_SET_FOCUS_NOdağı minimuma doğru N adım kaydır (−)adım sayısı82 64 00 XX

XX alanı CRC-8'dir; her pakette farklıdır.

Diyafram değeri kodlaması

Diyafram değerleri f-stop × 100 tam sayısı olarak gönderilir:

f-stopKontrol değeriÖrnek v4l2-ctl
f/3.5350iris_absolute=350
f/5.6560iris_absolute=560
f/8.0800iris_absolute=800
f/11.01100iris_absolute=1100
f/16.01600iris_absolute=1600
f/22.62260iris_absolute=2260
ZOOM LENS UYARISI

Zoom lenslerde focal length değiştikçe geçerli diyafram aralığı da değişir. Firmware, her komut öncesinde mevcut aralığı low-side protokol üzerinden sorgular. Aralık dışı değer gönderilirse lens engage olmaz ve kontrol sessizce görmezden gelinir. iris_relative'in etki etmesi için önce en az bir kez iris_absolute gönderilmiş olması gerekir.

V4L2 → opcode eşleme kodu

cef168.c:118-139 — cef168_set_ctrl()
static int cef168_set_ctrl(struct v4l2_ctrl *ctrl)
{
    struct cef168_device *dev = to_cef168(ctrl);
    u8 cmd;

    switch (ctrl->id) {
    case V4L2_CID_FOCUS_ABSOLUTE:
        return cef168_i2c_write(dev, INP_SET_FOCUS, ctrl->val);

    case V4L2_CID_FOCUS_RELATIVE:
        cmd = ctrl->val < 0 ? INP_SET_FOCUS_N : INP_SET_FOCUS_P;
        return cef168_i2c_write(dev, cmd, abs(ctrl->val));

    case V4L2_CID_IRIS_ABSOLUTE:
        return cef168_i2c_write(dev, INP_SET_APERTURE, ctrl->val);

    case V4L2_CID_IRIS_RELATIVE:
        cmd = ctrl->val < 0 ? INP_SET_APERTURE_N : INP_SET_APERTURE_P;
        return cef168_i2c_write(dev, cmd, abs(ctrl->val));

    case CEF168_V4L2_CID_CUSTOM(calibrate):
        return cef168_i2c_write(dev, INP_CALIBRATE, 0);
    }
    return -EINVAL;
}

03 cef168_data Struct'ı — Bayt Düzeni, Endian, CRC

Karttan dönen her yanıt bu sabit boyutlu packed struct'tır. Hem kernel hem userspace aynı tanımı kullanır; cef168.h her iki tarafın ortak başlığıdır.

cef168.h:9-20 — yanıt struct'ı
struct cef168_data {
    __u8  lens_id;              // [0]    Canon lens tip baytı (ör. 0xEB)
    __u8  moving      : 1;      // [1] b0 motor aktif mi
    __u8  calibrating : 2;      // [1] b1-b2  0=boş, 2=kalibrasyon aktif
    __u16 moving_time;          // [2-3]  son işlem süresi (ms), LE
    __u16 focus_position_min;   // [4-5]  kalibrasyon sonrası min adım, LE
    __u16 focus_position_max;   // [6-7]  kalibrasyon sonrası max adım, LE
    __u16 focus_position_cur;   // [8-9]  mevcut odak adımı, LE
    __u16 focus_distance_min;   // [10-11] cm cinsinden yakın mesafe, LE
    __u16 focus_distance_max;   // [12-13] cm cinsinden uzak mesafe, LE (0xFFFF ≈ sonsuz)
    __u8  crc8;                 // [14]  önceki 14 bayt üzerinden CRC-8
} __packed;                     // toplam: 15 bayt

Bayt düzeni diyagramı

 Offset:  0      1      2    3    4    5    6    7    8    9   10   11   12   13   14
          ┌──────┬──────┬────┴────┬────┴────┬────┴────┬────┴────┬────┴────┬────┴────┐
          │lens  │flags │moving_  │pos_min  │pos_max  │pos_cur  │dist_min │dist_max │ crc
          │  id  │[1b2b]│  time   │  (LE)   │  (LE)   │  (LE)   │  (LE)   │  (LE)  │  8
          └──────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
                  bit0=moving
                  bit1-2=calibrating

Alan açıklamaları

lens_idCanon lens tipi. Her lens modelinin kendine özgü ID baytı vardır (ör. EF-S 10-18mm = 0xEB). Kalibrasyon EEPROM'a bu ID ile kaydedilir; lens değiştirildiğinde kalibrasyon korunur.
moving1 bit — odak veya diyafram motoru hareket ediyorken 1 olarak set edilir. libcamera bu biti FOCUS_ABSOLUTE control'ün volatile okumalarında kullanmaz; kullanıcı alanı için bilgilendirme amaçlıdır.
calibrating2 bit durum alanı: 0 = hazır / boşta, 2 = kalibrasyon taraması devam ediyor. calibrate.cpp bu alanı polling döngüsünde her 500 µs'de bir kontrol eder.
moving_timeSon odak veya diyafram hareketinin milisaniye cinsinden süresi. Kalibrasyon aracı bu değerden step_frames parametresini hesaplar: <300ms→4, <350ms→5, else→6.
focus_position_{min,max,cur}Kalibrasyon tamamlanmadan önce min/max değerleri anlamlı değildir (genellikle 0). Kalibrasyon sonrası libcamera bu aralığı FOCUS_ABSOLUTE control'ün min/max değeri olarak kullanır.
focus_distance_{min,max}Odak motorunun bulunduğu pozisyona göre lens'in netlediği mesafe aralığı (cm). 65535 (0xFFFF) sonsuzu temsil eder — gerçek bir mesafe değildir. Kalibrasyon sırasında bu değer her hareket adımında güncellenir.
crc8Önceki 14 bayt üzerinden hesaplanan CRC-8. Kernel sürücüsü ve calibrate.cpp uyuşmazlık durumunda okumayı hata olarak işaretler.

Endian dönüşümü

Struct kabloda little-endian gelir. Sürücü, tüm __u16 alanlarını le16_to_cpup() ile host byte sırasına çevirir. Userspace calibrate.cpp da aynı dönüşümü __le16_to_cpu() ile yapar. ARM ve x86 küçük-endian olduğundan pratikte bu dönüşüm no-op'tur; ancak big-endian mimariler için doğruluk açısından zorunludur.

STRUCT'I OKUMA

Custom data control'ü (CEF168_V4L2_CID_CUSTOM(data)) tam struct snapshot'ını döndürür. v4l2-ctl ile doğrudan okunabilir:

bash — ham struct okuma
# 15 baytlık veri kontrolünü oku (hexdump olarak göster):
v4l2-ctl -d $DEV_LENS --get-ctrl=0x00980aa9 | xxd
# Çıktı (örnek): EB 00 5F 00 00 00 B4 04 95 02 17 00 A8 19 CRC
#                ↑lens_id  ↑mt    ↑min   ↑max   ↑cur   ↑dmin  ↑dmax

04 Kernel Sürücüsü — Probe, V4L2 Sub-Device, Control Haritası

cef168.c standart bir Linux I²C client sürücüsüdür; kendini V4L2 medya grafiğine MEDIA_ENT_F_LENS tipiyle kaydettirir.

Sürücü kaydı

cef168.c:302-317 — device table ve driver yapısı
/* Device Tree eşleme tablosu */
static const struct of_device_id cef168_of_table[] = {
    { .compatible = "pinefeat,cef168" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, cef168_of_table);

static struct i2c_driver cef168_i2c_driver = {
    .driver = {
        .name = CEF168_NAME,
        .of_match_table = cef168_of_table,
    },
    .probe  = cef168_probe,
    .remove = cef168_remove,
};
module_i2c_driver(cef168_i2c_driver);

Probe fonksiyonu

cef168.c:253-290 — cef168_probe()
static int cef168_probe(struct i2c_client *client)
{
    struct cef168_device *cef168_dev;
    int rval;

    /* sürücü özel yapısını tahsis et */
    cef168_dev = devm_kzalloc(&client->dev, sizeof(*cef168_dev), GFP_KERNEL);
    if (!cef168_dev)
        return -ENOMEM;

    /* I²C client → sub-device bağlantısını kur */
    v4l2_i2c_subdev_init(&cef168_dev->sd, client, &cef168_ops);
    cef168_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
                            V4L2_SUBDEV_FL_HAS_EVENTS;

    /* V4L2 control handler'ı başlat */
    rval = cef168_init_controls(cef168_dev);
    if (rval) goto err_cleanup;

    /* media entity: sıfır pad, fonksiyon = LENS */
    rval = media_entity_pads_init(&cef168_dev->sd.entity, 0, NULL);
    if (rval < 0) goto err_cleanup;
    cef168_dev->sd.entity.function = MEDIA_ENT_F_LENS;

    /* asenkron kayıt: kamera sensörü hazır olduğunda otomatik bağlanır */
    rval = v4l2_async_register_subdev(&cef168_dev->sd);
    if (rval < 0) goto err_cleanup;

    /* CRC-8 tablosunu başlat (polinom 168, MSB-first) */
    crc8_populate_msb(cef168_crc8_table, CEF_CRC8_POLYNOMIAL);
    return 0;

err_cleanup:
    v4l2_ctrl_handler_free(&cef168_dev->ctrls);
    media_entity_cleanup(&cef168_dev->sd.entity);
    return rval;
}

V4L2 control haritası

Control IDHexYönFlag'lerOpcode
V4L2_CID_FOCUS_ABSOLUTE0x009a0901R/WVOLATILE · EXECUTE_ON_WRITE0x80 (yaz) / struct (oku)
V4L2_CID_FOCUS_RELATIVE0x009a0902W0x81 (+) / 0x82 (−)
V4L2_CID_IRIS_ABSOLUTE0x009a0903W onlyWRITE_ONLY · EXECUTE_ON_WRITE0x7A
V4L2_CID_IRIS_RELATIVE0x009a0904W0x7B (+) / 0x7C (−)
CEF168_V4L2_CID_CUSTOM(lens_id)0x00980aa8R onlyVOLATILE · READ_ONLY · U8struct.lens_id
CEF168_V4L2_CID_CUSTOM(data)0x00980aa9R onlyVOLATILE · READ_ONLY · U8[]tüm struct (15 bayt)
CEF168_V4L2_CID_CUSTOM(calibrate)0x00980aaaW onlyWRITE_ONLY · EXECUTE_ON_WRITE · Button0x22

Özel ID'ler: V4L2_CID_USER_BASE | 168 + {0,1,2} = 0x00980000 | 0xA8 + idx.

FOCUS_ABSOLUTE: dinamik aralık güncellemesi

Control, başlangıçta [0, S16_MAX] yer tutucu aralıkla oluşturulur. Her g_volatile_ctrl çağrısında karttan okunan gerçek değerlerle güncellenir:

cef168.c:141-166 — cef168_get_ctrl()
static int cef168_get_ctrl(struct v4l2_ctrl *ctrl)
{
    struct cef168_data data;
    struct cef168_device *dev = to_cef168(ctrl);
    int rval = cef168_i2c_read(dev, &data);
    if (rval < 0) return rval;

    switch (ctrl->id) {
    case V4L2_CID_FOCUS_ABSOLUTE:
        /* min/max aralığını kartın gerçek değerleriyle güncelle */
        __v4l2_ctrl_modify_range(ctrl,
                                 data.focus_position_min,
                                 data.focus_position_max, 1, 0);
        ctrl->val = data.focus_position_cur;
        return 0;

    case CEF168_V4L2_CID_CUSTOM(lens_id):
        ctrl->p_new.p_u8[0] = data.lens_id;
        return 0;

    case CEF168_V4L2_CID_CUSTOM(data):
        memcpy(ctrl->p_new.p_u8, &data, sizeof(data));
        return 0;
    }
    return -EINVAL;
}
VOLATILE FLAG NEDİR?

V4L2_CTRL_FLAG_VOLATILE, kernel'e "bu control'ün değeri asenkron olarak değişebilir, her seferinde cihazdan oku" der. cef168 için bu kritiktir: fokus motoru libcamera'nın isteği dışında (AF/MF switch, self-test) hareket edebilir. EXECUTE_ON_WRITE ise write işleminin cache'e değil doğrudan donanıma gitmesini garantiler.

05 Kullanıcı Alanı Aracı — calibrate.cpp Derinlemesine

calibrate.cpp, lens'e özgü odak parametrelerini üretir. Çıktısı libcamera'nın rpi.af tuning bölümüne eklenir ve autofocus algoritmasının temelini oluşturur.

Sınıf hiyerarşisi

calibrate.cpp:18-43 — IDevice arayüzü
class IDevice {
  protected:
    int fd;
    const char *dev;
  public:
    IDevice(const char *dev);        // open(dev, O_RDWR)
    virtual ~IDevice();             // close(fd)
    virtual void calibrate() = 0;  // saf sanal — arka uca göre davranır
    virtual void getData(struct cef168_data &data) = 0;
};

class V4L2SubDev : public IDevice { ... }; // ioctl (VIDIOC_S_CTRL, VIDIOC_G_EXT_CTRLS)
class I2CDev    : public IDevice { ... }; // ham write(4) / read(sizeof struct)

V4L2SubDev arka ucu

calibrate.cpp:52-93 — V4L2 üzerinden kalibrasyon ve okuma
void V4L2SubDev::calibrate()
{
    struct v4l2_control cal = {
        .id = CEF168_V4L2_CID_CUSTOM(calibrate),
        .value = 0,
    };
    if (ioctl(fd, VIDIOC_S_CTRL, &cal) < 0) { ... }
}

void V4L2SubDev::getData(struct cef168_data &data)
{
    struct v4l2_ext_control ext_ctrl = {
        .id   = CEF168_V4L2_CID_CUSTOM(data),
        .size = sizeof(struct cef168_data),
        .ptr  = &data,
    };
    struct v4l2_ext_controls ctrls = {
        .ctrl_class = V4L2_CTRL_CLASS_USER,
        .count      = 1,
        .controls   = &ext_ctrl,
    };
    if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) { ... }
}

I2CDev arka ucu

calibrate.cpp:96-170 — ham I²C erişimi
I2CDev::I2CDev(const char *dev, const int addr) : IDevice(dev), addr(addr)
{
    if (ioctl(fd, I2C_SLAVE, addr) < 0) { ... }  // adres 0x0d
}

void I2CDev::calibrate()
{
    /* hardcoded paket: {0x22, 0x00, 0x00, CRC} — CRC = 0x30 */
    const __u8 tx[4] = { INP_CALIBRATE, 0, 0, 0x30 };
    if (write(fd, &tx, sizeof(tx)) != sizeof(tx)) { ... }
}

void I2CDev::getData(struct cef168_data &rx)
{
    if (read(fd, &rx, sizeof(rx)) != sizeof(rx)) { ... }
    /* CRC doğrula */
    u8 crc = crc8_msb((u8*)&rx, sizeof(rx) - 1);
    if (crc != rx.crc8) throw std::runtime_error("CRC mismatch");
    /* endian dönüşümü */
    rx.moving_time        = __le16_to_cpu(rx.moving_time);
    rx.focus_position_min = __le16_to_cpu(rx.focus_position_min);
    /* ... */
}

PWL (Piecewise Linear) noktası toplama

calibrate.cpp:304-329 — kalibrasyon döngüsü
device->calibrate();

cef168_data data;
do {
    usleep(500);                  // ~2 kHz polling
    device->getData(data);

    if (data.calibrating == 2 && data.focus_distance_min != 0) {
        /* mesafe cm → diyoptri dönüşümü: 1/m = 100/cm */
        update(points, {
            100 / (double)data.focus_distance_min,  // x ekseni: ters mesafe (m⁻¹)
            data.focus_position_cur,               // y ekseni: donanım adımı
        });
    }
} while (data.calibrating != 0 ||
          elapsed_ms() < 100);    // en az 100 ms bekle

/* step_frames: son hareket süresinden hesapla */
int stepFrames = data.moving_time < 300 ? 4 :
                 data.moving_time < 350 ? 5 : 6;

Kalibrasyon taraması sırasında (calibrating == 2) odak motoru minimum'dan sonsuz'a hareket eder. Her adımda focus_distance_min mevcut netleme mesafesini verir. Araç bu değeri diyoptri (ters metre) birimine dönüştürür çünkü libcamera autofocus algoritması diyoptri uzayında çalışır.

Örnek kalibrasyon çıktısı

terminal — ./calibrate -d $DEV_LENS -v
Focus:     0, time:   0 ms, distance: 655.35 -655.35 m, status: 0/2
Focus:   108, time:  95 ms, distance:   6.50 -  6.50 m, status: 1/2
Focus:   280, time:  87 ms, distance:   2.25 -  2.25 m, status: 1/2
Focus:   512, time:  92 ms, distance:   0.88 -  0.88 m, status: 1/2
Focus:   780, time:  88 ms, distance:   0.46 -  0.46 m, status: 1/2
Focus:  1069, time:  95 ms, distance:   0.23 -  0.23 m, status: 0/0
------------------------------------------------------------
Parameters for "rpi.af" section of camera tuning JSON file
------------------------------------------------------------
Inverse of focus distance, m⁻¹:
    "min": 0.00153,
    "max": 4.35,
    "default": 4.35,
Speed:
    step_frames: 4
PWL function:
    "map": [ 0.00153, 1069, 0.154, 1042, 0.446, 996, 0.719, 969,
             0.971, 941, 1.2, 901, 1.45, 860, 1.72, 805, 2.04, 764,
             2.44, 682, 2.86, 559, 3.45, 388, 3.85, 252, 4.35, 0 ]
ARKA UÇ SEÇİMİ

--device argümanı "i2c" içeriyorsa I2CDev seçilir, yoksa V4L2SubDev. Kernel sürücüsü kurulu değilken debug için I²C modu kullanışlıdır. Üretim ortamında her zaman V4L2 modu tercih edilmeli.

06 UART Seri Arayüz — 115200-8N1 ASCII Protokolü

I²C'den tamamen bağımsız ikincil arabirim. Kernel sürücüsü gerektirmez; INDI, Python script'leri veya herhangi bir terminal ile kullanılabilir.

Fiziksel katman

KonnektörMolex PicoBlade, 1.25 mm pitch — 4 pin
PinoutPin 1: GND · Pin 2: RXD (kart girişi) · Pin 3: TXD (kart çıkışı) · Pin 4: VCC
Voltaj3.3 V TTL
Baud rate115200
Frame formatı8 data bit · parity yok · 1 stop bit (8N1)
Akış kontrolüYok
Satır sonu\r, \n veya her ikisi
Yanıt sonu\r\n

Terminal kurulumu

bash — minicom veya picocom
# minicom ile bağlan (local echo açık, satırsonu çevirisi kapalı):
sudo minicom -D /dev/ttyUSB0 -b 115200 --noinit

# picocom ile (daha minimal):
picocom -b 115200 --echo /dev/ttyUSB0

# pyserial ile (script'ten test):
python3 -c "
import serial, time
s = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
s.write(b'h\r'); time.sleep(0.1); print(s.read(200).decode())
"

Tam komut tablosu

KomutÖrnekAçıklamaYanıt örneğiI²C eşdeğeri
hhLens bilgisi (ID, focal length, zoom, diyafram, odak)Lens ID: 0xEB...
ccKalibrasyon başlat (EEPROM'a lens ID ile yaz)ok0x22
llFocal length (mm)12
ddOdak mesafesi aralığı (m)2.24-6.48struct alanları
rrOdak pozisyonu aralığı (fw ≥1.3)0-1203struct alanları
f · pfMutlak odak pozisyonunu oku405struct.pos_cur
f<val> · m<val>f500Mutlak odak ayarlaok0x80
f+<val> · m+<val>f+100Sonsuza doğru kaydırok0x81
f-<val> · m-<val>f-200Minimuma doğru kaydırok0x82
aaDiyafram aralığını oku (f-stop)3.5-22.6
a<val>a5.6Mutlak diyafram (f-stop)ok0x7A
a+<val>a+4.0Göreceli açok0x7B
a-<val>a-1.0Göreceli kapatok0x7C
iiOdağı sonsuza taşıok
mmOdağı minimuma taşıok
s<val>s2Odaklama hızı 1–4 (STM/USM)okyalnızca UART
eeMotor aktif mi?y / nstruct.moving
ttSon işlem süresi (ms)95struct.moving_time
nnKart seri numarasıPINEFEAT CEF1680000011yalnızca UART
vvFirmware sürümü1.3yalnızca UART

Örnek oturum

terminal oturumu
> v
1.3
> n
PINEFEAT CEF1680000011
> h
Lens ID: 0xEB
Focal length: 10-22 mm
Zoom: 1x
Aperture: f/3.5-22.6
Focus distance: 0.23-0.23 m
> c
ok
> e
y
> e
n
> r
0-1069
> f
1069
> f500
ok
> a5.6
ok
> a
3.5-22.6

Hata kodları

okKomut başarıyla uygulandı
erBilinmeyen komut veya geçersiz parametre
ncLens fiziksel olarak bağlı değil veya EF bağlantısında hata var

07 Canon EF Mount — Low-Side Protokolü (SPI Mode 3)

Kartın lens ile konuştuğu katman. Bu repoda implementasyonu yoktur; Canon'ın EF protokolü tersine mühendislik ile elde edilmiş olup kart MCU firmware'inin içindedir.

KAYNAK NOTU

Bu bölüm kamuya açık EF tersine mühendislik çalışmalarından ve host tarafındaki kodun bu katman hakkında dolaylı olarak ortaya koyduklarından derlenmektedir. Canon bu protokolü hiç belgelememiştir.

Mount kontakları

PinİsimYönAçıklamaGerilim
1VBATGüçMotor / IS güç hattı~6 V
2P-GNDGNDMotor akımı dönüş yolu
3VDDGüçLens MCU mantık beslemesi~5 V
4DCLKamera → LensKomut veri hattı (MOSI karşılığı)5 V CMOS
5DLCLens → KameraYanıt veri hattı (MISO karşılığı)5 V CMOS
6LCLKKamera → LensPaylaşılan shift clock (master = cef168)5 V CMOS
7D-GNDGNDMantık toprağı (P-GND'den ayrı — motor EMI'yi izole eder)
LEVEL SHIFT

cef168 kartının MCU'su 3.3 V'ta çalışır (host I²C/UART ile uyumlu), ancak Canon EF bus 5 V CMOS mantık kullanır. Kart bu iki voltaj alanı arasında level shifting yapar.

Sinyalleme analizi

  LCLK ───┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐
           └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └─
  DCL  ─ D7 ─ D6 ─ D5 ─ D4 ─ D3 ─ D2 ─ D1 ─ D0 ─────
  DLC  ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ R7 ─
          MSB                                    LSB  (sonraki byte)
SPI ModuMode 3 (CPOL=1, CPHA=1) — saat boşta yüksek, yükselen kenarda örnekleme. MSB-first, 8 bit.
Saat hızıLens tipine göre değişir: klasik EF ~62.5 kHz, yeni EF-S/STM/IS nesil ~96 kHz
Chip SelectYok — lens ile bire bir bağlantı; çerçeveleme komut baytları ve inter-byte zamanlama ile yapılır
DuplexPratikte yarı-duplex: kamera DCL'de komut gönderirken lens sonraki clock döngülerinde DLC'de yanıt verir. DLC, lens konuşmadığında high-Z / pull-up'ta bekler.

Neden "SPI değil"?

CS eksikliğiStandart SPI'da her slave'in CS/SS hattı vardır. EF bus bu hattı kullanmaz; bus tek bir lens için ayrılmıştır.
Inter-byte zamanlamaLens, iki byte arasında işlem zamanı ister. Bu süre protokol açısından anlamlıdır; DLC'nin durumu "meşgul" sinyali verir. Standart SPI bu kavramı bilmez.
Üst katmanKomut byte değerleri, yanıt uzunlukları ve frame yapısı Canon'a özgüdür. SPI bir fiziksel katman tanımıdır, bu protokol ise uygulama katmanı içerir.
Voltaj5 V — tipik gömülü SPI uygulamaları 3.3 V veya daha düşük kullanır.

Mantıksal komut katmanı

İşlem (topluluk adı)YönHost arayüzündeki iz
LENS_IDLens → Kameralens_id alanı, UART h çıktısı
GET_FOCAL_LENLens → KameraUART l12 (mm)
GET_APERTURE_RANGELens → KameraUART a3.5-22.6
GET_FOCUS_DISTANCELens → Kamerafocus_distance_{min,max}
FOCUS_DRIVE_NEARKamera → LensI²C 0x82, UART f-
FOCUS_DRIVE_INFKamera → LensI²C 0x81, UART f+
FOCUS_STOPKamera → LensLimiter tespitinde firmware tetikler
FOCUS_STATUSLens → Kameramoving biti, UART e
APERTURE_DRIVEKamera → LensI²C 0x7A/0x7B/0x7C, UART a/a+/a-
SET_SPEEDKamera → LensUART s<val> (STM/USM)

Odak adım tabanlı tasarımın kanıtı

Canon EF lensleri mutlak pozisyon bildirmez; yalnızca göreceli hareket komutlarına yanıt verir ve hareketi tamamladığında "bitti" sinyali verir. Bu yüzden:

  • Kalibrasyon zorunludur — min/max adım sayısını bulmak için limiterlerden limiterlere tarama yapılır.
  • EEPROM'a lens ID ile kayıt yapılır — lens değiştirilince yeniden tarama yapılmaması için.
  • focus_position_cur kart tarafından yazılım sayacı olarak tutulur, lens'ten okunmaz.
  • step_frames, motor + mekanik yerleşme süresine göre ayarlanır — sabit tablo değil.

Motor tipleri ve zamanlama etkisi

DC / AFDYavaş, gürültülü, büyük mekanik adımlar. Tuning dosyasında step_frames artırılmalı (genellikle 6+). Hareketi durdurulabilir ama pozisyon geri bildirimi yok.
USM (Ultrasonic Motor)Hızlı, sessiz, kısa pulse'lara duyarlı. Yüksek end lenslerde (EF 70-200 f/2.8 vb.). step_frames=4 genellikle yeterli.
STM (Stepping Motor)En sessiz ve yumuşak. Video AF için ideal. 1–4 hız kademesi (UART s komutu). Bu proje için en uygun motor tipi.

08 Device-Tree Entegrasyonu ve Build Akışı

Kernel sürücüsü tek başına yeterli değildir. libcamera'nın cef168'i lens sürücüsü olarak tanıması için kamera sensörünün Device Tree overlay'ine enjeksiyon gerekir; bu işlem üç betik tarafından otomatikleştirilir.

Enjekte edilen DT node'u (cef168.dtsi)

cef168.dtsi — sensor overlay'lerine eklenen node
vcm_node: cef168@d {
    compatible = "pinefeat,cef168";
    reg = <0x0d>;
    status = "disabled";          /* dtoverlay=...,vcm parametresiyle etkinleştirilir */
    vcc-supply = <&vdd_3v3_reg>;
};

Kamera node'una eklenen lens-focus fragment'ı:

fragment — cam_node'a lens bağlantısı
fragment@N {
    target = <&cam_node>;
    __overlay__ {
        lens-focus = <&vcm_node>;
    };
};

/* __overrides__ bölümüne vcm parametresi: */
vcm = <&vcm_node>, "status",
      <0>, "=0";   /* fragment index */

/* dosya sonunda: */
&vcm_node {
    status = "okay";
};

vcm.sh — iki strateji

Strateji 1: DeğiştirOverlay'de zaten vcm: vcmX@addr { ... } varsa (IMX708, OV5647, IMX219 gibi yerleşik VCM'li sensörler): node bloku cef168.dtsi içeriğiyle yerinde değiştirilir. __overrides__'dan eski güç rail referansı silinir.
Strateji 2: EkleVCM yoksa (IMX477 HQ Camera vb.): cef168 node kamera node'unun hemen arkasına eklenir. fragment@N oluşturulur, __overrides__ güncellenir, sona &vcm_node { status = "okay"; }; yazılır.

download.sh — sürüm sabitleme

download.sh — firmware versiyonu ile DTS indirme
# 1. Kurulu RPi firmware versiyonunu bul:
upstream_version=$(zgrep 'raspi-firmware' \
    '/usr/share/doc/raspi-firmware/changelog.Debian.gz' \
    | head -1 | awk '-F[ ():-]' '{print $5}')
# ör: 1:1.20240902+ds-1

# 2. Bu versiyona karşılık gelen kernel Git hash'ini bul:
commit=$(wget -qO- \
    "https://raw.githubusercontent.com/raspberrypi/firmware/refs/tags/${upstream_version}/extra/git_hash")
# ör: abc123def456...

# 3. Sensor overlay DTS dosyasını indir (#include zinciri takip edilir):
wget "${download_path}${file}"  # ör: imx477-overlay.dts + içerdiği .dtsi'lar

Bu yaklaşım sürüm sabitler: overlay, kurulu firmware ile aynı kernel commit'ten gelir. Firmware güncellendikten sonra configure.sh tekrar çalıştırılmalıdır.

Makefile.template — derleme hedefleri

Makefile.template
# Kernel modülü + sensor DTBO'ları (configure.sh sensor isimlerini ekler)
obj-m := cef168.o

KERNEL_SRC ?= /lib/modules/`uname -r`/build

default:
    $(MAKE) -C "$(KERNEL_SRC)" M="$(CURDIR)"    # cef168.ko
    $(CXX) -Wall -Wextra -o calibrate calibrate.cpp  # kalibrasyon aracı

install:
    $(MAKE) ... modules_install  # /lib/modules/.../kernel/drivers/media/i2c/
    depmod -A
    cp *.dtbo /boot/firmware/overlays --backup=numbered  # eski dosya korunur

Boot sonrası doğrulama

bash — kurulum sonrası kontrol
# Modül yüklenmiş mi?
lsmod | grep cef168
# cef168   16384  0

# Kernel log'unda hata var mı?
dmesg | grep cef168
# [  2.341] cef168 1-000d: cef168_init_controls ok

# Media grafiğinde görünüyor mu?
media-ctl -d $DEV_MEDIA -p | grep -A3 cef168
# entity 3: cef168 1-000d (1 pad, 0 link)
#   type V4L2 subdev subtype Lens

09 libcamera Entegrasyonu — Tuning ve Autofocus

V4L2 altyapısı kurulduktan sonra libcamera, lens sürücüsünü otomatik bulur. Autofocus'un doğru çalışması için kalibrasyon çıktısının kamera tuning JSON dosyasına eklenmesi gerekir.

Tuning dosyasını bul

bash
# Raspberry Pi 5 (pisp ISP):
ls /usr/share/libcamera/ipa/rpi/pisp/
# imx477.json  imx708.json  ov5647.json  ...

# Raspberry Pi 4 (vc4):
ls /usr/share/libcamera/ipa/rpi/vc4/
# Çalışma kopyasını oluştur:
cp /usr/share/libcamera/ipa/rpi/vc4/imx477.json ~/imx477-EF-S10-18mm.json

rpi.af bölümü — kalibrasyon değerleri ile

imx477-EF-S10-18mm.json — rpi.af bölümü
{
    "rpi.af":
    {
        "ranges":
        {
            "normal":
            {
                "min": 0.00153,      // calibrate çıktısından
                "max": 4.35,
                "default": 4.35
            }
        },
        "speeds":
        {
            "normal":
            {
                "step_coarse": 0.2,
                "step_fine": 0.05,
                "contrast_ratio": 0.75,
                "retrigger_ratio": 0.8,
                "retrigger_delay": 10,
                "pdaf_gain": 0.0,       // Canon lensle PDAF kapalı
                "pdaf_squelch": 0.0,
                "max_slew": 2.0,
                "pdaf_frames": 0,
                "dropout_frames": 0,
                "step_frames": 4        // calibrate çıktısından
            }
        },
        "conf_epsilon": 0,
        "conf_thresh": 0,
        "conf_clip": 0,
        "skip_frames": 5,
        "check_for_ir": false,
        "map": [ 0.00153, 1069, 0.154, 1042, 0.446, 996, 4.35, 0 ]
    }
}

Otofokusla çekim

bash
# Sürekli AF ile önizleme:
rpicam-hello --tuning-file ~/imx477-EF-S10-18mm.json --timeout 0

# AF debug logu:
LIBCAMERA_LOG_LEVELS=RPiAf:DEBUG rpicam-hello \
    --tuning-file ~/imx477-EF-S10-18mm.json --timeout 0

# Fotoğraf çek (AF tamamlanana kadar bekle):
rpicam-still --tuning-file ~/imx477-EF-S10-18mm.json -o photo.jpg

# Video (AF aktif):
rpicam-vid --tuning-file ~/imx477-EF-S10-18mm.json -o video.h264 -t 10000

AF parametrelerini ayarlama

step_coarseİlk tarama adım büyüklüğü (diyoptri). Büyük değer → hızlı ama titrek. Küçük değer → yavaş ama kararlı. Başlangıç: 0.2
step_fineİnce ayar adımı. Çok küçük → uzun süre. Çok büyük → hassas değil. Başlangıç: 0.05
step_framesHer odak hareketi sonrası beklenen kare sayısı. DC motorlu lensler için artır (6+). STM için 4 yeterli.
skip_framesBaşlangıçta geçilen kare sayısı (ISP stabilizasyonu için). 5 önerilen değer.
pdaf_*Canon lenslerle PDAF çalışmaz (sensör-lens uyumsuzluğu). Tüm pdaf parametreleri 0 olmalı.

Öz-Test Modu

prosedür
# 1. Raspberry Pi'nin açık ve kartı besliyor olması gerekiyor
# 2. Lens üzerindeki AF/MF switch'ini 15 saniye içinde 3 kez değiştir
# 3. Kart otomatik olarak şu diziyi çalıştırır:
#    - Odağı minimumdan sonsuza 4 adımda götür
#    - Tekrar minimuma döndür
#    - Diyaframı tam kapat
#    - 4 adımda tam açıklığa aç
# Yazılım komutu gerekmez — firmware tarafından tetiklenir

10 Son Kullanıcı Komutları — Tam Referans

Kurulum tamamlandıktan sonra günlük kullanım için gereken tüm komutlar.

Ortam değişkenleri (bir kez tanımla)

bash — ~/.bashrc
export DEV_MEDIA=$(v4l2-ctl --list-devices \
    | awk '/unicam|rp1-cfe/ {found=1} found && /\/dev\/media/ {print; exit;}')

export DEV_LENS=$(media-ctl -d $DEV_MEDIA -p \
    | awk '/entity.*cef168.*-000d/ {found=1} found && /\/dev\/v4l-subdev/ {print $4; exit;}')

Kalibrasyon

bash
# Zorunlu — her lens için bir kez. Sonucu tuning dosyasına ekle.
./calibrate -d $DEV_LENS

# Adım adım görmek için verbose:
./calibrate -d $DEV_LENS -v

# Kernel sürücüsü kurulu değilse — ham I²C ile:
./calibrate -d /dev/i2c-1 -a 0x0d

Diyafram kontrolü

bash — v4l2-ctl diyafram komutları
# Mutlak diyafram (f-stop × 100):
v4l2-ctl -d $DEV_LENS -c iris_absolute=350   # f/3.5 — tam açık
v4l2-ctl -d $DEV_LENS -c iris_absolute=560   # f/5.6
v4l2-ctl -d $DEV_LENS -c iris_absolute=800   # f/8.0
v4l2-ctl -d $DEV_LENS -c iris_absolute=2260  # f/22.6 — tam kapalı

# Göreceli (minimum adım = 100 = 1 f-stop):
v4l2-ctl -d $DEV_LENS -c iris_relative=+100  # 1 f-stop kapat
v4l2-ctl -d $DEV_LENS -c iris_relative=-100  # 1 f-stop aç

Odak kontrolü

bash — v4l2-ctl odak komutları
# Mevcut konumu oku:
v4l2-ctl -d $DEV_LENS --get-ctrl=focus_absolute
# focus_absolute: 500

# Mutlak konum:
v4l2-ctl -d $DEV_LENS -c focus_absolute=0     # minimum (sonsuz)
v4l2-ctl -d $DEV_LENS -c focus_absolute=1069  # maximum (yakın)

# Göreceli hareket:
v4l2-ctl -d $DEV_LENS -c focus_relative=+100  # sonsuza doğru
v4l2-ctl -d $DEV_LENS -c focus_relative=-100  # yakına doğru

Autofocus ile çekim

bash
# Sürekli AF — önizleme:
rpicam-hello --tuning-file ~/imx477-EF-S10-18mm.json --timeout 0

# Tek fotoğraf:
rpicam-still --tuning-file ~/imx477-EF-S10-18mm.json -o photo.jpg

# Video:
rpicam-vid --tuning-file ~/imx477-EF-S10-18mm.json -o video.h264 -t 10000

# AF debug çıktısı ile:
LIBCAMERA_LOG_LEVELS=RPiAf:DEBUG rpicam-hello \
    --tuning-file ~/imx477-EF-S10-18mm.json --timeout 0 2>&1 | grep AF

Lens durumunu oku

bash
# Tüm kontrolleri listele:
v4l2-ctl -d $DEV_LENS --list-ctrls

# Lens ID oku:
v4l2-ctl -d $DEV_LENS --get-ctrl=0x00980aa8
# User class 0x00980aa8 (u8) : 0xEB

# Ham data struct oku (15 bayt hex):
v4l2-ctl -d $DEV_LENS --get-ctrl=0x00980aa9

11 Özet Cheat-Sheet

Üç katmanın tamamını tek sayfada özetleyen referans tablosu.

Katman 1 — I²C (birincil, kernel)

  • Adres: 0x0d · compatible: "pinefeat,cef168" · 3.3 V
  • Yazma: 4 bayt {opcode, LSB, MSB, CRC8} · little-endian u16 · polinom 168 · seed 0xFF
  • Okuma: 15 bayt struct · son bayt CRC8 · LE→host dönüşümü gerekli
  • Opcode: 0x22 kal., 0x7A/7B/7C diyafram abs/+/−, 0x80/81/82 odak abs/+/−
  • Retry: 3× −EIO veya −EREMOTEIO'da

Katman 2 — V4L2 / libcamera (kernel → kullanıcı)

  • Sub-device: MEDIA_ENT_F_LENS · v4l2_async_register_subdev
  • FOCUS_ABSOLUTE: R/W volatile, aralık her okumada güncellenir
  • FOCUS_RELATIVE / IRIS_ABSOLUTE / IRIS_RELATIVE: write-only
  • Custom: lens_id (RO) · data snapshot (RO 15 bayt) · calibrate button
  • Custom base: V4L2_CID_USER_BASE | 168 + {0,1,2}

Katman 3 — UART ASCII (opsiyonel, kernel gerekmez)

  • 115200-8-N-1 · 3.3 V TTL · Molex PicoBlade: GND/RXD/TXD/VCC
  • Tek karakter komut + opsiyonel parametre + CR/LF
  • I²C üst kümesi: n,v,s,l,t,e,i,m yalnızca UART'ta
  • Yanıtlar: ok / er / nc

Low-Side — Canon EF Mount (firmware içinde)

  • Kontaklar: VBAT (~6V), P-GND, VDD (~5V), DCL (MOSI), DLC (MISO), LCLK (SCK), D-GND
  • Topoloji: SPI Mode 3 · MSB-first · 5V CMOS · CS yok · yarı-duplex
  • Hız: ~62.5 kHz (klasik EF) / ~96 kHz (STM/USM yeni nesil)
  • Protokol: bayt komut/yanıt, adım tabanlı odak, 1/8-stop diyafram, lens ID

Hızlı komut referansı

HedefKomut
Kalibrasyon (V4L2)./calibrate -d $DEV_LENS
Kalibrasyon (ham I²C)./calibrate -d /dev/i2c-1 -a 0x0d
Diyafram f/5.6v4l2-ctl -d $DEV_LENS -c iris_absolute=560
Diyafram 1 stop kapatv4l2-ctl -d $DEV_LENS -c iris_relative=+100
Diyafram 1 stop açv4l2-ctl -d $DEV_LENS -c iris_relative=-100
Odak adımı okuv4l2-ctl -d $DEV_LENS --get-ctrl=focus_absolute
Odak ayarlav4l2-ctl -d $DEV_LENS -c focus_absolute=500
AF önizlemerpicam-hello --tuning-file <dosya>.json --timeout 0
Lens bilgisi (UART)h
UART kalibrasyonc
UART odak ayarlaf500
UART diyaframa5.6
UART firmware ver.v
Öz-TestAF/MF switch'i 15 sn içinde 3×