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


martes, 22 de febrero de 2022

202 - Lectura Pulsador EP2C5

Lectura de Pulsador

 

Fig1. Pulsador SPST 10mm

En esta entrada del blog, veremos como hacer una lectura correcta de un pulsador en la placa EP2C5, nuevamente recordar que ya contamos con los conocimientos acerca de los que son las FPGA y el manejo de lenguajes descriptivos, mas propiamente Verilog, y que también tienes  instalado el software de diseño Quartus Web, un programador USB-Blaster y la placa EP2C5, aunque puedes utilizar cualquier otra tarjeta siempre que sea de la marca Altera. En todo caso algo muy interesante de esta tecnología es que el modulo descrito en este ejemplo puede ser sintetizado para cualquier FPGA.  

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. Te recomiendo le des una mirada haciendo click al siguiente enlace:   Tarjeta EP2C5T

Descripción del circuito

Crearemos un circuito digital para leer el nivel lógico de un pulsador y reflejar su estado en un un diodo led por lo que haremos uso de los siguiente elementos: 

  • Pulsador de entrada conectado al pin 114(Externo);
  • Indicador de estado, LED conectado al pin3(Interno);
  • Oscilador principal conectado al pin17(Interno);
  • Pulsador de reinicio conectado al pin144(Interno).

Describiremos en nuestro ejemplo el modelo de una maquina de estados finitos FSM, donde se tomara en cuenta para validar el estado del pulsador los tiempos de espera debido al rebote mecánico y un intervalo mínimo de repetición entre pulsaciones.

Un pulsador es básicamente una interfaz que permite establecer un nivel lógico de entrada a un circuito digital, la validación de su estado se origina por la acción mecánica de cerrar y abrir los contactos internos ocasionando a una escala mínima de tiempo, oscilaciones no deseable que deben ser consideradas en un sistema digital.

Fig2. Ruido en cambio de estado de un pulsador

Como notara en la imagen de la fig2, el ruido esta formado por pequeñas transiciones que se propagan por casi 40ms, este tiempo se conoce como tiempo de rebote(bounce), y algunas técnicas para minimizar este efecto en nuestro circuito pueden ser:
  • Adicionar un filtro RC en la entrada del pulsador;
  • Adicionar un circuito discriminador de ruido.

Nuestro ejemplo pasa por aplicar la segunda opción donde diseñaremos una maquina de estados finito para conseguir un nivel estable durante la transición del pulsador, tal como se ilustra en el diagrama de tiempos de la siguiente imagen.

Fig3. Diagrama de tiempo para discriminar el rebote

Los tiempos de rebote mínimo dependerán en parte de la calidad de construcción de un determinado pulsador, pero en general estará en el rango de 10-50ms, el tiempo de espera(debounce time) puede diferir entre la transición bajo a alto y alto a bajo.

A continuación se muestra el diagrama de estados para la maquina FSM, misma que estará sincronizada por una señal de reloj, usualmente de 1ms.

 

Fig4. Diagrama de estados

El código verilog drv_button.v del circuito discriminador comenta claramente la definición de los estados utilizados, el ultimo estado UNPRESS tiene por finalidad otorgar un intervalo de espera entre pulsaciones del botón, tomando en cuenta la interacción humana. Todos los estados dependen del valor que posee el contador, mismo que se incrementa continuamente y se reinicia cada vez que hay un cambio de estado, además se considera que el pulsador se activa en nivel bajo por lo que deberá configurar el modo pullup.

module drv_button(clk, rstn, butn, out);
input clk, rstn, butn;
output reg out = 0;
parameter INTERVAL = 500 //Intervalo de repetición mayor a 50
localparam WIDTH = $clog2(INTERVAL); //Dimensiona contador
localparam INITIAL = 0; //Inicializa estado
localparam WAITING = 1; //Verifica pulsador presionado
localparam PRESSED = 2; //Verifica pulsador liberado
localparam UNPRESS = 3; //Tiempo de espera
reg [1:0] state, nextstate;//registros de estado
reg [WIDTH-1:0] cnt = 0;//contador de pulsos clk

