1. Anasayfa
  2. FPGA Kartları

Basys 3 ile VGA Uygulamaları: Statik Görüntüden Gerçek Zamanlı Kamera Görüntüsüne

Basys 3 ile VGA Uygulamaları: Statik Görüntüden Gerçek Zamanlı Kamera Görüntüsüne
Basys 3 ile VGA Uygulamaları: Statik Görüntüden Gerçek Zamanlı Kamera Görüntüsüne
0

VGA zamanlaması, hareket mantığı, OV7670 kamera arayüzü, framebuffer ve gerçek zamanlı piksel işleme — bu yazıda Basys 3 (Artix-7 XC7A35T) üzerinde VHDL ile geliştirilen ve Vivado 2023.1 ile donanımda test edilen üç aşamalı bir VGA görüntüleme sürecini ele alıyoruz.

FPGA üzerinde VGA ile görüntü üretmek ilk bakışta ekrana bir renk basmaktan ibaret görünür. Ancak görüntünün kaynağı değiştikçe tasarımın çözmesi gereken problem de değişir. Önce doğru zamanlamayı üretmek gerekir; görüntüye hareket eklendiğinde durum bilgisinin yönetilmesi gerekir; görüntü bir kameradan gelmeye başladığında ise veri yakalama, bellek, farklı saat alanları ve gerçek zamanlı piksel işleme devreye girer.

1. Zamanlama Önce Gelir

Basys 3’ün VGA arabirimi her renk kanalı için 4 bitlik bir direnç merdiveni kullanır; yani çıkış 12 bit RGB444 ve toplam 4096 renktir. Ancak monitör, gelen rengin hangi piksele ait olduğunu renk hattından anlayamaz. Bunu HSYNC ve VSYNC darbelerinin zamanlamasından çıkarır. Bu yüzden VGA tasarımında ilk iş renk üretmek değil, sayaçları doğru kurmaktır.

640×480 @ 60 Hz için görünür alanın yanında front porch, sync ve back porch aralıkları da taranır:

Eksen Görünür Front porch Sync Back porch Toplam
Yatay (piksel) 640 16 96 48 800
Dikey (satır) 480 10 2 33 525

800 × 525 × 60 Hz ≈ 25,175 MHz piksel saati gerekir. İlk iki projede ayrı bir saat üretmek yerine 100 MHz sistem saatinden dört saatte bir aktif olan bir pixel enable üretildi; 25,000 MHz ile 25,175 MHz arasındaki fark monitörlerin tolerans aralığında kalıyor.

-- 100 MHz -> 25 MHz piksel temposu (ayrı saat alanı açmadan)
process(clk100)
begin
  if rising_edge(clk100) then
    pix_div <= pix_div + 1; -- 2 bitlik sayaç
    pix_en <= '0';
    if pix_div = "11" then
      pix_en <= '1';
    end if;
  end if;
end process;

VGA sayaçları yalnızca bu işaret aktifken ilerler. Senkron darbeleri ve görünür alan bilgisi doğrudan sayaç değerlerinden türetilir:

if pix_en = '1' then
  if h_cnt = 799 then
    h_cnt <= 0;
    if v_cnt = 524 then v_cnt <= 0; else v_cnt <= v_cnt + 1; end if;
  else h_cnt <= h_cnt + 1;
  end if;
end if;

video_on <= '1' when (h_cnt < 640 and v_cnt < 480) else '0';
hsync    <= '0' when (h_cnt >= 656 and h_cnt < 752) else '1'; -- 640+16 .. +96
vsync    <= '0' when (v_cnt >= 490 and v_cnt < 492) else '1'; -- 480+10 .. +2
frame_tick <= '1' when (h_cnt = 0 and v_cnt = 480) else '0'; -- kare sonu

Buradaki frame_tick ikinci projede kritik hale gelecek. Bu noktadan sonra görüntü üretimi tek bir soruya indirgenir: “şu anda taranan (x,y) pikseli hangi renk olmalı?”

2. Proje 1 — Statik Görüntü: Zinciri Doğrulamak

