FPGA: 7 Segmentanzeige (Permalink)

Als Fortsetzung zu meinen ersten Gehversuchen mit VHDL und FPGAs soll nun ein 1-Byte-Display aus 2 Siebensegmentanzeigen entstehen. So etwas ist auf besseren FPGA-Boards meist bereits integriert, nicht jedoch auf dem preiswerten Basisboard. In diesem Artikel wird z.B. beschrieben, wie auf einem Digilent Basys 3 Artix-7 FPGA Board diese Anzeige aufgebaut ist und wie man sie in Verilog anspricht. Hier werden nun leichte Abwandlungen davon vorgenommen.

Hardware

Auf dem Basys 3 Board werden Siebensegmentanzeigen mit gemeinsamer Anode und PNP-Transistoren verwendet. In meiner Bastelkiste fanden sich aber nur 2 Siebensegmentanzeigen und NPN-Transistoren (BC 337-16). Zusätzlich benötigt man die passenden Vorwiderstände. Die Vorwiderstände für die LED Segmente ergeben sich R = U/I = 3.3 V / 0.002 A = 150 Ohm (die LEDs benötigen 20mA zum Leuchten). Für die Transistoren verwende ich einen Vorwiderstand von jeweils 4k7 Ohm (Hilfe bei den Berechnungen hatte ich von Dr. Kilian). Die vorgestellte Schaltung skaliert natürlich auch für mehr als 2 Siebensegmentanzeigen.

Schaltplan
Schaltplan

Fertig aufgebaut auf Lochraster sieht das dann so aus. Ja, 20mm Siebensegmentanzeigen sind eigentlich zu groß:

Aufgebaute Schaltung
Aufgebaute Schaltung

Die Pins auf der linken Seite besitzen folgende Bedeutung:

PIN Nummer Bedeutung
Pin 0 / GND (oben) Anschluss für Masse
Pin 1 / S0 Für die Auswahl der rechten Siebensegmentanzeige auf High
Pin 2 / S1 Für die Auswahl der linken Siebensegmentanzeige auf High
Pin 3 / A Aktiviert Segment A der ausgewählten Siebensegmentanzeige
Pin 4 / B Aktiviert Segment B der ausgewählten Siebensegmentanzeige
Pin 5 / C Aktiviert Segment C der ausgewählten Siebensegmentanzeige
Pin 6 / D Aktiviert Segment D der ausgewählten Siebensegmentanzeige
Pin 7 / E Aktiviert Segment E der ausgewählten Siebensegmentanzeige
Pin 8 / F Aktiviert Segment F der ausgewählten Siebensegmentanzeige
Pin 9 / G Aktiviert Segment G der ausgewählten Siebensegmentanzeige
Pin 10 / DP (unten) Aktiviert den Punkt der ausgewählten Siebensegmentanzeige

Zuordnung der Segmente A bis G und DP auf der Siebensegmentanzeige:

Zuordnung der LEDs
Zuordnung der LEDs

Ansteuerung

DieTrägheit des Auges wird bei dieser Schaltung ausgenutzt, da die beiden Siebensegmentanzeigen jeweils abwechselnd aktiviert werden (mit ca. 1 kHz).

Folgende VHDL Entity soll implementiert werden:

Entity byte_display Is
  Port (
    clk      : In std_logic;
    enable   : In std_logic;
    data     : In std_logic_vector(7 Downto 0);
    segments : Out std_logic_vector(1 Downto 0);
    leds     : Out std_logic_vector(6 Downto 0)
  );
End byte_display;

Der clk-Eingang wird dem globalen Clock-Signal verbunden und wird für die Umschaltung der einzelnen Siebensegmentanzeige verwendet. Der Wert data wird intern gespeichert, wenn enable eine steigende Flanke hat und bleibt solange angezeigt, bis ein neuer Wert gesetzt wird. Die beiden ausgehenden Bits in segments aktivieren jeweils mit high-Werten eine der beiden Siebensegmentanzeigen und die Bits aus leds jeweils die entsprechende LED.

