Controlador VGA – Parte 2

VGA controlador VGA caracteres ASCII com controlador VGA
Este post faz parte da série Controlador VGA

Este segundo artigo tem como objetivo demonstrar na prática como um controlador VGA pode ser construído. Assim, será utilizada a placa DE0-Nano para descrever o hardware do controlador utilizando VHDL. Cabe frisar que o objetivo é demonstrar como transformar os parâmetros de controle, apresentados no primeiro artigo, em um hardware. Portanto, o FPGA e o VHDL são apenas recursos utilizados no exemplo. Para mais informações sobre a placa e a linguagem consulte as referências listadas no final do artigo. Vamos lá!

Elaborando o Controlador VGA

Como a DE0-Nano possui um oscilador de 50 MHz, será utilizado como caso de teste a configuração VGA com resolução de 800×600, frequência de atualização de 72 Hz e apenas 1 bit para cada componente do pixel.

No primeiro artigo foi apresentada a tabela com os parâmetros de controle do frame VGA.

Tabela 1 – Temporização VGA.

De acordo com a Tabela 1, os parâmetros B, C, D e E são fornecidos como pulsos de clock, já os parâmetros P, Q, R e S são fornecidos com base na contagem de linhas. Deste modo um hardware básico pode ser construído de forma a contar os pulsos de clock e com base em comparações do valor contado pode-se determinar o estado dos sinais H_SYNC, V_SYNC e das componentes RGB.

Modelo do controlador VGA
Figura 1 – Modelo do controlador VGA

As linhas e colunas são contadores modificados conforme a entrada de Clock. Os sinais de sincronismo são alterados conforme esses contadores e devem manter estado até que outra condição de controle seja disparada.

Uma forma simples de criar esse mecanismo de controle é considerar que os contadores estão na região ativa de vídeo durante os primeiros X pulsos contados, sendo que X representa o número de linhas e/ou colunas.

Para o sincronismo horizontal devemos considerar os parâmetros B, C, D e E. Considerando que o contador é iniciado com valor zero, será necessário realizar as operações listadas abaixo: 

  • A linha permanece na região ativa durante os D primeiros pulsos, sendo que D é o número de pixels por linha, neste caso, 800;
  • Após atingir D é necessário aguardar mais E pulsos, sendo E a região de Front Porch. Até esse ponto temos um total de 856 pulsos (D + E);
  • Após a região de Front Porch o sinal H_SYNC deve ser posto em zero, permanecendo nesse estado durante B pulsos. Até esse ponto temos um total de 976 pulsos (D + E + B);
  • Depois de gerar o sinal de sincronismo é necessário aguardar C pulsos da região de Back Porch, totalizando 1040 pulsos (D + E + B + C).

Para o sincronismo vertical devemos considerar os parâmetros P, Q, R e S. Considerando que o contador é iniciado com valor zero e que o seu valor é incrementado sempre que uma linha é finalizada: 

  • O quadro permanece na região ativa durante os R primeiros pulsos, sendo que R é o número de linhas, neste caso, 600;
  • Após atingir R é necessário aguardar mais S pulsos, sendo S a região de Front Porch. Até esse ponto temos um total de 637 pulsos (R + S);
  • Após a região de Front Porch o sinal V_SYNC deve ser posto em zero, permanecendo nesse estado durante P pulsos. Até esse ponto temos um total de 643 pulsos (R + S + P);
  • Depois de gerar o sinal de sincronismo é necessário aguardar Q pulsos da região de Back Porch, totalizando 666 pulsos (R + S + P + Q).

Com esses requisitos é possível determinar o tamanho dos contadores. Para o contador horizontal (colunas) serão necessários 11 bits, pois o total a ser contado é 1040 e com 11 bits é possível contar até 2048. Já o contador vertical (linhas) pode ser de 10 bits, pois o maior valor que será armazenado é 666.

Descrevendo o Controlador VGA

Para descrever o controlador VGA será utilizado o mesmo modelo ilustrado na Figura 1. Portanto, será necessário criar um mecanismo de sincronismo e outro para definição dos pixels.

Abaixo é listado o código da entidade para o bloco de sincronismo.

