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.
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/