sábado, 19 de julho de 2014

[FPGA] Tutorial 4 - LEDs pulsantes em VHDL

Olá pessoal,
Estamos aqui mais uma vez para mostrar um outro pequeno projeto feito com a DE0 Board, os LEDs pulsantes. Esse efeito consiste em aumentar a intensidade do brilho de cada LED da placa gradativamente e, ao atingir o brilho máximo, diminuir. Se você não conhece e/ou não entendeu, é só checar o vídeo abaixo.



A primeira pergunta que deve surgir na sua cabeça para criar esse efeito provavelmente é: como controlar a intensidade do brilho de um LED usando apenas lógica digital, aonde não existe meio termo (ligado/desligado, zero ou um)?

Primeiramente, para responder essa pergunta temos que arremeter a um efeito chamado persistência da visão. Esse efeito faz com que luz capturada (vista) pelo olho humano fique "presa" na retina dos nossos olhos um pouco mais de tempo. Caso esteja interessado em ler mais sobre isso, o wikipédia explica.

OK, entendi... a luz fica na retina um pouco mais de tempo, mas e dai? 
Bom, graças a isso, podemos controlar a intensidade dos LEDs desligando e ligando eles em diferentes "velocidades". Por exemplo, se em 20 mili-segundos eu deixo todos os LEDs ligados, o brilho deles nesse período de tempo é constante e máximo. Se dentro dos próximos 20 mili-segundos eu consigo desligar por 10 mili-segundos e ligar pelos restantes 10 mili-segundos a impressão para quem vê é de que o brilho foi atenuado. 

É basicamente isso, ligar e desligar em diferentes tempos de forma que tenhamos uma "média" como resultado para o brilho. Em eletrônica essa técnica é conhecida como PWM (do inglês, Pulse-Width Modulation), não entrarei em detalhes sobre PWM porque não interfere muito no resultado e levaria muuuito tempo para cobrir todo o assunto, mas se você deseja saber mais, basta pesquisar.


O projeto.

Crie um novo projeto no Quartus II (no caso, v. 13.1) e complete com todas as informações necessárias. 

Nota: qualquer dificuldade em como criar um projeto, pin assignments ou qualquer outra coisa relacionada a criação do projeto, recorra aos tutoriais antigos.

Figura 1: Criando um novo projeto.

Crie 3 novos arquivos VHDL. Um chamado fading_led.vhd, outro fading_freq.vhd e fading_pwm.vhd.

Como foi dito, estaremos usando três arquivos VHDL, cada arquivo terá uma função: o arquivo _freq.vhd irá ser o divisor de frequência e o _pwm.vhd fará o controle do liga/desliga dos leds. Esses arquivos devem ser conectados e para que isso seja possível, o uso do COMPONENT é indispensável. O component diz como as portas são usadas e conectadas. 
O component é declarado dentro do architecture e inicializados pelo PORT MAP, como estaremos vendo a seguir.

fading_freq.vhd

-- inicialização das bibliotecas
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
-- criacao da entidade fading_freq
ENTITY fading_freq IS
-- contador para div. de freq. aprox. 8 miliseg.
GENERIC(TEMPO: INTEGER := 400000);
-- entradas/saidas
PORT(
-- clock de entrada
CLK: IN STD_LOGIC;
-- indica a porcentagem de tempo q fica ligado
CONT: OUT INTEGER RANGE 0 TO 100
);
END fading_freq;

ARCHITECTURE behavioral OF fading_freq IS
SIGNAL temporal: INTEGER RANGE 0 TO TEMPO;
-- 0 = fade in | 1 = fade out
SIGNAL fade_inout: STD_LOGIC := '0'; 
-- valor de saida de CONT
SIGNAL cnt: INTEGER RANGE 0 TO 100;
BEGIN
CONT <= cnt;
PROCESS(CLK)
BEGIN
-- a cada borda de subida do clock
IF rising_edge(CLK) THEN
-- se o TEMPO for alcançado
IF (temporal = TEMPO) THEN
temporal <= 0;
-- se cnt = 99%
IF (cnt = 99) THEN
-- executa fade out
fade_inout <= '1';
-- se cnt = 1%
ELSIF (cnt = 1) THEN
-- executa fade in
fade_inout <= '0';
END IF;
-- fade in, incrementa tempo ligado
-- fade out, decrementa.
CASE fade_inout IS
WHEN '0' => cnt <= cnt + 1;
WHEN '1' => cnt <= cnt - 1;
END CASE;
-- continua contando TEMPO
ELSE
temporal <= temporal + 1;
END IF;
END IF;
END PROCESS;
END behavioral;