ENTITY VGASync IS
	PORT(
		RESET : IN STD_LOGIC; -- Entrada para reiniciar o estado do controlador
		F_CLOCK : IN STD_LOGIC; -- Entrada de clock (50 MHz)
		F_HSYNC : OUT STD_LOGIC; -- Sinal de controle VGA: H_SYNC
		F_VSYNC : OUT STD_LOGIC; -- Sinal de controle VGA: V_SYNC
		F_ROW : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); -- Índice da linha que está sendo processada
		F_COLUMN : OUT STD_LOGIC_VECTOR(10 DOWNTO 0); -- Índice da coluna que está sendo processada
		F_DISP_ENABLE : OUT STD_LOGIC --Indica a região ativa do frame
	);
END ENTITY VGASync;

Para definir a operação do módulo, será apresentada a arquitetura dividia em 6 partes. Na listagem abaixo são mostrados os sinais internos do módulo.

-- Sinais de controle Horizontal
SIGNAL H_CMP1 : STD_LOGIC; -- Indica se o contador Horizontal está com o valor D
SIGNAL H_CMP2 : STD_LOGIC; -- Indica se o contador Horizontal está com o valor D+E
SIGNAL H_CMP3 : STD_LOGIC; -- Indica se o contador Horizontal está com o valor D+E+B
SIGNAL H_CMP4 : STD_LOGIC;	-- Indica se o contador Horizontal está com o valor D+E+B+C
SIGNAL HSync_Next : STD_LOGIC; -- Valor de H_SYNC no próximo pulso de clock
SIGNAL HSync_Prior : STD_LOGIC; -- Último valor atribuído em H_SYNC
SIGNAL HDataOn_Next : STD_LOGIC; -- Indica se o contador Horizontal está na região ativa
SIGNAL HDataOn_Prior : STD_LOGIC; -- Último valor atribuído em HDataOn
SIGNAL HCount_Next : STD_LOGIC_VECTOR(10 DOWNTO 0); --próximo valor do contador Horizontal
SIGNAL HCount_Prior : STD_LOGIC_VECTOR(10 DOWNTO 0); --valor atual do contador Horizontal

-- Sinais de controle Vertical
SIGNAL V_CMP1 : STD_LOGIC; -- Indica se o contador Vertical está com o valor R
SIGNAL V_CMP2 : STD_LOGIC; -- Indica se o contador Vertical está com o valor R+S
SIGNAL V_CMP3 : STD_LOGIC;	-- Indica se o contador Vertical está com o valor R+S+P
SIGNAL V_CMP4 : STD_LOGIC; -- Indica se o contador Vertical está com o valor R+S+P+Q
SIGNAL VSync_Next : STD_LOGIC; -- Valor de V_SYNC no próximo pulso de clock
SIGNAL VSync_Prior : STD_LOGIC; -- Último valor atribuído em V_SYNC
SIGNAL VDataOn_Next : STD_LOGIC; -- Indica se o contador Vertical está na região ativa
SIGNAL VDataOn_Prior : STD_LOGIC; -- Último valor atribuído em VDataOn
SIGNAL VCount_Next : STD_LOGIC_VECTOR(9 DOWNTO 0); --próximo valor do contador Vertical
SIGNAL VCount_Prior : STD_LOGIC_VECTOR(9 DOWNTO 0); --valor atual do contador Vertical

Com base nos valores dos contadores, horizontal e vertical, é mostrado na listagem abaixo a identificação dos parâmetros B, C, D, E, P, Q, R e S. 

--=============================================
--COMPARADORES
--=============================================
	
--CONTADOR = D (800)
H_CMP1 <= '1' WHEN HCount_Prior = 799 ELSE '0';
	
--CONTADOR = D + E (D + E = 800 + 56 = 856)
H_CMP2 <= '1' WHEN HCount_Prior = 855 ELSE '0';
	
--CONTADOR = D + E + B (D + E + B = 800 + 56 + 120 = 976)
H_CMP3 <= '1' WHEN HCount_Prior = 975 ELSE '0';
	
--CONTADOR = D + E + B + C = (D + E + B + C = 800 + 56 + 120 + 64 = 1040)
H_CMP4 <= '1' WHEN HCount_Prior = 1039 ELSE '0';
						
--CONTADOR = R 600
V_CMP1 <= '1' WHEN VCount_Prior = 599 ELSE '0';
	
--CONTADOR >= R + S 600 + 37 = 637
V_CMP2 <= '1' WHEN VCount_Prior = 636 ELSE '0';
	
--CONTADOR = R + S + P = 600 + 37 + 6 = 643
V_CMP3 <= '1' WHEN VCount_Prior = 642 ELSE '0';
	
