Ta strona używa ciasteczek (plików cookies), dzięki którym może działać lepiej. Dowiedz się więcejRozumiem i akceptuję

FPGA (własne programy #5): Obraz na monitorze VGA

Autor Wiadomość
Elvis 



Posty: 1738
Pomógł: 105 razy
Otrzymał 303 piw(a)
Skąd: wawa
Programuję w:
C, asm

Wysłany: 25-12-2017, 17:11   FPGA (własne programy #5): Obraz na monitorze VGA

W niniejszym artykule chciałbym opisać krótko jak za pomocą zestawu ElbertV2 uzyskać obraz na monitorze VGA. Moje rozwiązanie nie jest idealne, pojawiają się problemy z pierwszą linią i kolumną, o czym pisałem w wątku: https://www.forbot.pl/for...ga-vt14911.htm, więc jeśli ktoś ma pomysł na rozwinięcie, udoskonalenie, czy zwykłe poprawienie kodu, proszę śmiało pisać :)

Warto przy okazji zwrócić uwagę, że ten projekt pokazuje zalety FPGA w porównaniu z mikrokontrolerami. Przykładowy sterownik zrealizujemy za pomocą niewielkiej części zasobów naszego i tak niewielkiego układu. Wygenerowanie obrazu za pomocą mikrokontrolera, o ile w ogóle możliwe zajmowałoby ogromną ilość jego zasobów.

Projekt początkowo bazował na blogu, którego link podaje producent płytki (Numato):
https://langster1980.blogspot.co.uk/2015/08/driving-vga-port-using-elbert-v2-and_7.html
Zachęcam do porównania obu rozwiązań.

Interfejs VGA
Standard VGA używa sygnału analogowego do sterowania wyświetlaniem obrazu (w odróżnieniu od np. HDMI). Aby wyświetlić obraz musimy odpowiednio wysterować linie:
• hsync - synchronizacja pozioma
• vsync - synchronizacja pionowa
• red, green, blue - kolor aktualnego piksela
Podłączenie do zestawu ElbertV2 znajdziemy w jego dokumentacji:

Rezystory na liniach kolorów realizują prosty przetwornik cyfrowo-analogowy.

Obraz na monitorze VGA
Tradycyjne monitory używały kineskopów, gdzie obraz był generowany na warstwie luminoforu za pomocą wiązki elektronow. W danej chwili wiązka elektronów była z ogniskowana w jednym punkcie ekranu tworzącym piksel. Elekromagnesy pozwalały na odchylenie tej wiązki i skierowanie w konkretny punkt na ekranie. Cykliczne "przemiatanie" obrazu sprawiało, że użytkownik widział stailny obraz.
Obecnie monitory LCD właściwie wyparły tradycyjne CRT, jednak sposób sterowania pozostał niezmieniony. Obraz składa się z poziomych linii, każda linia z jednakowej liczby pikseli. Po wyświetleniu linii generowane są odpowiednie sygnały synchronizacji poziomej (hsync), a po narysowaniu ostatniej linii, sygnał synchronizacji pionowej (vsync).

Aby uzyskać obraz na monitorze musimy więc wygenerować odpowiednie sygnały na liniach hsync, vsync oraz sam obraz za pomocą linii koloru.

Dokładniejszy opis sterowania liniami znajdziemy na stronie: https://eewiki.net/pages/viewpage.action?pageId=15925278. Poniżej widzimy obrazek pobrany właśnie stamtąd:


Sygnał synchronizacji ma ujemną polaryzację, czyli aktywny stan to logiczne "0". Jak widzimy podczas rysowania linii najpierw generowane są piksele obrazu, następnie mała przerwa (Front Porch), sygnał synchronizacji (Sync Pulse) oraz kolejna przerwa (Back Porch). Po niej rysowana jest kolejna linia. Synchronizacja pionowa przebiega podobnie, chociaż czasy są znacznie dłuższe.

Rozdzielczość obrazu
Obraz na ekranie może być generowany w różnych trybach - z różną częstotliwością odświerzania oraz rozdzielczością. Jako przykład wykorzystamy tryb o rozdzielczości 640 x 480 pikseli, z odświerzaniem 60Hz.
Parametry dla tego trybu znajdziemy w tabeli na stronie podlinkowanej wyżej oraz w bardzo wielu miejscach w sieci.
W każdym razie chcielibyśmy uzyskać następujące parametry:
• Pixel clock: 25.175 MHz
Synchronizacja pozioma:
• Front Porch: 16
• Sync Pulse: 96
• Back Porch: 48
Synchronizacja pionowa:
• Front Porch: 10
• Sync Pulse: 2
• Back Porch: 33

Pętla PLL
W dokumentacji zestawu ElbertV2 znajdziemy informację o częstotliwości zegara taktującego, wynosi ona 12 MHz. Jak widzimy jest to za mało, ponieważ do wyświetlania potrzebny jest nam zegar o wyższej częstotliwości. Na szczęście Spartan3A, w który nasz zestaw jest wyposażony w moduły pętli PLL, dzięki której możemy zwiększyć częstotliwość pracy układu.
Tworzymy nowe projekt, a następnie dodajemy nowy plik źródłowy (New Source). Jako typ projektu wybieramy "IP (CORE Generator & Architecture Wizard)". Następnie odnajdujemy komponent "Single DCM_SP".

Przechodzimy przez kolejne opcja generatora. Następnie otworzy się nowe okno, tym razem z opcjami konfiguracji modułu.
W pierwszym oknie wprowadzamy częstotliwość zegara, czyli 12MHz. Wybieramy również sygnał CLKFX:

Opcje w kolejnych oknach możemy zostawić niezmienione, aż dojdziemy do ustawień generowanego zegara. Tutaj wprowadzamy docelową częstotliwość, czyli 25.175 MHz.

Teraz możemy zakończyć konfigurację komponentu, powinniśmy zobaczyć nowy plik dodany do projektu. Jeśli będziemy chcieli, zawsze możemy zmienić konfigurację dwukrotnie klikając na tym pliku.

Konfiguracja pinów
Każdy projekt zawiera definicję używanych pinów. Do sterowania VGA musimy zdefinować podłączenia linii synchornizacji, kolorów oraz zegara dla samego układu Spartan. Plik ma postać:
Kod programu: Zaznacz cały
NET "clk"           LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
NET "hsync"         LOC = P93   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "vsync"         LOC = P92   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "blue[2]"       LOC = P98   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "blue[1]"       LOC = P96   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "green[2]"      LOC = P102  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "green[1]"      LOC = P101  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "green[0]"      LOC = P99   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "red[2]"        LOC = P105  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "red[1]"        LOC = P104  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "red[0]"        LOC = P103  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;


Generowanie obrazu
Zacznijmy niejako od końca, czyli od generowania danych obrazu. Moduł, który potrzebujemy będzie dostawał współrzęne piksela i zwracał jego kolor:
Kod programu: Zaznacz cały
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity display is port(
    clk : in std_logic;
    x : in integer range 0 to 1023;
    y : in integer range 0 to 1023;
    color : out std_logic_vector(7 downto 0));
end display;

architecture Behavioral of display is
begin

process (clk) is
begin
    if rising_edge(clk) then
        if (x < 640) and (y < 480) then
            if (x = 0) or (x = 639) or (y = 0) or (y = 479) then    -- biala ramka
                color <= x"ff";           
            else
                if (y < 160) then -- czerwony pasek na gorze
                    color <= x"e0";
                elsif (y < 320) then -- zielony w srodku
                    color <= x"1c";
                else -- niebieski na dole
                    color <= x"03";
                end if;
            end if;
        else
            color <= x"00";
        end if;
    end if;
end process;

end Behavioral;

Aktualny kod to pierwszy i prosty test. W przyszłości należałoby go zastąpić czymś bardziej użytecznym, np. wyświetlaniem stanu gry (jeśli np. napiszemy grę w pong-a), albo użyć pamięci blokowej i wyświetlać zgromadzone w niej dane. Na razie na sztywno zapisany "obraz kontrolny" musi nam wystarczyć.

Sygnały synchronizacji
Kolejny krok to napisanie modułu generującego sygnały synchronizacji oraz zliczającego pozycję aktualnego piksela. Na ekranie rysowane są 640 piksele w linii, ale zliczanie współrzędnej x odbywa się od 0 do 799 - nadmiarowe wartości są używane do stwerowania sygnałem synchronizacji. Podobnie jest w przypadku synchronizacji pionowej: zmienna y obejmuje wartości od 0 do 525, ale tylko 480 linii jest widocznych.
Warto zwrócić uwagę na "sztuczkę" ze zmiennymi next_x oraz next_y. Moduł odpowiedzialny za określanie kolorów pikseli musi ustawić dane niejako kolejnego piksela, podczas gdy wyświetlany jest poprzedni. Stąd użycie dwóch par zmiennych (z next_ i bez).

Kod programu: Zaznacz cały
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity timings is port(
    clk : in std_logic;
    hsync : out std_logic;
    vsync : out std_logic;
    red : out std_logic_vector(2 downto 0);
    green : out std_logic_vector(2 downto 0);
    blue : out std_logic_vector(2 downto 1)
);   
end timings;

architecture Behavioral of timings is

signal x : integer range 0 to 1023;
signal y : integer range 0 to 1023;
signal next_x : integer range 0 to 1023;
signal next_y : integer range 0 to 1023;
signal rgb : std_logic_vector(7 downto 0);

begin

timings: process (clk) is
begin
    if rising_edge(clk) then
        if (next_x = 799) then
            next_x <= 0;
            if (next_y = 525) then
                next_y <= 0;
            else
                next_y <= next_y + 1;
            end if;
        else
            next_x <= next_x + 1;
        end if;
           
        if (y >= 490) and (y < 492) then
            vsync <= '0';
        else
            vsync <= '1';
        end if;
           
        if (x >= 656) and (x < 752) then
            hsync <= '0';
        else
            hsync <= '1';
        end if;
       
        x <= next_x;
        y <= next_y;
    end if;   
end process;

fb: entity display port map (clk => clk, x => next_x, y => next_y, color => rgb);

red <= rgb(7 downto 5);
green <= rgb(4 downto 2);
blue <= rgb(1 downto 0);

end Behavioral;


Główny moduł
Teraz gdy mamy już wszystkie elementy układanki przygotowane pozostaje połączyć je w moduł główny. Musimy w nim utorzyć instancje komponentu zegara (pll) oraz sterownika obrazu (timinig).
Kod jest krótki i chyba nie wymaga komentarza:
Kod programu: Zaznacz cały
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity vga_disp is
port(
        clk : in std_logic;
        hsync : out std_logic;
        vsync : out std_logic;
        red : out std_logic_vector(2 downto 0);
        green : out std_logic_vector(2 downto 0);
        blue : out std_logic_vector(2 downto 1));       
end vga_disp;

architecture Behavioral of vga_disp is

component pll is
   port ( CLKIN_IN        : in    std_logic;
          CLKFX_OUT       : out   std_logic);
end component;

signal clk_pll : std_logic;
begin   

pixel_clock : pll port map (CLKIN_IN => clk, CLKFX_OUT => clk_pll);
display : entity timings port map (clk => clk_pll, hsync => hsync, vsync => vsync,                                                                                       
                red => red, green => green, blue => blue);
     
end Behavioral;


Teraz pozostaje zsyntetyzować projekt, wgrać i cieszyć się obrazem na monitorze VGA.


Dalsze plany
Opisany projekt można dalej rozbudowywać. Użycie zasobów Elbert-a jest na poziomie 2%, więc jeszcze sporo można byłoby dodać, np. wbudowany mikrokontroler, obsługę UART-a, pamięć obrazu itd.

Popularny artykuł » ILIFE V7S - autonomiczny odkurzacz z Gearbest


Ostatnio zmieniony przez Elvis 25-12-2017, 18:19, w całości zmieniany 4 razy  
Postaw piwo autorowi tego posta
 
 
Więcej szczegółów
Wystawiono 1 piw(a):
FlyingDutch
FlyingDutch 




Posty: 287
Pomógł: 10 razy
Otrzymał 17 piw(a)
Skąd: Bydgoszcz
Programuję w:
C++,Python
Wysłany: 25-12-2017, 18:25   Re: FPGA (własne programy #5): Obraz na monitorze VGA

Elvis napisał/a:

Warto przy okazji zwrócić uwagę, że ten projekt pokazuje zalety FPGA w porównaniu z mikrokontrolerami. Przykładowy sterownik zrealizujemy za pomocą niewielkiej części zasobów naszego i tak niewielkiego układu. Wygenerowanie obrazu za pomocą mikrokontrolera, o ile w ogóle możliwe zajmowałoby ogromną ilość jego zasobów.


Cześć Elvis,

nawet nie wiesz jak się cieszę, że ktoś to w końcu napisał :-)
Wszyscy moi znajomi uważają, że mikro-kontrolery są najlepsze do wszystkiego, a układy programowalne są dla maniaków i nie mają żadnych realnych zastosowań.

Dzięki za bardzo ciekawy artykuł, jutro postaram się poćwiczyć kod który napisałeś :mrgreen: , może będę miał jakieś uwagi (jak już wspomniałem muszę trochę poczytać o temacie). Udało mi się zsyntetyzować PicoBlaze (z UARTem) tak, że mikro-kontroler do twojego projektu VGA będzie gotowy (walczę tylko z generacją zawartości ROM dla programu PicoBlaze bo dostarczony z projektem assembler "KCPSM3.EXE" to program DOS i nie mogę go uruchomić pod Windows 10), a linki do "pBlazeIDE" prowadzą w próżnię.

Pozdrawiam


Anioły to też demony - lepiej ich nie wkurzaj
Ostatnio zmieniony przez FlyingDutch 25-12-2017, 18:31, w całości zmieniany 1 raz  
Postaw piwo autorowi tego posta
 
 
FlyingDutch 




Posty: 287
Pomógł: 10 razy
Otrzymał 17 piw(a)
Skąd: Bydgoszcz
Programuję w:
C++,Python
Wysłany: 05-02-2018, 20:32   Odpowiedź

Cześć Elvis,

dzisiaj miałem czas, żeby pobawić się twoim kodem. To dziwne, ale u mnie białą ramkę prawidłowo rysuje przy takich warunkach w entity display:

Kod programu: Zaznacz cały

    entity display is port(
        clk : in std_logic;
        x : in integer range 0 to 1023;
        y : in integer range 0 to 1023;
        color : out std_logic_vector(7 downto 0));
    end display;

    architecture Behavioral of display is
    begin

    process (clk) is
    begin
        if rising_edge(clk) then
            if (x < 640) and (y < 480) then
                if (x = 3) or (x = 639) or (y = 0) or (y = 478) then    -- biala ramka
                    color <= x"ff";           
                else
                    if (y < 160) then -- czerwony pasek na gorze
                        color <= x"e0";
                    elsif (y < 320) then -- zielony w srodku
                        color <= x"1c";
                    else -- niebieski na dole
                        color <= x"03";
                    end if;
                end if;
            else
                color <= x"00";
            end if;
        end if;
    end process;

    end Behavioral;


Pozdrawiam


Anioły to też demony - lepiej ich nie wkurzaj
Postaw piwo autorowi tego posta
 
 
JTyburski 



Posty: 68
Pomógł: 4 razy
Otrzymał 6 piw(a)
Skąd: Warszawa
Programuję w:
VHDL, Verilog

Wysłany: 13-02-2018, 11:05   

VGA to jak to ujmują zwłaszcza na Politechnice Warszawskiej "wymierający standard" (bo taka prawda) - niemniej jak jużto potraficie to zróbcie HDMI (jako ciekawostkę dodam, że jednym z jego elementów składowych jest właśnie sterownik VGA, a więc już połowa roboty wykonana - teraz tylko podłączyć do VGA kodery TMDS i do tych koderów rejestry przesuwne i macie HDMI - jak widać zatem jest to w rzeczywistości "udoskonalone" VGA, albo też "obudowane" czy też "rozszerzone" VGA - dzięki temu nie trzeba nie wiadomo jakich modyfikacji wprowadzać - ot starczy rozszerzyć to co istnieje. Podejście do zastosowania zarówno w FPGA jak w ogóle w samej elektronice (jak ma się VGA na scalaku jakimś to można na FPGA tylko dorobić kodery z rejestrami, spiąć to i mamy już przystosowany np: stary sprzed 20 lat generator obrazów kontrolnych czy starą kartę graficzną, też sprzed 20 lat do współczesnych odbiorników - i tyle :) - prawda że zajebiście proste? :) ))

