jueves, 24 de febrero de 2022

203 - Transmision de datos UART EP2C5

Enviar datos serie UART con FPGA

 


Hoy quiero mostrar una de las muchas maneras de implementar la comunicación serial asíncrona UART en una FPGA, utilizando la placa EP2C5. Para realizar esto primero explicaremos como enviar un dato byte y luego un mensaje conformado por varios bytes consecutivos codificados en ASCII. 

Doy por entendido que ya contamos con los conocimientos acerca de los que son las FPGA y el manejo de lenguajes descriptivos, mas propiamente Verilog, así mismo deberás tener instalado el software de diseño Quartus, un programador USB-Blaster y la placa EP2C5. Si vives en Bolivia estos dispositivos lo puedes conseguir en la tienda SAWERS,  aquí encontraras incluso modelos menos anticuados que la Cyclone II y en función a tu economía puedes comprar cualquiera siempre que sea de la marca Altera.

En una entrada anterior de mi blog resumo como introducción algunos datos adicionales acerca de la tarjeta EP2C5 y lo que necesitas para crear tus circuitos digitales. Dejo el enlace:   Tarjeta EP2C5T

Aquí les paso un vídeo de ELECTRONOOBS con la explicación clara de los que es la comunicación serial.

 

Descripción del circuito

La comunicación serial asíncrona requiere que ambos dispositivos emisor y receptor fijen la velocidad de transmisión de cada bit y la longitud del dato, para que este pueda enviarse por una linea o pin de salida, observe la siguiente imagen que nos ilustra la distribución de cada bit en periodos de tiempo.

Fig1. Diagrama de tiempo Fuente.
Fig1. Diagrama de tiempo Fuente.

La linea o pin de salida de emisor se mantiene inactivo con un nivel lógico alto, y solo cambiara a nivel lógico bajo para indicar al receptor que iniciara la transmisión de un dato, esta transición se conoce como bit de inicio(Start bit). Luego el emisor enviara cada bit del dato, empezando por el bit menos significativo hasta completar el total de bit que conforman el dato y colocara el pin de salida en un nivel lógico alto lo cual representa al bit de parada(Stop bit), todos los bits incluidos el de inicio y parada tienen una duración o periodo de un tiempo de bit.

El tiempo de bit dependerá de la velocidad de transmisión, que se determina por la cantidad de bits enviados por segundo(bps), siendo el Baudio la unidad de medida utilizada, entonces el tiempo de bit se calcula como la inversa del baudio. Una forma común de representar la configuración de una comunicación serie asíncrona es la siguiente:

9600N81

  • 9600 La velocidad en baudios, en este caso el tiempo de bit es 104us
  • N Bit de paridad opcional, N=Sin paridad O=par y E=impar 
  • 8 La longitud del dato en este caso es 8 bits 
  • 1 Los bits de parada utilizados, en este caso un solo bit.

Ahora trabajaremos en la descripción del modulo verilog drv_uarttx.v el cual permite enviar un byte de dato de forma serial como se observa en el siguiente diagrama.

Fig2. Diagrama del transmisor UART

El dato byte a enviar esta presente en la entrada de ocho lineas txreg, y un pulso alto en la entrada txstart iniciara la transferencia de cada bit incluidos los de inicio y parada en la salida txd. La salida txbusy se activa en nivel alto cuando inicia la transmisión y regresa a nivel bajo cuando finaliza al transmisión, la finalidad de esta salida es indicar el instante donde se puede iniciar la transferencia de otro byte, puesto que una transferencia implica que al menos transcurran 10 tiempos de bit, intentar iniciar antes sobrecargaría el registro interno produciendo una perdida del dato previo y hacerlo mucho después implicaría tiempos de espera innecesarios. El generador de baudios es el encargado de manejar los de bits utilizando como base los pulsos presentes en la entrada clock.

Ahora explicaremos con mas detalle el código descrito en el modulo drv_uarttx.v empezando en primer lugar por la siguiente sección:

Fig3. Símbolo gráfico del modulo drv_uarttx

 