--CONTADOR = R + S + P + Q = 600 + 37 + 6 + 23 = 666
V_CMP4 <= '1' WHEN VCount_Prior = 665 ELSE '0';

Esses sinais indicam se uma das condições (parâmetros) foi alcançada e são utilizados para determinar o próximo estado dos sinais de sincronismo.

--=============================================
--VALORES DE ENTRADA DOS FFD
--=============================================
	
--Sincronização - Horizontal
-- HSYNC = 0 após 856 pulsos e permanece em zero até 976 pulsos
HSync_Next <= '0' WHEN H_CMP2 = '1' ELSE --Reset
              '1' WHEN H_CMP3 = '1' ELSE --Set
              HSync_Prior;               --Memória
	
--Sincronização Vertical
--VSYNC = 0 após 637 pulsos e permanece em zero até 643 pulsos	
VSync_Next <= '0' WHEN V_CMP2 = '1' ELSE --Reset
              '1' WHEN V_CMP3 = '1' ELSE --Set
              VSync_Prior;               --Memória
	
--Região Ativa - Horizontal
HDataOn_Next <= '0' WHEN H_CMP1 = '1' ELSE --Reset
                '1' WHEN H_CMP4 = '1' ELSE --Set
                 HDataOn_Prior;            --Memória

--Região Ativa - Vertical
VDataOn_Next <= '0' WHEN V_CMP1 = '1' ELSE --Reset
                '1' WHEN V_CMP4 = '1' ELSE --Set
                VDataOn_Prior;             --Memória

Na listagem abaixo é mostrada a lógica para incrementar/reiniciar os contadores.

--=============================================
--CONTADORES
--=============================================
	
--Contador - Horizontal
-- O contador é reiniciado após 1040 pulsos
HCount_Next <= (others => '0') WHEN H_CMP4 = '1' ELSE
               HCount_Prior + 1;
	
-- Contador - Vertical
-- O contador é reiniciado após 666 pulsos
-- O contador é incrementado somente após a finalização de uma linha
VCount_Next <= (others => '0') WHEN V_CMP4 = '1' ELSE
               VCount_Prior + 1 WHEN H_CMP4 = '1' ELSE
               VCount_Prior;

Esses sinais apenas determinam qual será o valor futuro, sendo necessário atualizar o estado atual. No processo listado abaixo, sempre que ocorre uma transição de borda de subida no clock os sinais de controle são atualizados.

--=============================================
--Atualiza o sinal de saída dos FFD conforme o 
--sinal de Clock/Reset
--=============================================
PROCESS(F_CLOCK, RESET)
BEGIN
	
	IF (RESET = '0') THEN

		HCount_Prior <= (others => '0');
		VCount_Prior <= (others => '0');
		HSync_Prior <= '0';
		VSync_Prior <= '0';
		HDataOn_Prior <= '0';
		VDataOn_Prior <= '0';
			
	ELSIF RISING_EDGE(F_CLOCK) THEN
			
		--Contadores
		HCount_Prior <= HCount_Next;
		VCount_Prior <= VCount_Next;
			
		--Sinais de sincronismo
		HSync_Prior <= HSync_Next;
		VSync_Prior <= VSync_Next;
		HDataOn_Prior <= HDataOn_Next;
		VDataOn_Prior <= VDataOn_Next;
			
	END IF;
		
END PROCESS;

Por fim, esses sinais devem ser atribuídos na saída do bloco de sincronismo. A listagem abaixo mostra a atribuição dos sinais de controle.

--=============================================
--SINAIS DE CONTROLE DO MÓDULO VGA
--=============================================
F_HSYNC <= HSync_Prior;
F_VSYNC <= VSync_Prior;
F_ROW <= VCount_Prior;
F_COLUMN <= HCount_Prior;
F_DISP_ENABLE <= HDataOn_Prior AND VDataOn_Prior;

O código completo do bloco de sincronismo VGA é mostrado abaixo.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

ENTITY VGASync IS
	PORT(
		RESET : IN STD_LOGIC; -- Entrada para reiniciar o estado do controlador
		F_CLOCK : IN STD_LOGIC; -- Entrada de clock (50 MHz)
		F_HSYNC : OUT STD_LOGIC; -- Sinal de controle VGA: H_SYNC
		F_VSYNC : OUT STD_LOGIC; -- Sinal de controle VGA: V_SYNC
		F_ROW : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); -- Índice da linha que está sendo processada
		F_COLUMN : OUT STD_LOGIC_VECTOR(10 DOWNTO 0); -- Índice da coluna que está sendo processada
		F_DISP_ENABLE : OUT STD_LOGIC --Indica a região ativa do frame
	);