Ostatnio popularny » Kurs STM32 - #12 - I2C w praktyce, akcelerometr


Ostatnio zmieniony przez JTyburski 13-02-2018, 11:06, w całości zmieniany 1 raz  
Postaw piwo autorowi tego posta
 
 
FlyingDutch 




Posty: 287
Pomógł: 10 razy
Otrzymał 17 piw(a)
Skąd: Bydgoszcz
Programuję w:
C++,Python
Wysłany: 13-02-2018, 12:03   

JTyburski napisał/a:
VGA to jak to ujmują zwłaszcza na Politechnice Warszawskiej "wymierający standard" (bo taka prawda) - niemniej jak jużto potraficie to zróbcie HDMI (jako ciekawostkę dodam, że jednym z jego elementów składowych jest właśnie sterownik VGA, a więc już połowa roboty wykonana - teraz tylko podłączyć do VGA kodery TMDS i do tych koderów rejestry przesuwne i macie HDMI - jak widać zatem jest to w rzeczywistości "udoskonalone" VGA, albo też "obudowane" czy też "rozszerzone" VGA - dzięki temu nie trzeba nie wiadomo jakich modyfikacji wprowadzać - ot starczy rozszerzyć to co istnieje. Podejście do zastosowania zarówno w FPGA jak w ogóle w samej elektronice (jak ma się VGA na scalaku jakimś to można na FPGA tylko dorobić kodery z rejestrami, spiąć to i mamy już przystosowany np: stary sprzed 20 lat generator obrazów kontrolnych czy starą kartę graficzną, też sprzed 20 lat do współczesnych odbiorników - i tyle :) - prawda że zajebiście proste? :) ))