________________________

fading_pwm.vhd

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

ENTITY fading_pwm IS
-- entradas e saidas
PORT(
-- clock, 
CLK: IN STD_LOGIC;
-- valor do contador vindo do divisor de freq
CONTADOR: IN INTEGER RANGE 0 TO 100;
-- saida para os LEDs
PWM_SAIDA: OUT STD_LOGIC_VECTOR (9 downto 0)
);
END fading_pwm;

ARCHITECTURE behavioral OF fading_pwm IS
-- contador pwm para comparar com contador do divisor
SIGNAL pwm_cont: INTEGER RANGE 0 TO 100;
-- ligar/desligar os LEDs
SIGNAL pwm_signal: STD_LOGIC := '0';
BEGIN
PROCESS(CLK)
BEGIN
-- na borda de subida do clock
IF rising_edge(CLK) THEN
-- se o contado chegou no máximo
IF (pwm_cont = 99) THEN
-- zera
pwm_cont <= 0;
-- senão, conta...
ELSE
pwm_cont <= pwm_cont + 1;
END IF;
END IF;
-- se o contador pwm for menor que o contador do divisor
IF(pwm_cont < CONTADOR) THEN
-- liga o led
pwm_signal <= '1';
-- senao, desliga
ELSE 
pwm_signal <= '0';
END IF;
-- sao 10 LEDs com o valor de pwm_signal
PWM_SAIDA <= (OTHERS => pwm_signal);
END PROCESS;
END behavioral;
________________________

fading_led.vhd

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
-- entity fading_led
ENTITY fading_led IS
PORT(
-- LEDs da placa são saidas
LEDG: OUT STD_LOGIC_VECTOR (9 downto 0);
-- e o clock da placa, entrada.
CLOCK_50:IN STD_LOGIC
);
END fading_led;

-- aqui serão declaradas e inicializados os components
ARCHITECTURE behavioral OF fading_led IS
-- o component para o primeiro entity, _freq
-- eh igual ao entity fading_freq
COMPONENT fading_freq IS
PORT(
CLK: IN STD_LOGIC;
CONT: OUT INTEGER RANGE 0 TO 100
);
END COMPONENT;
-- o component para o segundo entity, _pwm
-- eh igual ao entity fading_pwm
COMPONENT fading_pwm IS
PORT(
CLK: IN STD_LOGIC;
CONTADOR: IN INTEGER RANGE 0 TO 100;
PWM_SAIDA: OUT STD_LOGIC_VECTOR (9 downto 0)
);
END COMPONENT;
-- inicializa um sinal para o contador passar
-- de fading_freq para fading_pwm
SIGNAL cnt: INTEGER RANGE 0 TO 100;
BEGIN
-- inicialização dos components usando PORT MAP
-- cnt passa por fading_freq e fading_pwm
fading_freq_i: fading_freq PORT MAP(CLOCK_50, cnt);
fading_pwm_i: fading_pwm PORT MAP(CLOCK_50, cnt, LEDG);
END behavioral;

________________________

O pin assignment é bem simples, utilizaremos apenas os LEDs verdes como saída e o clock da placa como entrada (chamados de LEDG e CLOCK_50).


Se você deseja mudar o tempo, basta brincar com a constante TEMPO criada no arquivo fading_freq.vhd. Para calcular o valor é simples. 
A placa possui um clock de 50Mhz, então, se você deseja 60Hz.


TEMPO = 50 000 000/ 60 
TEMPO ~ 833 333.

Assim você terá um tempo de 1/60 segundos. Ou seja, 16.67ms.
Também é recomendável estudar um pouco sobre PWM, essa técnica de modulação é muito importante para controle dos mais imagináveis e inimagináveis procedimentos. Realmente muito importante, bem relevante. 

É basicamente isso, até a próxima :D





segunda-feira, 30 de junho de 2014

[FPGA] Tutorial 3 - Shift Registers em VHDL

Olá pessoal,
Nesse tutorial a gente vai falar sobre shift registers, o que são? Para que serve? E ainda montar um projeto com um resultado muito interessante, o resultado vocês podem ver no vídeo abaixo.


- O que é um shift registers?

Shift registers, como o próprio nome propõe, é um circuito digital que possui dados que são armazenados e shifted, ou seja, "substituidos" como se saltassem a cada clock, juntos. 
Eu recomendo ao leitor dar uma olhada na página do wikipedia sobre shift registers e conferir todas as imagens e entedê-las.

Esse tipo de circuito é bem útil e usado para os vários propósitos, e em projetos maiores ele pode ser conveniente.


