Este projeto tem como função criar parte de uma UART (apenas o reciver), que ao receber um valor enviado por um computador, apresente seu valor em hexadecimal no display de 7 segmentos por 4 dígitos.
A UART consiste de uma entrada que recebe um byte, transmitindo-o de forma sequencial e alterando os estados desta porta. Por se tratar de uma comunicação assíncrona, é necessário que tanto o receptor quanto o transmissor saibam a velocidade de comunicação (baut rate).
Um ótimo artigo, que apresenta, detalhadamente, como detectar automaticamente o valor de baud rate é: “UART Autobaud em VHDL” escrito por Fábio Pereira.
Aqui, diferente do artigo citado, vamos nos concentrar em um projeto prático e mais simples, como enviar o valor de uma tecla de um computador para uma porta no FPGA.
Tabela Ascii
Nesta proposta, cada caractere recebido é mostrado no display com seu valor definido na tabela Ascii em hexadecimal. Essa tabela é definida na Wikipedia como :
A tabela ascii é a (do inglês American Standard Code for Information Interchange; “Código Padrão Americano para o Intercâmbio de Informação”) — geralmente pronunciado [áski] — é um código binário (cadeias de bits: 0s e 1s) que codifica um conjunto de 128 sinais: 95 sinais gráficos (letras do alfabeto latino, algarismos arábicos, sinais de pontuação e sinais matemáticos) e 33 sinais de controle, utilizando 7 bits para representar todos os seus símbolos.
Abaixo podemos ver uma representação da tabela Ascii.
Portanto, esperamos verificar o valor do caractere enviado no display de 4 dígitos, como no exemplo: Tecla “m” = Display “006D”
Hardware
Para executar o projeto é necessário um hardware intermediário entre o computador e a placa de desenvolvimento. Para tanto utilizaremos um conversor USB para RS232.
A seguir, podemos ver um esquemático que representa a conexão do sistema.
TeraTerm
O envio de dados é feito utilizando o software open source TeraTerm, com o conversor UBS conectado. Para configurar, vá na guia Setup > Serial Port e a seguinte tela será apresentada.
Ajuste o item Speed como 115200. Esse parâmetro representa o baud rate.
Top Level
Na figura abaixo é possível verificar o top level do projeto. Ele consiste em uma entrada de comunicação serial, quatro conversores bcd para sete segmentos e um multiplexador para separar os dígitos no display de 7 segmentos por 4 dígitos (os dois últimos citados, estão presentes no projeto anterior).
Máquina de estados
Para o controle do Reciver é necessário desenvolver uma máquina de estados composta de 5 estados:
Idle_s: Detecta o start bit foi para 0.
RX_Start_Bit_s: Verifica mais uma vez se o start bit continua em 0.
RX_Data_Bits_s: Responsável por coletar os bits de dados.
RX_Stop_Bits_s: Sinaliza o fim da recepção e ativa o RX_DV_r.
Cleanuo_s: Desativa RX_DV_r e retorna para Idle_s.
UART RX
Como base para esse trecho do projeto, usamos a descrição de hardware disponibilizada no site Nandland. Um dos pontos mais interessantes desta exposição é a forma como devemos calcular o baud rate, (seguindo a fórmula abaixo) e então passarmos este valor através do parâmetro genérico CLKS_PER_BIT_g.
Valor = Frequência de clock / Frequência de uart
No caso dessa concepção, o cristal de clock disponível na placa de desenvolvimento é de 50 Mhz, e o baud rate que usaremos é 115200, sendo assim, a conta resultante é:
Valor = 50.000.000 / 115200 ≈ 434
Podemos ver a descrição do pino RX, a seguir.
----------------------------------------------------------------------
-- Arquivo original https://www.nandland.com
----------------------------------------------------------------------
-- Este arquivo contem um receptor UART. Este receptor pode receber
-- 8 bits de dados seriais, um start bit, um stop bit,
-- e sem bit de paridade. Quando a recepção esta completa
-- RX_DV_o vai estar em valor alto por um ciclo de clock.
--
-- Exemplo de como configurar CLKS_PER_BIT_g:
-- CLKS_PER_BIT_g = (Frequencia de clock)/(Frequencia do UART)
-- exemplo: 10 MHz Clock, 115200 baud UART
-- (10000000)/(115200) = 87
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.all;
entity UART_RX is
generic (
CLKS_PER_BIT_g : integer := 115 -- precisa ser corrigido
);
port (
clock_i : in std_logic;
RX_Serial_i : in std_logic;
RX_DV_o : out std_logic;
RX_Byte_o : out std_logic_vector(7 downto 0)
);
end UART_RX;
architecture rtl of UART_RX is
-- maquina de estados
type SM_Main_t is (Idle_s, RX_Start_Bit_s, RX_Data_Bits_s,
RX_Stop_Bit_s, Cleanuo_s);
signal SM_Main_r : SM_Main_t := Idle_s;
signal RX_Data_R_r : std_logic := '0';
signal RX_Data_r : std_logic := '0';
signal Clk_Count_r : integer range 0 to CLKS_PER_BIT_g-1 := 0;
signal Bit_Index_r : integer range 0 to 7 := 0; -- 8 Bits Total
signal RX_Byte_r : std_logic_vector(7 downto 0) := (others => '0');
signal RX_DV_r : std_logic := '0';
begin
-- Proposito : Registrar duas vezes os dados de entrada.
-- (Isso remove problemas de metaestabilidade)
SAMPLE_p : process (clock_i)
begin
if rising_edge(clock_i) then
RX_Data_R_r <= RX_Serial_i;
RX_Data_r <= RX_Data_R_r;
end if;
end process SAMPLE_p;
-- Proposito: controle de maquina de estados RX
UART_RX_p : process (clock_i)
begin
if rising_edge(clock_i) then
case SM_Main_r is
-- valor inicial
when Idle_s =>
RX_DV_r <= '0';
Clk_Count_r <= 0;
Bit_Index_r <= 0;
if RX_Data_r = '0' then -- se start bit é detectado
SM_Main_r <= RX_Start_Bit_s; -- vai para o proximo estado
else
SM_Main_r <= Idle_s;
end if;
-- verifica se o start bit continua em baixo
when RX_Start_Bit_s =>
if Clk_Count_r = (CLKS_PER_BIT_g-1)/2 then
if RX_Data_r = '0' then -- se o start bit continuar em zero
Clk_Count_r <= 0; -- vai para o proximo estado
SM_Main_r <= RX_Data_Bits_s;
else
SM_Main_r <= Idle_s;
end if;
else
Clk_Count_r <= Clk_Count_r + 1;
SM_Main_r <= RX_Start_Bit_s;
end if;
-- espera CLKS_PER_BIT_g-1 ciclos de clock para coletar o dado serial
when RX_Data_Bits_s =>
if Clk_Count_r < CLKS_PER_BIT_g-1 then
Clk_Count_r <= Clk_Count_r + 1;
SM_Main_r <= RX_Data_Bits_s;
else
Clk_Count_r <= 0;
RX_Byte_r(Bit_Index_r) <= RX_Data_r; -- grava bit recebido no vetor de dados
-- verifica se ja recebeu todos os 8 bits de dados
-- caso sim, pula para o proximo estado
if Bit_Index_r < 7 then
Bit_Index_r <= Bit_Index_r + 1;
SM_Main_r <= RX_Data_Bits_s;
else
Bit_Index_r <= 0;
SM_Main_r <= RX_Stop_Bit_s;
end if;
end if;
-- Recebe o stop bit. Stop bit = 1
when RX_Stop_Bit_s =>
-- aguarda CLKS_PER_BIT_g-1 ciclos de clock para o fim do Stop bit
-- sinaliza RX_DV_r como alto e pula para o proximo estado
if Clk_Count_r < CLKS_PER_BIT_g-1 then
Clk_Count_r <= Clk_Count_r + 1;
SM_Main_r <= RX_Stop_Bit_s;
else
RX_DV_r <= '1';
Clk_Count_r <= 0;
SM_Main_r <= Cleanuo_s;
end if;
-- fica aqui por 1 ciclo de clock
-- coloca RX_DV_r em baixo (RX_DV_r fica em alto apenas por um ciclo)
-- e então retorna para o valor inicial da maquina de estados
when Cleanuo_s =>
SM_Main_r <= Idle_s;
RX_DV_r <= '0';
when others =>
SM_Main_r <= Idle_s;
end case;
end if;
end process UART_RX_p;
RX_DV_o <= RX_DV_r;
RX_Byte_o <= RX_Byte_r;
end rtl;
TOP Level VHDL
Quando recebido o stop_bit, o dado do byte coletado fica disponível em um vetor com 8 posições (RX_Byte_o), na qual os 4 bits mais significativos irão para o conversor BCD para sete segmentos, e os outros 4 bits são conectados ao outro conversor BCD.
library ieee;
use ieee.std_logic_1164.all;
entity UART_RX_To_7_Seg_Top is
port (
-- Main Clock (50 MHz)
clock_i : in std_logic;
reset_i : in std_logic;
-- UART RX Data
i_UART_RX : in std_logic;
-- digit output and anode select
digit_o : out std_logic_vector(6 downto 0);
anode_o : out std_logic_vector(3 downto 0)
);
end entity UART_RX_To_7_Seg_Top;
architecture RTL of UART_RX_To_7_Seg_Top is
signal w_RX_DV : std_logic;
signal w_RX_Byte : std_logic_vector(7 downto 0);
signal digit1_seven, digit2_seven, digit3_seven, digit4_seven : std_logic_vector(6 downto 0);
component UART_RX is
generic (
CLKS_PER_BIT_g : integer := 115 -- Needs to be set correctly
);
port (
clock_i : in std_logic;
RX_Serial_i : in std_logic;
RX_DV_o : out std_logic;
RX_Byte_o : out std_logic_vector(7 downto 0)
);
end component;
component bcd_seven_seg is
port (
clock_i : in std_logic;
bcd_i : in std_logic_vector(3 downto 0);
seven_o : out std_logic_vector(6 downto 0)
) ;
end component ;
component four_digit_mux is
port (
clock_i : in std_logic;
reset_i : in std_logic;
digit1_i : in std_logic_vector (6 downto 0);
digit2_i : in std_logic_vector (6 downto 0);
digit3_i : in std_logic_vector (6 downto 0);
digit4_i : in std_logic_vector (6 downto 0);
segment_o : out std_logic_vector (6 downto 0);
anode_o : out std_logic_vector (3 downto 0)
) ;
end component ;
begin
UART_RX_Inst : UART_RX
generic map (CLKS_PER_BIT_g => 434) -- 50,000,000 / 115,200
port map (
clock_i => clock_i,
RX_Serial_i => i_UART_RX,
RX_DV_o => w_RX_DV,
RX_Byte_o => w_RX_Byte
);
-- Binary to 7-Segment Converter for Upper Digit
bcd_1 : bcd_seven_seg port map (clock_i => clock_i, bcd_i => w_RX_Byte(3 downto 0), seven_o => digit1_seven);
bcd_2 : bcd_seven_seg port map (clock_i => clock_i, bcd_i => w_RX_Byte(7 downto 4), seven_o => digit2_seven);
bcd_3 : bcd_seven_seg port map (clock_i => clock_i, bcd_i => "0000", seven_o => digit3_seven);
bcd_4 : bcd_seven_seg port map (clock_i => clock_i, bcd_i => "0000", seven_o => digit4_seven);
four_digit_mux_1 : four_digit_mux
port map(
clock_i => clock_i,
reset_i => reset_i,
digit1_i => digit1_seven,
digit2_i => digit2_seven,
digit3_i => digit3_seven,
digit4_i => digit4_seven,
segment_o => digit_o,
anode_o => anode_o
);
end architecture RTL;
A apresentação desse componente e do multiplexador de 4 dígitos estão disponíveis abaixo e sua explicação está detalhada no projeto anterior.
BCD para 7 segmentos
-------------------------------------------------
--
-- Mux for 4 digits display,
-- cathode comom, variate anodes
--
-- value_1HZ_c = clock_i / 2;
--
-- Author : Vinicius Mylonas
-------------------------------------------------
library ieee ;
use ieee.std_logic_1164.all ;
use ieee.std_logic_unsigned.all ;
entity four_digit_mux is
port (
clock_i : in std_logic;
reset_i : in std_logic;
digit1_i : in std_logic_vector (6 downto 0);
digit2_i : in std_logic_vector (6 downto 0);
digit3_i : in std_logic_vector (6 downto 0);
digit4_i : in std_logic_vector (6 downto 0);
segment_o : out std_logic_vector (6 downto 0);
anode_o : out std_logic_vector (3 downto 0)
) ;
end four_digit_mux ;
architecture arch of four_digit_mux is
signal period_display : std_logic_vector (17 downto 0);
signal led_activating_counter : std_logic_vector (1 downto 0);
begin
-- process add value in period_display
process (clock_i,reset_i)
begin
if reset_i = '0' then
period_display <= (others => '0');
elsif rising_edge(clock_i)then
period_display <= period_display + 1;
end if;
end process;
-- chance value each 25M/65536 = 2.6 ms
led_activating_counter <= period_display(17 downto 16);
process (led_activating_counter)
begin
case led_activating_counter is
when "11" =>
anode_o <= "0111";
segment_o <= digit1_i;
when "10" =>
anode_o <= "1011";
segment_o <= digit2_i;
when "01" =>
anode_o <= "1101";
segment_o <= digit3_i;
when others =>
anode_o <= "1110";
segment_o <= digit4_i;
end case;
end process;
end architecture;
Mux para 4 dígitos
-------------------------------------------------
--
-- simple BCD ~ seven segments display
--
-- Author : Vinicius Mylonas
-------------------------------------------------
library ieee ;
use ieee.std_logic_1164.all ;
use ieee.numeric_std.all ;
entity bcd_seven_seg is
port (
clock_i : in std_logic;
bcd_i : in std_logic_vector(3 downto 0);
seven_o : out std_logic_vector(6 downto 0)
) ;
end bcd_seven_seg ;
architecture rtl of bcd_seven_seg is
begin
process (clock_i)
begin
if rising_edge(clock_i)then
case bcd_i is
when "0000" => seven_o <= "0000001"; -- "0"
when "0001" => seven_o <= "1001111"; -- "1"
when "0010" => seven_o <= "0010010"; -- "2"
when "0011" => seven_o <= "0000110"; -- "3"
when "0100" => seven_o <= "1001100"; -- "4"
when "0101" => seven_o <= "0100100"; -- "5"
when "0110" => seven_o <= "0100000"; -- "6"
when "0111" => seven_o <= "0001111"; -- "7"
when "1000" => seven_o <= "0000000"; -- "8"
when "1001" => seven_o <= "0000100"; -- "9"
when "1010" => seven_o <= "0000010"; -- a
when "1011" => seven_o <= "1100000"; -- b
when "1100" => seven_o <= "0110001"; -- C
when "1101" => seven_o <= "1000010"; -- d
when "1110" => seven_o <= "0110000"; -- E
when others => seven_o <= "0000001"; -- F
end case;
end if;
end process;
end architecture ;
Teste
Após efetuar a compilação do projeto devemos fazer o pin planer (a conexão entre o pino físico e o pino gerado pela descrição), a tabela está disponível na figura abaixo.
E, por último, conectamos o TX e o GND do conversor FTDI aos pinos i_UART_RX (76 do conector P2) e GND (do conector P2), respectivamente.
Com o sistema ligado, ao pressionarmos uma tecla no TeraTerm podemos ver o resultado no display da placa de desenvolvimento, como no vídeo a seguir:
Conclusão
Uma vantagem do FPGA é poder utilizar grande parte dos pinos como parte de UART, independentes entre si. O exemplo apresentado é apenas para fins didáticos, mas ajuda a compreender a mecânica da comunicação serial.
Na próxima etapa devo apresentar a descrição de hardware do TX juntamente com o Testbench do RX e TX, então acompanhe o blog para mais novidades.
Caso deseje consultar, os arquivos deste projeto estão disponíveis neste link do Github. Em caso de dúvidas por favor entre em contato.
Saiba Mais
VERILOG vs VHDL – Precisamos falar sobre esse assunto
Dica de livro: VHDL – Descrição e Síntese de Circuitos Digitais










Muito bom ! Parabens pelo exemplo vai me ajudar muito aqui. Estou precisando de uma implementação para ler/gravar um SDCARD. Existe aqui no embarcados algum exemplo ? Voce teria como me ajudar ? Obrigado.
Ola Marco, obrigado pelo apoio e fico feliz que esta te ajudando.
No portal aqui para leitura de SDCard acho que não existe esse exemplo, existe alguns exemplos espalhados pela internet.
Posso tentar de ajudar sim, mas tem um grupo de wpp com pessoas mais capacitadas que eu vai nos ajudar a resolver este problema.
Por favor, entre em contato comigo pelo email ou linkedin.
https://www.linkedin.com/in/vinicius-mylonas/ vinimyls@hotmail.com
Olá Vinicius !
Muito obrigado pela resposta. Estarei te enviando um email.
Abraço.