Eine Siebensegmentanzeige kann hexadezimal ein Nibble (also ein halbes Byte oder 4 Bit) darstellen. Die Umsetzung dieser 4 Bit in die 7 LEDs der Anzeige wird in einer eigenen VHDL-Prozedur durchgeführt:

PROCEDURE display_digit(
    SIGNAL digit : IN std_logic_vector (3 DOWNTO 0);
    SIGNAL leds : OUT std_logic_vector(6 DOWNTO 0)
  ) IS
BEGIN
 CASE digit IS          -- GFEDCBA
   WHEN "0000" => leds <= "0111111"; -- 0 ABCDEF
   WHEN "0001" => leds <= "0000110"; -- 1 BC
   WHEN "0010" => leds <= "1011011"; -- 2 ABDEG
   WHEN "0011" => leds <= "1001111"; -- 3 ABCDG
   WHEN "0100" => leds <= "1100110"; -- 4 BCFG
   WHEN "0101" => leds <= "1101101"; -- 5 ACDFG
   WHEN "0110" => leds <= "1111101"; -- 6 ACDEFG
   WHEN "0111" => leds <= "0000111"; -- 7 ABC
   WHEN "1000" => leds <= "1111111"; -- 8 ABCDEFG
   WHEN "1001" => leds <= "1101111"; -- 9 ABCDFG
   WHEN "1010" => leds <= "1110111"; -- A ABCEFG
   WHEN "1011" => leds <= "1111100"; -- B CDEFG
   WHEN "1100" => leds <= "1011000"; -- C DEG
   WHEN "1101" => leds <= "1011110"; -- D BCDEG
   WHEN "1110" => leds <= "1111001"; -- E ADEFG
   WHEN "1111" => leds <= "1110001"; -- F AEFG
 END CASE;
END display_digit;

Hier werden jeweils ein Eingangs- und ein Ausgangssignal  mit einem VHDL-Case-When umgesetzt. Es sind sicher noch andere Schreibweisen vorstellbar.

Die Übernahme des Eingabebytes data bei steigendem enable-Bit wird durch einen eigenen Prozess umgesetzt:

PROCESS (enable)
BEGIN
  IF rising_edge(enable) THEN
    FOR i IN 0 TO 1 LOOP
      -- digits sind ein internes Signal
      digits(i) <= data(((4 * i) + 3) DOWNTO (4 * i));
    END LOOP;
  END IF;
END PROCESS;

Steigt hier das enable-Signal an, so teilt die For-Schleife das anliegende data-Signal in die beiden internen digits auf. Dabei hilft die gleichzeitige Zuordnung mehrerer Signale mit der DOWNTO Schreibweise.

Ein paralleler Prozess wechselt mit 1 kHz zwischen beiden Siebensegmentanzeigen hin und her und zeigt dort jeweils das aktuelle Nibble an:

PROCESS (clk)
  VARIABLE digit_cntr : INTEGER := 0;
  VARIABLE digit : INTEGER := 0;
BEGIN
  -- select proper digit
  IF rising_edge(clk) THEN
    digit_cntr := digit_cntr + 1;
    IF (digit_cntr > 50000) THEN
      digit_cntr := 0;
      digit := digit + 1;
      IF (digit > 1) THEN
        digit := 0;
      END IF;
    END IF;
  END IF;

 -- display the nibble
  IF (digit = 0) THEN
    segments <= "01";
    display_digit(digits(0), leds);
  ELSE
    segments <= "10";
    display_digit(digits(1), leds);
  END IF;
END PROCESS;

Den vollständigen Code inkl. Zählerbeispiel findet man in master.zip (den Code zum Entprellen des Tasters habe ich dem eewiki entnommen, vielen Dank für den verständlichen Artikel). In den FPGA übertragen und korrekt verkabelt ergibt das folgendes:

Viel Erfolg und Spass beim Nachvollziehen!

Update 26.02.2018
Jetzt auch im GIT:https://gitea.lusiardi.de/jlusiardi/vhdl_7seg