- O nosso projeto

Como de costume, abra o Quartus (aqui estamos utilizando a v. 13.1) e crie um novo projeto. 


Figura 1: Criando um novo projeto.

Qualquer maior dificuldades nesse passo, consulte o tutorial 01. Em File > New > VHDL File, crie um arquivo VHDL em branco.


Figura 2: Criando um novo arquivo VHDL.

Agora é só descrever o nosso hardware.

-- declara ao compilador a biblioteca usada
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
                                              
-- entidade shift_register           
ENTITY shift_register IS             
-- listagem de entradas e saidas 
PORT(                                      
LEDG: OUT STD_LOGIC_VECTOR (9 downto 0);
SW:         IN STD_LOGIC_VECTOR (9 downto 0);
CLOCK_50:                 IN STD_LOGIC                                
);                           
END shift_register;   
                             
-- descrever o comportamento do circuito
ARCHITECTURE behavioral OF shift_register IS
-- contador para o tempo (aprx. um quarto de segundo)
SIGNAL temporal: INTEGER RANGE 0 TO 12500000 := 0; 
-- inicialmente nenhum LED é ligado.
SIGNAL vetor_temp: STD_LOGIC_VECTOR (9 downto 0) := (OTHERS => '0');
-- indice para deslocar o vetor
SIGNAL i: INTEGER RANGE 0 TO 9 := 0;
BEGIN
-- LEDs possuirao o valor de vetor_temp
LEDG <= vetor_temp;
-- CLOCK_50 na lista de sensibilidade do process
PROCESS (CLOCK_50)
BEGIN
-- na borda de subida do clock
IF rising_edge(CLOCK_50) THEN
-- caso tenha percorrido 1/4 segundo
IF(temporal = 12500000) THEN
-- reseta a variavel
temporal <= 0;
-- desloca os valores
vetor_temp(9 downto 1) <= vetor_temp (8 downto 0);
-- transfere valor da chave compassadamente para o vetor_temp(0)           
vetor_temp(0) <= SW(9-i);
-- reseta o indice quando chegar no limite
IF (i = 9) THEN
i <= 0;
ELSE            
i <= i + 1;
END IF;        
ELSE                    
-- senao, continua contando (tempo correndo...)
temporal <= temporal + 1;
END IF;
END IF;
END PROCESS;
END behavioral;

Antes de compilar, importamos (ou declaramos) os pinos da placa. Para isso, Assignments -> Import Assignments.


Figura 3: Declarando os pinos da placa.

E finalmente, Processing> Start Compilation

Nota: qualquer dificuldade em algum desses passos, leia os tutoriais anteriores.


- Existem outras maneiras de fazê-lo?

Claro que sim. Assim como existem várias maneiras de descrever um objeto, existem várias maneiras de descrever um shift register em VHDL. Talvez uma possível solução seria utilizando o for loop e executar a mudança dos valores dentro do loop... seria um ótimo exercício tentar uma outra aproximação para arranjar o shift register.

É isso pessoal, e não esqueçam de ler a página do wikipédia :-) .



segunda-feira, 23 de junho de 2014

[FPGA] Tutorial 2 - Relógio Digital em VHDL


Olá pessoal, 
Mais um novo projeto com VHDL, nesse post iremos criar um simples e pequeno relógio digital utilizando VHDL.
Primeiramente, abra o Quartus II (a versão utilizada aqui é a 13.1)...
em File > New > New Quartus II Project crie um novo projeto (caso tenha alguma dúvida de como fazer, leia o primeiro tutorial)



Figura 1: Criando um novo projeto para o relógio digital.

Logo após, crie dois novos documentos VHDL File > New > VHDL File
Dai você pergunta, por que dois? Bom, nesse tutorial a gente vai criar uma função que irá converter números inteiros em um STD_LOGIC_VECTOR com valores correspondente a sua imagem no display de 7 segmentos, e para isso, precisaremos de um novo arquivo VHDL.

Antes de começar a programar, vamos entender como funciona o display de 7 segmentos... primeiramente, vale lembrar que cada display é composto por 7 LEDs (light emitting diode). No nosso caso, o display é conectado em ânodo comum, ou seja, para ligar o LED devemos atribuir um sinal de tensão baixo (zero) e para desligar devemos atribuir um sinal alto (um). Caso o display seja ligado em cátodo comum, basta inverter a lógica.



Figura 2: Esquemático do display de 7 segmentos.

Por exemplo, para "escrever" o número 5, escreveriamos um vetor com o seguinte valor:

| g | f | e | d | c | b | a |
| 0 | 0 | 1 | 0 | 0 | 1 | 0 |

assim, "b" e "e" estariam apagados e o restante ligado.
Ou o número 9 (apenas "e" desligado): 

| g | f | e | d | c | b | a |
| 0 | | 1 | 0 | 0 | 0 | 0 |

Para facilitar e diminuir o nosso código criaremos uma função que irá converter os números em seus respectivos códigos para o display. Chamaremos essa função de "int2seg". int2seg deve ser criada em um arquivo contendo uma estrutura determinada, necessitaremos criar um PACKAGE. Com os PACKAGES você pode compartilhar soluções padrões para certos problemas e compartilhar entre diferentes estruturas. Para criar um PACKAGE precisamos:

- inserir as bibliotecas;
- declarar o package e o seu nome;
- criar o package body;
- criar suas funções e/ou procedures;
- inserir seu package em seu código principal.

a estrutura de um package

PACKAGE nome_do_package IS
             -- DECLARA AS FUNÇÕES, CONSTANTES, SINAIS, PROCEDURES
END nome_do_package;
                                 
PACKAGE BODY nome_do_package IS
            -- CRIA SUAS DECLARAÇÕES
END nome_do_package ;

Para criar a função criamos um package com nome PACOTE e dentro dele declaramos nossa função.

