Obsługa wyświetlacza 7-segmentowego VHDL

W obecnych czasach trudno jest sobie wyobrazić maszyny czy też urządzenia użytku domowego, które nie posiadałyby żadnego interfejsu graficznego. Diody LED, wyświetlacze, ekrany… istnieje naprawdę wiele sposobów na graficzne przedstawienie postępów w wykonywanym programie czy zadaniu. Jednak, aby wszystkie informacje wyświetlane były w poprawny sposób, należy odpowiednio obsłużyć wybrany wyświetlacz. W tym artykule dowiesz się jak uruchomić wyświetlacz 7-segmentowy na układzie FPGA.

Wyświetlacze 7-segmentowe

Koncepcja wyświetlacza 7-segmentowego jest genialna w swej prostocie. Kilka diod LED ułożonych w taki sposób, aby zaświecenie odpowiednich segmentów pokazywało znaną nam liczbę bądź znak. Wyróżnić możemy dwa typy wyświetlaczy:

  • Wyświetlacz o wspólnej anodzie – wspólny (+) dla wszystkich segmentów,

  • wyświetlacz o wspólnej katodzie – wspólny (-) dla wszystkich segmentów.

Wyświetlacz 7-segmentowy możemy obsłużyć na dwa sposoby. W pierwszym z nich każdy segment obsługiwany jest przez osobny pin układu FPGA lub mikrokontrolera. Jeśli używamy tylko jednego wyświetlacza sposób ten, będzie idealny, ale jeśli ilość wyświetlaczy się zwiększa, może pojawić się problem w braku wolnych wyjść układów. Aby ten problem rozwiązać powstał sposób obsługi wyświetlaczy, w którym wszystkie piny są ze sobą połączone. Sygnał o wyświetlanym znaku trafia do wszystkich wyświetlaczy jednocześnie, ale w tym czasie tylko jeden wyświetlacz jest uruchomiony. Dzieje się to tak szybko, że ludzkie oko nie jest w stanie wychwycić zmian w wyświetlanych znakach, dlatego wydaje nam się, że cyfry wyświetlane są w sposób ciągły. Taki sposób obsługi wyświetlacza nazywany jest multipleksowaniem.     

