El puerto serie es el periférico mas utilizado en sistemas electrónicos para transmitir y recibir datos de forma sencilla. A pesar de ser un estandar muy antiguo, su simplicidad de uso le hace perfecto para la transmisión de pequeñas cantidades de datos o para mostrar consolas de texto.

En sistemas digitales es muy utilizado para poder controlar una shell de comandos de linux u otro sistema operativo sin necesidad de utilizar teclados ni monitores externos. También en sistemas como Arduino se utiliza para descargar el programa a ejecutar a la memoria del microcontrolador. Esto es debido a que es un modulo hardware muy simple y no necesita ningún protocolo ni driver complejo para su uso, haciendo que cualquier sistema pueda acceder a él con una cantidad minima de código.

En este artículo vamos a crear un módulo VHDL que envíe la cadena Hola Mundo! a través del puerto serie. Lo implementaremos sobre la FPGA de Altera que hemos utilizado en otros tutoriales, pero lo hemos probado en otras placas como la Nexys4 de Xilinx y funciona sin ningún problema.

El puerto serie es un estándar bastante antiguo y aunque se sigue utilizando, los ordenadores actuales no incluyen el conector DB9 de 9 pines utilizado por el puerto original. Además la placa de Altera que utilizamos no tiene ni siquiera este conector, así que tenemos que utilizar un adaptador. El módulo que vamos a usar se llama FT232RL y os dejo en enlace a Amazon a continuación:

[amazon box=»B01N9RZK6I»]

Este modulo tiene un conector USB y un pequeño chip que convierte las señales RS232 a señales USB y viceversa. Podemos utilizarlo para transmitir simplemente conectando un cable al pin marcado como RX y otro a GND. A partir de ese momento podemos enviar paquetes con la FPGA y recibirlos en el PC utilizando un programa como TeraTerm.

Al conectar el modulo FT232 al ordenador este lo detectará como un puerto serie normal y podremos conectar TeraTerm a él.

A continuación os dejo el video donde vemos todo el proceso de creación del módulo que transmite por el puerto serie en VHDL y su funcionamiento.

Y aquí os dejamos el código VHDL que hemos utilizado para que podais copiarlo.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity uart_tx is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           tx : out STD_LOGIC;
           led_o : out std_logic);
end uart_tx;

architecture Behavioral of uart_tx is
  signal clk_div : std_logic;
  signal clk_counter : integer range 0 to 2604;
  
  signal tx_bit : integer range 0 to 7;
  
  type tx_state_t is (IDLE, START, DATA, STOP);
  signal tx_state : tx_state_t; 
  
  signal char_tx : std_logic_vector(7 downto 0);
  signal char_pointer : integer range 0 to 10;
  
  signal wait_50 : integer range 0 to 50;
  
begin

-----------------------------------------------------
-- Memoria ROM que almacena el texto a enviar
-----------------------------------------------------
process(char_pointer)
begin

  case(char_pointer) is
     when 0 =>
       char_tx <= conv_std_logic_vector(character'pos('H'),8);
     when 1 =>
       char_tx <= conv_std_logic_vector(character'pos('o'),8);
     when 2 =>
       char_tx <= conv_std_logic_vector(character'pos('l'),8);
     when 3 =>
       char_tx <= conv_std_logic_vector(character'pos('a'),8);
     when 4 =>
       char_tx <= conv_std_logic_vector(character'pos(' '),8);
     when 5 =>
       char_tx <= conv_std_logic_vector(character'pos('M'),8);
     when 6 =>
       char_tx <= conv_std_logic_vector(character'pos('u'),8);
     when 7 =>
       char_tx <= conv_std_logic_vector(character'pos('n'),8);
     when 8 =>
       char_tx <= conv_std_logic_vector(character'pos('d'),8);
     when 9 =>
       char_tx <= conv_std_logic_vector(character'pos('o'),8);
     when 10 =>
       char_tx <= conv_std_logic_vector(character'pos('!'),8);
     when others =>
       char_tx <= (others=>'0');
  end case;       

end process;


-----------------------------------------------------
-- Divisor de reloj de 50Mhz a 9600bps
-----------------------------------------------------

process(clk,reset)
begin
  if (reset='0') then
    clk_div <='0';
  elsif(rising_edge(clk)) then
    if(clk_counter = 2604) then    
      clk_div<=not(clk_div);
      clk_counter <= 0;
    else
      clk_counter <= clk_counter +1;
    end if;
    
  end if;  
 end process;    
  
-----------------------------------------------------
-- Maquina de estados que lee la ROM y forma la trama
-----------------------------------------------------

 process(clk_div,reset)
 begin
  if (reset='0') then
    tx <= '1';
    tx_state <= IDLE;
    tx_bit <= 0;
    led_o <= '0';
    char_pointer <= 0;
    wait_50 <= 0;
  elsif(rising_edge(clk_div)) then
    case(tx_state) is
      when IDLE =>  
        tx <= '1';
        tx_bit <= 0;
        if(wait_50=50) then
          tx_state <= START;
          wait_50 <= 0;
        else
          wait_50 <= wait_50 +1;
        end if;	 
      when START =>
        tx <= '0';
        tx_state <= DATA;
      when DATA =>
        tx <= char_tx(tx_bit);
        if(tx_bit=7) then
          tx_state <= STOP;
        else
          tx_bit <= tx_bit+1;
        end if;
      when STOP =>
        tx <= '1';
        if(char_pointer=10) then
          led_o <= '1';
        else  
          tx_state <= IDLE;
          char_pointer <= char_pointer+1;
        end if;          
     end case;
   end if;
end process;     
end Behavioral;