LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Mikrokontrolery AVR XMEGA w praktyce, część 10. Układ PLL

Znamy już generatory RC oraz kwarcowe, opisane w poprzednich odcinkach kursu mikrokontrolerów XMEGA. Niniejsza część, podobnie jak wszystkie pozostałe o sygnałach zegarowych, będzie wykorzystywać schemat z rysunku 1 i te same pliki źródłowe, które można pobrać na dole strony. Układ demonstracyjny wykorzystuje moduł prototypowy X3-DIL64 z Leon Instruments, dostępny w ofercie KAMAMI.

 

Rys. 1. Schemat układu demonstracyjnego

Rys. 1. Schemat układu demonstracyjnego

 

Układ PLL

PLL, czyli pętla synchronizacji fazy, jest przeciwieństwem preskalera i służy do zwiększania częstotliwości sygnału. Układy PLL dotychczas stosowane były w mikrokontrolerach z wyższej półki oraz w FPGA, ale w XMEGA to rozwiązanie dostępne jest we wszystkich modelach, nawet w tych najtańszych za kilka złotych!

Mamy możliwość pomnożenia częstotliwości wybranego generatora do 31 razy. Do wyboru mamy następujące generatory:

  • wbudowany RC 2 MHz,
  • wbudowany RC 32 MHz, ale wstępnie podzielony przez 4, czyli 8 MHz,
  • kwarcowy 0,4–16 MHz,
  • zewnętrzny sygnał zegarowy.

Układ PLL nie może współpracować z generatorami 32 kHz.

Zwróć uwagę, że niewłaściwie konfigurując układ PLL można mocno przetaktować mikrokontroler, co może prowadzić do jego niestabilnej pracy. Pamiętaj, że rdzeń procesora może pracować z sygnałem takującym o częstotliwości 32 MHz. Częstotliwość na wyjściu układu PLL nie powinna być niższa niż 10 MHz, ani wyższa niż 200MHz. W razie potrzeby możesz wykorzystać preskalery (widoczne na rysunku 1 w 7 części kursu), by zmniejszyć częstotliwość zegara.

Napiszemy program, który pozwala zmienić konfigurację układu PLL podczas pracy procesora. Wciskając przycisk podłączony do pinu F4 na płytce X3-DIL64 z Leon Instruments, będziemy zwiększać mnożnik PLL o 1 aż do 31, a po kolejnym wciśnięciu mnożnik ustawi się na 1, by móc go znów zwiększać. Jako źródło sygnału wykorzystany generator 2 MHz, a częstotliwości uzyskane dzięki PLL będą sięgać nawet 62 MHz, co daleko przekracza dopuszczalny limit! Zmiany sygnału zegarowego będziemy obserwować dzięki mrugającej diodzie, a dodatkową informacją będzie wyświetlenie częstotliwości na wyświetlaczu LCD.

Prześledźmy, co dzieje się w funkcji OscPLL. Pierwszym krokiem jest uruchomienie generatora, który będzie źródłem sygnału dla PLL i ustawienie go jako źródła. Trzeba w tym momencie wyraźnie zaznaczyć, że nie można zmieniać konfiguracji układu PLL podczas, gdy jest on uruchomiony, a tym bardziej kiedy jest źródłem sygnału zegarowego.

