Verilog Práctico
Capítulo 8 - Debouncing Buttons
En este tutorial veremos varias técnicas para eliminar los rebotes en pulsadores e interruptores.
Los rebotes o bouncing son un fenómeno que se da cuando accionamos pulsadores e interruptores y consiste en una serie de picos en la señal, que pueden afectar de manera negativa al comportamiento de un circuito.
Para probar el funcionamiento del código Verilog vamos a utilizar un pulsador externo con una resistencia pull-down en la entrada D7. La salida libre de ruido la configuramos en el pin D8.
Colocaremos las sondas del osciloscopio en los pines D7 y D8 para ver el resultado.
Ejemplo 1.
Este circuito detecta cuando se pulsa el switch de entrada y almacena su valor en un registro. Pasado un tiempo pone la salida a 1. Veamos cómo funciona.
Declaramos las E/S: btn_in es el pulsador de entrada y out el pulso de salida.
module debouncer(input wire btn_in, clk, output wire out);
Los registros btn_prev y btn_out sirven para almacenar los valores intermedios.
reg btn_prev = 0;
reg btn_out = 0;
Declaramos un contador reg [16:0] counter = 0; que utilizaremos como temporizador hasta que la entrada se estabilice y después ponemos la salida a 1.
La sentencia always @(posedge clk) begin es conocida por todos; con cada flanco de reloj comprobamos que:
Si pulsamos el botón
if (btn_prev ^ btn_in == 1'b1)
Ponemos el temporizador a cero
counter <= 0
Capturamos el 1 de la entrada y lo almacenamos en el registro btn_prev
btn_prev <= btn_in;
Ahora btn_prev y btn_in son iguales, entonces hay que comprobar si ha pasado el tiempo predefinido, antes de poner la salida a 1.
Esto se hace con el contador: si el bit más significativo es cero, quiere decir que no hemos llegado al tiempo establecido
if (counter[16] == 1'b0) luego hay que incrementar el contador counter <= counter + 1
Cuando llegamos al tiempo programado, ponemos a 1 la variable intermedia btn_out
btn_out <= btn_prev
y asignamos a la salida el valor del registro intermedio btn_out
assign out = btn_out
Ponemos todo el código junto y queda así:
//-- Debouncer Circuit
//-- It produces a stable output when
//-- input signal is bouncing
module debouncer(input wire btn_in, clk, output wire out);
reg btn_prev = 0;
reg btn_out = 0;
reg [16:0] counter = 0;
always @(posedge clk) begin
//-- If btn_prev and btn_in are differents (^ = XOR operator)
if (btn_prev ^ btn_in == 1'b1)
begin
counter <= 0; //-- Reset the counter
btn_prev <= btn_in; //-- Capture the button status
end
else if (counter[16] == 1'b0) //-- If no timeout, increase the counter
counter <= counter + 1;
else
btn_out <= btn_prev; //-- Set the output to the stable value
end
assign out = btn_out;
endmodule
Archivo .pcf
set_io btn_in 112
set_io out 138
set_io clk 21
Lo subimos a la Alhambra y lo que vemos es un pulso de igual duración que el original, pero retrasado unos 5.4 milisegundos para dar tiempo a que la señal se estabilice y así eliminar los rebotes.
El tiempo de espera se configura con el tamaño del contador; si incrementamos los bits del registro contador, aumenta el tiempo de espera antes de poner la salida a 1.
Por ejemplo si cambiamos el 16 por un 18 en las líneas reg [18:0] counter = 0; y else if (counter[18] == 1'b0) el tiempo de espera pasa de los 5.4 ms originales a 20 ms.
Ejemplo 2
Este ejemplo produce un pulso de 80 nanosegundos de duración, independientemente del tiempo que este pulsado el switch. EL pulso tiene lugar 5 milisegundos después de pulsar el botón.
El pulso es extremadamente breve, apenas dos ciclos de reloj de la Alhambra, por eso aparece y desaparece antes de que levantemos el dedo del pulsador.
// Sincronizacion. Evitar
// problema de la metaestabilidad
module debouncer (input wire btn_in, clk, output wire out);
reg d2;
reg r_in;
always @(posedge clk)
d2 <= btn_in;
always @(posedge clk)
r_in <= d2;
reg btn_prev = 0;
reg btn_out = 0;
reg [16:0] counter = 0;
always @(posedge clk) begin
if (btn_prev ^ r_in == 1'b1) begin
counter <= 0;
btn_prev <= r_in;
end
else if (counter[16] == 1'b0)
counter <= counter + 1;
else
btn_out <= btn_prev;
end
//-- Generar tic en flanco de subida del boton
reg old;
always @(posedge clk)
old <= btn_out;
assign out = !old & btn_out;
endmodule
Archivo .pcf
set_io btn_in 112
set_io out 139
set_io clk 21
Subimos el código a la Alhambra y vemos que a los 5 ms de pulsar el botón, se produce un único pulso...
… de 80 ns de duración.
EL código de este ejemplo es muy parecido al del ejemplo 1, entonces ¿cómo se consigue el pulso único ? Veamos.
Con las sentencias btn_out <= btn_prev y old <= btn_out dentro cada una de una declaración always @(posedge clk) diferente, obtenemos un desfase de un ciclo de reloj entre ambas señales:
Y al hacer la operación assign out = !old & btn_out; obtenemos un pulso de duración un ciclo de reloj:
Ejemplo 3. Múltiples switches
En caso de tener varios pulsadores, Verilog permite crear un módulo principal y enlazar tantos pulsadores como deseemos sin necesidad de copiar y pegar el código para cada pulsador que queramos añadir.
Por ejemplo, en el esquema de la imagen tenemos dos pulsadores SW1 y SW2 cuyas entradas queremos eliminar los rebotes. Las salidas sin rebotes son OUT1 y OUT2.
Vamos a modificar el código del ejemplo 1 para añadir un segundo pulsador.
Definimos un módulo principal, donde declaramos las entradas y salidas del circuito.
module debouncer_master (input clk, in1_D0, in2_D2, output out1_D1, out2_D3);
Dentro del módulo principal creamos las instancias debouncer sw1 y debouncer sw2 con las respectivas E/S. Lo que hacemos con esta declaración es enlazar las entradas de la instancia con las del módulo debouncer, es decir, enlazamos clk con clk; in1_D0 con btn_in y out1_D1 con out.
debouncer sw1 (clk, in1_D0, out1_D1);
debouncer sw2 (clk, in2_D2, out2_D3);
El código del módulo debouncer no cambia.
module debouncer(input clk, btn_in, output out);
reg btn_prev = 0;
reg btn_out = 0;
reg [16:0] counter = 0;
always @(posedge clk) begin
if (btn_prev ^ btn_in == 1'b1)
begin
counter <= 0;
btn_prev <= btn_in;
end
else if (counter[16] == 1'b0)
counter <= counter + 1;
else
btn_out <= btn_prev;
end
assign out = btn_out;
endmodule
Lo ponemos todo junto y queda así:
module debouncer_master (input clk, in1_D0, in2_D2, output out1_D1, out2_D3);
debouncer sw1 (clk, in1_D0, out1_D1);
debouncer sw2 (clk, in2_D2, out2_D3);
endmodule
module debouncer(input clk, btn_in, output out);
reg btn_prev = 0;
reg btn_out = 0;
reg [16:0] counter = 0;
always @(posedge clk) begin
if (btn_prev ^ btn_in == 1'b1)
begin
counter <= 0;
btn_prev <= btn_in;
end
else if (counter[16] == 1'b0)
counter <= counter + 1;
else
btn_out <= btn_prev;
end
assign out = btn_out;
endmodule
Archivo .pcf
set_io in1_D0 119
set_io out1_D1 118
set_io in2_D2 117
set_io out2_D3 116
set_io clk 21
Eso es todo por el momento, espero que os haya gustado.
¡Hasta pronto!
Links
Los ejemplos 1 y 2 los he sacado de las colecciones del curso digital-electronics-with-open-FPGAs
Icono del reloj de arena de las animaciones gif cortesía de canstockphoto.es
Descargas
Descarga los ejemplos en verilog aquí abajo.