Kurs XMEGA: PWM (13)

|

PWM to skrót od Pulse Width Modulation czyli modulacja szerokości impulsu. Generowany jest sygnał o stałej amplitudzie i częstotliwości, a zmieniać może się jedynie współczynnik wypełnienia. Dzięki takiemu zabiegowi można bardzo łatwo sterować różnymi urządzeniami: prędkością silnika, jasnością żarówki lub diody LED.

Mikrokontrolery XMEGA mają możliwość wygenerowania jednocześnie bardzo wielu sygnałów PWM przy pomocy timerów (zobacz odcinak timery w XMEGA). Timer typu 0 może generować cztery takie sygnały, a typ 1 może tylko dwa. Jednak mając do dyspozycji cztery timery typu 0 oraz trzy timery typu 1, możemy uzyskać aż 22 kanały PWM. W nowszych XMEGA (takich jak np. ATxmega128A3U) timery 16-bitowe można podzielić na 8-bitowe timery typu 2, a każdy z nich ma 4 kanały. W końcowym rozrachunku można mieć nawet 32 kanały PWM!

Jeśli wiesz jak działa PWM – przeskocz kilka akapitów i zacznij czytać opis kodu programu.

Timer generujący PWM może pracować w trybie single slope, czyli licząc zawsze w tym samym kierunku albo może pracować w trybie dual slope, czyli liczyć naprzemiennie w górę i w dół.

W trybie single slope timer zaczyna liczyć od zera do wartości określonej w rejestrze PER. Kiedy rejestr licznika CNT i PER zrównają się, wówczas CNT zostaje wyzerowany i timer zaczyna liczyć od początku. Sygnał PWM dostępny jest na nóżce OCxx. Na początku cyklu, na pinie OCxx jest stan logiczny wysoki. Timer liczy w górę, zwiększając wartość licznika przechowywaną w rejestrze CNT. W chwili, kiedy rejestr CNT zrówna się z liczbą wpisaną do rejestru CCX, wówczas pin OCxx ustawia się w stan niski i pozostaje tak do końca cyklu.



W trybie dual slope, timer liczy od zera do wartości PER, a potem zmienia kierunek liczenia i wraca do zera. Zmiana stanu pinu OCxx dokonuje się w chwili zrównania się rejestrów CNT i CCx, jednak zależy jeszcze od kierunku liczenia timera. Gdy timer liczy w górę, przy zrównaniu się, stan pinu OCxx zmienia się z wysokiego na niski, a w przypadku liczenia w dół jest odwrotnie. Aby opis był bardziej zrozumiały zamieściłem wykresy.



Należy mieć na uwadze, że częstotliwość sygnału będzie dwukrotnie mniejsza niż przy identycznie ustawionym trybie single slope, ponieważ cykl pracy jest dwukrotnie dłuższy.

Tryb dual slope czasami nazywany jest określeniem PWM z korekcją fazy. Czym on się różni od single slope? Niektórzy twierdzą, że jest lepszy i wynika to z teorii sygnałów… Otóż, jeśli wykorzystujemy tylko jeden kanał timera, to w obu trybach możemy uzyskać całkowicie identyczny sygnał. Różnica jest wtedy, gdy jeden timer kontroluje kilka kanałów PWM, a tym samym kilka odbiorników. W przypadku single slope, na początku cyklu wszystkie odbiorniki, którymi sterujemy, włączają się jednocześnie. Bardzo często PWM wykorzystywany jest do sterowania urządzeniami dużej mocy. Jednoczesne załączenie kilku takich urządzeń może powodować spadki napięć na szynach zasilających. Nie bez powodu mówi się, że PWM sieje zakłoceniami. Rozwiązaniem problemu jest zastosowanie PWM dual slope. Spójrz jeszcze raz na wykres PWM dual slope – w tym przypadku w danej chwili otwierany lub zamykany jeden z trzech kanałów PWM. Z pewnością ograniczy to zakłócenia generowane przez PWM.


Przejdźmy wreszcie do pisania programu. Po włączeniu zasilania, uruchomione zostaną cztery kanały PWM do których podłączymy diody LED. Aby zaobserwować jak działa PWM, po starcie programu będzie on skonfigurowany z bardzo dużym preskalerem. Dopiero po wciśnięciu przycisku FLIP zostanie uruchomiona normalna prędkość pracy, a poszczególne diody LED będą świecić się z różną jasnością. W tym przykładzie wykorzystamy timer E0, który jest identyczny jak opisywany w poprzedniej części C0.
Pliki do pobrania:



Program zaczynamy standardowo od konfiguracji układów wejścia i wyjścia, dokładnie tak jak to opisałem w odcinku na temat portów w XMEGA.

