Existen en el mercado multitud de placas de entrenamiento FPGA. Ya hemos visto algunas en este blog, pero os quiero hablar de una que me ha llamado la atención por su enfoque diferente. Elegir una placa de entrenamiento a veces se complica, ya que no sólo hay que buscar una con la familia de FPGA que nos interesa, también hay que evaluar qué elementos periféricos trae consigo.
¿Necesitamos un display de siete segmentos? ¿botones? ¿salida VGA o entrada para teclado? La aproximación de Waveshare me ha resultado interesante, ya que lejos de incluir una serie de elementos periféricos a la FPGA, ha optado por un sistema totalmente modular, lo que tiene ciertas ventajas (y desventajas, claro).La idea es ofrecer una placa FPGA básica, a la que llaman core, con la FPGA que necesitemos (ya sea de Altera o XILINX). Esta placa core puede acoplarse a una placa de expansión con una buena cantidad de conectores. En la fotografía anterior hay una FPGA de la familia Cyclone IV de Altera (se trata de la placa CoreEP4CE6) junto con la expansión, en este caso la DVK601. Cuidado porque dependiendo del modelo de FPGA hay que elegir una placa de expansión u otra. Por ejemplo si queremos usar la placa FPGA EP4CE10, tendremos que optar por la expansión DVK600.
La ventaja radica en que a esta placa de expansión podemos conectar aquellos módulos que vayamos necesitando (ya sean de los vendidos por Waveshare o de cualquier otro fabricante).
Es esta imagen hay módulos que añaden un display de cuatro dígitos, pulsadores, lectores de memoria flash, una memoria SDRAM y un puerto VGA y PS/2. Hay mucha variedad. Por ejemplo, el display lo adquirí en la propia web del fabricante, pero los pulsadores son de Dealextreme.
Para probarla, he adaptado el código VHDL del reloj digital que ya vimos en un artículo anterior y demás he usado el circuito antirrebote de la semana pasada para añadir la funcionalidad de adelantar el minutero para ajustar la hora pulsando un botón. Así queda la placa de expansión con la FPGA, el display y los pulsadores.
Tengo que decir que el display se ilumina muy poco. La placa de expansión suministra 3.3V en lugar de 5V, que según las especificaciones del display, es la tensión correcta. El código VHDL del reloj, tras adaptarlo, es el siguiente.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity digital_clock is
port (
clk50mhz: in STD_LOGIC;
display: out STD_LOGIC_VECTOR(7 downto 0);
cur_display: out STD_LOGIC_VECTOR(3 downto 0);
point: out STD_LOGIC;
button: in STD_LOGIC
);
end digital_clock;
architecture beh of digital_clock is
constant max_count: INTEGER := 25000000; -- 50000000/2
constant max_refresh_count: INTEGER := 100000; -- 50Mzh/100000=500Hz
signal count: INTEGER range 0 to max_count;
signal refresh_count: INTEGER range 0 to max_refresh_count;
signal refresh_state: STD_LOGIC_VECTOR(1 downto 0) := (others => '0');
signal clk_state: STD_LOGIC := '0';
signal display_sel: STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
shared variable hora1, hora2, min1, min2: INTEGER range 0 to 10 := 0;
shared variable segundos: INTEGER range 0 to 59 := 0;
constant CNT_SIZE : integer := 19;
function digito(numero:INTEGER) return STD_LOGIC_VECTOR is
variable salida: STD_LOGIC_VECTOR(7 downto 0);
begin
case numero is
when 0 => salida := "11000000"; -- 0
when 1 => salida := "11111001"; -- 1
when 2 => salida := "10100100"; -- 2
when 3 => salida := "10110000"; -- 3
when 4 => salida := "10011001"; -- 4
when 5 => salida := "10010010"; -- 5
when 6 => salida := "10000011"; -- 6
when 7 => salida := "11111000"; -- 7
when 8 => salida := "10000000"; -- 8
when 9 => salida := "10010000"; -- 9
when others => salida := "11111111";
end case;
return(salida);
end digito;
begin
cur_display <= display_sel;
gen_clock: process(clk50mhz, clk_state, count)
begin
if clk50mhz'event and clk50mhz='1' then
-- contador 1HZ
if count < max_count then
count <= count + 1;
else
clk_state <= not clk_state;
count <= 0;
end if;
-- contador 500Hz (para refresco del display)
if refresh_count < max_refresh_count then
refresh_count <= refresh_count + 1;
else
refresh_state <= refresh_state + 1;
refresh_count <= 0;
end if;
end if;
end process;
show_display: process(refresh_state)
begin -- selección del display
case refresh_state is
when "00" => display_sel <= "0001"; -- display 0
when "01" => display_sel <= "0010"; -- display 1
when "10" => display_sel <= "0100"; -- display 2
when "11" => display_sel <= "1000"; -- display 3
when others => display_sel <= "1111";
end case;
-- mostrar hora
case display_sel is
when "0001" => display <= digito(hora2); -- display 0
when "0010" => display <= digito(hora1); -- display 1
when "0100" => display <= digito(min2); -- display 2
when "1000" => display <= digito(min1); -- display 3
when others => display <= "11111111";
end case;
-- parpadeo del punto
point <= clk_state;
end process;
persecond: process (clk_state)
begin
if clk_state'event and clk_state='1' then
-- contador de segundos
if segundos < 59 then
-- comprobar pulsacion de boton
if button = '0' then
segundos := 0;
min1 := min1 + 1; -- +1 minuto
else
segundos := segundos + 1;
end if;
else
segundos := 0;
min1 := min1 + 1; -- +1 minuto
end if;
-- segundo dígito minutero
if min1 = 10 then
min2 := min2 + 1;
min1 := 0;
end if;
-- primer dígito hora
if min2 = 6 then
hora1 := hora1 + 1;
min2 := 0;
end if;
-- segundo dígito hora
if hora1 = 10 then
hora2 := hora2 + 1;
hora1 := 0;
end if;
if hora2=2 and hora1=4 then
hora2 := 0;
hora1 := 0;
end if;
end if;
end process;
end beh;
Los cambios más importantes son los siguientes:
Aprovechando que el display tiene dos puntos para separar horas y minutos, se ha añadido una señal llamada point para controlar el parpadeo. Otra diferencia es que ahora, tal y como se ve en el siguiente esquema, la selección del display de siete segmentos se hace a través de un transistor configurado en emisor común, lo que quiere decir que ahora el display se selecciona con una señal a nivel alto (el primer display se selecciona con 0001 en lugar de 1110 como hacíamos en el artículo anterior).
También se ha añadido la circuitería necesaria para adelantar el minutero al pulsar el botón. En cada ciclo de un segundo se comprueba si el botón está pulsado y si es así, se incrementa el tiempo en un minuto.
El pulsador antirrebote no ha cambiado, pero lo vuelvo a recordar aquí de todas formas.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity debouncer is
port (
clk : in std_logic;
btn_in : in std_logic;
btn_out : out std_logic);
end debouncer;
architecture beh of debouncer is
constant CNT_SIZE : integer := 19;
signal btn_prev : std_logic := '0';
signal counter : std_logic_vector(CNT_SIZE downto 0) := (others => '0');
begin
process(clk)
begin
if (clk'event and clk='1') then
if (btn_prev xor btn_in) = '1' then
counter <= (others => '0');
btn_prev <= btn_in;
elsif (counter(CNT_SIZE) = '0') then
counter <= counter + 1;
else
btn_out <= btn_prev;
end if;
end if;
end process;
end beh;
Llamadme nostálgico, pero cuando hay que acoplar pocos dispositivos, como en este caso, prefiero usar el capturador de esquemáticos en lugar de hacer un mapeo con VHDL. Este es el conexionado del reloj y el circuito antirrebote.
Los pines están bien documentados en la wiki del propio fabricante, así que no entraré en detalle. Simplemente os dejo la configuración en pin planner para el conexionado del display y el botón.
En este vídeo se puede ver el reloj en funcionamiento.







Comentarios
Publicar un comentario