Tutorial: Implementar comunicación UART en FPGA

Descubre cómo implementar la comunicación UART en una FPGA

# Introducción

La comunicación UART es una de las más utilizadas en el mundo de la electrónica, ya que es una de las más sencillas de implementar y es muy útil para la comunicación entre dispositivos. En este tutorial aprenderás a implementar la comunicación UART en una FPGA, utilizando el lenguaje de descripción de hardware Verilog.

# ¿Cómo funciona la comunicación UART?

La comunicación UART es una comunicación asíncrona, lo que significa que no se necesita un reloj para sincronizar los datos. En su lugar, se utiliza un bit de inicio y un bit de parada para sincronizar los datos. La comunicación UART se basa en el envío de bytes, que son paquetes de 8 bits (puede variar pero 8 bits es la mas extendida). Cada byte se envía en serie, es decir, un bit a la vez. El bit de inicio es un bit de nivel bajo que indica que se va a enviar un byte. El bit de parada es un bit de nivel alto que indica que se ha terminado de enviar el byte. Entre el bit de inicio y el bit de parada se envían los 8 bits del byte. La siguiente imagen muestra un ejemplo de la comunicación UART.

# Baudrate

El baudrate es la velocidad a la que se envían los datos. Se mide en baudios, que es el número de bits que se envían por segundo. Por ejemplo, si el baudrate es de 9600 baudios, se envían 9600 bits por segundo. El baudrate se puede calcular con la siguiente fórmula:

$$baudrate = \frac{\text{Frecuencia del reloj}}{\text{divisiones de relog}}$$

El baudrate tiene valores estandar por lo cual lo que se calcula en realidad son las divisiones de reloj, lo cual cambia la formula a la siguiente:

$$\text{divisiones de reloj} = \frac{\text{Frecuencia del reloj}}{\text{baudrate}}$$

Para un reloj de 27 Mhz a un baudrate de 115200, las divisiones de reloj serían:

$$\text{divisiones de reloj} = \frac{27 \text{ Mhz}}{115200} = 234.375$$

Como las divisiones de reloj es un numero entero, el resultado se redondea a 234. Por lo tanto, para un reloj de 27 Mhz a un baudrate de 115200, las divisiones de reloj serían 234.

# Leer datos de la UART

Para leer datos de la UART, se debe leer el bit de inicio, los 8 bits del byte y el bit de parada. Para la lectura y asegurarnos de leer correctamente los datos de la UART, se debe utilizar un reloj que sea 16 veces más rápido que el baudrate. Por ejemplo, si el baudrate es de 115200, el reloj debe ser de 1843200 Hz. Esto se debe a que se deben leer 10 bits (1 bit de inicio, 8 bits del byte y 1 bit de parada) y el reloj debe ser 16 veces más rápido que el baudrate. La lectura no se hace 16 veces por segundo, sino que se lee un bit cada 8 ciclos de reloj para asegurarnos que leemos el dato en la mitad del tiempo que tarda en llegar el siguiente bit. La siguiente imagen muestra un ejemplo de la lectura de datos de la UART.

# Escribir datos en la UART

La escritura es simular a la lectura, para escribir datos en la UART, se debe escribir el bit de inicio, los 8 bits del byte y el bit de parada. Para la escritura y asegurarnos de escribir correctamente los datos en la UART, se debe utilizar un reloj que sea 16 veces más rápido que el baudrate. Esto se debe a que se deben escribir 10 bits (1 bit de inicio, 8 bits del byte y 1 bit de parada) al igual que en la lectura. La escritura no se hace 16 veces por segundo, sino que se escribe un bit cada 8 ciclos de reloj para asegurarnos que escribimos el dato en la mitad del tiempo que tarda en llegar el siguiente bit. Ambos procesos (lectura y escritura) se pueden hacer con al mismo tiempo ya que son independientes entre si y por cables diferentes.

# Implementación en Verilog

A continuación se muestra el código en Verilog para implementar la comunicación UART en una FPGA. El código se puede descargar desde Github

# Maquina de estados

Para implementar la comunicación UART vamos a usar dos maquinas de estados, una para la lectura y otra para la escritura. La maquina de estados para la lectura se llama RX y la maquina de estados para la escritura se llama TX. Ambas maquinas de estados se ejecutan al mismo tiempo ya que son independientes entre si y por cables diferentes.

# Maquina de estados RX

