Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

Generator funkcyjny DDS, zniekształcenia

mas24 02 Gru 2016 12:56 2946 137
  • #1 02 Gru 2016 12:56
    mas24
    Poziom 16  

    Witam,

    Z tym problemem borykam się już dość długo. Chodzi o generowanie przebiegów okresowych z wykorzystaniem DDS na mikrokontrolerze (zakres audio). Przy czym nie jest bardzo istotny tu typ mikrokontrolera, byle by miał DAC (próbowałem na Xmega i przeróżnych ARMach).

    Jest tak:
    Mam w pamięci tablicę, np. 1024 próbek, a w przerwaniach odczytuję tą tablicę mniej więcej tak:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Im większa wartość akumulatora fazy dX, tym wyższa częstotliwość i program "przeskakuje" więcej próbek.
    Idealnie jest, gdy wartość dX jest z szeregu 2^n, dla innych wartości, zwłaszcza dla dużych wartości dX, pojawiają się zniekształcenia, zwłaszcza dla generowanych przebiegów o ostrych zboczach (prostokąt, piła). Widać je na oscyloskopie w postaci "drgających zboczy" i słychać jako dodatkowy dźwięk.

    Moje pytanie teraz brzmi: jak odtwarzać próbki z tablicy, by uniknąć tych zniekształceń?

    0 29
  • #2 02 Gru 2016 13:02
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Zacznij od wrzucenia konkretnego kodu, bo to co wrzuciłeś teraz to najwyżej skaluje amplitudę, wiec niezbyt wiadomo w czym _MOŻE_ być problem. Najlepiej wraz z jakimiś przykładowymi wartościami dla dX i S które powodują problem.

    Niemniej jednak efektu nie da się wyeliminować całkowicie. Jeśli w 1024 próbkach masz przykładowo liniowe narastanie od 0 do 1023, to w zależności od tego jakie będziesz miał owe dX niektóre okresy wygenerowanego przebiegu będą różniły się długością od innych. Po prostu tak działają liczby całkowite. Przykładowo jeśli Twoje dX wynosiłoby (po przeskalowaniu) dokładnie 100, to po prostu większość okresów będzie trwała 10 cykli, jednak co któryś będzie trwał 9.

    Pewnym rozwiązaniem może być używanie liniowej interpolacji i nieco innego algorytmu, ale to w zasadzie sprawi, że problem będzie się trafiał (dużo) "rzadziej". Chodzi o to, żebyś próbek nie brał bezpośrednio z tablicy jeśli faza nie trafia idealnie w dany element tablicy, tylko żebyś używał liniowej interpolacji dwóch sąsiednich próbek, przy czym wagą dla nich będzie po prostu to jaka jest dokładna wartość fazy.

    Przykładowo teraz masz 10 bitów tablicy i 32-bitową zmienną fazy, więc 22 bity odrzucasz całkowicie. Zamiast tego użyj tych 22 bitów jako współczynnika potrzebnego do wykonania liniowej interpolacji dla sąsiednich wartości - https://en.wikipedia.org/wiki/Linear_interpolation Właśnie tak powinna działać synteza DDS.

    Jeszcze bardziej konkretny przykład. Dajmy na to, że Twoje dX wynosi 1000000. Przy Twoim aktualnym algorytmie dla pierwszych 4 przerwań na DAC podajesz dokładnie tą samą wartość z tablica[0]. Przy prawidłowo zrobionym DDS do DAC wartość tablica[0] powinna trafić tylko w pierwszym przerwaniu. W drugim powinna się tam znaleźć wartość równa:

    tablica[0] + (1000000 - (0 << 22)) * (tablica[1] - tablica[0]) / ((1 << 22) - (0 << 22))

    Jest to użycie wzoru z artykułu w Wikipedii.

    Oczywiście sam musisz zadbać o prawidłowe zachowanie tego algorytmu dla sytuacji gdy przechodzisz z końca tablicy na jej początek - wtedy po prostu robisz interpolację dla pierwszej i ostatniej wartości. Choć można też zrobić to inaczej - np. ostatni element tablicy równy jest pierwszemu.

    0
  • #3 02 Gru 2016 13:08
    2675900
    Użytkownik usunął konto  
  • #4 02 Gru 2016 13:10
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Piotrus_999 napisał:
    Sama idea takiego generowania w przerwaniach jest zła. Musisz mieć 20GHz procesor. Od tego jest DMA i odpowiednio trygierowana timerami transmisja.

    Tylko jeśli chcesz mieć przebieg 20GHz. W każdym innym przypadku można to bezproblemowo zrobić w przerwaniach i osiągnąć "adekwatne" częstotliwości do mocy obliczeniowej.

    0
  • #5 02 Gru 2016 13:20
    2675900
    Użytkownik usunął konto  
  • #6 02 Gru 2016 13:43
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Tyle że przy użyciu DMA musisz mieć _DODATKOWĄ_ tablicę, do której w "wątku" generujesz próbki wg algorytmu który opisałem w pierwszym poście. Zresztą nawet jakby maił tylko wybierać próbki z tablicy, bez interpolacji, to tak samo potrzebna jest dodatkowa tablica.

    Piotrus_999 napisał:
    PS Freddie nie wiesz ile zajmuje powrót z przerwania w M4 - bo jakoś nie mogę tego odszukaż w żadnej dokumentacji.

    Da się znaleźć na stronie ARM, która od tygodnia chodzi jakby chciała umrzeć, wiec obecnie się znaleźć nie da [;

    0
  • #7 02 Gru 2016 15:55
    2675900
    Użytkownik usunął konto  
  • #8 02 Gru 2016 16:33
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Piotrus_999 napisał:
    Algorytmów zwiększania i zmniejszania ilości sampli jest sporo - dużo jest przykładów dla STM-ów używajacych rozkazów DSP.

    Tyle że typów interpolacji które dają sensowny wynik dla różnych funkcji (nie znanych z góry) to już tak sporo nie ma. Liniowa interpolacja jest przy okazji najprostszym i daje całkiem niezłe wyniki. Inny algorytm który kojarzę - cubic spline interpolation - już taki prosty nie jest, a wcale też cudów w nim się nie osiągnie.

    No chyba że mówimy o typowych zabawach w DSP, to tu coś sensownego znalazłem. http://dspguru.com/dsp/faqs/multirate/interpolation Tyle że przy założeniu, że częstotliwość sygnału wyjściowego ma być "dowolna", to algorytm robi się nadzwyczaj skomplikowany, gdyż wymaga interpolacji i decymacji.

    0
  • #9 02 Gru 2016 17:22
    2675900
    Użytkownik usunął konto  
  • #10 02 Gru 2016 17:36
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Piotrus_999 napisał:
    Ja wmoim oscyloskopiku zaimplementowałem cztery rózne - liniową, wielomianowa, spline i cubiic.

    To popatrz sam co robią inne interpolacje niż liniowa z piłą.

    Wrzuć coś takiego:
    0 0
    1 1
    2 2
    3 3
    4 0
    5 1
    6 2
    7 3
    8 0

    tu:
    http://tools.timodenk.com/linear-interpolation
    http://tools.timodenk.com/quadratic-interpolation
    http://tools.timodenk.com/polynomial-interpolation
    http://tools.timodenk.com/cubic-spline-interpolation

    Piła oczywiście jest problematycznym przebiegiem, niemniej jednak akurat przy liniowej interpolacji wygląda najlepiej <: Przy wielomianowej i kwadratowej to w ogóle szkoda patrzeć.

    0
  • #11 02 Gru 2016 18:03
    2675900
    Użytkownik usunął konto  
  • #12 02 Gru 2016 19:36
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Piotrus_999 napisał:
    Inna sprawa że Twój przykład jest wyjątkowo nieuczciwy .

    Sorry, autor wątku w pierwszym poście sam pisał o pile (;

    0
  • #13 02 Gru 2016 20:11
    2675900
    Użytkownik usunął konto  
  • #14 02 Gru 2016 22:24
    mas24
    Poziom 16  

    Trochę to zagmatwane, ale popatrzę sobie na spokojnie. Co do piły, to tak na prawdę każdą tablicę "odczytuje się piłą", gdzie następuje narost liniowy akumulatora fazy, który po zeskalowaniu pointuje tablicę LUT.

    Rzeczywiście, popełniłem błąd w zapisie algorytmu odczytu tablicy. Skalowaniu powinien ulegać akumulator, a nie amplituda. Jeśli mamy tablicę 1024 próbek 12-bitowych i przetwornik DAC też jest 12-bitowy, to nie trzeba skalować amplitudy, a jedynie fazę:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Tak wygląda "mój" algorytm, z którym mam problemy.

    Dodano po chwili:

    Czyli z tą interpolacją jest tak, ze odczytuję tablicę co drugie przerwanie, a pomiędzy nimi obliczam punkt z równania. Muszę jednak mieć poprzedni punkt zapisany do DAC i następny, który prześlę do DAC?

    0
  • #15 03 Gru 2016 00:19
    2675900
    Użytkownik usunął konto  
  • #16 03 Gru 2016 01:10
    piotrva
    Moderator na urlopie...

    mas24 napisał:
    Trochę to zagmatwane, ale popatrzę sobie na spokojnie. Co do piły, to tak na prawdę każdą tablicę "odczytuje się piłą", gdzie następuje narost liniowy akumulatora fazy, który po zeskalowaniu pointuje tablicę LUT.

    Strasznie gmatwasz. Dopiszę się do przedmówcy - powiedz co ma robić gotowe urządzenie ;)

    0
  • #17 03 Gru 2016 08:33
    Freddie Chopin
    Specjalista - Mikrokontrolery

    mas24 napisał:
    Czyli z tą interpolacją jest tak, ze odczytuję tablicę co drugie przerwanie, a pomiędzy nimi obliczam punkt z równania. Muszę jednak mieć poprzedni punkt zapisany do DAC i następny, który prześlę do DAC?

    Absolutnie nie.

    Wejdź na wiki na tą stronkę https://en.wikipedia.org/wiki/Linear_interpolation i zerknij na trzeci obrazek - ten z fragmentem "niby-sinusa". Twoja tablica zawiera czerwone kropki dla indeksów 0, 1, 2, 3, ... . Twoje X zmienia się jednak "płynnie" pomiędzy tymi wartościami i dla jakiejś tam częstotliwości będzie się zmieniało np. tak 0, 0.0011, 0,0022, 0,0033, 0,0044, ... . Wartości wpisywane do DAC musisz zasadniczo _ZAWSZE_ liczyć interpolując dwie sąsiednie wartości z tablicy, znajdujące się "obok" danego X, chyba że X wyjdzie Ci _DOKŁADNIE_ taki jak indeks w tablicy. Zwykle będzie to następowało bardzo rzadko i tak właśnie ma być.

    Sprawa jest naprawdę prostsza niż się spodziewasz i warto to zrobić. Wzór który przedstawiłem w pierwszym poście da się łatwo dopasować do sytuacji uniwersalnej:

    value = tablica[X >> S] + (X - ((X >> S) << S)) * (tablica[(X + dX) >> S] - tablica[X >> S]) / ((((X + dX) >> S) << S) - ((X >> S) << S))

    Potraktuj to jako wskazówkę, bo to dosyć uproszczony wzór który sprawdzi się jedynie wtedy gdy dX jest mniejsze niż odległość między dwoma punktami w tablicy. Lepszy byłby wzór, w którym liczysz sobie po prostu indeks punktu odpowiadającego danemu X. Jeśli wychodzi idealnie, to po prostu odczytujesz z tablicy. Jeśli wychodzi nieco większy (czyli reszta z dzielenia będzie różna od zera), to robisz interpolację tego punktu i kolejnego punktu z tablicy.

    Wzór który jest na wikipedii pod "Solving this equation for y, which is the unknown value at x, gives" to jedyny wzór jaki jest Ci potrzebny. x to Twoje X, y to wartość którą chcesz wpisywać do DAC. x0 i x1 to idealne wartości X odpowiadające wartościom z tablicy, skoro X jest 32-bitowe, a tablica ma 10-bitowy indeks, to wartości x0 i x1 będą równe np. 0 << (32 - 10), 1 << (32 - 10), 2 << (32 - 10), ... Wartości y0 i y1 to wartości z tablicy odpowiadające danemu x0 i x1. Tak wiec jedyny "problem" to przekształcenie Twojego X na indeksy dla tablicy, jednak jest to operacja nadzwyczaj prosta - to po prostu całkowite dzielenie:

    X / (1 << (32 - 10))

    albo:

    X >> (32 - 10)

    Indeks kolejny jest po prostu "kolejny" z uwzględnieniem tego co będzie na końcu tablicy, a więc najprościej z użyciem operacji modulo.

    Tak naprawdę, to zwróć uwagę, że do tej pory robiłeś taką "prawie" interpolację tego typu - https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation . Jedyna różnica jest taka, że ułamki zawsze zaokrąglałeś w dół, a nie do najbliższej wartości, ale idea jest praktycznie identyczna.

    0
  • #18 03 Gru 2016 10:00
    mas24
    Poziom 16  

    Dzięki za obszerne wyjaśnienie, muszę to teraz przetrawić.
    A do czego jest mi to potrzebne? Ano właśnie do zbudowania generatora funkcyjnego dla audio, ale nie tylko.
    Chcę zbudować także syntezator cyfrowy. Mam już nawet do tego płytki na STM32F405 i 429, więc moc obliczeniowa jest.Bez trudu można znaleźć inny mój wątek o obsłudze klawiatury.

    Mam jeszcze kilka pytań:
    Jaką optymalnie dużą tablicę wybrać? Czytałem, ze 256 próbek to minimum, więc 512 czy właśnie 1024 powinno wystarczyć.
    Jaką częstotliwość próbkowania Fsampl wybrać? czy 44kHz czy większą? Jako częstotliwość próbkowania rozumiem częstotliwość, z jaką wywoływane są przerwania od timera czy systicka.

    0
  • #19 03 Gru 2016 11:27
    2675900
    Użytkownik usunął konto  
  • #20 03 Gru 2016 11:47
    mas24
    Poziom 16  

    Próbowałem już przy 16k próbek. Napisałem sobie programik na PC, który mi liczy i wrzuca w kod przebieg okresowy, można zapodać parametry, jak bitowość (od 4 do 24 bitów (zwykle daję 12), oraz długość tablicy (od 16 bajtów do 16 kB). Przebiegów można wyliczyć kilka i potem je mieszać (miksować) w programie.
    Miewają ekstremum, zwłaszcza przebiegi zerznięte z gotowych sampli, mające imitować jakieś instrumenty, lecz metoda z interpolacją wydaje się obiecująca.


    Ale wracając do meritum, napisałem taki oto teoretyczny kod na podstawie informacji Frediego:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    nie wiem, czy dobrze, dodałem kilka predefiniowanych zmiennych Xs i Xd, które w równaniu są liczone kilka razy, by liczył je tylko raz, dla zaoszczędzenia taktów zegara. Można pominąć także Y, ale może lepiej liczyć to wszystko w dziedzinie 32 bitowej, a potem tylko skalować na 12-bit DAC przesuwając o S.

    0
  • #21 03 Gru 2016 11:55
    2675900
    Użytkownik usunął konto  
  • #22 03 Gru 2016 11:59
    Freddie Chopin
    Specjalista - Mikrokontrolery

    mas24 napisał:
    ((Xd)-(Xs<<S))

    To na pewno jest źle.

    Zanim zabierzesz się za nic nie znaczące optymalizacje, uruchom to bez ŻADNYCH optymalizacji. Proponowałbym też, abyś sprawdził sobie to najpierw na PC - napisz prosty programik konsolowy, albo po prostu wykorzystaj arkusz kalkulacyjny. Za przebieg wybierz coś na czym ową interpolację będzie widać - na tym co wrzuciłeś sąsiednie wartości różnią się od siebie o 1, więc tu nie ma co interpolować - pomiędzy 123 a 124 algorytm ten wyliczy Ci 123 albo 124 choćbyś nie wiem jak czarował, co w zasadzie da praktycznie identyczny przebieg jak Twój oryginalny kod (będzie przesunięty o pół kroku i to cała różnica). Żeby to miało sens, to pomiędzy wartościami muszą być duże różnice.

    0
  • #23 03 Gru 2016 12:16
    deus.ex.machina
    Poziom 32  

    Mam wrażenie ze w wątku miesza się dwie różne rzeczy: DDS (NCO) vs Wavetable...

    0
  • #24 03 Gru 2016 12:26
    2675900
    Użytkownik usunął konto  
  • #25 03 Gru 2016 14:03
    mas24
    Poziom 16  

    Chyba czegoś tu nie rozumiem. Początkowo chodziło mi o to, by uniknąć zakłóceń podczas odtwarzania przebiegów z tablicy, np. piły, na której bardzo dobrze to widać. Problem polegał na tym, że wartość kroku była inna niż wartość tablicy stąd powstawały te zakłócenia, widziane na oscyloskopie jako "drgające zbocza". Jak więc interpolacja ma pomóc mi w moim problemie? Przecież mogę przygotować sobie próbki już "zinterpolowane" i odpowiednio dokładne, ale nie o to mi chodziło.

    0
  • #26 03 Gru 2016 14:20
    2675900
    Użytkownik usunął konto  
  • #27 03 Gru 2016 15:11
    Freddie Chopin
    Specjalista - Mikrokontrolery

    mas24 napisał:
    Chyba czegoś tu nie rozumiem.

    Dlatego proponuję, abyś sobie to przeanalizował na PC w arkuszu kalkulacyjnym, gdzie sobie od razu możesz zrobić ładne wykresy. Interpolacja pomoże na Twoje problemy, chyba że masz zupełnie inny problem niż opisałeś. Bo albo chcesz zrobić resampling tego co masz (w czym pomoże Ci choćby interpolacja liniowa, wbrew pozorom działa całkiem dobrze w większości przypadków), albo po prostu musisz generować przebiegi o takiej częstotliwości jak należy. Nie ma innych metod, no bo niby jakie? Albo masz poprawne dane (i wtedy jest idealnie) albo ich nie masz i musisz robić resampling (więc idealnie nie jest, ale może być "całkiem dobrze", a okres na 100% będzie poprawny i stały). W pierwszym poście opisałem Ci, co powoduje te różnice w okresach piły jakie obserwowałeś przy swoim algorytmie - on po prostu jest zbyt uproszczony, żeby mógł działać dobrze.

    Uwierz mi, że metoda którą opisałem działa i to bardzo dobrze. Bez cudów które opisuje Piotrus_999 wyżej (;

    mas24 napisał:
    Przecież mogę przygotować sobie próbki już "zinterpolowane" i odpowiednio dokładne, ale nie o to mi chodziło.

    Dobrze by też było, żebyś opisał dokładnie o co Ci chodzi w takim razie. Jeśli szukasz funkcji usun_zaklocenia_i_spraw_aby_sygnal_byl_idealny(), to jej nie znajdziesz <:

    0
  • #28 03 Gru 2016 15:13
    2675900
    Użytkownik usunął konto  
  • #29 03 Gru 2016 15:39
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Piotrus_999 napisał:
    Dlaczego cudów?

    Chodziło mi tutaj o to wyszukiwanie extremów o którym kilka razy pisałeś.

    0
  • #30 03 Gru 2016 15:50
    mas24
    Poziom 16  

    Moim celem jest stworzenie programu, który odczytywałby klawiaturę muzyczną (to inny temat). Numer klawisza jest pointerem do tablicy z wyliczonym krokiem dX (m. in. na podstawie fsampl, długości tablicy itd) odpowiadającym określonym nutom na skali muzycznej. Jest też inna tablica próbek w ramie, którą mogę podmieniać z dowolną inną tablicą zawartą we flash, czyli przetłaczać generowany dźwięk np. z sinusa na prostokąt, lub na piłę, lub na jakikolwiek inny. To mam już opanowane.
    Także modulacje amplitudową (alFO) i częstotliwościową (fLFO) mam już opanowaną. Podczas odtwarzania dźwięku nie jest ciągle zmieniana wartość tablicy, tablica jest taka sama.
    Dość dobrze rozumiem problem, ale nie wiem, jak mu przeciwdziałać. Gdy ilość kroków jest mniejsza lub większa od tablicy, następuje "przebicie akumulatora fazy w połowie kroku, co daje dodatkowy dźwięk.
    Przećwiczę jednak to równanie w Excelu i zobaczę, co mi wyjdzie.

    0