İlk tasarımda görüntü belleği yoktur. Renk doğrudan taranan koordinattan hesaplanır:

RGB = f(x, y)

Ekranın ortasında sabit bir kare çizilir ve karenin rengi 12 anahtardan doğrudan RGB444 olarak sürülür. Böylece hem zamanlama hem de giriş → görüntü yolu aynı anda doğrulanmış olur:

-- SW(11 downto 8)=R, SW(7 downto 4)=G, SW(3 downto 0)=B
square <= '1' when (px >= X0 and px < X0 + SIZE and
                     py >= Y0 and py < Y0 + SIZE) else '0';

rgb <= sw(11 downto 0) when (video_on = '1' and square = '1') else
       BG_NAVY          when video_on = '1' else
       (others => '0'); -- blanking sırasında siyah zorunlu

Son satır sık atlanan bir ayrıntıdır: blanking aralığında RGB hatlarının sıfırlanması gerekir, aksi halde bazı monitörler görüntüyü kilitleyemez veya kenarlarda hayalet sütunlar oluşur.

Bu aşamanın kazanımı ekranda bir kare göstermek değil, HSYNC/VSYNC/koordinat/RGB zincirinin bağımsız olarak doğrulanmasıdır. Bir sonraki projede bu blok hiç değiştirilmeden kullanıldı.

Görsel 1 — Proje 1’in statik VGA çıktısı. Ekranın merkezindeki sabit kare koordinat karşılaştırmasıyla oluşturulmakta, rengi ise Basys 3 üzerindeki anahtarlarla RGB444 olarak belirlenmektedir.

3. Proje 2 — Hareketli Grafik: Sisteme Durum Eklemek

İkinci projede VGA altyapısı aynı kalır; eklenen tek şey durum bilgisidir. Renk artık yalnızca koordinata değil, nesnenin o andaki konumuna da bağlıdır:

RGB = f(x, y, state)

Kritik nokta konumun ne zaman güncellendiğidir. Konum her piksel saatinde değiştirilirse monitör bir kareyi tararken nesne yer değiştirir ve görüntü yırtılır. Bu yüzden güncelleme yalnızca kare sonunda üretilen frame_tick ile, yani 60 Hz’de yapılır:

if frame_tick = '1' and pause = '0' then
  if dir_x = '1' then                          -- saga dogru
    if box_x + box_size + step >= 640 then dir_x <= '0';
    else box_x <= box_x + step; end if;
  else                                          -- sola dogru
    if box_x < step then dir_x <= '1';
    else box_x <= box_x - step; end if;
  end if;
  -- dikey eksen icin bire bir ayni yapi (box_y / dir_y / 480 siniri)
end if;

Böylece iki görev net biçimde ayrılır: VGA zamanlaması hangi pikselin tarandığını, durum mantığı nesnenin nerede olduğunu belirler; render bloğu bu ikisini birleştirir. Bu ayrım kamera projesindeki daha karmaşık mimarinin provasıdır.

Tasarım çalışma anında anahtarlarla yönetilebilir hale getirildi: SW[11:0] renk, SW[13:12] hız, SW[15:14] kare boyutu (32/48/64/96 piksel), BTNC duraklatma; LED0–LED3 ise nesnenin o anki yönünü gösterir. Yön LED’leri, sınır mantığı hatalı olduğunda ekrana bakmadan hata bulmayı kolaylaştırdığı için baştan eklendi.

Görsel 2 — Proje 2’nin hareketli VGA çıktısı. Karenin konumu frame_tick ile kare bazında güncellenirken aynı VGA zamanlama altyapısı kullanılmaktadır.

4. Proje 3 — OV7670: Görüntü Artık Dışarıdan Geliyor

Üçüncü projede sistemin karakteri değişir. Görüntüyü artık FPGA üretmez; kaynak OV7670 kamera modülüdür ve FPGA’nın işi veriyi yakalamak, dönüştürmek, saklamak ve doğru zamanda geri okumaktır.