END ENTITY VGASync;

ARCHITECTURE arch OF VGASync IS

-- Sinais de controle Horizontal
SIGNAL H_CMP1 : STD_LOGIC; -- Indica que o contador Horizontal está com o valor D
SIGNAL H_CMP2 : STD_LOGIC; -- Indica que o contador Horizontal está com o valor D+E
SIGNAL H_CMP3 : STD_LOGIC; -- Indica que o contador Horizontal está com o valor D+E+B
SIGNAL H_CMP4 : STD_LOGIC;	-- Indica que o contador Horizontal está com o valor D+E+B+C
SIGNAL HSync_Next : STD_LOGIC; -- Valor de H_SYNC no próximo pulso de clock
SIGNAL HSync_Prior : STD_LOGIC; -- Último valor atribuído em H_SYNC
SIGNAL HDataOn_Next : STD_LOGIC; -- Indica que o contador Horizontal está na região ativa
SIGNAL HDataOn_Prior : STD_LOGIC; -- Último valor atribuído em HDataOn
SIGNAL HCount_Next : STD_LOGIC_VECTOR(10 DOWNTO 0); --próximo valor do contador Horizontal
SIGNAL HCount_Prior : STD_LOGIC_VECTOR(10 DOWNTO 0); --valor atual do contador Horizontal

-- Sinais de controle Vertical
SIGNAL V_CMP1 : STD_LOGIC; -- Indica que o contador Vertical está com o valor R
SIGNAL V_CMP2 : STD_LOGIC; -- Indica que o contador Vertical está com o valor R+S
SIGNAL V_CMP3 : STD_LOGIC;	-- Indica que o contador Vertical está com o valor R+S+P
SIGNAL V_CMP4 : STD_LOGIC; -- Indica que o contador Vertical está com o valor R+S+P+Q
SIGNAL VSync_Next : STD_LOGIC; -- Valor de V_SYNC no próximo pulso de clock
SIGNAL VSync_Prior : STD_LOGIC; -- Último valor atribuído em V_SYNC
SIGNAL VDataOn_Next : STD_LOGIC; -- Indica que o contador Vertical está na região ativa
SIGNAL VDataOn_Prior : STD_LOGIC; -- Último valor atribuído em VDataOn
SIGNAL VCount_Next : STD_LOGIC_VECTOR(9 DOWNTO 0); --próximo valor do contador Vertical
SIGNAL VCount_Prior : STD_LOGIC_VECTOR(9 DOWNTO 0); --valor atual do contador Vertical


BEGIN
	
	--=============================================
	--SINAIS DE CONTROLE DO MÓDULO VGA
	--=============================================
	F_HSYNC <= HSync_Prior;
	F_VSYNC <= VSync_Prior;
	F_ROW <= VCount_Prior;
	F_COLUMN <= HCount_Prior;
	F_DISP_ENABLE <= HDataOn_Prior AND VDataOn_Prior;
	
	--=============================================
	--Atualiza o sinal de saída dos FFD conforme o 
	--sinal de Clock/Reset
	--=============================================
	PROCESS(F_CLOCK, RESET)
	BEGIN
	
		IF (RESET = '0') THEN

			HCount_Prior <= (others => '0');
			VCount_Prior <= (others => '0');
			HSync_Prior <= '0';
			VSync_Prior <= '0';
			HDataOn_Prior <= '0';
			VDataOn_Prior <= '0';
			
		ELSIF RISING_EDGE(F_CLOCK) THEN
			
			--Contadores
			HCount_Prior <= HCount_Next;
			VCount_Prior <= VCount_Next;
			
			--Sinais de sincronismo
			HSync_Prior <= HSync_Next;
			VSync_Prior <= VSync_Next;
			HDataOn_Prior <= HDataOn_Next;
			VDataOn_Prior <= VDataOn_Next;
			
		END IF;
		
	END PROCESS;
	
	--=============================================
	--CONTADORES
	--=============================================
	
	--Contador - Horizontal
	-- O contador é reiniciado após 1040 pulsos
	HCount_Next <= (others => '0') WHEN H_CMP4 = '1' ELSE
	               HCount_Prior + 1;
	
	-- Contador - Vertical
	-- O contador é reiniciado após 666 pulsos
	-- O contador é incrementado somente após a finalização de uma linha
	VCount_Next <= (others => '0') WHEN V_CMP4 = '1' ELSE
 	               VCount_Prior + 1 WHEN H_CMP4 = '1' ELSE
	               VCount_Prior;
						
	--=============================================
	--COMPARADORES
	--=============================================
	
	--CONTADOR = D (800)
	H_CMP1 <= '1' WHEN HCount_Prior = 799 ELSE '0';
	
	--CONTADOR = D + E (D + E = 800 + 56 = 856)
	H_CMP2 <= '1' WHEN HCount_Prior = 855 ELSE '0';
	
	--CONTADOR = D + E + B (D + E + B = 800 + 56 + 120 = 976)
	H_CMP3 <= '1' WHEN HCount_Prior = 975 ELSE '0';
	
	--CONTADOR = D + E + B + C = (D + E + B + C = 800 + 56 + 120 + 64 = 1040)
	H_CMP4 <= '1' WHEN HCount_Prior = 1039 ELSE '0';
						
	--CONTADOR = R 600
	V_CMP1 <= '1' WHEN VCount_Prior = 599 ELSE '0';
	
	--CONTADOR = R + S 600 + 37 = 637
	V_CMP2 <= '1' WHEN VCount_Prior = 636 ELSE '0';
	
	--CONTADOR = R + S + P = 600 + 37 + 6 = 643
	V_CMP3 <= '1' WHEN VCount_Prior = 642 ELSE '0';
	
	--CONTADOR = R + S + P + Q = 600 + 37 + 6 + 23 = 666
	V_CMP4 <= '1' WHEN VCount_Prior = 665 ELSE '0';
	
	
	--=============================================
	--VALORES DE ENTRADA DOS FFD
	--=============================================
	
	--Sincronização - Horizontal
	-- HSYNC = 0 após 856 pulsos e permanece em zero até 976 pulsos
	HSync_Next <= '0' WHEN H_CMP2 = '1' ELSE --Reset
	              '1' WHEN H_CMP3 = '1' ELSE --Set
	              HSync_Prior;	         --Memória
	
	--Sincronização Vertical
	--VSYNC = 0 após 637 pulsos e permanece em zero até 643 pulsos	
	VSync_Next <= '0' WHEN V_CMP2 = '1' ELSE --Reset
	              '1' WHEN V_CMP3 = '1' ELSE --Set
	              VSync_Prior;	         --Memória
	
	--Região Ativa - Horizontal
	HDataOn_Next <= '0' WHEN H_CMP1 = '1' ELSE --Reset
	                '1' WHEN H_CMP4 = '1' ELSE --Set
	                HDataOn_Prior;		   --Memória

	--Região Ativa - Vertical
	VDataOn_Next <= '0' WHEN V_CMP1 = '1' ELSE --Reset
	                '1' WHEN V_CMP4 = '1' ELSE --Set
	                VDataOn_Prior;		   --Memória
	
	
END ARCHITECTURE arch;

Para gerar os pixels será criado um novo bloco. Esse bloco utiliza a posição do pixel (linha e coluna) e um sinal que indica se a varredura está na região ativa para determinar os sinais RGB. Para o exemplo, foi testado o valor da coluna de forma que a cada 100 pixels será exibida uma combinação de cores.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;


ENTITY PixelGen IS
	PORT(
		RESET : IN STD_LOGIC; -- Entrada para reiniciar o estado do controlador
		F_CLOCK : IN STD_LOGIC; -- Entrada de clock (50 MHz)
		F_ON : IN STD_LOGIC; --Indica a região ativa do frame
		F_ROW : IN STD_LOGIC_VECTOR(9 DOWNTO 0); -- Índice da linha que está sendo processada
		F_COLUMN : IN STD_LOGIC_VECTOR(10 DOWNTO 0); -- Índice da coluna que está sendo processada
		R_OUT : OUT STD_LOGIC; -- Componente R
		G_OUT : OUT STD_LOGIC; -- Componente G
		B_OUT : OUT STD_LOGIC -- Componente B
	);

END ENTITY PixelGen;

ARCHITECTURE arch OF PixelGen IS
SIGNAL RGBp : STD_LOGIC_VECTOR(2 DOWNTO 0); -- Valor atual do pixel
SIGNAL RGBn : STD_LOGIC_VECTOR(2 DOWNTO 0); -- Último valor definido
BEGIN

	-- Cada componente deve ser ativada somente se o frame estiver na região ativa
	R_OUT <= RGBp(2) AND F_ON;
	G_OUT <= RGBp(1) AND F_ON;
	B_OUT <= RGBp(0) AND F_ON;
		
	-- Define um novo valor RGB de acordo com índice da coluna
        RGBn <= "000" WHEN F_COLUMN = "0000000000" ELSE -- Preto (Coluna = 0)
                "001" WHEN F_COLUMN = "0001100100" ELSE -- Azul (Coluna = 100)
                "010" WHEN F_COLUMN = "0011001000" ELSE -- Verde (Coluna = 200)
                "011" WHEN F_COLUMN = "0100101100" ELSE -- Ciano (Coluna = 300)
                "100" WHEN F_COLUMN = "0110010000" ELSE -- Vermelho (Coluna = 400)
                "101" WHEN F_COLUMN = "0111110100" ELSE -- Magenta (Coluna = 500)
                "110" WHEN F_COLUMN = "1001011000" ELSE -- Amarelo (Coluna = 600)
                "111" WHEN F_COLUMN = "1010111100" ELSE -- Branco (Coluna = 700)
                RGBp; --Último valor definido
	
	PROCESS(F_CLOCK, RESET)
	BEGIN
		IF (RESET = '0') THEN
			RGBp <= (others => '0');
		ELSIF RISING_EDGE(F_CLOCK) THEN
			RGBp <= RGBn;
		END IF;
	END PROCESS;
	
END ARCHITECTURE arch;

A listagem abaixo mostra a declaração dos dois blocos dentro de um terceiro arquivo. Neste arquivo são estabelecidas as conexões entre os módulos e o acionamento dos pinos de saída da DE0-Nano.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

ENTITY DE0_NANO IS
    PORT ( 
		CLOCK_50 : IN STD_LOGIC;
		Reset		: IN STD_LOGIC;
		H_SYNC	: OUT STD_LOGIC;		
		V_SYNC	: OUT STD_LOGIC;
		R			: OUT STD_LOGIC;
		G			: OUT STD_LOGIC;
		B			: OUT STD_LOGIC);
END DE0_NANO;

ARCHITECTURE arch OF DE0_NANO IS

COMPONENT VGASync IS
	PORT(
		RESET : IN STD_LOGIC;
		F_CLOCK : IN STD_LOGIC;
		F_HSYNC : OUT STD_LOGIC;
		F_VSYNC : OUT STD_LOGIC;
		F_ROW : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);
		F_COLUMN : OUT STD_LOGIC_VECTOR(10 DOWNTO 0);
		F_DISP_ENABLE : OUT STD_LOGIC
	);
END COMPONENT VGASync;

COMPONENT PixelGen IS
	PORT(
		RESET : IN STD_LOGIC;
		F_CLOCK : IN STD_LOGIC;
		F_ON : IN STD_LOGIC;
		F_ROW : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
		F_COLUMN : IN STD_LOGIC_VECTOR(10 DOWNTO 0);
		R_OUT : OUT STD_LOGIC;
		G_OUT : OUT STD_LOGIC;
		B_OUT : OUT STD_LOGIC
	);
END COMPONENT PixelGen;

--Índice da linha/coluna atual
SIGNAL CURRENT_ROW : STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL CURRENT_COLUMN : STD_LOGIC_VECTOR(10 DOWNTO 0);
SIGNAL DISP_ENABLE : STD_LOGIC;

BEGIN

	--Módulo de sincronismo
	VGA : VGASync PORT MAP(
				RESET => RESET,
				F_CLOCK => CLOCK_50, 
				F_HSYNC => H_SYNC, 
				F_VSYNC => V_SYNC, 
				F_ROW => CURRENT_ROW,
				F_COLUMN => CURRENT_COLUMN,
				F_DISP_ENABLE => DISP_ENABLE);

	--Módulo para gerar os pixels
	PIXELS : PixelGen PORT MAP(
				RESET => RESET,
				F_CLOCK => CLOCK_50, 
				F_ON => DISP_ENABLE,
				F_ROW => CURRENT_ROW,
				F_COLUMN => CURRENT_COLUMN,
				R_OUT => R,
				G_OUT => G,
				B_OUT => B);
	
END arch;