always @(negedge clk or negedge rstn)//flanco NEG para actualizar estado
if(!rstn) state <= INITIAL;
else state <= nextstate;

always @(posedge clk)
case(state)
  INITIAL://Condicion de reinicio
  begin
    cnt <= 0;
    out <= 1'b0;
    nextstate <= WAITING;
  end
  WAITING://Espando pulso del botón
  if(!butn)
  begin
    cnt <= cnt + 1'b1;
    if(cnt == 30) //Espera al menos 30 pulsos
    begin
      cnt <= 0;
      nextstate <= PRESSED;
    end
  end
  else cnt <= 0;
  PRESSED://Pulsador presionado
  if(butn)
  begin
    cnt <= cnt + 1'b1;
    if(cnt == 20) //Espera al menos 20 pulsos
    begin
      cnt <= 0;
      out <= 1;
      nextstate <= UNPRESS;
    end
  end
  else cnt <= 0;
  UNPRESS: //Pulsador liberado
  begin
    out <= 0;
    if(cnt >= INTERVAL) //Intervalo de repetición
    begin
      cnt <= 0; //Reinicia contador
      nextstate <= WAITING;
    end
    else cnt <= cnt + 1'b1;//Incrementa contador
  end
endcase
endmodule

 

Implementación del circuito

El código descrito previamente se muestra como símbolo dentro del esquema de circuito Quartus Web, Fig5. 

Fig5. Modulo drv_button.v

Antes de continuar con la interconexión del circuito, fue necesario llevar a cabo unas pruebas al modulo drv_button.v utilizando el siguiente modulo de estimulo:

`timescale 100us/10us
module button_tb;
reg clk, rstn, but;
wire out;
drv_button u1(clk, rstn, ~but, out);
initial begin
 clk = 0; rstn = 0; but = 0;
 forever #5 clk = ~clk; //Tiempo 5x100us
end
initial begin
  #10 rstn = 1;//1ms
  #20 but = 1; //2ms
  #40 but = 0; //4ms
  #60 but = 1; //6ms
  #400 but = 0; //40ms
  end
endmodule

Las pruebas llevadas a cabo nos muestran el siguiente diagrama de tiempos, donde se observa el adecuado funcionamiento.

Fig6. Pulso de salida sin rebote

  Continuando con nuestro circuito discriminador, ahora necesitaremos de un circuito que provea una señal de reloj con periodo de 1ms, este circuito es básicamente un divisor de frecuencia cuya entrada sera una el reloj de principal de 50MHz que posee la tarjeta EP2C5 y su salida oscilara a 1KHZ.

Para este propósito utilizaremos el modulo drv_clock cuya explicación en detalle se muestra en la siguiente entrada

Una vez agregado y convertido a símbolos los módulos drv_button y drv_clock agregaremos estos al diagrama de bloques un flip flop T que permitirá conmutar su salida con cada pulso capturado. La siguiente figura ilustra las respectivas conexiones hacia los pines de la placa EP2C5.

Fig7. Diagrama del circuito

Note que la entrada del pulsador en el modulo drv_button se activa en nivel bajo, debido a que es una entrada con resistencia pull-up, de la misma manera el led indicador conectado al flip flop,  esta en modo sumidero(sink), es decir se activa con nivel bajo.

Conclusiones

Para finalizar solo quiero agradecer tu visita a mi blog, espero que el contenido de esta entrada hubiera sido de ayuda, se que no detalle muchos pasos pero cualquier consulta al respecto puede escribirme al correo y con mucho gusto tratare de colaborar con lo que se pueda.

Enlace para descarga del proyecto Quartus Web: ep2button

Dejo un vídeo de explicativo que muestra como crear un proyecto en Quartus utilizando un diagrama de bloques. 



Referencias:

https://www.maximintegrated.com/

https://www.eejournal.com

https://softsolder.com

https://www.we-online.com


Pablo Zarate Arancibia

Electrónico
pablinzte@gmail.com