Tasarım şu modüllere ayrıldı: top, sccb_master, ov7670_config, ov7670_capture, framebuffer, brightness_control, vga_timing ve bir Clocking Wizard IP’si. Kamera JB Pmod’una 8 bitlik veri yolu (D0–D7), JC Pmod’una PCLK, HREF, VSYNC, XCLK, SCL, SDA, RESET ve PWDN olacak şekilde bağlandı.

4.1 Saat Mimarisi ve PCLK Sorunu

Clocking Wizard 100 MHz’den iki çıkış üretir: VGA tarafı için 25 MHz, kameraya verilen XCLK için 24 MHz. Kameranın geri döndürdüğü PCLK ise özel bir durumdur.

PCLK’yi doğrudan bir FPGA saat alanı olarak kullanmak mümkün görünse de pratikte sorunludur: sinyal bir Pmod pininden geliyor, global saat kaynağı değil ve kamera yapılandırılana kadar kararsız. Bu tasarımda PCLK veri gibi ele alındı — 100 MHz alanına iki flip-flop ile senkronize edilip kenarı algılandı:

-- PCLK ve kamera sinyalleri 100 MHz alaninda ornekleniyor
pclk_r  <= pclk_r(1 downto 0)  & cam_pclk;
href_r  <= href_r(1 downto 0)  & cam_href;
vsync_r <= vsync_r(1 downto 0) & cam_vsync;

pclk_rise <= '1' when pclk_r(2 downto 1) = "01" else '0';

XCLK 24 MHz olduğu için PCLK de yaklaşık 24 MHz’dir; 100 MHz sistem saatiyle örnekleme, bu tasarımın çalışma koşullarında PCLK kenarlarının algılanması için yeterli örnekleme payı sağlar.

Bu tasarımda PCLK, saat kaynağı olarak ayrılmış bir pinden değil bir Pmod pininden geldiği için XDC dosyasında ilgili pin için CLOCK_DEDICATED_ROUTE FALSE tanımlandı. Bu, her OV7670 tasarımında zorunlu bir yöntem değil; bu projenin pinleme ve saat yönlendirme koşullarında tercih edilen çözümdür.

4.2 Yapılandırma: SCCB Olmadan Hiçbir Şey Çalışmaz

OV7670 açılışta varsayılan olarak VGA/YUV modundadır; RGB565 ve QVGA istemek için SCCB (I²C benzeri, tek yönlü yazma) üzerinden register yazmak gerekir. Cihaz yazma adresi 0x42‘dir. Kritik register’lar şunlardır:

Register Adres Değer Anlamı
COM7 0x12 0x80 Yazılım reset — sonrasında ~1 ms beklenmeli
CLKRC 0x11 0x01 Dahili saat bölücü (kare hızı)
COM7 0x12 0x04 Çıkış formatı: RGB
COM15 0x40 0xD0 RGB565 + full range (0–255)
SCALING_PCLK_DIV 0x73 0xF1 QVGA için PCLK bölücü

Bunların yanında AWB, AEC, kazanç ve renk matrisi register’ları da açıkça yazıldı. Yazmalar arasına gerçek gecikmeler konulması gerekiyor: reset sonrası bekleme atlandığında kamera sonraki yazmaların bir kısmını kaçırıyor ve sonuç, tek tek bakıldığında hepsi doğru görünen bir register listesiyle bozuk bir görüntü oluyor.

4.3 Piksel Yakalama ve Format Dönüşümü

Veri yolu 8 bit, piksel ise 16 bit olduğu için her piksel iki aktarımda gelir. Capture mantığı bu iki baytı birleştirir ve aynı çevrimde RGB444’e indirir:

if pclk_rise = '1' and href_r(1) = '1' then
  if byte_sel = '0' then
    pix_hi  <= cam_data;         -- RGB565 ust bayt: RRRRRGGG
    byte_sel <= '1';
  else
    pix16 := pix_hi & cam_data;  -- alt bayt: GGGBBBBB
    fb_din <= pix16(15 downto 12) &  -- R: en anlamli 4 bit
              pix16(10 downto 7)  &  -- G
              pix16( 4 downto 1);    -- B
    fb_we    <= '1';
    byte_sel <= '0';
    fb_waddr <= fb_waddr + 1;
  end if;
