Un display de 7 segmentos es un conjunto de LED con forma de barras que permite representar cifras mediante combinaciones de encendido y apagado.
En este artículo vamos a aplicar lo que aprendimos sobre la sentencia case para construir un Descodificador Hexadecimal.
El objetivo es sencillo: tenemos un número de 4 bits dentro de la FPGA (del 0 al 15) y queremos que aparezca representado visualmente en el display.
El hardware: siete LED
Un display de 7 segmentos no tiene misterio; son literalmente 7 LEDs empaquetados con una forma específica (más un octavo para el punto decimal).
Cada segmento tiene asignada una letra estándar, de la a a la g, comenzando por arriba y girando en sentido horario, terminando con la barra central (g).
Para dibujar un número, simplemente tenemos que encender la combinación correcta de LEDs.
- Para el “1”: Encendemos b y c.
- Para el “8”: Encendemos todos (a,b,c,d,e,f,g).
Ánodo común y cátodo común
Aquí viene el primer tropiezo habitual. Dependiendo de cómo estén conectados los LEDs internamente, existen dos tipos de displays:
- Cátodo Común: Todos los negativos están unidos a Tierra (GND). Para encender un segmento, ponemos un 1 (Voltaje positivo) en su pin. (Lógica Positiva).
- Ánodo Común: Todos los positivos están unidos a VCC (3.3V). Para encender un segmento, ponemos un 0 (Tierra) en su pin para que fluya corriente. (Lógica Negativa).
La gran mayoría de placas de desarrollo FPGA (como la IceStick, Go Board o Alhambra) utilizan displays de Ánodo Común o drivers inversores. Esto significa que 0 enciende y 1 apaga.
Diseño del descodificador
Vamos a diseñar un módulo que tome un número de 4 bits (dato) y devuelva un bus de 7 bits (segmentos) donde cada bit corresponde a una letra: gfe_dcba.
En la universidad clásica, te harían dibujar una tabla de verdad gigante y resolver 7 Mapas de Karnaugh para encontrar la ecuación booleana de cada LED.
En Verilog, gracias a la abstracción, simplemente describimos la tabla de verdad usando un case. El sintetizador se encargará de hacer los Mapas de Karnaugh por nosotros.
Código Verilog para cátodo común
Primero lo haremos pensando en “1 enciende”, que es más natural para el cerebro.
module decodificador_7seg (
input wire [3:0] numero, // Valor de 0 a 15 (Hexadecimal)
output reg [6:0] seg // Salida gfe_dcba
);
// Definimos qué segmentos encender para cada número
// Formato: gfe_dcba
// 1 significa ENCENDIDO
always @(*) begin
case(numero)
// gfedcba
4'h0: seg = 7'b0111111; // 0: Todo menos g
4'h1: seg = 7'b0000110; // 1: Solo b y c
4'h2: seg = 7'b1011011; // 2
4'h3: seg = 7'b1001111; // 3
4'h4: seg = 7'b1100110; // 4
4'h5: seg = 7'b1101101; // 5
4'h6: seg = 7'b1111101; // 6
4'h7: seg = 7'b0000111; // 7
4'h8: seg = 7'b1111111; // 8: Todos
4'h9: seg = 7'b1101111; // 9
// Hexadecimal (A-F)
4'hA: seg = 7'b1110111; // A
4'hB: seg = 7'b1111100; // b (minúscula)
4'hC: seg = 7'b0111001; // C
4'hD: seg = 7'b1011110; // d (minúscula)
4'hE: seg = 7'b1111001; // E
4'hF: seg = 7'b1110001; // F
default: seg = 7'b0000000; // Apagado por seguridad
endcase
end
endmoduleAdaptación a ánodo común
Si cargas el código anterior en una placa típica, verás los números “en negativo” (los LEDs que deberían estar apagados se encenderán y viceversa).
Para arreglarlo, no hace falta reescribir todo el case con ceros. Simplemente invertimos la salida.
Podemos crear un módulo wrapper (envoltorio) o modificar la salida:
module top_display (
input wire [3:0] switches,
output wire [6:0] leds_display // Conectado a los pines físicos
);
wire [6:0] logica_positiva;
// 1. Instanciamos nuestro descodificador puro
decodificador_7seg mi_deco (
.numero(switches),
.seg(logica_positiva)
);
// 2. Invertimos los bits antes de salir al mundo físico
// Si logica_positiva es 1 (On), enviamos 0 al pin (Tierra) para encender.
assign leds_display = ~logica_positiva;
endmoduleConfiguración mediante parámetros
Si queremos hacer un código portable que funcione en cualquier placa, podemos añadir un parámetro de configuración.
module driver_display #(
parameter ANODO_COMUN = 1 // 1 para invertir, 0 para normal
) (
input wire [3:0] num,
output wire [6:0] salida_pines
);
reg [6:0] segmentos_internos;
always @(*) begin
case(num)
// ... (tabla del case) ...
endcase
end
// Operación XOR condicional:
// Si ANODO_COMUN es 1, invierte (segmentos ^ 111... = ~segmentos)
// Si ANODO_COMUN es 0, deja igual (segmentos ^ 000... = segmentos)
assign salida_pines = segmentos_internos ^ {7{ANODO_COMUN}};
endmodule