Verilog Práctico

Capítulo 5. Display de 4x7 segmentos.

17 de enero de 2021

En esta entrada aprenderemos cómo multiplexar un display de 7 segmentos x 4 dígitos de ánodo común, modelo 3461BS. Esta técnica de conmutación consiste en iluminar un dígito cada vez, pero a una frecuencia lo suficientemente alta como para engañar al ojo humano y que parezca que están todos encendidos a la vez, así ahorramos pines de E/S en la FPGA. 

El esquema de conexión es similar al utilizado en el capítulo 4 de esta colección, pero añadiendo cuatro MOSFET-P para conmutar los dígitos.

La imagen siguiente muestra el pinout de los componentes utilizados. 

1. Contador decimal

Empezaremos con un ejemplo sencillo, que nos permitirá además, comprobar que todo funciona correctamente. Luego añadiremos funciones extra al código original.

Se trata de un contador manual accionado mediante el pulsador SW1. Con el SW2 conmutamos los dígitos del display.

// decodificador decimal para display 3461BS

// contador manual por flanco de sw1

// conmutamos los dígitos con sw2

module BS3461( input sw1, input sw2, output reg [7:0] segmentos = 0, output reg [3:0] digitos =0);


// en este registro se almacena el valor del contador

reg [3:0] contador = 0;

always @(posedge sw1)

  begin


// comprobamos el contador