void OscPLL(uint8_t pllfactor) {
    
    // uruchomienie generatora 2MHz i ustawienie go jako źródła zegara
    OSC.CTRL        =    OSC_RC2MEN_bm;                // włączenie oscylatora 2MHz
    while(!(OSC.STATUS & OSC_RC2MRDY_bm));             // czekanie na ustabilizowanie się generatora
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_RC2M_gc;          // zmiana źródła sygnału zegarowego na RC 2MHz

Następnie, możemy przystąpić do konfiguracji układu PLL. Jednak jeśli jest on już włączony, to koniecznie musimy go najpierw wyłączyć. W przeciwnym razie próba zmiany konfiguracji będzie nieskuteczna. Kluczowy w tym fragmencie jest rejestr OSC.PLLCTRL, którego opis przedstawiono na rysunku 2. Ustawić w nim musimy źródło sygnału, współczynnik mnożący (zmienna pllfactor jest argumentem funkcji OscPLL), a opcjonalnie możemy częstotliwość sygnału wyjściowego podzielić przez dwa.

    // wyłączenie PLL
    OSC.CTRL       &=   ~OSC_PLLEN_bm;
            
    // konfiguracja PLL
    OSC.PLLCTRL     =    OSC_PLLSRC_RC2M_gc |          // wybór RC 2MHz jako źródło sygnału dla PLL
                         pllfactor;                    // mnożnik częstotliwości (od 1 do 31)
                        
    // uruchomienie PLL
    OSC.CTRL        =    OSC_PLLEN_bm;                 // włączenie układu PLL

Rys. 2. Fragment dokumentacji rejestru PLLCTRL

Rys. 2. Fragment dokumentacji rejestru PLLCTRL

 

Podobnie jak w przypadku innych generatorów, poczekać musimy aż sygnał zegarowy się ustabilizuje, poprzez sprawdzanie czy już został ustawiony odpowiedni bit w rejestrze statusowym. Dopiero wtedy możemy przełączyć źródło sygnału taktującego mikrokontroler.

    // czekanie na ustabilizowanie się generatora            
    while(!(OSC.STATUS & OSC_PLLRDY_bm));

    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_PLL_gc;           // wybór źródła sygnału zegarowego PLL

Układ PLL może stracić synchronizację fazy, jeśli sygnał zegarowy będzie zbyt wolny, zbyt szybki lub z jakiegoś powodu będzie niestabilny. Na szczęście mikrokontrolery XMEGA mają możliwość monitorowania układu PLL, podobnie jak w przypadku generatora kwarcowego. W razie stwierdzenia nieprawidłowości, automatycznie uruchomi się wbudowany generator 2 MHz oraz zostanie wygenerowane przerwanie OSC_OSCF_vect.

 // układ nadzorujący PLL
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie modyfikacji ważnych rejestrów 
    OSC.XOSCFAIL    =    OSC_PLLFDEN_bm;               // włączenie układu detekcji błędu sygnału zegarowego

Podczas niniejszych ćwiczeń przetaktowaliśmy rdzeń procesora prawie dwukrotnie. Zgodnie z danymi firmy Atmel, układ powinien być taktowany w zakresie od 10 MHz (minimalna częstotliwość wyjściowa PLL) do 32 MHz (maksymalna częstotliwość rdzenia). Ciekawy jestem, czy czytelnicy zauważyli jakieś nieprawidłowości w działaniu mikrokontrolera poza tym zakresem. W moim przypadku wszystko działało bez zarzutu. Mimo to, w normalnych zastosowaniach nigdy nie należy przekraczać dopuszczalnych zakresów podanych przez producenta układu!

Oto kod całego programu, demonstrującego możliwości układu generowania i dystrybucji sygnałów zegarowych.

#define F_CPU 62000000UL
#include 
#include 
#include 
#include "hd44780.h"

void Osc2MHz(void) {
    OSC.CTRL        =    OSC_RC2MEN_bm;                // włączenie oscylatora 2MHz
    while(!(OSC.STATUS & OSC_RC2MRDY_bm));             // czekanie na ustabilizowanie się generatora
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_RC2M_gc;          // zmiana źródła sygnału zegarowego na RC 2MHz
    LcdClear();                                        // czyszczenie wyświetlacza
    Lcd("RC 2MHz");                                    // komunikat o uruchomieniu generatora
}

void Osc32MHz(void) {
    OSC.CTRL        =    OSC_RC32MEN_bm;               // włączenie oscylatora 32MHz
    while(!(OSC.STATUS & OSC_RC32MRDY_bm));            // czekanie na ustabilizowanie się generatora
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_RC32M_gc;         // zmiana źródła sygnału zegarowego na RC 32MHz
    LcdClear();                                        // czyszczenie wyświetlacza
    Lcd("RC 32MHz");                                   // komunikat o uruchomieniu generatora
}

void OscXtal(void) {
        
    // konfiguracja generatora kwarcowego        
    OSC.XOSCCTRL    =    OSC_FRQRANGE_12TO16_gc |      // wybór kwarcu od 12 do 16 MHZ
                         OSC_XOSCSEL_XTAL_16KCLK_gc;   // czas na uruchomienie generatora
    OSC.CTRL        =    OSC_XOSCEN_bm;                // uruchomienie generatora kwarcowego
    
    // czekanie na ustabilizowanie się generatora
    for(uint8_t i=0; i<255; i++) {                        
        if(OSC.STATUS & OSC_XOSCRDY_bm) {
            CPU_CCP        =    CCP_IOREG_gc;          // odblokowanie zmiany źródła sygnału zegarowego
            CLK.CTRL       =    CLK_SCLKSEL_XOSC_gc;   // wybór źródła sygnału zegarowego na XTAL 16MHz
            LcdClear();                                // czyszczenie wyświetlacza
            Lcd("XTAL");                               // komunikat o uruchomieniu generatora
            
            // układ nadzorujący kwarc
            CPU_CCP        =    CCP_IOREG_gc;          // odblokowanie modyfikacji ważnych rejestrów 
            OSC.XOSCFAIL   =    OSC_XOSCFDEN_bm;       // włączenie układu detekcji błędu sygnału zegarowego
            return;                                    // wyjście z funkcji jeśli generator się uruchomił
        }
        _delay_us(10);
    }     
    
    // komunikat w przypadku braku uruchomienia generatora kwarcowego
    LcdClear();
    Lcd("Brak XTAL");
}

void OscPLL(uint8_t pllfactor) {
    
    // uruchomienie generatora 2MHz i ustawienie go jako źródła zegara
    OSC.CTRL        =    OSC_RC2MEN_bm;                // włączenie oscylatora 2MHz
    while(!(OSC.STATUS & OSC_RC2MRDY_bm));             // czekanie na ustabilizowanie się generatora
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_RC2M_gc;          // zmiana źródła sygnału zegarowego na RC 2MHz
    
    // wyłączenie PLL
    OSC.CTRL       &=   ~OSC_PLLEN_bm;
            
    // konfiguracja PLL
    OSC.PLLCTRL     =    OSC_PLLSRC_RC2M_gc |          // wybór RC 2MHz jako źródło sygnału dla PLL
                         pllfactor;                    // mnożnik częstotliwości (od 1 do 31)
                        
    // uruchomienie PLL
    OSC.CTRL        =    OSC_PLLEN_bm;                 // włączenie układu PLL
    
    // czekanie na ustabilizowanie się generatora            
    while(!(OSC.STATUS & OSC_PLLRDY_bm));

    // przełączenie źródła sygnału zegarowego
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie zmiany źródła sygnału zegarowego
    CLK.CTRL        =    CLK_SCLKSEL_PLL_gc;           // wybór źródła sygnału zegarowego PLL
            
    // układ nadzorujący PLL
    CPU_CCP         =    CCP_IOREG_gc;                 // odblokowanie modyfikacji ważnych rejestrów 
    OSC.XOSCFAIL    =    OSC_PLLFDEN_bm;               // włączenie układu detekcji błędu sygnału zegarowego
    
    // wyświetlenie komunikatu
    LcdClear();
    Lcd("PLL ");
    LcdDec(pllfactor*2);                               // *2 bo generator RC ma 2MHz
    Lcd("MHz");
}

int main(void) {
    
    // zmienna
    uint8_t pll = 4;
    
    // diody
    PORTE.DIR       =    PIN0_bm;                      // dioda LED
                    
    // przyciski
    PORTA.DIRCLR    =    PIN0_bm;                      // przycisk - RC 2MHz
    PORTA.PIN0CTRL  =    PORT_OPC_PULLUP_gc;           // podciągnięcie do zasilania
    PORTE.DIRCLR    =    PIN5_bm;                      // przycisk FLIP - RC 32MHz
    PORTE.PIN5CTRL  =    PORT_OPC_PULLUP_gc;           // podciągnięcie do zasilania
    PORTE.DIRCLR    =    PIN6_bm;                      // przycisk - XTAL
    PORTE.PIN6CTRL  =    PORT_OPC_PULLUP_gc;           // podciągnięcie do zasilania
    PORTF.DIRCLR    =    PIN4_bm;                      // przycisk - PLL
    PORTF.PIN4CTRL  =    PORT_OPC_PULLUP_gc;           // podciągnięcie do zasilania
    
    // wyświetlacz LCD
    LcdInit();
    
    // komunikat o źródła sygnału zegarowego
    LcdClear();
    Lcd("RC 2MHz");
    
    // włączenie przerwań
    sei();
    
    while(1) {
        PORTE.OUTTGL    =    PIN0_bm;
        _delay_ms(50);
        
        if(!(PORTA.IN & PIN0_bm)) Osc2MHz();
        if(!(PORTE.IN & PIN5_bm)) Osc32MHz();
        if(!(PORTE.IN & PIN6_bm)) OscXtal();
        if(!(PORTF.IN & PIN4_bm)) {    
            pll++;                                     // zwiększ zmienną pll
            if(pll > 31) pll = 1;                      // jeśli pll większe od 31 to ustaw na 1
            OscPLL(pll);                               // funkcja konfigurująca PLL
        }
    }
}

ISR(OSC_OSCF_vect) {                                   // przerwanie w razie awarii oscylatora
    OSC.XOSCFAIL    |=    OSC_XOSCFDIF_bm;             // kasowanie flagi przerwania
    LcdClear();
    Lcd("Awaria!");    
}

Temat generatorów sygnału zegarowego w mikrokontrolerach XMEGA zajął aż cztery odcinki, to jednak nie wyczerpują one tematu. Oprócz tego mamy do dyspozycji jeszcze:

  • układ synchronizujący DFLL,
  • zegar czasu rzeczywistego RTC,
  • generatory energooszczędne,
  • generator sygnału zegarowego do USB,
  • kalibrację generatorów.

Te możliwości zostały opisane w książce Tomasza Francuza AVR. Praktyczne projekty , którą szczerze polecam.

Dystrybutorem zestawu X3-DIL64 jest KAMAMI.pl.

Dominik Leon Bieczyński

http://leon-instruments.pl