Cześć,

dzięki za podpowiedź, jak będzie chwila czasu to popróbuję ;-)
Na razie to w pracy mam większe obciążenie.

Pozdrawiam


Anioły to też demony - lepiej ich nie wkurzaj
Postaw piwo autorowi tego posta
 
 
JTyburski 



Posty: 68
Pomógł: 4 razy
Otrzymał 6 piw(a)
Skąd: Warszawa
Programuję w:
VHDL, Verilog

Wysłany: 13-02-2018, 13:18   

Drobiazg - ja tylko mówię to jako ciekawostka jaka kryje się za tą całą magią HDMI (która szybko pryska jak się odkryje, że to w istocie VGA, tylko ulepszone!) - a także dobre to wskazanie, że stary sprzęt wcale nie taki stary i lekko można go przystosować do współczesnych realiów :)

Postaw piwo autorowi tego posta
 
 
Wyświetl posty z ostatnich:   
Odpowiedz do tematu
Nie możesz pisać nowych tematów
Nie możesz odpowiadać w tematach
Nie możesz zmieniać swoich postów
Nie możesz usuwać swoich postów
Nie możesz głosować w ankietach
Nie możesz załączać plików na tym forum
Możesz ściągać załączniki na tym forum
Wersja do druku

Skocz do:  

Nie rozwiązałeś swojego problemu? Zobacz podobne tematy: Realizacja pamięci p... [FPGA] Sterowanie se... Jak się projektuje u... Sposoby dołączenia u...
lub przeszukaj forum po wybranych tagach: fpga, monitorze, na, obraz, programy, vga, wlasne


Powered by phpBB modified by Przemo © 2003 phpBB Group
Popularne kursy: Arduinopodstawy elektroniki