Verilog Práctico
Capítulo 5. Display de 4x7 segmentos.
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: