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