FUNCTION int2seg(A: INTEGERRETURN STD_LOGIC_VECTOR;

Essa função tem como argumento um número inteiro (armazenado como A) e retorna um STD_LOGIC_VECTOR (o código correspondendo para o display de 7 segmentos)

No package body a mesma declaração é utilizada, uma variável chamada result é criada, ela armazenará o valor correto do display e será retornada com o comando RETURN 

FUNCTION int2seg(A: INTEGERRETURN STD_LOGIC_VECTOR IS
VARIABLE result: STD_LOGIC_VECTOR(6 downto 0);
BEGIN 
CASE IS
WHEN 0 => result := "1000000";
                        .                            
                        .                            
                        .                            
END CASE;
RETURN result;
END int2seg;

O comando USE insere o package no nosso arquivo com a nossa função.

USE work.PACOTE.all; -- INSERE O PACKAGE 

Agora que já podemos escrever no display, precisamos fazer "o tempo correr". Isso é feito por meio do clock. A placa DE0 possui um clock interno de 50Mhz, isso significa temos um período de 20ns de duração. Ou seja, se pudermos contar de 0 até 49999999 contaremos 50000000, se cada contagem demora 20ns, contaremos um segundo. Teoricamente isso é correto, porém na prática, existe um pequeno erro (que não interfere no resultado).

A contagem será feita dentro do bloco PROCESS. Com process somos capazes de utilizar operações sequenciais como if-else, case... dentro do bloco process tudo é executado sequencialmente. Process possui uma "lista de sensibilidade" que irá disparar os eventos dentro dele. No nosso relógio, o process é executado observando o CLOCK_50, que é o clock do FPGA.

PROCESS(CLOCK_50) 

O código para o relógio é encontrado abaixo.
____________________________________________________

relogio_digital.vhdl

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
USE work.PACOTE.all;
ENTITY relogio_digital IS
GENERIC (SEG: INTEGER := 49999999); -- DEFINE O NUMERO DE CLOCKS PARA CONTAR UM SEGUNDO
PORT(
        -- AQUI DEFIMOS AS ENTRADAS E SAIDAS
HEX0_D :OUT STD_LOGIC_VECTOR (6 downto 0);
HEX1_D :OUT STD_LOGIC_VECTOR (6 downto 0);
HEX2_D :OUT STD_LOGIC_VECTOR (6 downto 0);
HEX3_D :OUT STD_LOGIC_VECTOR (6 downto 0);
CLOCK_50 :IN STD_LOGIC
);
END relogio_digital;

ARCHITECTURE behavioural OF relogio_digital IS 
SIGNAL TEMPORAL: INTEGER RANGE 0 TO SEG;

SIGNAL SS0: INTEGER RANGE 0 TO 59 := 0; -- SEGUNDOS
SIGNAL HH0: INTEGER RANGE 0 TO 9 := 0; --PRIMEIRO CARACTERE DA HORA
SIGNAL HH1: INTEGER RANGE 0 TO 2 := 0; --SEGUNDO CARACTERE DA HORA
SIGNAL MM0: INTEGER RANGE 0 TO 9 := 0; --PRIMEIRO CARACTERE DOS MINUTOS
SIGNAL MM1: INTEGER RANGE 0 TO 5 := 0; --SEGUNDO CARACTERE DOS MINUTOS

BEGIN
        -- ATRIBUI OS VALORES DOS SINAIS PARA O DISPLAY DE 7 SEGMENTOS
HEX0_D <= int2seg(MM0);
HEX1_D <= int2seg(MM1);
HEX2_D <= int2seg(HH0);
HEX3_D <= int2seg(HH1);

PROCESS(CLOCK_50) 
BEGIN
IF rising_edge(CLOCK_50) THEN -- NA BORDA DE SUBIDA
                        -- SE O CONTADOR NAO CHEGAR NO FIM DE 1 SEG
IF(TEMPORAL /= SEG) THEN
TEMPORAL <= TEMPORAL + 1; -- INCREMENTA 
                        -- SENAO, RESETA O CONTADOR E ATRIBUI UM SEGUNDO
ELSE 
TEMPORAL <= 0;
SS0 <= SS0 + 1;
IF(SS0 = 59) THEN -- 60 segundos eh 1 minuto
SS0 <= 0;
MM0 <= MM0 + 1;
IF(MM0 = 9) THEN -- CASO PASSE 10 minutos
MM0 <= 0; -- RESETA O DISPLAY 0
MM1 <= MM1 + 1; -- INCREMENTA O DISPLAY 1
                                      -- 60 MINUTOS EH 1 HORA E ASSIM POR DIANTE...
IF(MM1 = 5) THEN 
MM1 <= 0;
HH0 <= HH0 + 1;
IF(HH0 = 9) THEN
HH0 <= 0;
HH1 <= HH1 + 1;
ELSIF (HH0 = 3 AND HH1 = 2) THEN HH1 <= 0; HH0 <= 0; MM1 <= 0; MM0 <= 0;
END IF;
END IF;
END IF;
END IF;
END IF;
END IF;
END PROCESS;
END behavioural;

________________________________________________________

relogio_digital_pk.vhdl

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
PACKAGE PACOTE IS
FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR;
END PACOTE;
PACKAGE BODY PACOTE IS
FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR IS
VARIABLE result: STD_LOGIC_VECTOR(6 downto 0);
BEGIN 
CASE A IS
WHEN 0 => result := "1000000";
WHEN 1 => result := "1111001";
WHEN 2 => result := "0100100";
WHEN 3 => result := "0110000";
WHEN 4 => result := "0011001";
WHEN 5 => result := "0010010";
WHEN 6 => result := "0000010";
WHEN 7 => result := "1011000";
WHEN 8 => result := "0000000";
WHEN 9 => result := "0010000";
WHEN OTHERS => result := (OTHERS=>'0');
END CASE;
RETURN result;
END int2seg;

END PACOTE;

________________________________________________________

Salve os arquivos e compile Processing > Start Compilation.


Figura 3: Salvando os arquivos...

Se nenhum erro for reportado, é hora de declarar a pinagem. Isso pode ser feito da maneira antiga (ensinada no tutorial 1) ou importando a pinagem da placa DE0 utilizando os arquivos dentro do DVD fornecido pela terasic junto com sua placa. 
Para importar os pinos: Assignments > Import Assignments e escolha o arquivo DE0_Default.qsf (esse arquivo deve ser facilmente encontrado pela internet, caso você não possua o DVD).


Figura 4: Importando todos os pinos da placa DE0.

Compile novamente.

- Salvando seu programa na memória EEPROM.

Para que seu programa possa rodar mesmo depois da placa ser desligada precisamos primeiro declarar o tipo de EEPROM. A DE0 possui um chip Altera EPCS4. Em Assignments > Device clique em Device and Pin Options. Na nova tela marque o checkbox "use configuration device" e escolha o chip EPCS4.

Figura 5: Assignments > Device.

Figura 6: Selecionando a EEPROM.

Compile seu programa novamente.
Na sua placa selecione o modo PROG.

Figura 7: Modo PROG.

Logo após, em Tools > Programmer, selecione a opção Mode: Active Serial Programming e procure pelo arquivo com a extensão .pof

Figura 8: Programando a placa.

Clique em open e start. Ao completar o processo, troque o modo da placa para RUN e voilà.

Figura 9: Resultado final.

- Incrementos para o relógio...

É fato que o relógio começa no 00:00 e, a não ser que você ligue ele na meia-noite, ele vai funcionar mais como um "cronômetro". Então seria um bom desafio fazer com que o usuário pudesse corrigir o valor do relógio a partir dos botões e chaves da placa. Com esse mesmo código acima eu fiz um relógio capaz de editar a hora, confira no vídeo abaixo.



É isso, qualquer dúvida é só comentar.