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
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ı
| Dosya | Tür | Rol |
|---|---|---|
| cef168.c | C — Kernel | I²C client sürücüsü, V4L2 sub-device, CRC-8 I/O, control handler |
| cef168.h | C — Başlık | Kernel + userspace paylaşımlı: opcode sabitler, struct tanımı, CRC polinomu |
| cef168.dtsi | Device Tree | Sensor overlay'lerine enjekte edilen lens node snippet'i |
| calibrate.cpp | C++ — Userspace | Kalibrasyon aracı; V4L2 ve ham I²C arka uçları, PWL üretimi |
| configure.sh | Bash | Orkestratör: sensor seç, overlay indir, yama yap, Makefile oluştur |
| download.sh | Bash | RPi firmware versiyonuna karşılık gelen DTS'leri GitHub'dan çek |
| vcm.sh | Bash + awk | Sensor .dts dosyasına VCM node ekle/değiştir, fragment yaz |
| Makefile.template | Makefile | Kernel modülü (.ko) + kalibrasyon aracı + DTBO derlemesi |
| doc/serial.md | Markdown | UART ASCII protokolü tam referans belgesi |
Kurulum özeti
# 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
reg = <0x0d>;"pinefeat,cef168" — kernel driver bu string ile eşleşirvcc-supply = <&vdd_3v3_reg>;cef168*-000d pattern'i — son 4 karakter adresCihazı bul
# 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
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;
}
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.
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ı
CEF_CRC8_POLYNOMIAL sabiti; crc8_populate_msb() ile tablo oluşturulurCRC8_INIT_VALUE, userspace I2CDev: explicit crc = 0xFFcrc8_populate_msb() çağrısı bu sırayı garantilercalibrate.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ış).
#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
| Hex | Sembol | Fonksiyon | Değer (u16, LE) | Örnek paket |
|---|---|---|---|---|
| 0x22 | INP_CALIBRATE | Tam odak aralığı kalibrasyonu başlat | 0 (kullanılmaz) | 22 00 00 30 |
| 0x7A | INP_SET_APERTURE | Mutlak diyafram — f-stop × 100 | ör. 560 = f/5.6 | 7A 38 02 XX |
| 0x7B | INP_SET_APERTURE_P | Diyaframı N × 0.01 f-stop aç (+) | 100 = 1 f-stop | 7B 64 00 XX |
| 0x7C | INP_SET_APERTURE_N | Diyaframı N × 0.01 f-stop kapat (−) | 100 = 1 f-stop | 7C 64 00 XX |
| 0x80 | INP_SET_FOCUS | Mutlak odak adımı — kalibrasyon sonrası geçerli | 0 .. max | 80 F4 01 XX |
| 0x81 | INP_SET_FOCUS_P | Odağı sonsuza doğru N adım kaydır (+) | adım sayısı | 81 64 00 XX |
| 0x82 | INP_SET_FOCUS_N | Odağı 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-stop | Kontrol değeri | Örnek v4l2-ctl |
|---|---|---|
| f/3.5 | 350 | iris_absolute=350 |
| f/5.6 | 560 | iris_absolute=560 |
| f/8.0 | 800 | iris_absolute=800 |
| f/11.0 | 1100 | iris_absolute=1100 |
| f/16.0 | 1600 | iris_absolute=1600 |
| f/22.6 | 2260 | iris_absolute=2260 |
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
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.
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ı
FOCUS_ABSOLUTE control'ün volatile okumalarında kullanmaz; kullanıcı alanı için bilgilendirme amaçlıdır.calibrate.cpp bu alanı polling döngüsünde her 500 µs'de bir kontrol eder.step_frames parametresini hesaplar: <300ms→4, <350ms→5, else→6.FOCUS_ABSOLUTE control'ün min/max değeri olarak kullanır.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.
Custom data control'ü (CEF168_V4L2_CID_CUSTOM(data)) tam struct snapshot'ını döndürür. v4l2-ctl ile doğrudan okunabilir:
# 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ı
/* 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
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 ID | Hex | Yön | Flag'ler | Opcode |
|---|---|---|---|---|
V4L2_CID_FOCUS_ABSOLUTE | 0x009a0901 | R/W | VOLATILE · EXECUTE_ON_WRITE | 0x80 (yaz) / struct (oku) |
V4L2_CID_FOCUS_RELATIVE | 0x009a0902 | W | — | 0x81 (+) / 0x82 (−) |
V4L2_CID_IRIS_ABSOLUTE | 0x009a0903 | W only | WRITE_ONLY · EXECUTE_ON_WRITE | 0x7A |
V4L2_CID_IRIS_RELATIVE | 0x009a0904 | W | — | 0x7B (+) / 0x7C (−) |
CEF168_V4L2_CID_CUSTOM(lens_id) | 0x00980aa8 | R only | VOLATILE · READ_ONLY · U8 | struct.lens_id |
CEF168_V4L2_CID_CUSTOM(data) | 0x00980aa9 | R only | VOLATILE · READ_ONLY · U8[] | tüm struct (15 bayt) |
CEF168_V4L2_CID_CUSTOM(calibrate) | 0x00980aaa | W only | WRITE_ONLY · EXECUTE_ON_WRITE · Button | 0x22 |
Ö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:
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;
}
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
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
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
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
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ı
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 ]
--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
\r, \n veya her ikisi\r\nTerminal kurulumu
# 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 | Örnek | Açıklama | Yanıt örneği | I²C eşdeğeri |
|---|---|---|---|---|
h | h | Lens bilgisi (ID, focal length, zoom, diyafram, odak) | Lens ID: 0xEB... | — |
c | c | Kalibrasyon başlat (EEPROM'a lens ID ile yaz) | ok | 0x22 |
l | l | Focal length (mm) | 12 | — |
d | d | Odak mesafesi aralığı (m) | 2.24-6.48 | struct alanları |
r | r | Odak pozisyonu aralığı (fw ≥1.3) | 0-1203 | struct alanları |
f · p | f | Mutlak odak pozisyonunu oku | 405 | struct.pos_cur |
f<val> · m<val> | f500 | Mutlak odak ayarla | ok | 0x80 |
f+<val> · m+<val> | f+100 | Sonsuza doğru kaydır | ok | 0x81 |
f-<val> · m-<val> | f-200 | Minimuma doğru kaydır | ok | 0x82 |
a | a | Diyafram aralığını oku (f-stop) | 3.5-22.6 | — |
a<val> | a5.6 | Mutlak diyafram (f-stop) | ok | 0x7A |
a+<val> | a+4.0 | Göreceli aç | ok | 0x7B |
a-<val> | a-1.0 | Göreceli kapat | ok | 0x7C |
i | i | Odağı sonsuza taşı | ok | — |
m | m | Odağı minimuma taşı | ok | — |
s<val> | s2 | Odaklama hızı 1–4 (STM/USM) | ok | yalnızca UART |
e | e | Motor aktif mi? | y / n | struct.moving |
t | t | Son işlem süresi (ms) | 95 | struct.moving_time |
n | n | Kart seri numarası | PINEFEAT CEF1680000011 | yalnızca UART |
v | v | Firmware sürümü | 1.3 | yalnızca UART |
Örnek oturum
> 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 parametrencLens fiziksel olarak bağlı değil veya EF bağlantısında hata var07 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.
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 | İsim | Yön | Açıklama | Gerilim |
|---|---|---|---|---|
| 1 | VBAT | Güç | Motor / IS güç hattı | ~6 V |
| 2 | P-GND | GND | Motor akımı dönüş yolu | — |
| 3 | VDD | Güç | Lens MCU mantık beslemesi | ~5 V |
| 4 | DCL | Kamera → Lens | Komut veri hattı (MOSI karşılığı) | 5 V CMOS |
| 5 | DLC | Lens → Kamera | Yanıt veri hattı (MISO karşılığı) | 5 V CMOS |
| 6 | LCLK | Kamera → Lens | Paylaşılan shift clock (master = cef168) | 5 V CMOS |
| 7 | D-GND | GND | Mantık toprağı (P-GND'den ayrı — motor EMI'yi izole eder) | — |
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)
Neden "SPI değil"?
Mantıksal komut katmanı
| İşlem (topluluk adı) | Yön | Host arayüzündeki iz |
|---|---|---|
LENS_ID | Lens → Kamera | lens_id alanı, UART h çıktısı |
GET_FOCAL_LEN | Lens → Kamera | UART l → 12 (mm) |
GET_APERTURE_RANGE | Lens → Kamera | UART a → 3.5-22.6 |
GET_FOCUS_DISTANCE | Lens → Kamera | focus_distance_{min,max} |
FOCUS_DRIVE_NEAR | Kamera → Lens | I²C 0x82, UART f- |
FOCUS_DRIVE_INF | Kamera → Lens | I²C 0x81, UART f+ |
FOCUS_STOP | Kamera → Lens | Limiter tespitinde firmware tetikler |
FOCUS_STATUS | Lens → Kamera | moving biti, UART e |
APERTURE_DRIVE | Kamera → Lens | I²C 0x7A/0x7B/0x7C, UART a/a+/a- |
SET_SPEED | Kamera → Lens | UART 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_curkart 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
step_frames artırılmalı (genellikle 6+). Hareketi durdurulabilir ama pozisyon geri bildirimi yok.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)
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@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
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.fragment@N oluşturulur, __overrides__ güncellenir, sona &vcm_node { status = "okay"; }; yazılır.download.sh — sürüm sabitleme
# 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
# 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
# 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
# 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
{
"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
# 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
Öz-Test Modu
# 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)
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
# 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ü
# 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ü
# 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
# 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
# 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ı
| Hedef | Komut |
|---|---|
| Kalibrasyon (V4L2) | ./calibrate -d $DEV_LENS |
| Kalibrasyon (ham I²C) | ./calibrate -d /dev/i2c-1 -a 0x0d |
| Diyafram f/5.6 | v4l2-ctl -d $DEV_LENS -c iris_absolute=560 |
| Diyafram 1 stop kapat | v4l2-ctl -d $DEV_LENS -c iris_relative=+100 |
| Diyafram 1 stop aç | v4l2-ctl -d $DEV_LENS -c iris_relative=-100 |
| Odak adımı oku | v4l2-ctl -d $DEV_LENS --get-ctrl=focus_absolute |
| Odak ayarla | v4l2-ctl -d $DEV_LENS -c focus_absolute=500 |
| AF önizleme | rpicam-hello --tuning-file <dosya>.json --timeout 0 |
| Lens bilgisi (UART) | h |
| UART kalibrasyon | c |
| UART odak ayarla | f500 |
| UART diyafram | a5.6 |
| UART firmware ver. | v |
| Öz-Test | AF/MF switch'i 15 sn içinde 3× |