int main(void) {
    
    // przycisk
    PORTE.DIRCLR      =    PIN5_bm;                    // pin E5 jako wejście (przycisk FLIP)
    PORTE.PIN5CTRL    =    PORT_OPC_PULLUP_gc;         // podciągnięcie do zasilania
    
    // diody od PWM
    PORTE.DIRSET      =    0b00001111;                 // piny 3..0 jako wyjście
    
    // wyświetlacz
    LcdInit();
 
Aby móc skorzystać z dobrodziejstw PWM, musimy nieco inaczej skonfigurować timer, a w szczególności jego rejestr CTRLB – spójrzmy na fragment dokumentacji.



Grupa konfiguracyjna WGMODE odpowiada za tryb pracy timera. W odcinku opisującym podstawy pracy timerów w mikrokontrolerach ATxmega wykorzystaliśmy tryb TC_WGMODE_NORMAL_gc. PWM możemy generować przy pomocy czterech trybów – jednego single-slope, w którym timer cały czas zlicza w tym samym kierunku oraz trzech trybów dual-slope. Tryby dual-slope różnią się jedynie chwilami, w których zostanie zgłoszone przerwanie przepełnienia licznika. Ponieważ przerwań w niniejszych przykładzie nie wykorzystujemy, to możemy wybrać dowolny tryb dual-slope.

Oprócz tego, w rejestrze CTRLB musimy wybrać, które kanały CCx zamierzamy wykorzystać, wpisując do rejestru odpowiednie stałe TC0_CCxEN_bm, gdzie x oznacza wybór kanału A, B, C oraz D.

    // konfiguracja timera
    TCE0.CTRLB        =    TC_WGMODE_DSBOTH_gc|        // tryb normalny
                           TC0_CCAEN_bm|
                           TC0_CCBEN_bm|
                           TC0_CCCEN_bm|
                           TC0_CCDEN_bm;
    TCE0.PER          =    10000;
    TCE0.CCA          =    2000;
    TCE0.CCB          =    4000;
    TCE0.CCC          =    6000;
    TCE0.CCD          =    8000;
    TCE0.CTRLA        =    TC_CLKSEL_DIV1024_gc;       // ustawienie preskalera i uruchomienie timera
 
...i to wystarczy, aby timer zaczął generować sygnały PWM (oczywiście odpowiednie piny muszą być skonfigurowane w rejestrze DIR jako wyjście, co zrobiliśmy na początku programu).

    while(1) {
        // wyświetlenie aktualnej wartości licznika CNT i PER
        // CNT = ...
        // PER = ...
        LcdClear();
        Lcd("CNT = ");
        LcdDec(TCE0.CNT);
        Lcd2;
        Lcd("PER = ");
        LcdDec(TCE0.PER);
        _delay_ms(100);    
        
        if(!(PORTE.IN & PIN5_bm)) {                    // przycisk FLIP przyspiesza PWM
            TCE0.PER        =    1000;
            TCE0.CCA        =    10;
            TCE0.CCB        =    50;
            TCE0.CCC        =    200;
            TCE0.CCD        =    500;
            TCE0.CTRLA      =    TC_CLKSEL_DIV1_gc;        
        }
        
    }
 
Po uruchomieniu programu, świecą się wszystkie diody, a rejestr CNT licznika jest oczywiście wyzerowany. Zwróć uwagę na wskazania wyświetlacza. Kiedy licznik CNT przekroczy 2000 to zgaśnie dioda podłączona do pinu E0, bo steruje nią kanał A, którego rejestr CCA wynosi 2000. W ten sposób po kolei wszystkie diody mają zgasnąć, aż licznik osiągnie wartość 10000 i zacznie liczyć w dół. Diody będą się po kolei zapalać. Po wciśnięciu przycisku FLIP, preskaler timera zmienimy na 1 zamiast 1024 i ustawimy nieco inne wartości CCx. Dzięki temu będziemy mieć wrażenie, że diody świecą się z różną jasnością, choć rzeczywistości odbywa się podobny proces jaki oglądaliśmy przed chwilą, ale z dużo większą prędkością.

Działanie programu przedstawia poniższy filmik i zdjęcie.



#define  F_CPU    2000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "hd44780.h"

int main(void) {
    
    // przycisk
    PORTE.DIRCLR      =    PIN5_bm;                    // pin E5 jako wejście (przycisk FLIP)
    PORTE.PIN5CTRL    =    PORT_OPC_PULLUP_gc;         // podciągnięcie do zasilania
    
    // diody od PWM
    PORTE.DIRSET      =    0b00001111;                 // piny 3..0 jako wyjście
    
    // wyświetlacz
    LcdInit();
    
    // konfiguracja timera
    TCE0.CTRLB        =    TC_WGMODE_DSBOTH_gc|        // tryb PWM dual-slope
                           TC0_CCAEN_bm|
                           TC0_CCBEN_bm|
                           TC0_CCCEN_bm|
                           TC0_CCDEN_bm;
    TCE0.PER          =    10000;
    TCE0.CCA          =    2000;
    TCE0.CCB          =    4000;
    TCE0.CCC          =    6000;
    TCE0.CCD          =    8000;
    TCE0.CTRLA        =    TC_CLKSEL_DIV1024_gc;       // ustawienie preskalera i uruchomienie timera
    
    while(1) {
        // wyświetlenie aktualnej wartości licznika CNT i PER
        // CNT = ...
        // PER = ...
        LcdClear();
        Lcd("CNT = ");
        LcdDec(TCE0.CNT);
        Lcd2;
        Lcd("PER = ");
        LcdDec(TCE0.PER);
        _delay_ms(100);    
        
        if(!(PORTE.IN & PIN5_bm)) {                    // przycisk FLIP przyspiesza PWM
            TCE0.PER        =    1000;
            TCE0.CCA        =    10;
            TCE0.CCB        =    50;
            TCE0.CCC        =    200;
            TCE0.CCD        =    500;
            TCE0.CTRLA      =    TC_CLKSEL_DIV1_gc;        
        }
        
    }
}
 
Kurs XMEGA:Moduły prototypowe:

3 komentarze :

Łukasz Patulski pisze...

Jak zwykle świetny odcinek kursu. Trzeba się starać aby nie zrozumieć :)

Anonimowy pisze...

A czy można tym osiągnąć mniejszą jasność diody RGB która jest wbudowana na płycie extrino XL?

Dominik Leon Bieczyński pisze...

Oczywiście - dioda RGB jest połączona do pinów F2, F1, F0. One też mają PWM.
Schemat http://www.eksel.user.icpnet.pl/leon_instruments/extrino/extrino-schemat.pdf

Prześlij komentarz

Skomentuj!

Sklep Leon Instruments