Spisu treści:

Cyfrowy syntezator dźwięku Basys3 FPGA: 5 kroków
Cyfrowy syntezator dźwięku Basys3 FPGA: 5 kroków

Wideo: Cyfrowy syntezator dźwięku Basys3 FPGA: 5 kroków

Wideo: Cyfrowy syntezator dźwięku Basys3 FPGA: 5 kroków
Wideo: Mikroelektronika w Technice i Medycynie, prodziekan dr hab. inż. Robert Stala, prof. Uczelni 2024, Lipiec
Anonim
Image
Image
Cyfrowy syntezator dźwięku Basys3 FPGA
Cyfrowy syntezator dźwięku Basys3 FPGA
Cyfrowy syntezator dźwięku Basys3 FPGA
Cyfrowy syntezator dźwięku Basys3 FPGA

Ten cyfrowy syntezator z klawiaturą sinusoidalną pobiera dane wejściowe użytkownika za pomocą szeregu chwilowych przełączników ułożonych jak klawiatura i wysyła falę dźwiękową przez głośnik. Na podstawie danych wprowadzonych przez użytkownika urządzenie wygeneruje fale sinusoidalne o różnych częstotliwościach od C4 do C6. Użytkownik może wprowadzać nuty od C4 do C6 (łącznie 25 nut) i do czterech klawiszy jednocześnie – jeśli naciśniesz więcej niż cztery klawisze, zostaną zagrane cztery najniższe dźwięki.

Ten projekt wykonali Ryan Morris i Mavis Tsoi dla naszej klasy Cal Poly CPE 133 Digital Design:)

Krok 1: Teoria

Płyta FPGA może wyprowadzać tylko sygnały cyfrowe. Innymi słowy, może wytwarzać tylko wysokie (3,3 V) napięcie lub niskie (0 V) napięcie. Jednak sygnały audio są analogowe i mogą mieć nieskończenie wiele przyrostów napięcia. Aby obejść ten problem, użyjemy sygnału PWM (modulacja szerokości impulsu) do emulacji fali analogowej. Jeśli nie wiesz, czym jest PWM, sprawdź to:

Krok 2: Składniki i narzędzia

  • Komputer z zainstalowanym Vivado
  • Będziemy używać Vivado w wersji 2017.2
  • Płyta FPGA Basys3
  • 25 wyłączników krańcowych SPDT (użyliśmy ich)
  • 30 przewodów połączeniowych (jeden koniec męski, drugi koniec nie ma znaczenia), 12 cali
  • Nożyce do drutu
  • Narzędzia do ściągania izolacji
  • Zapasowy drut do lutowania
  • Żywiczny rdzeń lutowniczy
  • Lutownica
  • ¼” żeńskie gniazdo audio
  • Wzmacniacz/głośnik
  • Coś do zamontowania przełączników (użyliśmy protoboard + drewniane pudełko)

Krok 3: Konfiguracja okablowania i sprzętu

Konfiguracja okablowania i sprzętu
Konfiguracja okablowania i sprzętu
Konfiguracja okablowania i sprzętu
Konfiguracja okablowania i sprzętu
Konfiguracja okablowania i sprzętu
Konfiguracja okablowania i sprzętu

architektura systemu

Zobacz Rysunek 1: 25 dostępnych wejść → Płytka Basys3 → wzmacniacz i głośnik.

Wyjście

Patrz Rysunek 2: Płyta Basys3 → 1/2 żeńskie gniazdo audio → Głośnik (ze wzmacniaczem)

Wejście

Połączenia pmod na płycie Basys3 muszą być podłączone do masy, aby zobaczyć niski sygnał wejściowy i nie będą działać prawidłowo, jeśli zostaną pozostawione jako obwód otwarty. Z tego powodu musimy używać przełączników SPDT dla wszystkich naszych klawiszy nut. Przełącznik SPDT w zasadzie pozwala użytkownikowi przełączać się między obwodami po naciśnięciu, więc użyjemy ich jako naszych „przycisków” do wprowadzania niskich (0 V) lub wysokich (3,3 V) sygnałów na płytkę Basys3.

