Criando uma UART e mostrando os dados recebidos em VHDL

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.

Tabela Ascii
fonte: TechTudo

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. 

conversor USB para RS232 - UART
fonte: AVR Freaks

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).

UART

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

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.

UART

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:

UART

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

Tabela ASCII

VERILOG vs VHDL – Precisamos falar sobre esse assunto

Dica de livro: VHDL – Descrição e Síntese de Circuitos Digitais

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
Marco Gonzaga
Marco Gonzaga
20/07/2021 10:21

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.

Marco Gonzaga
Marco Gonzaga
Reply to  Vinicius Mylonas
21/07/2021 10:58

Olá Vinicius !
Muito obrigado pela resposta. Estarei te enviando um email.

Abraço.

Home » Hardware » Criando uma UART e mostrando os dados recebidos em VHDL

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: