logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

AVR/Atmega644 - Generowanie sinusa PWM z tablicy 200-elementowej

fasset 31 Gru 2013 12:38 3966 11
  • #1 13119466
    fasset
    Poziom 13  
    Witam.
    Napisałem kod, który tworzy sinusa (falownik) z tablicy do której wgrane są odpowiednie wartości tworzące przebieg. Zamieszczam wyrywek kodu:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Zgodnie z powyższym kodem częstotliwość pwm wynosi 10kHz (przy taktowaniu procesora 8MHz). Dla 200-elementowej tablicy daje to 50Hz sinusa (10kHz/200).

    Obliczyłem odpowiednią ilość tablic aby mieć możliwość zmiany f wyj. co 2Hz do 50Hz- czyli 25 tablic.
    Dla powyższego kodu maksymalne wypełnienie wynosi 95%. (OCR2A 100, max. element tablicy to 95).

    Teraz chciałbym dodać możliwość regulacji wypełnienia np. co 10%.
    Pytanie jak to odpowiednio zrobić. Licze to w przerwaniu czyli np.
    OCR2B = (pwm1_50[y]/10)*9; - dla wypełnienia 85%.

    Działa, jednak mam wątpliwości co do tego rozwiązania. Czy to jest dobre rozwiązanie (obliczenia w przerwaniu, obciążenie mikrokontrolera)? Czy lepszym rozwiązaniem jest załadowanie dodatkowych tablic dla poszczególnych wypełnień?
  • #2 13119501
    tmf
    VIP Zasłużony dla elektroda
    A po co dodatkowe tablice? Prościej jest przeliczyć współczynniki z tablicy raz i potem tego używać do generacji przebiegu.
  • #3 13123260
    Andrzej__S
    Poziom 28  
    Na wstępie mała dygresja nie związana bezpośrednio z problemem - dla OCR2A=100 okres zliczania timer'a 2 wynosi 101, a nie 100 taktów.

    fasset napisał:
    Obliczyłem odpowiednią ilość tablic aby mieć możliwość zmiany f wyj. co 2Hz do 50Hz- czyli 25 tablic.

    Zamiast tworzyć tyle tablic do regulacji częstotliwości proponowałbym zastosować technikę zwaną DDS. Tutaj masz przedstawioną ideę, którą można dostosować do Twoich potrzeb. Może się to wiązać z pewną zmianą koncepcji, na przykład zastosowanie rozmiaru tablicy niebędącego potęgą 2 może nieco skomplikować sprawę, z drugiej strony przerwanie wypada co 800 taktów (dla częstotliwości PWM 10kHz), więc procedura obsługi przerwania powinna się wyrobić (zależy, co w tym czasie dodatkowo musi robić mikrokontroler). Zyskujesz natomiast możliwość regulacji częstotliwości z dużo większą rozdzielczością oraz nie musisz tworzyć tablic dla każdej częstotliwości osobno. Poza tym nie bardzo to rozumiem:
    fasset napisał:
    Dla 200-elementowej tablicy daje to 50Hz sinusa (10kHz/200)

    Zgodnie z tą logiką tablica dla 2Hz musiałaby mieć 5000 elementów?
    ( 10kHz/5000=2Hz )
    Czy może zmieniasz częstotliwość PWM na 400Hz?
    ( 400Hz/200=2Hz )

    Z Twojego kodu nie wynika gdzie i w jaki sposób zmienia się zmienna y, która indeksuje próbki z tablicy.

    Jeśli dobrze się domyślam, potrzebujesz też regulacji amplitudy sinusoidy, aby zachować stosunek U/f=const. Do tego, jak napisał wyżej kolega tmf, wystarczy raz przeliczyć całą tablicę przed wystartowaniem przebiegu.
  • #4 13124767
    fasset
    Poziom 13  
    Andrzej__S napisał:
    Na wstępie mała dygresja nie związana bezpośrednio z problemem - dla OCR2A=100 okres zliczania timer'a 2 wynosi 101, a nie 100 taktów.

    Zamiast tworzyć tyle tablic do regulacji częstotliwości proponowałbym zastosować technikę zwaną DDS. Tutaj masz przedstawioną ideę, którą można dostosować do Twoich potrzeb.

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Nie wiem jak to wykorzystać na PB4-6 otrzymuje PWM o f ok. 300kHz przy czym nie jest to na prawidłowy przebieg.

    Andrzej__S napisał:

    Zgodnie z tą logiką tablica dla 2Hz musiałaby mieć 5000 elementów?
    ( 10kHz/5000=2Hz )

    Poniżej 50Hz zmieniam częstotliwość wyj. sinusa kosztem częstotliwości PWM czyli zwiększając wartość rejestru ICR1/OCR2A - dzięki temu tablice nie osiągają tak dużych rozmiarów. Poniżej 20Hz zwiększam preskaler do 64.
    Andrzej__S napisał:
    Z Twojego kodu nie wynika gdzie i w jaki sposób zmienia się zmienna y, która indeksuje próbki z tablicy.

    W przerwaniu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #5 13125884
    Andrzej__S
    Poziom 28  
    fasset napisał:
    Nie wiem jak to wykorzystać na PB4-6 otrzymuje PWM o f ok. 300kHz przy czym nie jest to na prawidłowy przebieg.

    Przykład ze strony, do której łącze podałem, dotyczy generowania analogowego przebiegu sinusoidalnego za pomocą drabinki R-2R. Ty generujesz przebieg za pomocą PWM, więc musisz odpowiednio kod zmodyfikować, czyli nie wyprowadzać próbek na port wyjściowy, tylko wpisywać próbki do OCR2B. Poza tym kod z przykładu generuje przebieg za pomocą zwykłej funkcji, Ty musisz zrobić to w procedurze obsługi przerwania. Najlepiej byłoby też, gdybyś zmienił rozmiar tablicy na 256 elementów, to znacznie uprości kod.

    Podana przeze mnie strona miała służyć tylko zrozumieniu zasady działania DDS i zapoznania się z zasadami obliczania generowanej częstotliwości.
  • #6 13126216
    fasset
    Poziom 13  
    Andrzej__S napisał:
    fasset napisał:
    Nie wiem jak to wykorzystać na PB4-6 otrzymuje PWM o f ok. 300kHz przy czym nie jest to na prawidłowy przebieg.

    Przykład ze strony, do której łącze podałem, dotyczy generowania analogowego przebiegu sinusoidalnego za pomocą drabinki R-2R. Ty generujesz przebieg za pomocą PWM, więc musisz odpowiednio kod zmodyfikować, czyli nie wyprowadzać próbek na port wyjściowy, tylko wpisywać próbki do OCR2B. Poza tym kod z przykładu generuje przebieg za pomocą zwykłej funkcji, Ty musisz zrobić to w procedurze obsługi przerwania. Najlepiej byłoby też, gdybyś zmienił rozmiar tablicy na 256 elementów, to znacznie uprości kod.

    Podana przeze mnie strona miała służyć tylko zrozumieniu zasady działania DDS i zapoznania się z zasadami obliczania generowanej częstotliwości.


    Rozumiem, że trzeba zmienić kod tak aby funkcja signalOUT zwracała wartości tablicy, które można wpisać do rejestru, a nie do poszczególnego bitu rejestru (jak tutaj w przypadku PORTB) - niestety nie znam asemblera.

    W C znalazłem coś takiego:
    https://engineering.purdue.edu/477grp2/documents/sinegen.pdf (dla Atmega od strony 9). Przy czym w ogóle to nie różni się od metody zastosowanej w moim programie.

    Dodano po 3 [godziny] 50 [minuty]:
  • Pomocny post
    #7 13128484
    Andrzej__S
    Poziom 28  
    fasset napisał:
    ...niestety nie znam asemblera.

    Nie musisz znać. Jak przeczytasz uważnie i zrozumiesz zasadę, to sam napiszesz procedurę obsługi przerwania w C. Dla częstotliwości PWM rzędu kilkunastu kiloherców i zegara CPU 8MHz przerwanie występuje co ponad 500 taktów, więc nawet procedura napisana w C nie będzie za długa (myślę, że nie powinna przekroczyć 100 taktów). Niestety C nie oferuje operacji na 24-bitowych liczbach, więc będziesz musiał wybrać między uint16_t (mniejsza precyzja i rozdzielczość) a uint32_t (dłuższa procedura obsługi przerwania).

    fasset napisał:
    Rozumiem, że trzeba zmienić kod tak aby funkcja signalOUT zwracała wartości tablicy, które można wpisać do rejestru, a nie do poszczególnego bitu rejestru (jak tutaj w przypadku PORTB)...

    Robisz to bardzo podobnie jak robiłeś, tylko:
    1. Zmieniasz tablicę na 256 elementową, co pozwala pominąć sprawdzanie wartości zmiennej y i jej zerowanie.
    2. Nie musisz zmieniać częstotliwości PWM.
    3. Zamiast zmiennej y o 1, stosujesz zmienną accumulator, w przerwaniu zwiększasz jej wartość o wartość zmiennej acc_adder, a z tablicy pobierasz wartość próbki o indeksie równym wartości najbardziej znaczącego bajtu zmiennej accumulator i wpisujesz ją do OCR2B.

    Jak obliczyć wartość zmiennej acc_adder, aby uzyskać żądaną częstotliwość sinusoidy, to już musisz sam doczytać.
    Może opis na tej stronie będzie łatwiej zrozumieć (z tą różnicą, że tam tablica ma 1024 elementy, ale zasada jest ta sama).

    fasset napisał:
    W C znalazłem coś takiego:
    https://engineering.purdue.edu/477grp2/documents/sinegen.pdf (dla Atmega od strony 9). Przy czym w ogóle to nie różni się od metody zastosowanej w moim programie.

    No bo w tym kodzie nie ma zaimplementowanego DDS.
  • #8 13129538
    fasset
    Poziom 13  
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Niestety w czymś jest błąd bo dostaje same śmieci na wyjściach (również przy wartościach acc_adder 1350 13500). Narazie nie liczyłem wartości acc_adder dla konkretnych f, ponieważ chcę to w ogóle przetestować.

    "(F_CPU/(clocks for one sample output))" clocks for one sample output - to jest ilość taktów co ile wywoływane jest przerwanie?
  • Pomocny post
    #9 13130058
    dondu
    Moderator na urlopie...
    1. Używasz nowych wektorów przerwań, ale starej funkcji SIGNAL. Zamiast niej używaj ISR(): http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

    2. Zmienna accumulator jest 16-to bitowa a nie dbasz o sprawdzanie, czy już przekroczyła rozmiar tablicy.

    3. Poza tym, jaki sens mają dwa przerwania, skoro przerwanie od Timer2 opiera się o tę samą wartość zmiennej accumulator? Być może nie rozumiem Twoich intencji.

    Zobacz przykład prostego dwukanałowego generatora funkcyjnego: http://mikrokontrolery.blogspot.com/2011/03/avr-fast-pwm-sposob-na-dac.html
  • #10 13130350
    fasset
    Poziom 13  
    dondu napisał:
    1. Używasz nowych wektorów przerwań, ale starej funkcji SIGNAL. Zamiast niej używaj ISR(): http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

    2. Zmienna accumulator jest 16-to bitowa a nie dbasz o sprawdzanie, czy już przekroczyła rozmiar tablicy.

    Rzeczywiście to pewnie jest jedna z przyczyn, zmieniłem na:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    Z tym, że wielkiej poprawy nie widzę więc gdzieś jest jeszcze błąd.
    dondu napisał:

    3. Poza tym, jaki sens mają dwa przerwania, skoro przerwanie od Timer2 opiera się o tę samą wartość zmiennej accumulator? Być może nie rozumiem Twoich intencji.
    Zobacz przykład prostego dwukanałowego generatora funkcyjnego: http://mikrokontrolery.blogspot.com/2011/03/avr-fast-pwm-sposob-na-dac.html

    To jest potrzebne do falownika 3f więc wymagane są trzy porty, a jeden timer w At644 obsługuje jedynie 2 porty dlatego konieczne jest użycie drugiego timera.
    Oczywiście wtedy pozmieniam tablice o 120 stopni itp.
    Przykład z linku, który podałeś (generowanie sinusa) jest koncepcją, którą właśnie rozwijam - niestety wymaga to np. generowania nowych tablic dla innych częstotliwości (jeżeli chcemy zachować stałe wypełnienie).

    EDIT:
    Błąd jest na pewno w algorytmie bo np. to działa bez problemu (sprawdziłem dla pewności):
    ISR(TIMER1_OVF_vect) { //obsługa przerwania Timer1
    k++;
    if(k==255) {k=0; }
    OCR1B = sinewave[k];
    OCR1A = sinewave[k];

    }

    EDIT2: Przy okazji mam pytanie:
    Jeżeli mam np. zmienne pwm1, pwm2, pwm3, pwm4, pwm5 itp. i zmienną f, która też przyjmuje wartości 1, 2, 3, 4, 5 to czy da się zrobić tak żeby np. wpisując pwm(f) zmienić nazwę zmiennej (zmieniając f) w wykonywanym programie np. z pwm1 na pwm2?
  • Pomocny post
    #11 13130937
    Andrzej__S
    Poziom 28  
    Nie mam w tej cwili czasu na dokładniejszą analizę, ale zauważ że w celu uzyskania bardziej znaczącego bajtu zmiennej typu uint16_t należy podzielić przez 256, a nie przez 255.

    Zwiększ też wartość zmiennej ac_adder na 256, bo przy 24 otrzymasz przebieg poniżej 5Hz.

    fasset napisał:
    Błąd jest na pewno w algorytmie bo np. to działa bez problemu (sprawdziłem dla pewności):
    ISR(TIMER1_OVF_vect) { //obsługa przerwania Timer1
    k++;
    if(k==255) {k=0; }
    OCR1B = sinewave[k];
    OCR1A = sinewave[k];

    }

    Jeżeli rozmiar tablicy jest równy 256 i zmienna k jest zadeklarowana jako uint8_t, to ta konstrukcja if(k==255) {k=0; } jest zbędna, bo jej wartość nigdy nie wyjdzie poza zakres tablicy. Kiedy k osiągnie wartość 255, instrukcja k++ spowoduje jej wyzerowanie. Podobnie jest w przypadku liczb 16-bitowych, tylko taka sytuacja ma miejsce po przekroczeniu wartości 65535. Problem w tym, że jak dzielisz liczby zbliżone do wartości 65535 przez 255 (a nie 256, w celu uzyskania starszego bajtu), to otrzymasz liczbę większą od 255, czyi indeks tablicy będzie poza zakresem.
    Możesz też zamiast dzielenia przez 256 użyć przesunięcia bitowego w prawo o 8 bitów. Moim zdaniem intencje programisty będą wtedy czytelniejsze, a przy włączonej optymalizacji kompilator wygeneruje prawdopodobnie ten sam kod.
  • #12 13131098
    fasset
    Poziom 13  
    Działa po zmianie dzielnika na 256 :)

    Jest jeszcze tylko jeden mały problem:
    Częstotliwość PWM to ok. 31kHz (8MHz takt). Nie ma możliwości uzyskania częstotliwości PWM ok. 10-15kHz (max. f sterowania tranzystorami IGBT) ponieważ preskaler oferuje dzielniki tylko 8, 64 itp. Tutaj przydatny byłby dzielnik przez 2. Da się to jakoś "przeskoczyć" bez stosowania zewn. rezonatora?

    EDIT (rozwiązanie powyższego problemu):
    CLKPR = (1<<CLKPCE);
    CLKPR = 1;
REKLAMA