Każdy przełącznik będzie miał zacisk NO (normalnie otwarty) podłączony do 3,3 V, zacisk NC (normalnie zamknięty) podłączony do GND i zacisk COM (wspólny) podłączony do wejścia FPGA. Zobacz rysunek 3.

Ponieważ mamy 25 wyłączników krańcowych, wszystkie będą dzielić wspólną linię 3.3V i wspólną linię GND. Następnie linia sygnałowa z każdego wyłącznika krańcowego zostanie połączona w grupy po 8 sztuk i podłączona do połączeń pmod na płycie Basys3 za pomocą rozpinanych przewodów połączeniowych, aby zminimalizować monumentalny bałagan, który zrobimy. Zobacz Rysunek 4 lub przykład pierwszych ośmiu klawiszy.

Krok 4: Konfiguracja VHDL (Vivado)

Konfiguracja VHDL (Vivado)
Konfiguracja VHDL (Vivado)
Konfiguracja VHDL (Vivado)
Konfiguracja VHDL (Vivado)

Generator fal sinusoidalnych i generator PWM zostały najpierw przetestowane, aby upewnić się, że nasza koncepcja działa, a następnie zintegrowano ogranicznik wejściowy i sumator/przesuwnik amplitudy. Szczegóły funkcji i we/wy każdego bloku procesu pokazano na rysunku. Kod jest pokazany poniżej, ale również dołączony jako pliki VHD i txt. Jeśli są rozbieżności, przejdź do plików VHD.

BTW: prawdopodobnie powinniśmy skrócić nasze wiersze, ale osadzanie kodu w Instructables również okazało się dość irytujące, więc odstępy nie są największe i nie ma podświetlania składni. Jeśli masz Vivado i chcesz śledzić kod, zalecamy po prostu pobranie pliku.

Najpierw spójrzmy na moduł generatora fal sinusoidalnych.

biblioteka IEEE;użyj IEEE. STD_LOGIC_1164. ALL; użyj IEEE. NUMERIC_STD. ALL; obiekt Wave_Generator to Port (Trigger: w STD_LOGIC; -- Naciśnięcie klawisza Freq_Cnt: w STD_LOGIC_VECTOR (15 do 0); -- Wartość licznika = 100MHz / (Uwaga Częstotliwość*64 dywizji sinusoidalnej) (zaokrąglenie do najbliższej liczby) - zmieniono nazwę z Freq wavegenCLK: w STD_LOGIC; -- Basys3 100MHz CLK WaveOut: out STD_LOGIC_VECTOR(9 do 0)); -- Podpisana amplituda końca fali Wave_Generator; architektura Zachowanie Wave_Generator to signal i: liczba całkowita od 0 do 64:= 0; -- indeksem amplitudy banku pamięci typu memory_type jest tablica (0 do 63) z zakresu liczb całkowitych -64 do 63; -- utwórz bank pamięci (ROM) do przechowywania wartości amplitudy -- czy ten RAM lub ROM się zastanawia … amplituda sygnału: typ_pamięci:= (0, 7, 13, 19, 25, 30, 35, 40, 45, 49, 52, 55, 58, 60, 62, 63, 63, 63, 62, 60, 58, 55, 52, 49, 45, 40, 35, 30, 25, 19, 13, 7, 0, -7, -13, -19, -25, -30, -35, -40, -45, -49, -52, -55, -58, -60, -62, -63, -63, -63, -62, - 60, -58, -55, -52, -49, -45, -40, -35, -30, -25, -19, -13, -7); -- bank pamięci amplitudy dla początku procesu sinusoidalnego (wavegenCLK, Trigger) licznik zmiennych: unsigned (15 do 0):= to_unsigned(0, 16); -- licznik dzielnika zegara, przemianowany z count1 begin if (rising_edge(wavegenCLK)) then if (Trigger = '1') then -- wciśnięty klawisz counter:= counter + 1; if (counter = unsigned(Freq_Cnt)) then -- Freq_Cnt = 100Mhz / (zwróć uwagę freq * 64 działki przebiegu sinusoidalnego) -- zresetuj licznik i przypisz dane amplitudy do licznika wyjściowego:= to_unsigned(0, 16); WaveOut <= STD_LOGIC_VECTOR (do_signed(amplituda(i), 10)); -- inkrementuj i dla następnego odczytu i <= i + 1; -- zresetuj i jeśli jeden przebieg sinusoidalny został zakończony if(i = 63) to i <= 0; koniec jeśli; koniec jeśli; -- (counter = unsigned(Freq_Cnt)) else -- klawisz nie jest wciśnięty -- resetuje wyjście, indeks amplitudy i licznik WaveOut <= "0000000000"; ja <= 0; licznik:= to_unsigned(0, 16); --output Amplituda = -64 gdy żadna nuta nie jest grana end if; -- (Trigger = '1') koniec jeśli; -- (rising_edge(CLK)) koniec procesu; koniec Behawioralny;