end if;

if vsync_rise = '1' then  -- her karede adres basa doner
  fb_waddr <= (others => '0');
  byte_sel <= '0';
end if;

RGB565’ten RGB444’e geçiş bir dönüşüm değil, kırpmadır: her kanalın en anlamlı 4 biti alınır, yeşilin 2 ve mavinin 1 alt biti atılır. Bu, düşük kontrastlı yüzeylerde görülebilir renk basamaklanmasına yol açar; kaynağı VGA çıkışının 12 bit olmasıdır, kamerada bir arıza değildir.

VSYNC ile yazma adresinin sıfırlanması da göründüğünden önemlidir. Adres sıfırlanmazsa tek bir kayıp bayt bile bütün karenin kaymasına ve görüntünün eğik akmasına neden olur.

4.4 Framebuffer Neden Zorunlu?

İlk iki projede piksel gerektiği anda hesaplanabiliyordu. Kamera sisteminde ise VGA’nın o an istediği piksel ile kameranın o an gönderdiği piksel aynı değildir: kamera kendi akışıyla üretir, monitör kendi tarama düzeniyle tüketir. Framebuffer bu iki tarafı birbirinden ayıran mimari katmandır.

Kapasite hesabı çözünürlük seçimini doğrudan belirledi. 640×480 RGB444 bir kare 640 × 480 × 12 = 3.686.400 bit tutar; XC7A35T’nin toplam blok RAM’i ise 1.800 Kbit ≈ 1.843.200 bit’tir. Yani tam çözünürlüklü kare cihaza sığmaz. QVGA’da ise 320 × 240 × 12 = 921.600 bit gerekir; bu, BRAM’in yaklaşık yarısıdır ve rahatça sığar.

Framebuffer çift portlu kuruldu: bir port capture tarafından yazılır, diğer port VGA tarafından okunur. Buradaki en sinsi ayrıntı okuma gecikmesidir — blok RAM okuması bir saat çevrimi gecikmelidir. Adres doğru üretilse bile veri bir çevrim geç gelir, dolayısıyla senkron sinyallerinin de aynı miktarda geciktirilmesi gerekir:

-- BRAM cikisi 1 cevrim gecikmeli; senkronlar da 1 cevrim geciktirilir
hs_d  <= hs;
vs_d  <= vs;
von_d <= video_on;

vga_hs  <= hs_d;
vga_vs  <= vs_d;
vga_rgb <= fb_dout when von_d = '1' else (others => '0');

4.5 320×240 Görüntüyü 640×480’e Yaymak

Kamera QVGA üretirken çıkış 640×480’dir. Aradaki fark 2× nearest-neighbor ölçekleme ile kapatıldı: her kamera pikseli yatayda ve dikeyde iki kez okunur. Ölçekleme için yeni piksel hesaplanmaz, yalnızca okuma adresi dönüştürülür:

Adres = (y / 2) × 320 + (x / 2)

İkiye bölme bir bit kaydırmadır; 320 ile çarpma ise çarpıcı gerektirmez, çünkü 320 = 256 + 64:

ry <= py(9 downto 1); -- y / 2
rx <= px(9 downto 1); -- x / 2

-- 320 = 2^8 + 2^6 -> carpici yerine iki kaydirma ve bir toplama
fb_raddr <= shift_left(ry_ext, 8) + shift_left(ry_ext, 6) + rx_ext;

Sonuç: interpolasyon yok, DSP dilimi yok, ek gecikme yok. Görüntü bir miktar bloklu görünür ama maliyeti neredeyse sıfırdır.

5. Gerçek Zamanlı Piksel İşleme: Parlaklık Kontrolü

Framebuffer ile VGA çıkışı arasına bir işlem bloğu eklendiğinde görüntü ekrana ulaşmadan değiştirilebilir hale gelir. Bu projede örnek olarak anahtarlarla kontrol edilen parlaklık ayarı uygulandı.

