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.

Zastępczy generator 8x PWM sterowany przez UART

katakrowa 13 Lut 2019 22:29 2460 33
  • Zastępczy generator 8x PWM sterowany przez UART
    Niniejszy projekt to nic odkrywczego, ale może się komuś przyda.
    Czasem potrzebujemy wygenerować sygnał PWM dla większej ilości kanałów a akurat nie mamy pod ręką dedykowanego układu - tak było w moim przypadku gdy robiłem mały projekt z oświetleniem i wentylatorami przy "modowaniu PC" - tak to się pisze? To nie mój PC :-) (Ja osobiście jestem przeciwnikiem światełek w komputerach, ale znajomy poprosił więc pomogłem).
    Oczekiwanie na dedykowany układ z Chin nie miało sensu a i ceny takich układów nie były zachęcające. Postanowiłem poradzić sobie z tym co akurat było w szufladzie. A w niej na szczęście mam zazwyczaj sporo różnych uC. W obroty wziąłem attiny2313.

    Po kilku godzinach pisania powstał prosty generator PWM sterowany przez port szeregowy (UART) za pomocą prostego protokołu tekstowego więc bez trudu można sterować układem z portu szeregowego PC.
    Układ można konfigurować na kilka sposobów:
    - można określać ilość wykorzystywanych kanałów;
    - można określać rozdzielczość/głębokość PWM wartości od 0 do 255;
    - można określić wypełnienie PWM dla każdego kanału osobno (w zakresie ustalonej głębokości);

    Ustalona konfiguracja zapisywane jest w wewnętrznej pamięci EEPROM więc po ponownym uruchomieniu układ działa z ostatnio przesłanymi parametrami.

    Myślę, że układ może być ciekawą i tanią alternatywą w wielu zastosowaniach tym bardziej, że cena układu atTiny jest bardzo niska a czasem niższa od dedykowanych generatorów PWM.
    Rozwiązanie ma swoje ograniczenia ale jak wspomniałem powstało z potrzeby chwili zapewne można to zrobić inaczej i lepiej jednak do sterowania kolorowymi diodami i 4 wentylatorami w PC sprawdziło się doskonale.

    W zależności od konfiguracji głębokości modulacji oraz ilości aktywnych kanałów zmieniają się maksymalne częstotliwości generowanego sygnału PWM a przedstawia je poniższa tabela oraz załącznik xls.
    Wartości skrajne oraz przykładowe to:
    - 476kHz dla 1 kanału i głębokości PWM = 4;
    - 2,5kHz dla 8 kanałów i głębokości PWM = 255;
    - 25,5kHz dla 8 kanałów i głębokości PWM = 26;
    - 25,5kHz dla 8 kanałów i głębokości PWM = 26;
    - 10,0kHz dla 8 kanałów i głębokości PWM = 64;
    - 102kHz dla 4 kanałów i głębokości PWM = 10;
    * Wartości podane dla kwarcu 20Mhz.

    Na obrazku wybrane wartości częstotliwości PWM w zależności od ilości kanałów i głębokości (w załączniku xls pełne dane):

    Zastępczy generator 8x PWM sterowany przez UART





    Opis protokołu:

    Code:


       /*   
         Opis protokołu komunikacji:
        
         Ramki przychodzące:
         -------------------   
         Programowanie wypełnienia PWM wybranego kanału:
         bajt 0 : numer kanału w ascii ( wartości : '0', '1', '2', '3', '4', '5', '6', '7' )
         bajt 1 : starsze 4 bity wypełnienia PWM jako HEX ( '0' .. 'F' )
         bajt 2 : młodsze 4 bity wypełnienia PWM jako HEX ( '0' .. 'F' )
         bajt 3 : 'X' zakończenie ramki
        
         Zmiana ilości kanałów:
         bajt 0 : 'C'
         bajt 1 : starsze 4 bity ilości kanałów zawsze = '0'
         bajt 2 : młodsze 4 bity ilości kanałów jako HEX ( '0' .. '8' )
         bajt 3 : 'X' zakończenie ramki
        
         Zmiana rozdzielczości PWM :
         bajt 0 : 'R'
         bajt 1 : starsze 4 bity rozdzielczości PWM ( '0' .. 'F' )
         bajt 2 : młodsze 4 bity rozdzielczości PWM ( '0' .. 'F' )
         bajt 3 : 'X' zakończenie ramki
       
         Prośba o status :
         bajt 0 : 'G'
         bajt 1 : cokolwiek
         bajt 2 : cokolwiek
         bajt 3 : 'X' zakończenie ramki

        
         Odpowiedzi:
         -----------
         - przyjęcie ramki potwierdzane jest znakiem '#' ;
         - błąd w interpretacji ramki sygnalizowany jest znakiem '!' ;

        Przykład użycia:
        ----------------
        C04X  ( ustaw 4 kanały )
        #     ( potwierdzenie od sterownika )
        RFFX  ( ustaw rozdzielczość PWM na 255 )
        #     ( potwierdzenie od sterownika )
        366X  ( ustaw wypełnienie PWM kanału 3 na 0x66 )
        #     ( potwierdzenie od sterownika )
        223X  ( ustaw wypełnienie PWM kanału 2 na 0x23 )
        #     ( potwierdzenie od sterownika )
       
       */


    Kod źródłowy:

    Uwagę zwrócą zapewne powtórzone pętle... Zastosowałem ich kilka ze względu na optymalizację i chęć uzyskania jak najwyższych częstotliwości. Tak samo wstawka assemblerowa ma na celu optymalizację przebiegu każdej pętli o 1 takt (kompilator nie chciał tego zoptymalizować po mojej myśli).

    Kod: c
    Zaloguj się, aby zobaczyć kod


    W załączniku kod źródłowy oraz pliki HEX (uwaga, HEX dla kwarcu 11.059Mhz).


    Fajne! Ranking DIY
    Potrafisz napisać podobny artykuł? Wyślij do mnie a otrzymasz kartę SD 64GB.
  • #2 14 Lut 2019 13:43
    LChucki
    Poziom 28  

    katakrowa napisał:
    Uwagę zwrócą zapewne powtórzone pętle ... Zastosowałem ich kilka ze względu na optymalizację i chęć uzyskania jak najwyższych częstotliwości.

    to
    Kod: c
    Zaloguj się, aby zobaczyć kod

    też optymalizacja?

    Dodano po 2 [minuty]:

    To
    Kod: c
    Zaloguj się, aby zobaczyć kod

    i następne 0..15 to "optymalizacja" jak największego zużycia FLASH?

  • #3 14 Lut 2019 13:46
    tmf
    Moderator Mikrokontrolery Projektowanie

    Ponieważ pokazałeś kod, więc kilka uwag, dotyczących najistotniejszych IMHO porażek:
    1. Przede wszystkim to PWM cechuje się masakrycznym jitterem. Przede wszystkim wprowadzanym, przez nieprzewidywalną czasowo transmisję UART - jeśli coś jest nadawane, to będzie to wpływać na generowane przebiegi. Dodatkowo wielokrotne warunki też wprowadzają zmienny czas wykonania, w efekcie przebiegi wpływają na siebie.
    2. Poczytaj o switch/case - dzięki temu zastąpisz wielokrotne ify.
    3. Zamiast wysyłać informację przez UART po znaku, prościej napisać funkcję przesyłającą łańcuchy...
    4. Gdybyś channel zrobił jako tablicę to:
    a) konstrukcje typu if(1,2,3,4 itd.) można by zastąpić channel [nr kanału]
    b) wielokrotne read/write eeprom można by zamienić na odpowiednie instrukcje blokowe - dzięki temu można jedną instrukcją czytać ustawienia dla wszystkich kanałów na raz.
    5. Funkcje toByte, toHex to koszmarek - zwykłe odejmowanie rozwiązuje sprawę, żadne ify nie są potrzebne.
    6. Po co masz oddzielne funkcje loopchannel? Przecież można zrobić jedną i tylko indeksować tyle kanałów, ile aktualnie jest aktywnych.
    7. PWM można generować w oparciu o przerwania - zakładając, że odpowiednia funkcja obsługi wykonywana byłaby max 100 taktów, to miałbyś PWM o f=200 kHz, z minimalnym jitterem, dla praktycznie dowolnej liczby kanałów. Zapewne ISR trwałby krócej, czyli nawet wyższą częstotliwość PWM można by bez problemu uzyskać.
    8. Sztywna alokacja rejestrów zwykle robi więcej szkody niż pożytku.
    Pomijam, że np. użycie ATTiny814 (jeśli o tej serii już mowa) daje nam 8 sprzętowych kanałów PWM, co całkowicie eliminuje problem. A cena 0,46$ (2,48zł w Farnellu).

  • #4 14 Lut 2019 14:07
    katakrowa
    Poziom 20  

    Uwagi kolegi tmf jak najbardziej słuszne a wynikają z tego, że c++ to zupełnie nie mój język i poza hobbystyczą elektroniką nigdzie go nie używam. Chodziło o ty by zadziałało i zadziałało.
    Jak napisałem jestem pewien, że można to zrobić lepiej.
    AVR'y jak widać programuję topornie i nie znam ich peryferiów ale czy z użyciem przerwań uzyskałby się większe częstotliwości dla 8 kanałów i tych samych głębokości modulacji - nie jestem pewien i wątpię. Co konkretnie miałoby to umożliwić - która funkcjonalność AVR daje taką możliwość ?

  • #5 14 Lut 2019 15:46
    eDZio
    Poziom 16  

    katakrowa napisał:
    Co konkretnie miałoby to umożliwić - która funkcjonalność AVR daje taką możliwość ?

    tmf napisał:
    PWM można generować w oparciu o przerwania

    To jest odpowiedź. Przerwania licznika.

  • #6 14 Lut 2019 15:50
    katakrowa
    Poziom 20  

    eDZio napisał:
    PWM można generować w oparciu o przerwania

    Ale nie ma dostępnych 8 liczników. Natomiast jak do przerwania wrzucimy kod obsługujący 8 liczników to też już przestanie być tak ciekawie i łatwo jak koledzy to opisują..

  • #7 14 Lut 2019 15:54
    LChucki
    Poziom 28  

    katakrowa napisał:
    eDZio napisał:
    PWM można generować w oparciu o przerwania

    Ale nie ma dostępnych 8 liczników. Natomiast jak do przerwania wrzucimy kod obsługujący 8 liczników to też już przestanie być tak ciekawie i łatwo jak koledzy to opisują..

    20 8-bit PWM 100Hz na AVR 8MHz robiłem. Niestety, zajętość czasu CPU ok 50%.

  • #8 14 Lut 2019 16:12
    katakrowa
    Poziom 20  

    LChucki napisał:
    20 8-bit PWM 100Hz na AVR 8MHz robiłem. Niestety, zajętość czasu CPU ok 50%.

    100Hz czy 100 kilo Hz ?

    Przedstawiony projekt był robiony dla wentylatorów wstępnie 6-ciu. Podobno wentylatory muszą być sterowane PWM powyżej 25kHz ( mówią, że coś piszczy jak jest niżej ). I takie były założenia.
    Dla 8 niezależnie sterowanych kanałów udało mi się uzyskać głębię modulacji 80 i częstotliwość >25kHz ( czyli założenia spełnione - do sterowania wiatrakami wystarczające ). Przy tej częstotliwości i zastosowaniu niedoskonałości sygnału wynikające z "przebiegów pętli" nie mają najmniejszego znaczenia.
    Jak wcześniej napisałem może kod programu jest toporny bo c++ to nie moja bajka ( jest to najbardziej irytujący mnie język ) ale sama idea była przemyślna i sprawdziła się.

    p.s. Kolego LChucki jeśli nie stoi nic na przeszkodzie odblokuj mnie na priv - tak się składa, że przypadkiem przez kłótnię trafiłem na Twój projekt, z którego coś mnie bardzo interesuje.

  • #9 14 Lut 2019 17:07
    acctr
    Poziom 15  

    LChucki napisał:
    if ( hx == '0' ) return 0 ;
      if ( hx == '1' ) return 1 ;
      if ( hx == '2' ) return 2 ;
      if ( hx == '3' ) return 3 ;
      if ( hx == '4' ) return 4 ;
      if ( hx == '5' ) return 5 ;
      if ( hx == '6' ) return 6 ;
      if ( hx == '7' ) return 7 ;
      if ( hx == '8' ) return 8 ;
      if ( hx == '9' ) return 9 ;
      if ( hx == 'A' ) return 10 ;
      if ( hx == 'B' ) return 11 ;
      if ( hx == 'C' ) return 12 ;
      if ( hx == 'D' ) return 13 ;
      if ( hx == 'E' ) return 14 ;
      if ( hx == 'F' ) return 15 ; 

    return hx > '9' ? hx - 'A' + 10 : hx - '0';

  • #10 14 Lut 2019 17:44
    katakrowa
    Poziom 20  

    acctr napisał:
    return hx > '9' ? hx - 'A' + 10 : hx - '0';


    Tego właśnie w c++ nie znoszę :-) Już assemblera wolę. Zaledwo krok mu do https://pl.wikipedia.org/wiki/Brainfuck
    Niestety będę się musiał przełamać bo to jedyny sensowny język, w którym można oprogramować uC a mam w planach dość złożoną aplikację z ogromem dynamicznych struktur alokacji pamieci itp.. itd ...

  • #11 14 Lut 2019 18:16
    LChucki
    Poziom 28  

    katakrowa napisał:
    mam w planach dość złożoną aplikację z ogromem dynamicznych struktur alokacji pamieci itp.. itd ...

    Na kiedy planujesz napisać tą aplikację? Był tu przypadek, że od zera w trzy dni naturalnie to niemożliwe.
    Na jaki uC lub system? Czy aplikacja wymaga obsługi peryferii?

  • #12 14 Lut 2019 18:22
    katakrowa
    Poziom 20  

    LChucki napisał:
    Na kiedy planujesz napisać tą aplikację? Był tu przypadek, że od zera w trzy dni naturalnie to niemożliwe.
    Na jaki uC lub system? Czy aplikacja wymaga obsługi peryferii?


    Zdecydowanie nie 3 dni - to generalnie grubszy temat m.in. własnie dokładnie z tym STm32 co użyłeś. Z STM mam tyle wspólnego, że mam ...
    Dlatego chciałbym skontaktować się na priv - obiecuję, że nie będę obrażał ;-)

  • #13 14 Lut 2019 18:32
    LChucki
    Poziom 28  

    katakrowa napisał:
    Dlatego chciałbym skontaktować się na priv

    Proszę bardzo.

  • #15 14 Lut 2019 19:22
    tmf
    Moderator Mikrokontrolery Projektowanie

    katakrowa napisał:
    Tego właśnie w c++ nie znoszę

    To w czym piszesz to nie żadne C++, lecz zwykłe C. O ile widzę po pobieżnym spojrzeniu na kod, nie korzystasz z żadnego elementu C++.

  • #16 14 Lut 2019 20:40
    katakrowa_
    Poziom 1  

    tmf napisał:
    To w czym piszesz to nie żadne C++, lecz zwykłe C. O ile widzę po pobieżnym spojrzeniu na kod, nie korzystasz z żadnego elementu C++.

    Być może, nie przeczę. Wiem, że poza obiektowością jakieś drobne różnice są. No i w c++ te wszystkie dziwne znaczki są, co mnie przerażają << >>brakiem czytelności :-)

  • #17 14 Lut 2019 21:54
    Robo24
    Poziom 10  

    W AVR pojedynczy licznik zazwyczaj może generować 2 przebiegi PWM bez użycia przerwań.
    Inne podejście to przeliczenie co ile musimy zmienić stan kolejnego pinu względem poprzedniego i co tyle wchodzimy w przerwanie od licznika ;)

    Te znaczki są bardzo czytelne gdy wiadomo co robią i reszta kodu to nie makaron.
    Z dynamiczną alokacją pamięci na mikrokontrolerach trzeba uważać, często powoduje problemy.

  • #18 14 Lut 2019 22:00
    acctr
    Poziom 15  

    Robo24 napisał:
    Z dynamiczną alokacją pamięci na mikrokontrolerach trzeba uważać, często powoduje problemy.

    Niektóre kompilatory nie dopuszczają opcji dynamicznej alokacji pamięci, nawet nie umożliwiają wywołania rekurencji (model reentrant i non-reentrant).
    Szczególnie dla uc 8-bitowych.

  • #19 14 Lut 2019 23:54
    khoam
    Poziom 27  

    Robo24 napisał:
    Z dynamiczną alokacją pamięci na mikrokontrolerach trzeba uważać, często powoduje problemy.


    Ponieważ zwykle w takich sytuacja sterta nachodzi na stos z powody niewystarczającej ilości pamięci (S)RAM. Nie tylko powinno się unikać dynamicznej alokacji, ale jak się piszę w C++ to raczej należy zapomnieć o poliformiźmie i wirtualnych metodach, na rzecz stosowania szablonów klas czy funkcji.

  • #20 15 Lut 2019 12:09
    tmf
    Moderator Mikrokontrolery Projektowanie

    acctr napisał:
    Robo24 napisał:
    Z dynamiczną alokacją pamięci na mikrokontrolerach trzeba uważać, często powoduje problemy.


    Niektóre kompilatory nie dopuszczają opcji dynamicznej alokacji pamięci, nawet nie umożliwiają wywołania rekurencji (model reentrant i non-reentrant).
    Szczególnie dla uc 8-bitowych.


    A jakież to konkretnie kompilatory nie umożliwiają alokacji dynamicznej? Przecież alokator jest częścią biblioteki standardowej a nie kompilatora. Od biedy można sobie napisać własny alokator - zresztą czasami się zamienia standardowy, np. na taki, który zmniejsza fragmentację sterty.

    Dodano po 2 [minuty]:

    khoam napisał:
    Ponieważ zwykle w takich sytuacja sterta nachodzi na stos z powody niewystarczającej ilości pamięci (S)RAM. Nie tylko powinno się unikać dynamicznej alokacji, ale jak się piszę w C++ to raczej należy zapomnieć o poliformiźmie i wirtualnych metodach, na rzecz stosowania szablonów klas czy funkcji.


    Polimorfizm i metody wirtualne zajmują pamięć w sposób statyczny, więc one nie wygenerują problemu, gdyż pamięć dla VMT nie jest rezerwowana ani na stosie, ani na stercie i nie zależy od liczby instancji. Z tych powodów jest to proste do analizy statycznej zajętości pamięci. Co oczywiście nie oznacza, że nie należy stosować szablonów - g++ w miarę dobrze dzięki temu unika tworzenia mietod wirtualnych, których wywołanie jest kosztowniejsze.

  • #22 15 Lut 2019 12:18
    tmf
    Moderator Mikrokontrolery Projektowanie

    acctr napisał:
    tmf napisał:
    A jakież to konkretnie kompilatory nie umożliwiają alokacji dynamicznej?
    Xc8


    Implementację malloc możesz sobie zrobić sam, lub pobrać dostosowane implementacje do 8-bitowych mikrokontrolerów z netu. Kompilator nie ma nic do tego.

  • #23 15 Lut 2019 12:29
    acctr
    Poziom 15  

    tmf napisał:
    Implementację malloc możesz sobie zrobić sam, lub pobrać dostosowane implementacje do 8-bitowych mikrokontrolerów z netu. Kompilator nie ma nic do tego.
    Teoretycznie tak, co jednak z modelem typu 'reentrant'? To już jest zależne od kompilatora bo dotyczy języka a dokladniej wywolania funkcji przez samą siebie.
    Ktoś kto zabiera się za pisanie w C nie myśli od tego jak napisać alokatory pamieci dynamicznej czy implementacji stosu, tylko chce bazować na tym co oferuje kompilator z bibliotekami.

  • #24 15 Lut 2019 12:51
    tmf
    Moderator Mikrokontrolery Projektowanie

    acctr napisał:
    tmf napisał:
    Implementację malloc możesz sobie zrobić sam, lub pobrać dostosowane implementacje do 8-bitowych mikrokontrolerów z netu. Kompilator nie ma nic do tego.

    Teoretycznie tak, co jednak z modelem typu 'reentrant'? To już jest zależne od kompilatora bo dotyczy języka a dokladniej wywolania funkcji przez samą siebie.


    Funkcje reentrant to nie funkcje, które same siebie wywołują, lecz takie, które mogą być bezpiecznie wykonywane współbieżnie. To na programiście ciąży odpowiedzialność za napisanie funkcji, która jest reentrant. C nie ma tu żadnych ułatwień.
    acctr napisał:
    Ktoś kto zabiera się za pisanie w C nie myśli od tego jak napisać alokatory pamieci dynamicznej czy implementacji stosu, tylko chce bazować na tym co oferuje kompilator z bibliotekami.


    No to bierze porządny kompilator :) Zarządzaniem stosem zawsze zajmuje się kompilator, więc tu nie ma problemu.

  • #25 15 Lut 2019 12:59
    acctr
    Poziom 15  

    tmf napisał:
    Funkcje reentrant to nie funkcje, które same siebie wywołują, lecz takie, które mogą być bezpiecznie wykonywane współbieżnie.

    Ogolnie to problem alokacji na stosie zmiennych lokalnych danej funkcji ,co pozbawia zapamiętania stanu wewnątrznego po ponownym wejściu do niej.
    Z jednej strony to dobrze, bo kompilator krzyczy, że jest niebezpieczeństwo, nawet w postaci łańcucha wywolań różnych funkcji, ktore ostatecznie tworza cykl.
    Z drugiej strony, nawet prostego algorytmu Euklidesa nie można wykonać w postaci rekurencyjnej.

  • #26 15 Lut 2019 13:11
    tmf
    Moderator Mikrokontrolery Projektowanie

    acctr napisał:
    tmf napisał:
    Funkcje reentrant to nie funkcje, które same siebie wywołują, lecz takie, które mogą być bezpiecznie wykonywane współbieżnie.


    Ogolnie to problem alokacji na stosie zmiennych lokalnych danej funkcji ,co pozbawia zapamiętania stanu wewnątrznego po ponownym wejściu do niej.
    Z jednej strony to dobrze, bo kompilator krzyczy, że jest niebezpieczeństwo, nawet w postaci łańcucha wywolań różnych funkcji, ktore ostatecznie tworza cykl.
    Z drugiej strony, nawet prostego algorytmu Euklidesa nie można wykonać w postaci rekurencyjnej.

    Z tego co widziałem na forach Microchipa to raczej kwestia przyjętego modelu pamięci z jakim kompilujesz program, a nie że kompilator nie potrafi. Gdyby nie potrafił, to nie byłby to kompilator C, bo łamałby wszelkie standardy języka.

  • #27 15 Lut 2019 13:16
    acctr
    Poziom 15  

    Piszą o tym w dokumentacji kompilatora i niestety trzeba się z takim modelem pogodzić.
    Zaś np. w Keilu dla '51 było coś takiego jak słowo kluczowe "reentrant" i kompilator coś tam sobie tworzył, że rekurencja byla możliwa.

    tmf napisał:
    No to bierze porządny kompilator.

    Na szczęście dzisiaj jest ogromny wybór i to jest słuszne, zdrowe podejście.

  • #28 15 Lut 2019 15:20
    khoam
    Poziom 27  

    tmf napisał:
    Polimorfizm i metody wirtualne zajmują pamięć w sposób statyczny, więc one nie wygenerują problemu

    Chodziło mi o to, że tego typu mechanizmy, nie stosowane z umiarem, mogą spowodować trudną do skontrolowania zajętość SRAM (właśnie głównie z powodu VTables), a to czy nałożenie się sterty na stos nastąpi z jednej lub drugiej strony, to już naprawdę nie ma znaczenia.

  • #29 15 Lut 2019 15:47
    Janusz_kk
    Poziom 19  

    Znasz takie przypadki albo masz taki przykład? czy tylko akademicko teoretyzujesz :)

  • #30 15 Lut 2019 15:58
    khoam
    Poziom 27  

    Janusz_kk napisał:
    nasz takie przypadki albo masz taki przykład? czy tylko akademicko teoretyzujesz

    Oczywiście, że nie teoretyzuję. Kilkanaście razy "walczyłem" z tego rodzaju problemami, kiedy uruchomiony program zachowywał się w sposób niedeterministyczny, a na końcu okazywało się, że wybór innej biblioteki do obsługi wyświetlacza cudownie przywracał poprawne działanie (a po analizie kodu, okazywało się że poprzednia biblioteka nadużywała metod wirtualnych). Spróbuj sam w takiej sytuacji "debugować" program np. na atmedze328 i szukać przyczyn błędów, to przejdziemy do dyskusji merytorycznej.