module drv_uarttx(clk, rstn, txstart, txbusy, txreg, txd);
parameter BAUD = 9600;
input clk, rstn;
input txstart; //Pin de inicio de transmisión
input [7:0] txreg; //Bus de entrada del dato
output reg txd; //Pin de salida bit
output txbusy; //Indicador de Ocupado
reg tx_flag; //Control Inicio y Fin de transmisión
assign txbusy = tx_flag; //Indicador de ocupado
. . . . .
always @(posedge clk or negedge rstn)
begin
    if(!rst_n) txd <= 1'b1; //Ajusta al estado inactivo
    else
       if(tx_flag) //Indicador de transmisión iniciada
       case(tx_cnt)//Contador de bit transmitido
       4'd0: txd <= 1'b0; // bit de inicio
       4'd1: txd <= tx_data[0]; //bit menos significativo
       4'd2: txd <= tx_data[1];
       4'd3: txd <= tx_data[2];
       4'd4: txd <= tx_data[3];
       4'd5: txd <= tx_data[4];
       4'd6: txd <= tx_data[5];
       4'd7: txd <= tx_data[6];
       4'd8: txd <= tx_data[7]; //bit mas significativo
       4'd9: txd <= 1'b1; //bit de parada
       default: ;
       endcase
    else txd <= 1'b1; //Ajusta al estado inactivo
end
endmodule

Observe que el dato byte esta en el registro interno tx_data, y el registro bandera tx_flag controla el inicio de la transmisión, cuando esta bandera se activa en nivel alto la sentencia selectiva case envía los bits de tx_data incluidos el inicio y parada según la posición indicada por el contador tx_cnt.

always @(posedge clk or negedge rstn)
begin   
  if(!rstn)
    begin  
    tx_flag <= 1'b0; //limpia bandera
    tx_data <= 8'd0; //limpia registro de datos
    end
  else
    if(en_flag) //Verifica pulso de inicio
      begin                  
      tx_flag <= 1'b1; //Indica el inicio de transmisión
      tx_data <= txreg; //Copia dato de entrada a TXREG
      end
    else
      if((tx_cnt == 4'd9)&&(clk_cnt == BCNT/2))
        begin              
        tx_flag <= 1'b0; //Indica el fin de transmisión
        tx_data <= 8'd0;
        end
      else
        begin
          tx_flag <= tx_flag;
          tx_data <= tx_data;
        end
end

Cuando un pulso de inicio es detectado mediante el indicador en_flag se da inicio a la transmisión tx_flag=1, donde el dato byte presente en la entrada txreg se carga al registro interno tx_data, una vez que el contador de bits tx_cnt llega al valor de 9 se finalizara la transmisión tx_flag=0.

reg en_d0, en_d1; wire en_flag;
assign en_flag = (~en_d1) & en_d0;
always @(posedge clk or negedge rstn)
begin         
    if (!rstn)
    begin
        en_d0 <= 1'b0;       
        en_d1 <= 1'b0;
        end                            
    else begin        
        en_d0 <= txstart;            
        en_d1 <= en_d0;             
        end
end

El pulso de inicio ocurre en el indicador en_flag cuya función lógica representada en el siguiente esquema de circuito obedece a los registros en_d0 y en_d1

Fig4. Generador de pulso en flanco ascendente

 

El registro en_d0 guarda el nivel presente en la entrada txstart y el registro en_d1 guarda el nivel previo, gracias a esta función es posible obtener un pulso de inicio sin importar el tiempo de activación en la entrada txstart, situación que se explica mejor en el siguiente diagrama.

Fig5. Diagrama de tiempo del generador de pulso
 

Para completar la explicación del modulo drv_uarttx faltara ver la sección que permite generar los baudios, cuya descripción es la siguiente:

always @(posedge clk or negedge rstn)
begin         
  if (!rstn)
    begin 
    clk_cnt <= 16'd0; //Inicia el contador de pulsos
    tx_cnt  <= 4'd0; //Inicia el contador de bits
    end   
  else
    if(tx_flag) //Solo si la transmisión inicio
      begin
        if(clk_cnt < (BCNT - 1)) //Tiempo de bit
          begin
            clk_cnt <= clk_cnt + 1'b1; //Incrementa contador de pulsos
            tx_cnt <= tx_cnt;
          end
        else
          begin
            clk_cnt <= 16'd0; //Reinicia el contador de pulsos
            tx_cnt  <= tx_cnt + 1'b1; //Incrementa contador de bits
          end
      end
    else
      begin
      clk_cnt <= 16'd0;
      tx_cnt  <= 4'd0;
      end
end

El parámetro BCNT contiene el valor utilizando por el contador de pulsos para determinar el tiempo de bit, y su valor puede calcularse de acuerdo a la frecuencia de entrada, ejemplos:

Si la entrada de reloj es 50MHz, el valor del contador BCNT sera:


Entonces un registro clk_cnt de 16-bit sera suficiente para conseguir ajustes de velocidad desde los 1200 baudios.

Ahora describiremos un circuito digital que nos permita enviar un mensaje "HOLA" cuando se presionamos un pulsador(activo en nivel bajo). El mensaje se almacenara memoria conformada por un arreglo de bytes cuyas posiciones son accesibles mediante un índice pos. ver imagen.

Fig6. Formato del mensaje
 
Como el receptor necesita saber cuando finaliza el mensaje, se utilizara para este propósito el carácter ASCII fin de linea 0x0A.

Fig7. Símbolo gráfico del modulo

La salida start se conecta a la entrada tx_start del modulo uart_send, el byte data a transmitir se obtiene del arreglo de bytes msg que contiene el mensaje “HOLA”.

Fig8. Diagrama de estados del modulo uartsenddata

A continuación de detalla la definición de los estados:

  • INIT Espera activación del pulsador but, es decir una lectura con nivel bajo y siempre que la entrada busy no este activa lo que indicaría que hay un dato previo en proceso.
  • SEND Carga el dato byte a transmitir desde la memoria msg a la salida data y da inicio a la operación activando la salida start.
  • WAIT Espera que la entrada busy pase a nivel bajo, indicando así que el proceso de transmisión del byte ha finalizado.
  • COMP Verifica si el dato transmitido es un indicador de final de mensaje, de ser así incrementa la posición del contador de bytes y regresa al estado SEND, caso contrario retorna al estado INIT.

module uartsenddata(clk, rstn, but, start, busy, data);
input clk, rstn, but, busy;
output reg start;
output reg [7:0] data;
reg [7:0] msg[0:7]; //Arreglo de bytes para el mensaje
localparam INIT = 0;
localparam SEND = 1;
localparam WAIT = 2;
localparam COMP = 3;
reg [1:0] state;
reg [3:0] pos;
   case (state)
    INIT: begin
          if((!but) && (!busy))
          state <= SEND;
          pos <= 4'd0;
        end
   SEND: begin
          data <= msg[pos];
          start <= 1'b1;
          state <= WAIT;
         end
    WAIT: begin
          start <= 1'b0;   
          if(!busy) state <= COMP;
          end
   COMP: begin
          if(msg[pos] != 8’d10)
           begin
              pos <= pos + 1'b1;
              state <= SEND;
           end
          else
            state <= INIT;
          end
   endcase
endmodule

Se debe tomar en cuenta el ruido mecánico presente en el pulsador de entrada y la espera entre pulsaciones, ya que una vez enviado el mensaje si el pulsador no se libera, la maquina volverá a ingresar al estado SEND enviando nuevamente el mensaje.  Aquí te dejo un enlace a una entrada previa en la que se explica el modulo button.v que nos permite hacer la lectura del pulsador considerando los aspectos mencionados.

<<Lectura sin rebote de un pulsador>>

Implementacion del circuito

Luego de convertir a símbolos cada unos de los módulos descritos previamente, realizamos las conexiones entre los bloques designando los pines de entrada y salida de la tarjeta EP2C5

Fig9. Esquema del circuito

A continuación se muestra una captura de pantalla en la terminal serie que recibe el mensaje de nuestro circuito, observe que al presionar el pulsador se reciben varios mensajes consecutivos debido a que no se eliminan los rebootes del pulsador y tampoco se controla el tiempo entre pulsaciones para evitar el reenvío del mensaje.

Fig10. Recepción de mensajes consecutivos

Luego de agregar al circuito, un modulo adicional para la lectura del pulsador, se puede observar que un solo mensaje es enviado por cada activación del pulsador.

Fig11. Recepción de único mensaje sin rebote

 

Conclusiones 

Dejo a continuación el enlace para descarga del proyecto, mismo que fue creado con el software Quartus Web versión 13.0.1: <<uartsenddata>>

Vídeo que muestra como implementar el proyecto.


Para finalizar solo quiero agradecer tu visita a mi blog, espero que el contenido de esta entrada hubiera sido de ayuda en tu formación educativa, favor cualquier consulta al respecto pueden escribirme al correo electrónico.

Referencias adicionales:

Pablo Cesar Zarate Arancibia.

Electrónico
pablinzte@gmail.com / pablinza@me.com


No hay comentarios:

Publicar un comentario