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.
İçindekiler
- 1 1. Zamanlama Önce Gelir
- 2 2. Proje 1 — Statik Görüntü: Zinciri Doğrulamak
- 3 3. Proje 2 — Hareketli Grafik: Sisteme Durum Eklemek
- 4 4. Proje 3 — OV7670: Görüntü Artık Dışarıdan Geliyor
- 5 5. Gerçek Zamanlı Piksel İşleme: Parlaklık Kontrolü
- 6 6. Karşılaşılan Problemler ve Çözümleri
- 7 7. Üç Mimarinin Karşılaştırılması
- 8 8. Buradan Sonra Ne Yapılabilir?
- 9 Sonuç
- 10 Önemli Not
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.