Wygenerujemy cyfrową falę sinusoidalną w Basys3 za pomocą wewnętrznego zegara i pamięci ROM. Ta pamięć ROM przechowuje 64 wartości, które reprezentują 64 amplitudy fali sinusoidalnej. Zobacz rysunek 1. 64 wartości, których używamy, emulują falę sinusoidalną o całkiem dobrej rozdzielczości.

Używając wewnętrznego zegara, liczymy do wartości, która reprezentuje prędkość zegara podzieloną przez częstotliwość fali, którą chcemy i 64: Clk div = 100MHz / (Freq * 64) Za każdym razem, gdy nasz licznik osiągnie tę wartość, wywołujemy liczbę z ROM i wyślij go z naszego modułu generatora fal. Częstotliwość naszej fali będzie zależeć od tego, jak szybko nazwiemy te amplitudy.

Będziemy mieć 25 podmodułów, każdy powiązany z jedną częstotliwością/nutą.

Oto pozostała część kodu wywołującego moduły generatora fal sinusoidalnych:

biblioteka IEEE;użyj IEEE. STD_LOGIC_1164. ALL; użyj IEEE. NUMERIC_STD. ALL; podmiot Two_Octave_Synth to Port (CLK: w STD_LOGIC; O4: w STD_LOGIC_VECTOR(11 do 0); O5: w STD_LOGIC_VECTOR(12 do 0); wyjście: na STD_LOGIC); koniec Two_Octave_Synth; architektura Zachowanie Two_Octave_Synth jest komponentem Wave_Generator jest Port (Trigger: w STD_LOGIC; Freq_Cnt: w STD_LOGIC_VECTOR(15 do 0); wavegenCLK: w STD_LOGIC; WaveOut: na STD_LOGIC_VECTOR(9 do 0)); element końcowy; ---------------------------sygnały wyjściowe z generatora fal---------------------- ----- sygnał WaveC4, WaveCs4, WaveD4, WaveDs4, WaveE4, WaveF4, WaveFs4, WaveG4, WaveGs4, WaveA4, WaveAs4, WaveB4, WaveC5, WaveCs5, WaveD5, WaveDs5, WaveE5, WaveF5, WaveFs5, WaveG5, WaveGs5, WaveA5, WaveAs5, WaveB5, WaveC6: ze znakiem (9 do 0); --------------------- dla logiki wyboru nut -------------- ------ sygnał C4, Cs4, D4, Ds4, E4, F4, Fs4, G4, Gs4, A4, As4, B4, C5, Cs5, D5, Ds5, E5, F5, Fs5, G5, Gs5, A5, As5, B5, C6: bez znaku (4 do 0); sygnał cntC4, cntCs4, cntD4, cntDs4, cntE4, cntF4, cntFs4, cntG4, cntGs4, cntA4, cntAs4, cntB4, cntC5, cntCs5, cntD5, cntDs5, cntE5, cntF5, cntFs5, cntG5, cntB5, cntB: bez znaku (4 do 0); błąd sygnału: STD_LOGIC; ------------------------do dodawania fal sinusoidalnych----------- --------------- sygnał Fala0, Fala1, Fala2, Fala3: ze znakiem (9 do 0); --sygnały z sygnału wyjściowego modułu Wave Generator WaveSum: STD_LOGIC_VECTOR(9 downto 0); --signal dla zsumowanych fal sinusoidalnych (komplement 2 -512 do 511) signal positiveWaveSum: STD_LOGIC_VECTOR(9 do 0); --unsigned 0 do 1023, do użycia w generatorze PWM ----------------------------------- do generowania PWM ------------------------------- długość_pingu sygnału: unsigned (9 do 0):= unsigned(positiveWaveSum); --signal off_length: unsigned (6 do 0):= to_unsigned(127, 7) - unsigned(WAVE); sygnał PWM: bez znaku (9 do 0):= to_unsigned (0, 10); begin Note_C4: Mapa portów Wave_Generator (Trigger => O4(0), Freq_Cnt => X"1755", wavegenCLK => CLK, podpisany(WaveOut) => WaveC4); --5973, 261.63 Hz Note_Cs4: Mapa portów Wave_Generator (Trigger => O4(1), Freq_Cnt => X"1606", wavegenCLK => CLK, sign(WaveOut) => WaveCs4);--5638, 277,18 Hz Note_D4: Mapa portów Wave_Generator (Trigger => O4(2), Freq_Cnt => X"14C9", wavegenCLK => CLK, podpisany(WaveOut) => WaveD4); --5321, 293,66 Hz Note_Ds4: Mapa portów Wave_Generator (Trigger => O4(3), Freq_Cnt => X"139F", wavegenCLK => CLK, sign(WaveOut) => WaveDs4);--5023, 311,13 Hz Note_E4: Mapa portów Wave_Generator (Trigger => O4(4), Freq_Cnt => X"1285", wavegenCLK => CLK, podpisany(WaveOut) => WaveE4); --4741, 329,63 Hz Note_F4: Mapa portów Wave_Generator (Trigger => O4(5), Freq_Cnt => X"117B", wavegenCLK => CLK, podpisany(WaveOut) => WaveF4); --4475, 349,23 Hz Note_Fs4: Mapa portów Wave_Generator (Trigger => O4(6), Freq_Cnt => X"1080", wavegenCLK => CLK, sign(WaveOut) => WaveFs4);--4224, 369,99 Hz Note_G4: Mapa portów Wave_Generator (Trigger => O4(7), Freq_Cnt => X"0F92", wavegenCLK => CLK, podpisany(WaveOut) => WaveG4); --3986, 392,00 Hz Note_Gs4: Mapa portów Wave_Generator (Trigger => O4(8), Freq_Cnt => X"0EB3", wavegenCLK => CLK, sign(WaveOut) => WaveGs4);--3763, 415,30 Hz Note_A4: Mapa portów Wave_Generator (Trigger => O4(9), Freq_Cnt => X"0DE0", wavegenCLK => CLK, podpisany(WaveOut) => WaveA4); --3552, 440,00 Hz Note_As4: Mapa portów Wave_Generator (Trigger => O4(10), Freq_Cnt => X"0D18", wavegenCLK => CLK, sign(WaveOut) => WaveAs4);--3352, 466,16 Hz Note_B4: Mapa portów Wave_Generator (Trigger => O4(11), Freq_Cnt => X"0C5C", wavegenCLK => CLK, podpisany(WaveOut) => WaveB4); -3164, 493,88 Hz -------------------------------------------- -------------------------------------------------- --------------------------- Note_C5: Mapa portów Wave_Generator (Trigger => O5(0), Freq_Cnt => X"0BAB", wavegenCLK => CLK, ze znakiem(WaveOut) => WaveC5); --2987, 523.25 Hz Note_Cs5: Mapa portów Wave_Generator (Trigger => O5(1), Freq_Cnt => X"0B03", wavegenCLK => CLK, sign(WaveOut) => WaveCs5);--2819, 554,37 Hz Note_D5: Mapa portów Wave_Generator (Trigger => O5(2), Freq_Cnt => X"0A65", wavegenCLK => CLK, podpisany(WaveOut) => WaveD5); --2661, 587,33 Hz Note_Ds5: Mapa portów Wave_Generator (Trigger => O5(3), Freq_Cnt => X"09D0", wavegenCLK => CLK, sign(WaveOut) => WaveDs5);--2512, 622,25 Hz Note_E5: Mapa portów Wave_Generator (Trigger => O5(4), Freq_Cnt => X"0943", wavegenCLK => CLK, podpisany(WaveOut) => WaveE5); --2371, 659,25 Hz Note_F5: Mapa portów Wave_Generator (Trigger => O5(5), Freq_Cnt => X"08Be", wavegenCLK => CLK, sign(WaveOut) => WaveF5); --2238, 698,46 Hz Note_Fs5: Mapa portów Wave_Generator (Trigger => O5(6), Freq_Cnt => X"0840", wavegenCLK => CLK, sign(WaveOut) => WaveFs5);--2112, 739,99 Hz Note_G5: Mapa portów Wave_Generator (Trigger => O5(7), Freq_Cnt => X"07CA", wavegenCLK => CLK, podpisany(WaveOut) => WaveG5); -1994, 783,99 Hz Note_Gs5: Mapa portów Wave_Generator (Trigger => O5(8), Freq_Cnt => X"075A", wavegenCLK => CLK, sign(WaveOut) => WaveGs5);--1882, 830,61 Hz Note_A5: Mapa portów Wave_Generator (Trigger => O5(9), Freq_Cnt => X"06F0", wavegenCLK => CLK, podpisany(WaveOut) => WaveA5); -1776, 880,00 Hz Note_As5: Mapa portów Wave_Generator (Trigger => O5(10), Freq_Cnt => X"068C", wavegenCLK => CLK, sign(WaveOut) => WaveAs5);--1676, 932.33 Hz Note_B5: Mapa portów Wave_Generator (Trigger => O5(11), Freq_Cnt => X"062E", wavegenCLK => CLK, podpisany(WaveOut) => WaveB5); -1582, 987.77 Hz Note_C6: Mapa portów Wave_Generator (Trigger => O5(12), Freq_Cnt => X"05D6", wavegenCLK => CLK, podpisany(WaveOut) => WaveC6); -1494, 1046,5 Hz ------------uwaga do logiki wyboru ------------- C4 <= "0000" & O4(0); CS4 <= "0000" & O4(1); D4 <= "0000" & O4(2); Ds4 <= "0000" & O4(3); E4 <= "0000" & O4(4); F4 <= "0000" & O4(5); Fs4 <= "0000" & O4(6); G4 <= "0000" & O4(7); Gs4 <= "0000" & O4(8); A4 <= "0000" & O4(9); As4 <= "0000" & O4(10); B4 <= "0000" & O4(11); C5 <= "0000" & O5(0); CS5 <= "0000" & O5(1); D5 <= "0000" & O5(2); Ds5 <= "0000" & O5(3); E5 <= "0000" i O5(4); F5 <= "0000" & O5(5); Fs5 <= "0000" & O5(6); G5 <= "0000" & O5(7); Gs5 <= "0000" & O5(8); A5 <= "0000" & O5(9); As5 <= "0000" & O5(10); B5 <= "0000" & O5(11); C6 <= "0000" i O5(12); cntC4 <= C4; cntCs4 <= C4 + CS4; cntD4 <= C4 + CS4 + D4; cntDs4 <= C4 + CS4 + D4 + DS4; cntE4 <= C4 + CS4 + D4 + DS4 + E4; cntF4 <= C4 + CS4 + D4 + DS4 + E4 + F4; cntFs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4; cntG4 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4; cntGs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4; cntA4 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4; cntAs4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4; cntB4 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4; cntC5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5; cntCs5 <= C4 + CS4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5; cntD5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5; cntDs5 <= C4 + Cs4 + D4 + Ds4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + Ds5; cntE5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5; cntF5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5; cntFs5 <= C4 + Cs4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + Cs5 + D5 + DS5 + E5 + F5 + Fs5; cntG5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5; cntGs5 <= C4 + Cs4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5 + Gs5; cntA5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5 + Gs5 + A5; cntAs5 <= C4 + Cs4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5; cntB5 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5 + B5; cntC6 <= C4 + CS4 + D4 + DS4 + E4 + F4 + Fs4 + G4 + Gs4 + A4 + As4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + Fs5 + G5 + Gs5 + A5 + As5 + B5 + C6; Wybór: proces (WaveC4, WaveCs4, WaveD4, WaveDs4, WaveE4, WaveF4, WaveFs4, WaveG4, WaveGs4, WaveA4, WaveAs4, WaveB4, WaveC5, WaveCs5, WaveD5, WaveDs5, WaveE5, WaveF5, WaveFs5, WaveG5, WaveGs5, WaveA5, WaveAs5, WaveB5, WaveC6) zaczynają się, jeśli (cntC6 = "00000"), a następnie ---------------jeśli nie są generowane żadne sygnały Wave0 <= "0000000000"; Fala1 <= "0000000000"; Fala2 <= "0000000000"; Fala3 <= "0000000000"; w przeciwnym razie, jeżeli (O4(0) = '1') to ------------------- uwaga C4 odtworzone Fala0 Fala0 Błąd Fala1 Fala0 Błąd Fali2 Fala0 Fala1 Błąd Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fali3 Fala0 Fala1 Błąd Fala2 Błąd Fali3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Błąd Fali2 Błąd Fali3 Fala0 Fala1 Błąd Fali3 Fala0 Fal1 Fala2 Błąd Fali3 Fala0 Fala1 Błąd Fali2 Błąd Fali3 Fala0 Fala1 Błąd Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Błąd Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Błąd Fala2 Błąd Fala3 Fala0 Fala1 Błąd Fala3 Fala0 Fal1 Fala2 Błąd Fala3 Fala0 Fala1 Błąd Fal2 Błąd Fali3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 Fala1 Fala2 Błąd Fala3 Fala0 < = Przebieg C6; Fala1 <= "0000000000"; Fala2 <= "0000000000"; Przebieg3 Przebieg1 <= PrzebiegC6; Fala2 <= "0000000000"; Przebieg3 Przebieg2 <= PrzebiegC6; Błąd Fali3, Fali3, Fali1 <= "0000000000"; Fala2 <= "0000000000"; Fala3 Fala2 <= "0000000000"; Błąd Fali3 Fali3 <= '1'; sprawa końcowa; koniec jeśli; koniec jeśli; koniec procesu; -------------Sinus sumator--------- WaveSum <= STD_LOGIC_VECTOR(Fala0 + Fala1 + Fala2 + Fala3); ---------uczyń falę sinusoidalną pozytywną dla pwm--------------------- positiveWaveSum <= not WaveSum(9) & WaveSum(8 downto 0); -------------Generator PWM--------------------- process(CLK) --liczba zmiennych: bez znaku (1 do 0):= to_unsigned(0, 2); rozpocznij, jeśli (rising_edge(CLK)) then --count:= liczba + 1; --if (count = to_unsigned(4, 2)) then --count:= to_unsigned(0, 2); --if (PWM = to_ if (PWM < długość_pingu) then output <= '1'; else output <= '0'; end if; PWM <= PWM + 1; ping_length <= unsigned(positiveWaveSum); --end jeśli; koniec jeśli; koniec procesu; koniec Behawioralny;

4 Note Selector Najtrudniejszą częścią tego projektu jest wybranie tylko czterech częstotliwości. Zrobiliśmy to za pomocą wielu instrukcji IF i użyliśmy sygnałów zamiast zmiennych, aby proces mógł być symulowany i debugowany. Próbowaliśmy innych metod wykorzystujących zmienne i pętle FOR, ale napotkaliśmy błędy w czasie wykonywania. Ostatecznie więc zdecydowaliśmy, że jeśli to zadziała, zostawimy to w spokoju. Nie naprawiaj tego, co nie jest zepsutym amirytem?

Cztery fale wyjściowe są oznaczone jako Fala0, Fala1, Fala2, Fala3 -- są one dodawane razem w celu utworzenia końcowego wyjścia.

Patrząc na kod, zobaczysz kilka sygnałów oznaczonych C4, Cs4, D4, Ds4 itd. Są to sygnały 5-bitowe, które pobierają odpowiedni wyzwalacz z O4 (oktawa 4) lub O5 (oktawa 5) i sprawiają, że są 5-bit do dodawania.

Następnie zmienne cntC4, cntCs4, itd. wskazują, ile nut niższych niż nuta docelowa zostało zagranych, łącznie z nutą docelową. Na przykład, jeśli grane są C4, E4, G4, A#4 i D5 (akord C9), cntC4 będzie 1, cntE4 będzie 2, cntG4 będzie 3 itd.

Następnie, za każdym razem, gdy zostanie zagrana nuta, licznik dla nuty docelowej zostanie zbadany, aby zobaczyć, dokąd podłączyć sygnał nuty. Na przykład, jeśli grana jest nuta D5 (co oznacza, że O5(2) jest wysokie), a cntD5 wynosi 3, to aktualnie grane są 3 nuty, z 2 nutami niższymi niż D5, więc podłączymy waveD5 do Wave2 (trzecia fala liczenie sygnału od Wave0). Alternatywnie, jeśli cntD5 wynosi 5, to aktualnie granych jest 5 nut, z 4 nutami niższymi niż D5, więc po prostu zostawimy falę D5 wiszącą i nic z nią nie zrobimy.

Oświadczenia IF są następnie powtarzane, aby objąć przypadki dla wszystkich 25 not.

Sumator amplitudy

Po wybraniu najniższych 4 fal musimy je zsumować. Powodem, dla którego dodamy tylko cztery nuty, jest to, że idea PWM, której używamy do naszego wyjścia, może mieć tylko określoną rozdzielczość, dopóki PWM nie będzie działać zbyt wolno, a głośnik zacznie odbierać falę prostokątną PWM. Na przykład, gdybyśmy mieli użyć rozdzielczości 8192 (13 bitów), każdy z tych 8192 punktów musi odpowiadać narastającemu zboczu zegara pokładowego. Tak więc 100 MHz / 8192 = 12,2 kHz, co mieści się w zakresie ludzkiego słuchu.

Rzeczywiste dodawanie amplitud jest bardzo proste, musisz tylko upewnić się, że może działać naprawdę szybko.

Wyjście PWM

Cykl pracy PWM będzie reprezentował amplitudę naszej fali wyjściowej w tej chwili. Na przykład, jeśli mamy zakres amplitudy od 0 do 128, 0 będzie cyklem pracy 0%, 64 będzie 50%, 128 będzie 100% itd. Ten PWM będzie działał bardzo szybko (nasz to 97,6 kHz), tak szybko, że głośnik nie rozpozna poszczególnych fal prostokątnych i zamiast tego spojrzy na średnie napięcie, tworząc nasz „analogowy” sygnał.

Plik ograniczeń

Być może podłączyłeś swój sprzęt inaczej, więc po prostu upewnij się, że plik ograniczeń pasuje.

Krok 5: Pobieranie kodu

Poniżej znajduje się kod, zarówno w formacie.txt, jak i.vhd dla Vivado. Wave_Generator to podmoduł generatora fal, a Two_Octave_Synth to najwyższy moduł ze wszystkim innym.

Zalecana: