-
Notifications
You must be signed in to change notification settings - Fork 195
Capítulo 20: Comunicaciones serie asíncronas
Ejemplos de este capítulo en github
Una forma de intercambiar datos entre dos sistemas digitales es mediante las comunicaciones serie asíncronas. Durante los siguientes capítulos aprenderemos a diseñar nuestra propia unidad de comunicaciones serie asíncronas (UART) que nos permitirá comunicar la FPGA con el PC y otras placas micocontroladoras (como Arduino, BQ Zum, etc).
En este capítulo empezaremos por lo más simple: reenviar al PC todo lo recibido tirando un cable en la FPGA que una el canal de recepción (Rx) con el de transmisión (Tx). Nos aseguramos así que el nivel físico está controlado.
Este tipo de comunicación tiene la ventaja de que sólo se necesita un hilo para cada sentido: del transmisor al receptor. El pin del transmisor, por donde salen los datos digitales, se denomina Tx. Y el pin del receptor es Rx. Los bits se envían secuencialmente, uno detrás de otro.
Las comunicaciones pueden ser en ambas direcciones simultáneamente (full duplex). En ese caso habrá dos hilos, y cada circuito hará tanto de transmisor (pin Tx) como de receptor (Rx), como se muestra en la figura.
Los pines tx y rx forman parte del denominado puerto serie. Y se utiliza muchísimo para conectar el PC con diferentes circuitos. Por ejemplo para cargar programas en las placas Arduino, o comunicarte con la BQ Zum a través del bluetooth-serie.
La placa iCEstick incluye un conversor USB-serie (chip FTDI) por el que llegan las señales de Tx y Rx a la FPGA, a través de los pines 8 y 9. De esta forma tenemos conexión directa entre el puerto serie del PC y la FPGA, y podremos implementar nuestra UART para la transferencia de datos.
Los puertos serie, además de Tx y Rx para la transferencia de datos, incorporan otras señales de control. Dos muy empleadas se denominan RTS y DTR, y van en sentido PC -> FPGA. Son 2 bits que podemos usar para próposito general. Por ejemplo, en las placas compatibles Arduino el PC usa una de estas señales para hacer reset y comenzar así la carga de programas.
Para trabajar con la iCEstick, en estos tutorial, lo que nos importa es lo resumido en la siguiente figura:
Para nosotros será como si nuestro PC tuviese un puerto serie nativo cuyas señales Tx, Rx, DTR, RTS están conectadas directamente a los pines de la FPGA. A partir de ahí empezaremos a "jugar" :-)
Para acceder al puerto serie del PC tenemos que instalar un terminal de comunicaciones. Con él podremos enviar datos a la FPGA, actuar sobre las señales DTR y RTS y recibir datos. El que utilizaremos en este tutorial es el gtkterm, aunque podemos utilizar cualquier otro, como por ejemplo el monitor serie del entorno Arudino.
En ubuntu se instala muy fácilmente con:
$ sudo apt-get install gtkterm
Para tener permisos de acceso al puerto serie, tenemos que meter a nuestro usuario dentro del grupo dialout, ejecutando el comando:
$ sudo adduser $USER dialout
Es necesario salir de la sesión y volver a entrar para que tenga efecto. Si instalamos el entorno de arduino, los permisos se nos crearán automáticamente sin necesidad de usar ningún comando
Conectamos la placa iCEstick al USB y ejecutamos el gtkterm:
$ gtkterm
Si es la primera vez que lo arrancamos, posiblemente obtengamos un mensaje de error. No hay problema. Primero tenemos que configurar el puerto serie pinchando en la opción configuration / port y se nos abrirá una ventana de configuración. Establecemos como puerto serie el /dev/ttyUSB1, a la velocidad de 115200.
Nota: En ubuntu 15.04, al pinchar la iCEstick nos aparecen 2 puertos serie. El que nos permite acceder a la FPGA como puerto serie es el /dev/ttyUSB1
Le damos al OK. Para recordar esta configuración pinchamos en la opción configuration / save configuration y le damos al ok.
El terminal tiene esta pinta:
Comprobaremos que todas las señales funcionan correctamente. Para ello haremos un circuito que simplemente haga un cableado entre señales: las señales rts y dtr se conectan a los pines de los leds. Esto nos permite visualizar su estado, y cambiarlo desde el PC. La señal RX se conecta físicamente a TX, de forma que todos los caracteres recibidos se reenvían de nuevo al PC, a nivel físico (no hay procesamiento del carácter en la fpga, es un simple cable)
La descripción en Verilog es inmediata:
//-- echowire1.v
module echowire1(input wire dtr,
input wire rts,
input wire rx,
output wire tx,
output wire D1,
output wire D2);
//-- Sacar senal dtr y rts por los leds
assign D1 = dtr;
assign D2 = rts;
//-- Conectar rx a tx para que se haga un eco "cableado"
assign tx = rx;
endmodule
Lo sintetizamos con el comando:
$ make sint
Los recursos empleados son:
Recurso | ocupación |
---|---|
PIOs | 4 / 96 |
PLBs | 0 / 160 |
BRAMs | 0 / 16 |
y lo cargamos en la FPGA con:
$ sudo iceprog echowire1.bin
Para probarlo lanzamos el gtkterm. Presionando la F7 cambiamos el estado de DTR y por tanto, cambia el led. Con la tecla F8 hacemos lo mismo pero con la señal RTS.
Cualquier carácter que pulsemos se enviará a la FPGA y se hará eco. El terminal saca por pantalla todo lo recibido. El resultado es que veremos en la pantalla todo lo que escribimos.
En este vídeo de youtube se muestra el ejemplo de funcionamiento. Con las teclas F7 y F8 se cambia el estado de las señales DTR y RTS por lo que los leds cambian. También se comprueba el eco
Haremos una simulación muy básica para comprobar que está todo en orden. El banco de trabajo es el siguiente:
//-- Fichero echowire1_tb.v
module echowire1_tb();
//-- Declaracion de los cables
reg dtr = 0;
reg rts = 0;
reg rx = 0;
wire tx, led1, led2;
//-- Instanciar el componente
echowire1
dut(
.dtr(dtr),
.rts(rts),
.D1(led1),
.D2(led2),
.tx(tx),
.rx(rx)
);
//-- Generar cambios en dtr. Los mismos deben reflejarse en el cable D1
always
#2 dtr <= ~dtr;
//-- Generar cambios en rts. Se deben reflejar en el cable D2
always
#3 rts = ~rts;
//-- Generar cambios en rs. Se reflejan en TX
always
#1 rx <= ~rx;
//-- Proceso al inicio
initial begin
//-- Fichero donde almacenar los resultados
$dumpfile("echowire1_tb.vcd");
$dumpvars(0, echowire1_tb);
# 200 $display("FIN de la simulacion");
$finish;
end
endmodule
Para simular ejecutamos:
$ make sim
Y el resultado es:
Se han agrupado por colores las señales que deben tener la misma forma. Se comprueba que todo funciona como se espera
Modificaremos el ejemplo anterior para que la conexión entre Tx y Rx se haga mediante un cable externo. De esta forma si quitamos el cable se interrumpirá la comunicación. Y en cuanto lo pongamos el eco volverá.
El esquema del componente es:
y su descripción en Verilog:
//-- echowire2.v
module echowire2(input wire dtr,
input wire rts,
input wire rx,
input wire tx2,
output wire tx,
output wire rx2,
output wire D1,
output wire D2);
//-- Sacar senal dtr y rts por los leds
assign D1 = dtr;
assign D2 = rts;
//-- Sacar señales tx y rx al exterior
assign rx2 = rx;
assign tx = tx2;
endmodule
Lo sintetizamos con el comando:
$ make sint2
Los recursos empleados son:
Recurso | ocupación |
---|---|
PIOs | 5 / 96 |
PLBs | 0 / 160 |
BRAMs | 0 / 16 |
y lo cargamos en la FPGA con:
$ sudo iceprog echowire2.bin
Las pruebas las hacemos igual que en el ejemplo anterior, pero ahora colocamos un cable externo que una los pines 44 y 45:
Cuando el cable está conectado, el eco se hace con normalidad. Si quitamos el cable, la comunicación se interrumpirá, como se puede ver en este vídeo de youtube
El código del banco de trabajo es similar al del ejemplo anterior, pero añadiendo un "cable externo"
module echowire2_tb();
//-- Declaracion de los cables
reg dtr = 0;
reg rts = 0;
reg rx = 0;
wire tx, led1, led2;
wire tx2, rx2;
wire extwire;
//-- Instanciar el componente
echowire2
dut(
.dtr(dtr),
.rts(rts),
.D1(led1),
.D2(led2),
.tx2(tx2),
.rx2(rx2),
.tx(tx),
.rx(rx)
);
//-- Generar cambios en dtr. Los mismos deben reflejarse en el cable D1
always
#2 dtr <= ~dtr;
//-- Generar cambios en rts. Se deben reflejar en el cable D2
always
#3 rts = ~rts;
//-- Generar cambios en rs. Se reflejan en TX
always
#1 rx <= ~rx;
//-- Conectar el cable externo
assign tx2 = rx2;
//-- Proceso al inicio
initial begin
//-- Fichero donde almacenar los resultados
$dumpfile("echowire2_tb.vcd");
$dumpvars(0, echowire2_tb);
# 200 $display("FIN de la simulacion");
$finish;
end
endmodule
Para simular ejecutamos:
$ make sim2
Y el resultado es:
- Avanzado: Hacer un programa en python que use la biblioteca pyserial para generar una señal de reloj en la señal DTR. Esto será muy útil para poder controlar la velocidad de nuestros circuitos desde el PC, para hacer pruebas
- Ya lo tenemos todo listo para empezar a implementar nuestra unidad de comunicaciones serie (UART)
0 You are leaving the privative sector (EN)
1 ¡Hola mundo! (EN) (RU)
2 De un bit a datos (EN)
3 Puerta NOT (EN)
4 Contador de 26 bits (EN)
5 Prescaler de N bits (EN)
6 Múltiples prescalers (EN)
7 Contador de 4 bits con prescaler (EN)
8 Registro de 4 bits (EN)
9 Inicializador (EN)
10 Registro de desplazamiento (EN)
11 Multiplexor de 2 a 1 (EN)
12 Multiplexor de M a 1 (EN)
13 Inicializando registros (EN)
14 Registro de N bits con reset síncrono
15 Divisor de frecuencias
16 Contador de segundos
17 Generando tonos audibles
18 Tocando notas
19 Secuenciando notas
20 Comunicaciones serie asíncronas
21 Baudios y transmisión
22 Reglas de diseño síncrono
23 Controladores y autómatas finitos
24 Unidad de transmisión serie asíncrona
25 Unidad de recepción serie asíncrona
26 Memoria ROM
27 Memoria ROM genérica
28 Memoria RAM
29 Puertas triestado
30 Hacia el microprocesador y más allá