Esta maquina tienen 5 estados, IDLE, START_BIT, READ_WAIT, READ y STOP_BIT. El estado IDLE es el estado inicial y se queda en este estado hasta que se detecta el bit de inicio. Cuando se detecta el bit de inicio, se pasa al estado START_BIT y se empieza a leer los 8 bits del byte, con el estado intermedio READ_WAIT que hace la division en 16 partes como explique en la teoría. Cuando se termina de leer los 8 bits del byte, se pasa al estado STOP_BIT y se espera a que llegue el bit de parada. Cuando se detecta el bit de parada, se pasa al estado IDLE y se termina la lectura del byte.

Nota: Usamos el localparam HALF_DELAY_WAIT y el DELAY_FRAMES para hacer la division en 16 partes. El DELAY_FRAMES es el numero de ciclos de reloj que se espera en el estado READ_WAIT y el HALF_DELAY_WAIT es la mitad del DELAY_FRAMES. El DELAY_FRAMES se calcula con la formula que mostré en la teoría.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
localparam RX_STATE_IDLE = 0;
localparam RX_STATE_START_BIT = 1;
localparam RX_STATE_READ_WAIT = 2;
localparam RX_STATE_READ = 3;
localparam RX_STATE_STOP_BIT = 5;

always @(posedge clk) begin
    case (rxState)
        RX_STATE_IDLE: begin
            if (uart_rx == 0) begin
                rxState <= RX_STATE_START_BIT;
                rxCounter <= 1;
                rxBitNumber <= 0;
                byteReady <= 0;
            end
        end 
        RX_STATE_START_BIT: begin
            if (rxCounter == HALF_DELAY_WAIT) begin
                rxState <= RX_STATE_READ_WAIT;
                rxCounter <= 1;
            end else 
                rxCounter <= rxCounter + 1;
        end
        RX_STATE_READ_WAIT: begin
            rxCounter <= rxCounter + 1;
            if ((rxCounter + 1) == DELAY_FRAMES) begin
                rxState <= RX_STATE_READ;
            end
        end
        RX_STATE_READ: begin
            rxCounter <= 1;
            dataIn <= {uart_rx, dataIn[7:1]};
            rxBitNumber <= rxBitNumber + 1;
            if (rxBitNumber == 3'b111)
                rxState <= RX_STATE_STOP_BIT;
            else
                rxState <= RX_STATE_READ_WAIT;
        end
        RX_STATE_STOP_BIT: begin
            rxCounter <= rxCounter + 1;
            if ((rxCounter + 1) == DELAY_FRAMES) begin
                rxState <= RX_STATE_IDLE;
                rxCounter <= 0;
                byteReady <= 1;
            end
        end
    endcase
end

# Maquina de estados TX

Con una aproximación similar realizamos la maquina de estados para la escritura. Esta maquina tienen 5 estados, IDLE, START_BIT, WRITE , STOP_BIT y por ultimo DEBOUNCE que es el estado diferente, este estado lo que hace es mantener en alto la salida, lo que indica que no se están enviando datos, recordemos que el start bit es un bit de nivel bajo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
localparam TX_STATE_IDLE = 0;
localparam TX_STATE_START_BIT = 1;
localparam TX_STATE_WRITE = 2;
localparam TX_STATE_STOP_BIT = 3;
localparam TX_STATE_DEBOUNCE = 4;

always @(posedge clk) begin
    case (txState)
        TX_STATE_IDLE: begin
            if (btn1 == 0) begin
                txState <= TX_STATE_START_BIT;
                txCounter <= 0;
                txByteCounter <= 0;
            end
            else begin
                txPinRegister <= 1;
            end
        end 
        TX_STATE_START_BIT: begin
            txPinRegister <= 0;
            if ((txCounter + 1) == DELAY_FRAMES) begin
                txState <= TX_STATE_WRITE;
                dataOut <= testMemory[txByteCounter];
                txBitNumber <= 0;
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_WRITE: begin
            txPinRegister <= dataOut[txBitNumber];
            if ((txCounter + 1) == DELAY_FRAMES) begin
                if (txBitNumber == 3'b111) begin
                    txState <= TX_STATE_STOP_BIT;
                end else begin
                    txState <= TX_STATE_WRITE;
                    txBitNumber <= txBitNumber + 1;
                end
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_STOP_BIT: begin
            txPinRegister <= 1;
            if ((txCounter + 1) == DELAY_FRAMES) begin
                if (txByteCounter == MEMORY_LENGTH - 1) begin
                    txState <= TX_STATE_DEBOUNCE;
                end else begin
                    txByteCounter <= txByteCounter + 1;
                    txState <= TX_STATE_START_BIT;
                end
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_DEBOUNCE: begin
            if (txCounter == 23'b111111111111111111) begin
                if (btn1 == 1) 
                    txState <= TX_STATE_IDLE;
            end else
                txCounter <= txCounter + 1;
        end
    endcase      
end

# Led de estado

Para verificar que nuestra comunicacion este funcionando para ambos sentidos, debido a que cada cable es independiente, usamos los leds integrados de en mi caso la FPGA Tang Nano 9K. Para esto usamos el siguiente codigo:

1
2
3
4
5
always @(posedge clk) begin
    if (byteReady) begin
        led <= ~dataIn[5:0];
    end
end

Nota: dataIn es el byte que se recibe de la UART y sale de la maquina de estados RX, para la tang nano 9k el led es activo bajo, por lo cual se debe negar el byte para que se encienda el led.

Para verificar el envio de datos vamos a usar el puerto serial para leer los datos, vamos a crear un arreglo de datos para enviar y lo vamos a enviar cada vez que se presione el boton 1. Para esto usamos el siguiente codigo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
reg [3:0] txState = 0;
reg [24:0] txCounter = 0;
reg [7:0] dataOut = 0;
reg txPinRegister = 1;
reg [2:0] txBitNumber = 0;
reg [4:0] txByteCounter = 0;

assign uart_tx = txPinRegister;

localparam MEMORY_LENGTH = 18;
reg [7:0] testMemory [MEMORY_LENGTH-1:0];

initial begin
    testMemory[0] = "F";
    testMemory[1] = "a";
    testMemory[2] = "b";
    testMemory[3] = "i";
    testMemory[4] = "a";
    testMemory[5] = "n";
    testMemory[6] = "a";
    testMemory[7] = "l";
    testMemory[8] = "v";
    testMemory[9] = "a";
    testMemory[10] = "r";
    testMemory[11] = "e";
    testMemory[12] = "z";
    testMemory[13] = ".";
    testMemory[14] = "d";
    testMemory[15] = "e";
    testMemory[16] = "v";
    testMemory[17] = " ";
end

y con esto ya tenemos nuestra comunicación UART implementada en Verilog.

# Pinout de la FPGA

En una FPGA cualquier pin sirve como RX y TX, en el caso de la Tang Nano 9K tiene una conexion directa con el puerto usb, por lo cual se puede conectar directamente a la computadora y usar el puerto serial para leer y escribir datos. En el caso de la Tang Nano 9K, el pin RX es el pin 18 y el pin TX es el pin 17. La siguiente imagen muestra el pinout de la Tang Nano 9K. Pinout de la Tang Nano 9K

# Conclusión

En este tutorial, has aprendido a implementar la comunicación UART en una FPGA utilizando el lenguaje de descripción de hardware Verilog. Comprendes cómo funciona la comunicación UART, cómo leer y escribir datos, y cómo implementarla en Verilog. Además, has aprendido a utilizar los LEDs integrados en la FPGA para verificar el funcionamiento de la comunicación. Si tienes alguna pregunta, no dudes en dejar un comentario, y estaré encantado de ayudarte a resolverla. ¡Buena suerte en tu proyecto de FPGA!

# Codigo completo

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
`default_nettype none

module top
#(
    parameter DELAY_FRAMES = 234 // 27,000,000 (27Mhz) / 115200 Baud rate
)
(
    input  clk,
    input  uart_rx,
    output uart_tx,
    output reg [5:0] led,
    input btn1
);

localparam HALF_DELAY_WAIT = (DELAY_FRAMES / 2);

reg [3:0] rxState = 0;
reg [12:0] rxCounter = 0;
reg [7:0] dataIn = 0;
reg [2:0] rxBitNumber = 0;
reg byteReady = 0;

localparam RX_STATE_IDLE = 0;
localparam RX_STATE_START_BIT = 1;
localparam RX_STATE_READ_WAIT = 2;
localparam RX_STATE_READ = 3;
localparam RX_STATE_STOP_BIT = 5;

always @(posedge clk) begin
    case (rxState)
        RX_STATE_IDLE: begin
            if (uart_rx == 0) begin
                rxState <= RX_STATE_START_BIT;
                rxCounter <= 1;
                rxBitNumber <= 0;
                byteReady <= 0;
            end
        end 
        RX_STATE_START_BIT: begin
            if (rxCounter == HALF_DELAY_WAIT) begin
                rxState <= RX_STATE_READ_WAIT;
                rxCounter <= 1;
            end else 
                rxCounter <= rxCounter + 1;
        end
        RX_STATE_READ_WAIT: begin
            rxCounter <= rxCounter + 1;
            if ((rxCounter + 1) == DELAY_FRAMES) begin
                rxState <= RX_STATE_READ;
            end
        end
        RX_STATE_READ: begin
            rxCounter <= 1;
            dataIn <= {uart_rx, dataIn[7:1]};
            rxBitNumber <= rxBitNumber + 1;
            if (rxBitNumber == 3'b111)
                rxState <= RX_STATE_STOP_BIT;
            else
                rxState <= RX_STATE_READ_WAIT;
        end
        RX_STATE_STOP_BIT: begin
            rxCounter <= rxCounter + 1;
            if ((rxCounter + 1) == DELAY_FRAMES) begin
                rxState <= RX_STATE_IDLE;
                rxCounter <= 0;
                byteReady <= 1;
            end
        end
    endcase
end

always @(posedge clk) begin
    if (byteReady) begin
        led <= ~dataIn[5:0];
    end
end

reg [3:0] txState = 0;
reg [24:0] txCounter = 0;
reg [7:0] dataOut = 0;
reg txPinRegister = 1;
reg [2:0] txBitNumber = 0;
reg [4:0] txByteCounter = 0;

assign uart_tx = txPinRegister;

localparam MEMORY_LENGTH = 18;
reg [7:0] testMemory [MEMORY_LENGTH-1:0];

initial begin
    testMemory[0] = "F";
    testMemory[1] = "a";
    testMemory[2] = "b";
    testMemory[3] = "i";
    testMemory[4] = "a";
    testMemory[5] = "n";
    testMemory[6] = "a";
    testMemory[7] = "l";
    testMemory[8] = "v";
    testMemory[9] = "a";
    testMemory[10] = "r";
    testMemory[11] = "e";
    testMemory[12] = "z";
    testMemory[13] = ".";
    testMemory[14] = "d";
    testMemory[15] = "e";
    testMemory[16] = "v";
    testMemory[17] = " ";
end

localparam TX_STATE_IDLE = 0;
localparam TX_STATE_START_BIT = 1;
localparam TX_STATE_WRITE = 2;
localparam TX_STATE_STOP_BIT = 3;
localparam TX_STATE_DEBOUNCE = 4;

always @(posedge clk) begin
    case (txState)
        TX_STATE_IDLE: begin
            if (btn1 == 0) begin
                txState <= TX_STATE_START_BIT;
                txCounter <= 0;
                txByteCounter <= 0;
            end
            else begin
                txPinRegister <= 1;
            end
        end 
        TX_STATE_START_BIT: begin
            txPinRegister <= 0;
            if ((txCounter + 1) == DELAY_FRAMES) begin
                txState <= TX_STATE_WRITE;
                dataOut <= testMemory[txByteCounter];
                txBitNumber <= 0;
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_WRITE: begin
            txPinRegister <= dataOut[txBitNumber];
            if ((txCounter + 1) == DELAY_FRAMES) begin
                if (txBitNumber == 3'b111) begin
                    txState <= TX_STATE_STOP_BIT;
                end else begin
                    txState <= TX_STATE_WRITE;
                    txBitNumber <= txBitNumber + 1;
                end
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_STOP_BIT: begin
            txPinRegister <= 1;
            if ((txCounter + 1) == DELAY_FRAMES) begin
                if (txByteCounter == MEMORY_LENGTH - 1) begin
                    txState <= TX_STATE_DEBOUNCE;
                end else begin
                    txByteCounter <= txByteCounter + 1;
                    txState <= TX_STATE_START_BIT;
                end
                txCounter <= 0;
            end else 
                txCounter <= txCounter + 1;
        end
        TX_STATE_DEBOUNCE: begin
            if (txCounter == 23'b111111111111111111) begin
                if (btn1 == 1) 
                    txState <= TX_STATE_IDLE;
            end else
                txCounter <= txCounter + 1;
        end
    endcase      
end
endmodule
comments powered by Disqus
Creado con Hugo
Tema Stack diseñado por Jimmy