Her kanal yalnızca 0–15 aralığını temsil edebildiğinden düz toplama taşmaya yol açar: 14 + 4 dört bitte 2 olarak sarılır ve parlak bölgeler aniden koyulaşır. Bu yüzden toplama daha geniş ve işaretli bir ara değerde yapılıp sonuç kırpılır:

function clamp4(v : signed(6 downto 0)) return unsigned is
begin
  if v > 15 then return to_unsigned(15, 4);
  elsif v < 0 then return to_unsigned( 0, 4);
  else return unsigned(std_logic_vector(v(3 downto 0)));
  end if;
end function;

r_out <= clamp4(resize(signed('0' & pix(11 downto 8)), 7) + resize(offset, 7));
g_out <= clamp4(resize(signed('0' & pix( 7 downto 4)), 7) + resize(offset, 7));
b_out <= clamp4(resize(signed('0' & pix( 3 downto 0)), 7) + resize(offset, 7));

Blok tamamen kombinasyoneldir; ek çevrim eklemediği için 4.4’te kurulan okuma gecikmesi hizalaması bozulmaz. Anahtarlardan biri bu bloğu bypass ederek ham görüntü ile işlenmiş görüntünün donanım üzerinde anında karşılaştırılmasını sağlar — bir DSP bloğunun doğru çalışıp çalışmadığını anlamanın en hızlı yolu budur.

Anahtarlar üzerinden uygulanan parlaklık ofsetinin etkisi donanım üzerinde doğrudan gözlemlenebilir. Görsel 3 ve Görsel 4, aynı kamera sahnesinin farklı SW parlaklık ayarlarındaki çıktılarını göstermektedir.

Görsel 3 — OV7670’ten alınan canlı görüntünün SW ile ayarlanan daha aydınlık hali.

Görsel 4 — Aynı kamera sahnesinin SW ile ayarlanan daha karanlık hali.

6. Karşılaşılan Problemler ve Çözümleri

Kamera projesindeki zamanın büyük kısmı RTL yazmakla değil, aşağıdaki dört problemle geçti. Hepsinin ortak özelliği, sentez ve implementasyonun temiz geçmesi — hatanın yalnızca donanımda görülmesi.

Yeşil Baskın, Yatay Çizgili Görüntü

İlk çalışan bitstream ekrana senkron bir görüntü verdi ama görüntü yeşil baskın ve yatay çizgiliydi. Veri yolu doğru görünüyordu, dolayısıyla ilk şüphe capture mantığına düştü. Gerçek sebep RTL’de değil, kamera yapılandırmasındaydı: COM15 (0x40) 0x80 yazılmıştı, yani RGB565 seçilmemişti; ayrıca SCALING_PCLK_DIV (0x73) 0xF0 kalmıştı, yani kamera QVGA temposunda veri üretmiyordu. Değerler 0xD0 ve 0xF1 yapıldığında görüntü düzeldi; capture ve framebuffer kodunda tek satır değişmedi.

Buradan çıkan ders şu: kamera arayüzlerinde görüntü bozukluğunun kaynağı çoğu zaman kendi RTL’iniz değil, karşı tarafın register ayarlarıdır. Datapath’i saatlerce incelemeden önce yapılandırmayı doğrulamak gerekir.

RGB565 Bayt Sırası

Piksel iki bayt halinde geldiğinden hangisinin üst bayt olduğu kritiktir; sıra ters yorumlandığında görüntü tanınabilir kalır ama renkler tamamen bozulur. Bu ihtimal simülasyonda değil donanımda test edilebilsin diye iki birleşim sırası arasında geçiş yapan bir anahtar eklendi. Böylece doğru sıra saniyeler içinde belirlendi.

Bir Çevrimlik Okuma Gecikmesi

