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