// y si llega a 10 lo reseteamos

      if (contador>=4'd10)

        contador = 4'd0;


// con cada pulsación de SW1 incrementamos el contador

    contador <= contador + 1;


    case (contador)

      4'd0: segmentos <= 8'b00000011;

      4'd1: segmentos <= 8'b10011111;

      4'd2: segmentos <= 8'b00100101;

      4'd3: segmentos <= 8'b00001101;

      4'd4: segmentos <= 8'b10011001;

      4'd5: segmentos <= 8'b01001001;

      4'd6: segmentos <= 8'b01000001;

      4'd7: segmentos <= 8'b00011111;

      4'd8: segmentos <= 8'b00000001;

      4'd9: segmentos <= 8'b00001001;

      default: segmentos <= 8'b11111111;

    endcase

end


// contador para conmutar los digitos

reg [1:0]  mpx = 0;

always @(posedge sw2)

  begin


// con cada pulsación de sw2 incrementamos el contador

    mpx <= mpx + 1;


// dependiendo del valor del contador "mpx"

// asignamos un valor al registro "digitos"

// para que ilumine el dígito correspondiente: uds, dec, cent, miles.

    case (mpx)

      2'd0: digitos <= 4'b1110;

      2'd1: digitos <= 4'b1101;

      2'd2: digitos <= 4'b1011;

      2'd3: digitos <= 4'b0111;

      default: digitos <=4'b1111;

    endcase

end

endmodule

El archivo .pcf queda así:

set_io sw1 10

set_io sw2 11

set_io segmentos[7] 119

set_io segmentos[6] 118

set_io segmentos[5] 117

set_io segmentos[4] 116

set_io segmentos[3] 115

set_io segmentos[2] 114

set_io segmentos[1] 113

set_io segmentos[0] 112

set_io digitos[0] 80

set_io digitos[1] 81

set_io digitos[2] 88

set_io digitos[3] 87

Lo compilamos y subimos a la Alhambra. Con el SW1 se incrementa el contador y con el SW2 conmutamos los dígitos. 

2. Contador decimal con multiplexor


En este ejemplo vamos a añadir un prescaler para conmutar el encendido de los dígitos de manera automática. 


Asignaremos a la señal clk_digitos el bit 15 del prescaler para que la conmutación sea lo suficientemente rápida al ojo humano y parezca que todos los dígitos se iluminan a la vez. Os invito a que hagáis pruebas asignando diferentes bits a la señal de refresco y ver cómo afecta al encendido de los bits; si ponemos un valor bajo veremos conmutar los dígitos. 

// Multiplexador para display 4x7 segmentos con

// contador manual por flanco de SW1 y conmutacion automática entre dígitos.


module BS3461( input sw1, input clk, output clk_digitos, output reg [7:0] segmentos = 0, output reg [3:0] digitos =0);

wire clk;

reg [17:0] prescaler = 0;           // registro prescaler

assign clk_digitos = prescaler[15]; // asignamos la frecuencia de refresco al bit 15 del prescaler

always @(posedge(clk))

begin

  prescaler <= prescaler + 1;

end


// en este registro se almacena el valor del contador 0-9

reg [3:0] contador = 0;

always @(posedge sw1)

  begin


// comprobamos el contador

// y si llega a 10 lo reseteamos

      if (contador>=4'd10)

        begin

          contador = 4'd0;

        end

// con cada pulsación de SW1 incrementamos el contador

    contador <= contador + 1;

    case (contador)

      4'd0: segmentos <= 8'b00000011;

      4'd1: segmentos <= 8'b10011111;

      4'd2: segmentos <= 8'b00100101;

      4'd3: segmentos <= 8'b00001101;

      4'd4: segmentos <= 8'b10011001;

      4'd5: segmentos <= 8'b01001001;

      4'd6: segmentos <= 8'b01000001;

      4'd7: segmentos <= 8'b00011111;

      4'd8: segmentos <= 8'b00000001;

      4'd9: segmentos <= 8'b00001001;

      default: segmentos <= 8'b11111111;

    endcase

end


// contador para los digitos

reg [1:0]  mpx = 0;

always @(posedge clk_digitos)

  begin

    mpx <= mpx + 1;

    case (mpx)

   2'd0: digitos <= 4'b1110;

      2'd1: digitos <= 4'b1101;

      2'd2: digitos <= 4'b1011;

      2'd3: digitos <= 4'b0111;

    endcase

end

endmodule

El archivo .pcf

set_io sw1 10

set_io clk 21

set_io clk_digitos 95

set_io segmentos[7] 119

set_io segmentos[6] 118

set_io segmentos[5] 117

set_io segmentos[4] 116

set_io segmentos[3] 115

set_io segmentos[2] 114

set_io segmentos[1] 113

set_io segmentos[0] 112

set_io digitos[0] 80

set_io digitos[1] 81

set_io digitos[2] 88

set_io digitos[3] 87 

Subimos el código a la Alhambra y vemos cómo con cada pulsación de SW1 se incrementa el contador, pero ahora se iluminan todos los dígitos a la vez. 

3. Contador automático

En este ejemplo vamos a utilizar dos frecuencias de reloj; una clk_digitos para seleccionar la frecuencia de refresco de los dígitos y otra clk_contador para seleccionar la velocidad con la que se incrementa el contador.

// Multiplexador automático para display 3461BS con prescaler


module BS3461( input clk, output clk_contador, output clk_digitos, output reg [7:0] segmentos = 0, output reg [3:0] digitos =0);

wire clk;

reg [22:0] prescaler = 0;

assign clk_digitos = prescaler[15];  // asignamos la frecuencia del multiplexor al bit 15 del prescaler

assign clk_contador = prescaler[22]; // asignamos la frecuencia del contador 0-9 al bit 22 del prescaler


always @(posedge(clk))

begin

  prescaler <= prescaler + 1;

end


// en este registro se almacena el valor del contador 0-9

reg [3:0] contador = 0;

always @(posedge clk_contador)

  begin


// comprobamos el contador

// y si llega a 10 lo reseteamos

      if (contador>=4'd10)

        begin

          contador = 4'd0;

        end


    contador <= contador + 1;


    case (contador)

      4'd0: segmentos <= 8'b00000011;

      4'd1: segmentos <= 8'b10011111;

      4'd2: segmentos <= 8'b00100101;

      4'd3: segmentos <= 8'b00001101;

      4'd4: segmentos <= 8'b10011001;

      4'd5: segmentos <= 8'b01001001;

      4'd6: segmentos <= 8'b01000001;

      4'd7: segmentos <= 8'b00011111;

      4'd8: segmentos <= 8'b00000001;

      4'd9: segmentos <= 8'b00001001;

      default: segmentos <= 8'b11111111;

    endcase

end


// contador para los digitos

reg [1:0]  mpx = 0;

always @(posedge clk_digitos)

  begin

    mpx <= mpx + 1;

    case (mpx)

   2'd0: digitos <= 4'b1110;

      2'd1: digitos <= 4'b1101;

      2'd2: digitos <= 4'b1011;

      2'd3: digitos <= 4'b0111;

    endcase

end

endmodule

El archivo .pcf es el siguiente: 

set_io clk 21

set_io clk_digitos 95

set_io clk_contador 96

set_io segmentos[7] 119

set_io segmentos[6] 118

set_io segmentos[5] 117

set_io segmentos[4] 116

set_io segmentos[3] 115

set_io segmentos[2] 114

set_io segmentos[1] 113

set_io segmentos[0] 112

set_io digitos[0] 80

set_io digitos[1] 81

set_io digitos[2] 88

set_io digitos[3] 87 

Subimos el código a la Alhambra y vemos como el contador se incrementa de manera automática. 

4. Contador automático

En este último ejemplo haremos un contador automático 0000-9999. Para ello vamos a modificar el módulo contador para contar unidades, decenas, centenas y miles.

Cada una de las cuatro cifras del contador tiene un valor entre 0 y 9, luego la podemos almacenar en registro de 4 bits.


reg [3:0] unds;

reg [3:0] decs;

reg [3:0] cents;

reg [3:0] miles;


Con cada flanco de subida del reloj incrementamos la cifra unidades en una unidad, y cuando llega a 10, incrementamos la siguiente cifra una unidad y así sucesivamente hasta llegar a los miles.


always @(posedge clk_contador)

begin

  unds <= unds + 1;

  if (unds >= 4'd9) begin

    decs <= decs + 1;

    unds <= 4'd0;

    if (decs >= 4'd9) begin

      cents <= cents + 1;

      decs <= 4'd0;

      if (cents >= 4'd9) begin

        miles <= miles + 1;

        cents <= 4'd0;

        if (miles >= 4'd9) begin

        miles <= 4'd0;


En los ejemplos anteriores los cuatro dígitos mostraban la misma cifra, pero ahora cada cifra es diferente. Modificaremos el módulo multiplexor para que cada dígito muestre la cifra adecuada.


Para ello necesitamos un registro auxiliar donde almacenamos la variable cifra. Dependiendo del dígito que se vaya a iluminar -unidades, decenas, centenas o millares- asignamos a la variable cifra el valor que le corresponde.


// modulo multiplexor para los dígitos

reg [3:0] cifra;

reg [1:0] mpx;

always @ (posedge clk_digitos)

begin

    mpx <= mpx + 1;

    case (mpx)

      2'b00: begin

        digitos <= 4'b1110;

        cifra <= unds;

        end

      2'b01: begin

        digitos <= 4'b1101;

        cifra <= decs;

        end

      2'b10: begin

        digitos <= 4'b1011;

        cifra <= cents;

        end

      2'b11: begin

        digitos <= 4'b0111;

        cifra <= miles;

        end

    endcase

end


Lo ponemos todo junto y el código queda como sigue.

module BS3461(

input clk,

output  clk_contador,

output clk_digitos,

output reg [7:0] segmentos,

output reg [3:0] digitos

);

// modulo prescaler

reg [23:0] prescaler;

assign clk_contador = prescaler[20];  // velocidad del contador

assign clk_digitos = prescaler[15];   // velocidad refresco digitos


always @(posedge clk)

begin

  prescaler <= prescaler + 1;

end


// modulo contador

reg [3:0] unds;

reg [3:0] decs;

reg [3:0] cents;

reg [3:0] miles;


always @(posedge clk_contador)

begin

  unds <= unds + 1;

  if (unds >= 4'd9) begin

    decs <= decs + 1;

    unds <= 4'd0;

    if (decs >= 4'd9) begin

      cents <= cents + 1;

      decs <= 4'd0;

      if (cents >= 4'd9) begin

        miles <= miles + 1;

        cents <= 4'd0;

        if (miles >= 4'd9) begin

        miles <= 4'd0;

        end

      end

    end

  end

end


// modulo multiplexor para los dígitos

reg [3:0] cifra;

reg [1:0] mpx;

always @ (posedge clk_digitos)

begin

    mpx <= mpx + 1;

    case (mpx)

      2'b00: begin

        digitos <= 4'b1110;

        cifra <= unds;

        end

      2'b01: begin

        digitos <= 4'b1101;

        cifra <= decs;

        end

      2'b10: begin

        digitos <= 4'b1011;

        cifra <= cents;

        end

      2'b11: begin

        digitos <= 4'b0111;

        cifra <= miles;

        end

    endcase

end


// modulo segmentos

always @(posedge clk)

begin

  case(cifra)

4'd0: segmentos <= 8'b00000011;

      4'd1: segmentos <= 8'b10011111;

      4'd2: segmentos <= 8'b00100101;

      4'd3: segmentos <= 8'b00001101;

      4'd4: segmentos <= 8'b10011001;

      4'd5: segmentos <= 8'b01001001;

      4'd6: segmentos <= 8'b01000001;

      4'd7: segmentos <= 8'b00011111;

      4'd8: segmentos <= 8'b00000001;

      4'd9: segmentos <= 8'b00001001;

      default: segmentos <= 8'b11111111;

    endcase

end

endmodule

El archivo .pcf es el siguiente: 

set_io clk 21

set_io clk_digitos 95

set_io clk_contador 96

set_io segmentos[7] 119

set_io segmentos[6] 118

set_io segmentos[5] 117

set_io segmentos[4] 116

set_io segmentos[3] 115

set_io segmentos[2] 114

set_io segmentos[1] 113

set_io segmentos[0] 112

set_io digitos[0] 80

set_io digitos[1] 81

set_io digitos[2] 88

set_io digitos[3] 87 

Lo subimos a la Alhambra y vemos un contador que se incrementa de manera automática y que además conmuta los dígitos lo suficientemente rápido para engañar al ojo humano y que parezca que todos los dígitos están iluminados a la vez. 

En el vídeo de abajo puedes ver un ejemplo de funcionamiento con dos velocidades distintas de incremento del contador; son dos sketch distintos, cada uno con un bit diferente de la señal clk_contador.

Eso es todo, espero que os haya gustado.

¡Hasta la próxima!

Links

Descarga el esquema y los ejemplos en Verilog aquí abajo: