El puerto serie, también llamado UART, 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 es facil de encontrar en Amazon.
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.
Codigo UART en VHDL
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;