LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

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 17 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

 

Do pobrania