Na Figura 2 são mostrados os dois módulos do controlador VGA.

Módulos do Controlador VGA
Figura 2 – Módulos do Controlador VGA

Os pinos do FPGA que foram utilizados como entrada e saída do controlador são mostrados na Figura 3.

Pinos utilizados no controlador VGA
Figura 3 – Pinos utilizados no controlador VGA

Testando o controlador

Neste exemplo, as saídas definidas do controlador estão localizadas nos pinos 2, 4, 6, 8 e 10 do conector GPIO1 (JP2). Os três pinos definidos para os canais R, G e B estão conectados a resistores de 1kΩ (vide Figura 4). 

Conexão da DE0-nano com o conector VGA
Figura 4 – Conexão da DE0-nano com o conector VGA

Na Figura 5 é mostrada a forma de onda do sinal H_SYNC. O intervalo determinado pelo cursor indica o período em que o sinal H_SYNC permanece em zero. Visto que o sinal H_SYNC permanece em zero durante 120 pulsos, o tempo é de 2,4 us.

controlador vga: sinal HSYNC
Figura 5 – Sinal H_SYNC

Abaixo é mostrado o tempo do frame correspondente às regiões C, D e E, isto é, o tempo do frame após o pulso H_SYNC.

controlador vga: sinal após HSYNC
Figura 6 – Região que corresponde aos parâmetros C, D e E

O tempo total de uma linha é de 1040 pulsos de clock. Na Figura 7 é mostrado o tempo de varredura de uma linha que corresponde a 20,8 us, isto é, a frequência de varredura da linha é de aproximadamente 48,07 kHz.

controlador vga: varredura de uma linha
Figura 7 – Tempo de varredura de uma linha

Na tabela 2 é mostrado o período de cada parâmetro de uma linha e o tempo total de varredura horizontal.

 Pixels (pulsos de clock)Tempo (microssegundos)
H_SYNC1202,4
Front porch561,12
Back porch641,28
Região ativa80016
Linha completa104020,08

Sabendo que o tempo de varredura de uma linha é de 20,8 us, o tempo total de varredura de todas as linhas do quadro é de 12,48 ms. O tempo total do quadro pode ser obtido somando o tempo das outras regiões de sincronismo vertical (Tabela 3).

 LinhasTempo (milissegundos) – 20,8 us x linhas
V_SYNC60,1248
Front porch370,7696
Back porch230,4784
Região ativa60012,48
Frame completo66613,8528

Da Tabela 3 temos o tempo total do frame que é de aproximadamente 13,85 ms, isto é, a frequência de atualização do quadro é próxima de 72,2 Hz. Na Figura 8 é mostrada a frequência do frame detectado pelo monitor.

Teste do controlador VGA
Figura 8 – Teste do controlador VGA

Conclusão

Nesta série de artigos foi apresentada uma breve descrição sobre o padrão VGA. A partir da caracterização deste padrão tão conhecido, procurou-se definir o escopo de um controlador bem simples. Com base em dois módulos principais, foi elaborado um projeto utilizando a linguagem VHDL e a placa de desenvolvimento DE0-Nano.

A simplicidade do controlador construído facilita a compreensão do padrão VGA. Um modelo simples como o descrito neste artigo pode ser construído utilizando poucos recursos lógicos, como portas AND (comparadores), contadores e flip-flops.

Fica demonstrado, de forma simples, como exibir uma imagem em um monitor VGA!

Para saber mais

Para aprender mais sobre FPGA confira os artigos de André Prado:

Confira também os artigos do Thiago Lima sobre a DE0-Nano:

Referências

– Tabela 1: https://martin.hinner.info/vga/timing.html

– Imagem destacada: https://en.wikipedia.org/wiki/Mode_13h#/media/File:VGA_palette_with_black_borders.svg

Controlador VGA

Controlador VGA – Parte 1 Exibindo caracteres ASCII com controlador VGA – Parte 3
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
3 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Anderson Schumacher
Anderson Schumacher
25/11/2015 15:34

muito obrigado amigo, me ajudou bastante!

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Anderson Schumacher
30/11/2015 08:44

Olá, Anderson.

Obrigado pelo retorno!

Pedro Freitas
Pedro Freitas
09/11/2016 20:52

Sensacional! Tu tem alguma indicação de material desse mesmo assunto, porém voltado para a descrição de hardware em Verilog?

Home » Hardware » Controlador VGA – Parte 2

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: