Verilog Práctico

Capítulo 8 - Debouncing Buttons

21 de marzo de 2021

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.