Framebuffer’dan okunan veri, adresin üretildiği çevrimde değil bir sonrakinde hazır olur. Senkron sinyalleri geciktirilmediğinde görüntü tüm ekran boyunca bir piksel kayar ve sağ kenarda ince bir artık sütun oluşur. Çözüm 4.4’teki üç satırlık gecikme hattıdır.

PCLK’nin Saat Olarak Kullanılması

Bu projenin pinlemesinde PCLK doğrudan saat girişi olarak kullanıldığında tasarım implementasyon aşamasında saat yönlendirme hatası verdi; ayrıca kamera yapılandırılmadan önceki kararsız PCLK de riskliydi. Sinyali 100 MHz alanında örnekleyip kenarını algılamak, bu tasarımda hem bu sorunu hem de saat alanları arası geçiş ihtiyacını ortadan kaldırdı.

Dört problem de aynı noktaya işaret ediyor: FPGA görüntü sistemlerinde tasarımın parçası yalnızca algoritma değil; zamanlama, veri formatı, bit genişliği ve karşı cihazın yapılandırması da tasarımın kendisidir.

7. Üç Mimarinin Karşılaştırılması

Özellik Statik VGA Hareketli VGA Kamera + VGA
Görüntü kaynağı Koordinat Koordinat + durum OV7670
Bellek Yok Yok BRAM (921.600 bit)
Görüntü modeli RGB = f(x,y) RGB = f(x,y,state) Bellekten oku → işle
Saat yapısı Tek saat + pix_en Tek saat + pix_en 100 / 25 / 24 MHz + PCLK
Temel problem Zamanlama Durum ve kare senkronu Veri yakalama ve bellek
Piksel işleme Renk üretimi Hareketli nesne Parlaklık kontrolü

Aradaki gerçek fark görüntünün kendisi değil, mimarinin büyümesidir:

Zamanlama → Koordinat → Durum → Harici veri → Bellek → Piksel işleme

8. Buradan Sonra Ne Yapılabilir?

Kurulan kamera–framebuffer–VGA zinciri daha ileri görüntü işleme için uygun bir temeldir. Doğal devam adımları şunlar:

  • Piksel bazlı işlemler: grayscale, eşikleme, renk segmentasyonu. Mevcut parlaklık bloğunun yerine takılabilecek yapıdadır.
  • Komşuluk gerektiren işlemler: kenar bulma ve konvolüsyon. Bunlar için satır tamponu (line buffer) ve gerçek bir piksel pipeline’ı gerekir.
  • Double buffering: kamera bir belleğe yazarken VGA diğerinden okur, kare bitince roller değişir. Ancak QVGA RGB444’te iki tampon 1.843.200 bit eder ve XC7A35T’nin BRAM’inin tamamını tüketir; bu yüzden pratikte renk derinliğini düşürmek veya harici belleğe geçmek gerekir.
  • Kareleri UART üzerinden bilgisayara aktarmak: donanımda gördüğünüz görüntüyü piksel piksel doğrulamanın en kesin yolu.

Bunlar mevcut projede uygulanmış özellikler değil, kurulan mimarinin izin verdiği bir sonraki adımlardır.

Sonuç

Statik bir kareyle başlayan süreç, aynı VGA çekirdeği korunarak gerçek zamanlı bir kamera sistemine dönüştü. İlk projede zamanlama doğrulandı, ikincisinde sisteme durum eklendi, üçüncüsünde görüntü harici bir kaynaktan alınarak veri yakalama, bellek, çözünürlük dönüşümü ve piksel işleme tek mimaride birleşti.

Bu Yazıya Tepkiniz Ne Oldu?
  • 0
    be_endim
    Beğendim
  • 0
    alk_l_yorum
    Alkışlıyorum
  • 0
    e_lendim
    Eğlendim
  • 0
    d_nceliyim
    Düşünceliyim
  • 0
    _rendim
    İğrendim
  • 0
    _z_ld_m
    Üzüldüm
  • 0
    _ok_k_zd_m
    Çok Kızdım

Bültenimize Katılın

Hemen ücretsiz üye olun ve yeni güncellemelerden haberdar olan ilk kişi olun.

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir