Kurs XMEGA: generator 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ć ten sam schemat i te same pliki źródłowe. Układ demonstracyjny wykorzystuje moduł prototypowy X3-DIL64 z Leon Instruments.

XMEGA schemat układu PLL

Generator PLL

Pliki do pobrania:

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 (link do 7 części)), 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 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

Rejestr OSC.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
    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
 

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, który tworzyliśmy od 7 części kursu do części 10.

 
#define F_CPU 62000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#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 
    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 
    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 
            CLK.CTRL       =    CLK_SCLKSEL_XOSC_gc;    // wybór źródła sygnału 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 
            return;                                     // wyjście z funkcji gdy generator działa
        }
        _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 
    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 
    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
    
    // 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łe 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.