Brak produktów
Czas wreszcie zrobić coś na FPGA bardziej sensownego niż liczniki i proste zegarki. Tym razem na tapetę idzie coś bardzo użytecznego - generator DDS, czyli cyfrowy generator dowolnych sygnałów analogowych.
Na początek chciałbym przedstawić mało w Polsce znany układ FPGA od Lattice. Jest to nowoczesny układ MachXO2. W przeciwieństwie do typowych FPGA jest on wyposażony w pamięć stałą, więc nie ma potrzeby stosowania dodatkowych (drogich!) pamięci konfiguracyjnych, zewnętrznych pamięci flash ani nic w tym stylu. Po włączeniu zasilania od razu działa. Oprócz tego jest też generator sygnału zegarowego, interfejsy szeregowe, pamięci oraz cała masa innych ciekawych rzeczy
Dostępna jest płytka testowa MachXO2 Pico Board, zawierająca układ MachXO2-1200, cztery przyciski dotykowe, wyświetlacz LCD, parę innych gadżetów, a przede wszystkim programator na USB oparty o scalak FTDI FT2232HL. Są w nim dwie przejściówki z USB: pierwsza na interfejs JTAG i służy do programowania, a drugą można wykorzystać do własnych celów, np. jako UART.
Pola dotykowe są fajne, ale mimo to stwierdziłem, że dolutuję zwykłe przyciski microswitch, bo są po prostu wygodniejsze do celów testowych. Ich obsługa zajmuje zdecydowanie mniej zasobów logicznych FPGA.
Do płytki dołączyłem prosty przetwornik R-2R złożony z szeregu rezystorów. Umożliwia przekształcenie sygnału cyfrowego 8-bitowego na sygnał analogowy. Przetwornik jest najprostszy z możliwych, nie ma nawet wzmacniacza na wyjściu.
DDS oznacza Direct Digital Synthesis czyli bezpośrednią syntezę cyfrową. Jedyne elementy analogowe w DDS to przetwornik cyfrowo-analogowy (co oczywiste) oraz ewentualnie jakiś wzmacniacz wyjściowy. Wszystkie elementy cyfrowe można schować do FPGA, dzięki czemu generator staje się bardzo prosty i łatwo go zmodyfikować, jeśli będzie taka potrzeba. Zaletą jest to, że można wygenerować zupełnie dowolny przebieg i nie jesteśmy tu ograniczeni do szablonowego sinusa, trójkąta i prostokąta.
Najprostszy generator DDS składa się ze źródła sygnału zegarowego, który kierujemy do licznika. Licznik ma wyjście 8-bitowe (w tym przykładzie!), które dalej prowadzimy do wejścia adresowego pamięci ROM. 8 bitów adresu daje nam 256 komórek pamięci, w których zapisana jest tablica poszczególnych wartości funkcji. Można ją wygenerować nawet w Excelu. Wyjście z pamięci (dla przykładu niech też będzie 8-bitowe) kierujemy do przetwornika cyfrowo-analogowego, który przekształca sygnał cyfrowy na napięcie, mogące przybrać 1 z 256 wartości.
Takie rozwiązanie ma dwie wady - chcąc zmienić częstotliwość musimy zmienić źródło zegarowe. Nie ma także możliwości regulowania amplitudy. Pierwszą wadę spróbujemy rozwiązać w przykładzie poniżej.
Pomiędzy źródło sygnału zegarowego a licznik dodajemy regulowany dzielnik częstotliwości. Jest to kolejny licznik, z tą różnicą, że nie musi liczyć do swojej maksymalnej wartości i można skrócić okres zliczania. Można w ten sposób zrobić regulowany dzielnik, o stopniu podziału od 2 do 256 w przypadku licznika 8-bitowego.
Czy to jest rozwiązanie optymalne? Jeszcze nie... Jest pewien dość istotny problem - jaka jest maksymalna częstotliwość sygnału wyjściowego z naszego generatora? Załóżmy, że na wejściu mamy 100MHz (sporo, ale MachXO2 może się rozpędzić do większych prędkości). Po przejściu przez pierwszy dzielnik dostajemy max 50MHz. Licznik adresu jest 8-bitowy, a więc dzieli sygnał przez 256, ponieważ tyle próbek przypada na jeden okres sygnału wyjściowego. Zatem otrzymujemy niecałe 0,2MHz... Ale lipa! Ze 100MHz zrobiło się 0,2MHz. Klęska totalna!
Zwiększanie częstotliwości wejściowej nie jest dobrym pomysłem - elementy elektroniczne mają pewne ograniczenia, których się nie przeskoczy. Musimy zatem problem obejść dookoła. W naszym przykładzie, na jeden pełny okres przypada 256 próbek. A gdybyśmy trochę popuścili na jakości i odczytywali co drugą próbkę? Wtedy częstotliwość sygnału wyjściowego skoczy do 0,4MHz. A jeśli co czwartą - 0,8MHz. Gdyby na okres przypadało tylko 8 próbek to już mamy 6,25MHz. Musimy także zrezygnować z regulowanego dzielnika częstotliwości, bo wprowadza nam podział przez minimum 2, a bez niego rozpędzimy się do 12,5MHz.
Ciekawym rozwiązaniem jest licznik z akumulatorem fazy - składa się on z licznika, który z każdym zboczem zegara zwiększa swoją wartość o liczbę z rejestru, przechowującego słowo przestrajające (tuning word). Jeśli w tym rejestrze jest 1, to licznik zlicza co 1. Jeśli 2, to wtedy wartość licznika przeskakuje od razu co 2. Proste!
Kolejna wada? Regulacja częstotliwości nie będzie płynna. Przy wysokich częstotliwościach, niewielkie zmiany rejestru przestrajającego będą dość gwałtownie zmieniać częstotliwość wyjściowego sygnału. Poza tym, tracimy na jakości sygnału poprzez wycinanie próbek. I na ten problem można znaleźć rozwiązanie. Licznik akumulatora ma długość znacznie większą niż szyna adresowa ROM. Wykorzystujemy wtedy najstarsze bity licznika, a młodsze ignorujemy.
Dość już o regulowaniu częstotliwości, ale jak zmienić amplitudę w sposób cyfrowy? Można do tego użyć układu mnożącego! Najpierw krótko o mnożeniu - jeśli pomnożymy przez siebie dwie liczby x-bitowe, to otrzymamy liczbę 2x-bitową. Po ludzku, mnożąc dwie liczby 8-bitowe, wynik należałoby zapisać w zmiennej 16-bitowej. Jednak przetwornik jest 8-bitowy. Zatem wykorzystamy tylko 8 najstarszych bitów, a pozostałe 8 odrzucimy. Taka operacja jest równoznaczna z podzieleniem wyniku przez 256. Dzielenie przez liczbę, będącą potęgą dwójki polega na odrzucaniu najmłodszych bitów - jest prostsze nawet niż dodawanie!
Przykład - wartość sygnału wynosi 100. W mnożniku mamy zapisaną wartość 128. Dostajemy zatem 12800. Dalej, dzielimy to co wyszło przez 256. Dostajemy 50. Wniosek? Mnożnik możemy traktować jako ułamek - w tym przykładzie przemnożyliśmy sygnał przez 128/256 czyli przez 1/2 i w wyniku uzyskaliśmy sygnał o takim samym kształcie, ale o dwukrotnie mniejszej amplitudzie.
Zaletą generator DDS jest możliwość generowania absolutnie dowolnego przebiegu. Wystarczy tylko zmienić zawartość pamięci ROM. Można także przygotować kilka pamięci i jedynie wybierać, z której pamięci chcemy pobrać sygnał. Do tego celu potrzebujemy prostego multipleksera, czyli elektronicznej zwrotnicy. Multiplekser w przykładzie ma 6 wejść 8-bitowych oraz jedno 3-bitowe wejście sterujące. Dlaczego 3-bitowe? 2 do potęgi 3 daje 8, zatem multiplekser mógłby być nawet 8-wejściowy.
Tematyka generatorów DDS i cyfrowego przetwarzania sygnałów jest bardzo szeroka. Napisano o tym grube książki i niejeden student został zamęczony ciężką teorią :) Tak czy inaczej, można by bez większego wysiłku wykonać jeszcze kilka prostych modyfikacji.
W projekcie wykorzystałem wbudowany generator RC. Stabilność generatora jest nieduża, co powoduje różne zniekształcenia i "pływanie" wykresu na oscyloskopie przy wyższych częstotliwościach, ale trudno, chodziło tu o zastosowanie edukacyjne generatora. Jego częstotliwość ustawiłem na 66,5MHz. Następnie, wykorzystano PLL, który podbija częstotliwość do 133MHz i ten sygnał został użyty do syntezy DDS. Oprócz tego PLL ma drugie wyjście, które użyłem do wygenerowania sygnału 2MHz dla elementów sterujących i kontrolnych (wyświetlacz, klawisze, ustawianie częstotliwości i amplitudy).
Tablice wartości funkcji są zapisane w blokach pamięci EBR. Przydatny okazał się IPexpress ze środowiska Diamond, dzięki czemu wykorzystanie peryferiów FPGA jest niezwykle łatwe. Każda tablica składa się z 256 słów 8-bitowych. Zostały wygenerowane w Excelu :)
Akumulator jest 16-bitowy, a słowo przestrajające jest 8-bitowe. Do adresowania pamięci wykorzystuję 8 najstarszych bitów akumulatora. Mnożarka została wygenerowana również przy pomocy IPexpress. Początkowo mnożarka była zsynchronizowana z zegarem, jednak projekt kompilował się bardzo długo. Po zmianie układu mnożącego na asynchroniczny, kompilacja projektu idzie zdecydowanie szybciej.
Rejestry "tuning word", "selektor" i "mnożnik" to tak naprawdę zwykłe liczniki. W zależności od tego czy odpowiednie klawisze są wciśnięte, zwiększają lub zmniejszają swoją wartość. Kod odpowiedzialny za obsługę wyświetlacza wziąłem z artykułu Zbigniewa Hajduka z Elektroniki Praktycznej.
Zobaczmy zatem, co potrafi ten prosty generatorek. Sygnał prostokątny generuje się prawidłowo i jest to rzecz najprostsza z możliwych :)
Sinus wygląda trochę gorzej. Charakterystyczne szpilki w pewnych stałych miejscach to skutek niedoskonałości przetwornika cyfrowo-analogowego.
Sprawdźmy jak działa regulacja amplitudy. Biały wykres został zapisany w pamięci oscyloskopu, kiedy amplituda była ustawiona na maksimum. Następnie zmniejszono amplitudę i porównano oba wykresy.
A teraz podkręcimy częstotliwość do maksimum - przy obecnej konfiguracji udało się uzyskać sygnał sinusoidalny o częstotliwości 16,5MHz! Niestety jest on trochę zdeformowany. Zapewne przetwornik ze zwykłych rezystorów robi swoje. Fioletowa linia to analiza FFT.
Zdjęcia pozostałych przebiegów nieregularnych.
Zamieszczam kompletny projekt napisany w języku Verilog i przygotowany w programie Lattice Diamond 2 oraz plik Excela do generowania tablic funkcji.
Pliki do pobrania: