Co to jest PWM

Wielu użytkowników Arduino zapewne zauważyło, że wśród pinów z grupy DIGITAL jest kilka oznaczonych jako “PWM” lub “~”. W tym artykule postaram się wyjaśnić co to znaczy i jak dokładnie działa. Napiszę też jak można to praktycznie wykorzystać.

PWM w teorii

PWM to skrót od angielskich słów “Pulse Width Modulation”, co oznacza po polsku “Modulacja Szerokości Impulsu”.

W życiu codziennym posługujesz się przełącznikami. One powodują, że włączasz jakieś urządzenie lub wyłączasz. Włączenie oznacza dostarczenie do urządzenia 100% energii elektrycznej, a wyłączenie zmniejsza tą ilość do 0%.

Jeśli masz w domu jakieś urządzenie z silnikiem to możesz zauważyć, że włączając i wyłączając je wiele razy w ciągu sekundy silnik nie zdąży się rozpędzić do maksymalnych obrotów. Wynika to z tego, że wolno się rozpędza. Zatem jeśli odetniesz mu prąd zanim osiągnie maksymalne obroty to będzie kręcił się wolniej i zwalniał do czasu, aż znowu go włączysz. W ten sposób można regulować jego prędkość.

Działanie PWM polega właśnie na tym, że im dłużej silnik jest włączony w ciągu sekundy, tym szybciej się kręci. A jeśli dłużej trwa czas wyłączenia tym wolniej. Czas włączenia to właśnie ten “Impuls” (“Pulse”) w nazwie, którego “Szerokość” (“Width”) regulujesz.

Na pierwszym wykresie urządzenie jest włączone przez 10% czasu całego cyklu. Na drugim wykresie przez 50% czasu cyklu a na trzecim przez 90%.

Fizycznie rzecz ujmując działanie PWM polega na dostarczeniu mniejszej ilości energii elektrycznej do urządzenia w przeciągu jakiegoś czasu. Czego skutkiem ubocznym są takie właśnie miłe efekty jak regulacja szybkości lub jasności.

Jak to dokładnie działa?

Działanie generatora PWM w urządzeniach cyfrowych polega na tym, że w rejestrze przechowującym jakąś liczbę trzymana jest wartość wypełnienia. Od wielkości tego rejestru zależy ile jest stopni wypełnienia. 8 bitowy rejestr może mieć 256 stopni wypełnienia, a 16 bitowy 65 tysięcy. Oznacza to z jaką dokładnością będzie można regulować długość impulsu.

1 / 256 * 100 = 0,39

Wynik ten podaje, że 8 bitowy rejestr PWM jest w stanie regulować wypełnienie z dokładnością 0,39%.

Oprócz rejestru w generatorze PWM jest też licznik. Licznik to urządzenie, które przechowuje liczbę i co jakiś czas dodaje do niej +1. Od szybkości dodawania tej wartości zależy szybkość z jaką pracuje PWM.

  1. Licznik zwiększa się o jeden (zwykle raz na jeden cykl zegara, ale można to zmienić)
  2. Wewnętrzny układ porównuje wartość licznika z wartością rejestru wypełnienia. Jeśli te wartości są sobie równe to wyjście PWM zmienia się w stan niski czyli wyłącza urządzenie. (jednak dalej będzie dodawać kolejne cyfry, póki nie dojdzie do pkt.3)
  3. Po dojściu licznika do ostatniej wartości jaką może policzyć, stan wyjścia PWM zmieniany jest na wysoki – włączający urządzenie i licznik zaczyna liczyć od nowa (przechodzimy do pkt. 1)
  4. </ol>

    Wykres działania generatora PWM dla wartości 10 w 8 bitowym rejestrze wypełnienia.

    Wykres działania generatora PWM dla wartości 15 w 8 bitowym rejestrze wypełnienia.

    Programowy PWM

    Z pewnością mogłeś mi nie uwierzyć, że to jest takie proste ;-). Dlatego, aby ci to udowodnić napiszę program do Arduino, który będzie udawał działanie generatora PWM i regulował jasność wbudowanej w Arduino diody LED.

    // numer pinu z wbudowaną diodą
    #define LED_PIN 13
    
    // Zmienna z wartością licznika
    byte pwmCounter = 0;
    // Zmienna z wartością rejestru wypełnienia
    byte pwmValue = 10;
    
    void setup()
    {
      // Ustawienie pinu diody na wyjście
      pinMode(LED_PIN, OUTPUT);
      // Ustawienie pinu diody na stan niski
      digitalWrite(LED_PIN, LOW);
    }
    
    void loop()
    {
      // Jeśli stan licznika się przekręci to ustaw pin w stan wysoki
      if (pwmCounter == 0)
      {
        digitalWrite(LED_PIN, HIGH);
      }
    
      // Jeśli stan licznika jest równy stanu rejestru to ustaw pin 
      // w stan niski
      if (pwmCounter == pwmValue)
      {
        digitalWrite(LED_PIN, LOW);
      }
    
      // Zwiększanie wartości licznika o 1
      pwmCounter++;
    }
    

    Działanie programu odpowiada temu co opisałem w zasadzie działania generatora PWM. Zmienna “pwmValue” reprezentuje rejestr regulujący szerokość impulsu czyli poziom wypełnienia. Wartość 10 odpowiada wypełnieniu 3,90%.

    10 / 256 * 100 = 3,90

    Zmienna “pwmCounter” to zmienna reprezentująca licznik generatora. Zarówno ta zmienna, jak i “pwmValue” są typu “byte”, zatem reprezentują wartości 8 bitowe, czyli mogą przechowywać liczby od 0 do 255. Jeśli licznik podczas dodawania przekroczy tą liczbę, to zaczyna liczyć od nowa od 0.

    Cały mechanizm działania generatora PWM znajduje się w funkcji “loop”. W pierwszym warunku “if” sprawdzane jest czy licznik nie zaczął liczyć od nowa. Jeśli tak to wyjście PWM ustawiane jest w stan wysoki.

    W drugim warunku “if” program sprawdza, czy wartość licznika jest równa wartości rejestru wypełnienia. Jeśli tak to zmienia stan wyjścia PWM na niski, który będzie utrzymany aż licznik zacznie liczyć od nowa czyli od 0.

    Jeśli uruchomiłeś ten program to powinieneś zauważyć w Arduino, że wbudowana dioda świeci bardzo słabo. Zmieniając wartość zmiennej “pwmValue” możesz zmieniać jasność świecenia tej diody. Wartość 255 to maksymalne świecenie, a 0 to minimalne.

    Takie rozwiązanie programowe jest proste, ale ma kilka wad. Pierwszą jest to, że dodając nowe elementy do programu powodujesz, że szybkość działania generatora spada, bo musi się on dzielić czasem procesora z innymi elementami programu. Inną wadą jest to, że wyjście takiego generatora może być niestabilne, przez to, że na działanie głównego programu mogą wpływać przerwania. Jednym z nich jest np. komunikacja Serial.

    Sprzętowy PWM

    Arduino UNO posiada w grupie pinów “DIGITAL” 6 wyjść PWM. Oznaczone są one na płytce przez napisy “PWM” lub znaczek “~”. Generowane przez te wyjścia sygnały PWM są tworzone sprzętowo przez wbudowane w procesor Arduino liczniki/timery. Oznacza to, że program procesora ani ich nie zwalania, ani nie wpływa na zakłócenia ich pracy. Domyślnie pracują one z prędkością 490 Hz i przyjmują wartości 8 bitowe.

    Funkcja odpowiadająca za działanie tych pinów nosi nazwę “analogWrite”. Jej pierwszym argumentem jest numer pinu PWM, a drugim wartość wypełnienia od 0 do 255.

    Aby ta funkcja zadziałała pin musi być najpierw ustawiony jako wyjście.

    // numer pinu z wbudowaną diodą
    #define PWM_PIN 3
    
    void setup()
    {
      pinMode(PWM_PIN, OUTPUT);
    
      analogWrite(PWM_PIN, 10);
    }
    
    void loop()
    {
    }
    

    Jak widzisz w funkcji “loop” nie trzeba już niczego zamieszczać. Sygnał generuje się automatycznie po wywołaniu “analogWrite”.

    Co można podłączyć?

    Niestety nie ma sprzętowego wyjścia PWM do pinu 13 wbudowanej diody LED. Zatem aby sprawdzić działanie wyjścia możesz podłączyć do niego inną diodę połączoną szeregowo z rezystorem 220 Ohm. Jej anodę podłączasz do wyjścia PWM, a katodę do GND.

    Można też w ten sposób podłączyć 3-kolorową diodę LED ze wspólną katodą, tak by każde wyjście PWM sterowało osobnym kolorem, co da płynną możliwość dobierania kolorów.

    Taka dioda LED składa się z 3 niezależnych diod w jednej obudowie. Barwy emitowane przez diody są podstawowymi barwami składowymi RGB – Czerwony, Zielony, Niebieski. Każda z tych barw podłączona jest do innego wyjścia PWM (pin 3 – zielony, pin 5 – niebieski, pin 6 – czerwony) przez rezystor 220 Ohm. Wspólna katoda diod podłączona jest do GND. Takie połączenie powinno dać teoretycznie 16 mln barw (256 poziomów na składową czyli 256^3 = 16777216).

    Do wyjścia PWM możesz podłączyć też silnik elektryczny i regulować prędkość jego wału. Ponieważ silnik pobiera znacznie więcej prądu niż może wygenerować cyfrowe wyjście Arduino, zalecam zastosowanie tranzystora MOSFET typu N. Tranzystory te wytrzymują znaczne prądy obciążenia i duże napięcia (w moim przypadku do 12A i 60V – tranzystor o oznaczeniu D12N06 (typu N) ze starej drukarki).

    Bramkę (Gate) tranzystora podłączyłem przez rezystor 1 KOhm do wyjścia PWM pin 3. Źródło (Source) tranzystora podłączyłem do GND. Natomiast Dren (Drain) połączyłem z silnikiem. Drugie wyprowadzenie silnika podłączyłem z jego zasilaniem. Do tego może służyć osobny zasilacz silnika lub pin Vin Arduino, jeśli silnik nie pobiera zbyt dużej mocy.

    W przypadku silnika, nie każda wartość wypełnienia impulsu PWM może wpływać na jego działanie. W moim przypadku silnik zaczął się kręcić dopiero od wartości 60 (wypełnienie 23,5%).

    Serwomechanizmy są elementami zależnymi od czasu trwania impulsu PWM. Szerokość impulsu decyduje o tym jak będzie ustawiony kąt osi serwomechanizmu. Moje serwomechanizmy (HTX900) przyjmują szerokości impulsów od 580 (0 stopni) do 2800 (180 stopni) mikrosekund. Do ich obsługi stosuje się raczej bibliotekę “Servo.h”, która dobrze spełnia swoje zadanie.

    Wyjście PWM może sterować też głośnikiem lecz wymaga generatora PWM o częstotliwości pracy w zakresie ultradzwięków (powyżej 20000 Hz). Częstotliwości niższe są słyszalne dla ucha człowieka i powodują, że zamiast sygnału jaki chciałeś wygenerować, słyszysz pisk sygnału PWM. W tym artykule nie napiszę o sposobach generowania takiego sygnału, ale pojawiło się już wiele takich projektów w społeczności Arduino i można je wyszukać.

    Sterowanie głośnikiem przez PWM pozwala na wytwarzanie przez głośnik fal o dowolnych kształtach i głośności, a nawet odtwarzanie sampli. Eksperymentatorzy będą mieli zapewne wiele zabawy.

    ...i na koniec:

    Mam nadzieję, że tym wpisem rozwiałem twoje wątpliwości dotyczące tego jak działają i do czego służą wyjścia PWM. Jeśli masz jeszcze jakieś pytania dotyczące PWM, pisz do nas Maile!

    LINKI