Fragment schematu Mimas v2 (https://numato.com/docs/mimas-v2-spartan-6-fpga-development-board-with-ddr-sdram/)

Płytka Mimas v2, na której oparty jest ten artykuł, posiada wbudowany trzyznakowy wyświetlacz 7-segmentowe, dlatego dzisiaj zajmiemy się drugim sposobem obsługi tego typu konstrukcji. Opisany poniżej kod poza samą obsługą wyświetlaczy realizuję również funkcję zwiększania co sekundę o jeden wyświetlanej liczby.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;

entity main is
    Port( 
		clock_100Mhz : in STD_LOGIC;		                --clock
      reset : in STD_LOGIC; 				                --reset
      Anode_Activate : out STD_LOGIC_VECTOR (2 downto 0);   --display activation
      LED_out : out STD_LOGIC_VECTOR (6 downto 0)	        --data bus
	  );
end main;

Na samym początku musimy zadeklarować odpowiednie biblioteki oraz opisać porty zewnętrzne projektu. Będą to: wejście zegarowe dla sygnału 100MHz, wejście reset podpięte do przycisku SW3, który umieszczony jest na płytce, 3-bitowe wyjście aktywujące poszczególne wyświetlacze oraz 7-bitowe wyjście danych dla wyświetlaczy.

architecture Behavioral of main is

signal one_second_counter: STD_LOGIC_VECTOR (27 downto 0);  --1s counter
signal one_second_enable: std_logic;			            --number +1
signal displayed_number: STD_LOGIC_VECTOR (11 downto 0);    --number displayed
signal LED_BCD: STD_LOGIC_VECTOR (3 downto 0);		        --bcd variable
signal refresh_counter: STD_LOGIC_VECTOR (19 downto 0);	    --refresh counter
signal LED_activating_counter: std_logic_vector(1 downto 0);--counter activating a single display

begin

W kolejnej części kodu zadeklarowane zostały wszystkie sygnały wewnętrzne użyte w projekcie, ich znaczenie wyjaśnione zostało w komentarzach.

process(LED_BCD)						--decoder bcd-7segment
  begin
    case LED_BCD is
    when "0000" => LED_out <= "0000001"; -- 0     
    when "0001" => LED_out <= "1001111"; -- 1 
    when "0010" => LED_out <= "0010010"; -- 2 
    when "0011" => LED_out <= "0000110"; -- 3 
    when "0100" => LED_out <= "1001100"; -- 4 
    when "0101" => LED_out <= "0100100"; -- 5 
    when "0110" => LED_out <= "0100000"; -- 6 
    when "0111" => LED_out <= "0001111"; -- 7 
    when "1000" => LED_out <= "0000000"; -- 8     
    when "1001" => LED_out <= "0000100"; -- 9 
    when "1010" => LED_out <= "0000010"; -- A
    when "1011" => LED_out <= "1100000"; -- B
    when "1100" => LED_out <= "0110001"; -- C
    when "1101" => LED_out <= "1000010"; -- D
    when "1110" => LED_out <= "0110000"; -- E
    when others => LED_out <= "0111000"; -- F
    end case;
end process;

W pierwszym bloku synchronicznym process zrealizowana została funkcja dekodująca sygnał LCD_BCD na kod 7-segmentowy przypisywany do wyjścia LED_out. Kod bazuje na funkcji case i pozwala na wyświetlanie wszystkich dostępnych znaków od 0 do F.

process(clock_100Mhz,reset)					--refresh counter 10.5ms
	begin 
    if(reset='0') then
        refresh_counter <= (others => '0');
    elsif(rising_edge(clock_100Mhz)) then
        refresh_counter <= refresh_counter + 1;
    end if;
end process;

LED_activating_counter <= refresh_counter(19 downto 18);

Następnie opisany został licznik, dzięki któremu wyświetlacze odświeżane będą co około 10,5ms. Jest to 20-bitowy licznik, którego wartość zwiększana jest o jeden przy każdym takcie zegara. Z naszej perspektywy odświeżanie na poziomie 100MHz jest zbyt duże, dlatego korzystamy z 19 i 18 bitu licznika, które przypisywane są do sygnału LED_activating_counter.

process(LED_activating_counter)							--mux for display
  begin
    case LED_activating_counter is
    when "00" =>
        Anode_Activate <= "011"; 
        LED_BCD <= displayed_number(11 downto 8);
    when "01" =>
        Anode_Activate <= "101";   
        LED_BCD <= displayed_number(7 downto 4);    
    when "10" =>
        Anode_Activate <= "110";    
        LED_BCD <= displayed_number(3 downto 0); 
    when others =>
        Anode_Activate <= "111"; 
        LED_BCD <= displayed_number(3 downto 0);     
    end case;
end process;

W kolejnej części kodu opisany został multiplekser bazujący na funkcji case. Dzięki niemu wygenerowana cyfra pokaże się na odpowiednim wyświetlaczu. Na podstawie zmiennej LED_activating_counter do sygnałów Anode_activate oraz LCD_BCD przypisywane są odpowiednie sygnały.

process(clock_100Mhz, reset)					--1s counter
	begin
        if(reset='0') then
            one_second_counter <= (others => '0');
        elsif(rising_edge(clock_100Mhz)) then
            if(one_second_counter>=x"5F5E0FF") then
                one_second_counter <= (others => '0');
            else
                one_second_counter <= one_second_counter + "0000001";
            end if;
        end if;
end process;

one_second_enable <= '1' when one_second_counter=x"5F5E0FF" else '0';

Dalej umieszczony został licznik, którego zadaniem jest generować stan '1′ dla sygnału one_second_enable co jedną sekundę. Jest to 28-bitowy licznik, który resetowany jest w momencie osiągnięcia stanu x”5F5E0FF”. Jest to liczba, która przy taktowaniu na poziomie 100MHz pojawi się na wyjściu licznika po około sekundzie.

process(clock_100Mhz, reset)						--counter of the displayed number
	begin
        if(reset='0') then
            displayed_number <= (others => '0');
        elsif(rising_edge(clock_100Mhz)) then
             if(one_second_enable='1') then
                displayed_number <= displayed_number + x"001";
             end if;
        end if;
end process;

end Behavioral;

W ostatniej części kodu umieszczony został kolejny licznik, który generuje wyświetlaną liczbę. Ma ona postać 12-bitowego kodu BCD, którego wartość zwiększana jest o jeden co jedną sekundę na podstawie sygnału one_second_enable.

Projekt zaimplementowałem na układzie Spartan 6, działanie możecie zobaczyć na filmie powyżej.

Każdy sprzęt elektroniczny musi być w jakiś sposób zasilany, bez odpowiedniego napięcia nie będzie działać wcale lub zadziała, ale tylko przez ułamek sekundy. Istnieje całkiem sporo rozwiązań i konstrukcji, których zadaniem jest doprowadzić energię do zaprojektowanego obwodu. Jednymi z najczęściej stosowanych są stabilizatory liniowe oraz przetwornice napięcia. Stabilizatory zostawimy sobie na inny materiał, natomiast dziś chciałbym opowiedzieć wam nieco o działaniu i budowie przetwornic. Poza tym przyjrzymy się też ich problemom i spróbujemy je rozwiązać.

LFSR (Linear Feedback Shift Register) jest jedną z prostszych konstrukcji, jakie możemy uruchomić na układzie FPGA, a jednocześnie jest ona bardzo przydatna. Tego typu aplikacje stosuje się przede wszystkimi przy.

Źródła:

  • https://numato.com/docs/mimas-v2-spartan-6-fpga-development-board-with-ddr-sdram/

Chcesz być na bieżąco?
Dołącz do newslettera

Otrzymywać będziesz powiadomienia o nowych artykułach oraz informacje o projektach, nad którymi pracuję.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewiń do góry