Projekt, którym zajmiemy się w tym odcinku kursu, będzie trochę bardziej skomplikowany niż konstrukcje stworzone przez nas wcześniej. Z tego powodu zaczniemy od zapoznania się ze schematem projektu, zaprezentowanym na rysunku 1 oraz hierarchią modułów.
Utwórz nowy projekt w Lattice Diamond. Wszystkie moduły, z wyjątkiem top i MelodyPlayer, omawialiśmy już w poprzednich odcinkach kursu. Każdy z nich możesz pobrać z repozytorium na GitHubie, a także pobrać gotowy projekt dla programu Diamond. Linki do źródeł znajdziesz w ramce.
Moduł top
Przeanalizujmy plik top.v.
Na liście portów mamy następujące wejścia i wyjścia:
- Reset – standardowo, jak w każdym poprzednim projekcie, reset asynchroniczny jest wyzwalany przyciskiem K0 na płytce MachXO2 Mega.
- Play_i oraz Stop_i – są to wejścia podłączone do przycisków, w enkoderach E41 (górny) i E42 (dolny). Te przyciski będą służyć do uruchomienia odtwarzania melodii oraz do jej zatrzymania.
- SoundWave_o – wyjście podłączone do tranzystora sterującego głośniczkiem.
- Segments_o[7:0] – 8-bitowe wyjście sterujące segmentami wyświetlacza multipleksowanego.
- Cathodes_o[7:0] – 8-bitowe wyjście sterujące katodami wyświetlacza multipleksowanego.
W pierwszej kolejności budujemy generator sygnału zegarowego, tworząc instancję modułu OSCH – dokładnie tak samo, jak w poprzednich odcinkach kursu.
Następnie musimy zająć się wykrywaniem wciśnięcia gałek enkoderów. Na płytce User Interface Board zastosowano już odpowiednie rezystory pull-up oraz filtry RC do odszumiania drgań styków, więc nie potrzebujemy żadnego dodatkowego kodu w tym celu. Wystarczą nam dwie instancje modułów Encoder – po jednej na każdy przycisk. Omówimy tylko jedną z nich, ponieważ obie działają w identyczny sposób.
W linii 2 tworzymy instancję modułu enkodera, który obsługiwać będzie enkoder E41 – przycisk E41 używany jest z kolei do uruchamiania odtwarzacza. Interesuje nas tylko wykrywanie wciśnięcia gałki enkodera, więc do jego wejścia AsyncS_i doprowadzamy sygnał Play_i. Moduł enkodera zapewnia synchronizację sygnałów wejściowych z domeną zegarową, a także wykrywanie zmian badanego sygnału. W tym przypadku używać będziemy tylko wyjścia ButtonPress_o, informującego o wykryciu wciśnięcia przycisku (linia 6). Łączymy je z sygnałem Play typu wire, który został utworzony w linii 1.
Nie interesuje nas wykrywanie obrotu gałki, zatem do wejść AsyncA_i oraz AsyncB_i doprowadzamy na stałe stan wysoki (linie 3 i 4), a wszystkie pozostałe wyjścia modułu enkodera pozostawiamy niepołączone.
Dalej, w linii 7 i kolejnej tworzymy dwie 16-bitowe zmienne wire Duration_ms oraz HalfPeriod_us. Posłużą one do przekazywania informacji o aktualnie odtwarzanym dźwięku z modułu MelodyPlayer (linie 11 i 12) do modułu sterującego wyświetlaczem DisplayMultiplex (linia 14).
Przechodzimy wreszcie do instancji odtwarzacza melodyjek (linia 8). W liniach 9 i 10 mamy wejścia uruchamiające i przerywające odtwarzanie melodii – są to sygnały wychodzące ze sterowników enkoderów. W linii 11 znajduje się wyjście sygnału dźwiękowego, które wyprowadzone jest na pin układu FPGA. Linie 12 i 13 to wyjścia informujące o długości oraz półokresie aktualnie odtwarzanego dźwięku, które służą tylko do celów informacyjnych. Można te wyjścia pozostawić niepodłączone.
Na końcu mamy instancję sterownika wyświetlacza LED, który już wielokrotnie stosowaliśmy w poprzednich odcinkach kursu.
Zwróć uwagę, że wejście Data_i modułu wyświetlacza jest 32-bitowe, ponieważ wyświetlacz ma osiem cyfr wyświetlających 4-bitową szesnastkową cyfrę od 0 do 9 i od A do F. Z tego powodu dwie 16-bitowe zmienne Duration_ms oraz HalfPeriod_us sklejamy ze sobą za pomocą operatora konkatenacji {}. W ten sposób cztery cyfry wyświetlacza z prawej strony będą pokazywać półokres odtwarzanego dźwięku, a pozostałe cztery cyfry z lewej będą pokazywały jego długość (linia 14). W celu zwiększenia czytelności ustawimy wyświetlanie kropki pomiędzy tymi dwiema liczbami (linia 15).