Kurs STM32 LL cz. 4. Pętla PLL i taktowanie układów peryferyjnych
Dotychczas zajmowaliśmy się źródłami zegarowymi w postaci oscylatorów. Pozwalają one dostarczyć do mikrokontrolera stabilny sygnał niezbędny do pracy rdzenia i układów peryferyjnych. Jeżeli ktoś programował mniejsze mikrokontrolery (np. z serii ATmega), ta problematyka była mu “w miarę” znana. Jak jednak można zauważyć, zastosowanie kwarcu nawet o częstotliwości 48 MHz (max) nie pozwoli nam uzyskać docelowej częstotliwości pracy dla STM32G071RBT6, czyli 64 MHz. Jak zatem uzyskać takie taktowanie? Do tego służy pętla PLL.
Lista lekcji „Kurs STM32 Low Layer”
- Kurs STM32 LL cz. 1. Biblioteki Low Layer, Nucleo-G071RB, STM32CubeIDE
- Kurs STM32 LL cz. 2. Przygotowanie projektu
- Kurs STM32 LL cz. 3. Wewnętrzne i zewnętrzne źródła zegara
- Kurs STM32 LL cz. 4. Pętla PLL i taktowanie układów peryferyjnych
- Kurs STM32 LL cz. 5. Budowa GPIO i sterowanie wyjściem
- Kurs STM32 LL cz. 6. Wyjście GPIO i przerwania EXTI
- Kurs STM32 LL cz. 7. Interfejs USART, transmisja danych w trybie polling
- Kurs STM32 LL cz. 8. Komunikacja USART w trybie przerwań
- Kurs STM32 LL cz. 9. Kontroler DMA, komunikacja USART w trybie DMA
- Kurs STM32 LL cz. 10. Rodzaje i budowa Timerów, Timer w funkcji licznika
- Kurs STM32 LL cz. 11. Timer w trybie Input Capture
- Kurs STM32 LL cz. 12. Timer w trybie Output Compare i PWM
- Kurs STM32 LL cz. 13. Wstęp do konwertera ADC
- Kurs STM32 LL cz. 14. Konwersja ADC Single Channel i Multi Channel w trybie Polling
- Kurs STM32 LL cz. 15. Konwersja ADC Single Channel i Multi Channel w trybie przerwań
- Kurs STM32 LL cz. 16. Konwersja ADC Single Channel i Multi Channel w trybie DMA
- Kurs STM32 LL cz. 17. Wstęp do magistrali I2C
- Kurs STM32 LL cz. 18. Komunikacja I2C w trybie polling
- Kurs STM32 LL cz. 19. Komunikacja I2C w trybie przerwań
- Kurs STM32 LL cz. 20. Komunikacja I2C w trybie DMA
- Kurs STM32 LL cz. 21. Wprowadzenie do interfejsu SPI
- Kurs STM32 LL cz. 22. Komunikacja SPI w trybie polling
- Kurs STM32 LL cz. 23. Komunikacja SPI w trybie przerwań
- Kurs STM32 LL cz. 24. Komunikacja SPI w trybie DMA
Pętla PLL
Pętla PLL (Phase Locked Loop, czyli pętla synchronizacji fazy) to obwód działający na zasadzie sprzężenia zwrotnego, który służy do automatycznej regulacji częstotliwości, w tym jej zwielokrotniania. Na schemacie poniżej blok PLL zaznaczyłem kolorem pomarańczowym.
Może on przyjmować jako wejściowy sygnał HSI16 (wewnętrzny rezonator) lub sygnał zewnętrzny HSE.
PLL ma wbudowany bloki mnożące i dzielące sygnał wejściowy, dzięki czemu możemy uzyskać różne wartości wyjściowe z bloku PLL. Wejściowy sygnał najpierw jest dzielony przez wartość PLLM (od 1 do 8), a następnie mnożona przez wartość PLLN (od 8 do 86). W ten sposób uzyskujemy wartość fVCO.
Blok PLL ma trzy wyjścia:
- PLLRCLK – sygnał zegarowy, który może być źródłem dla zegara systemowego SYSCLK
- PLLQCLK – używany jako sygnał dla generatora liczny losowych
- PLLPCLK – używany jako sygnał dla przetwornika ADC i interfejsu I2S
Każde z tych wyjść ma dodatkowy, indywidualny dzielnik oznaczany kolejno jako PLLR (od 2 do 8), PLLQ (od 2 do 8) oraz PLLP (od 2 do 32). Odpowiednio konfigurując te parametry możemy uzyskać częstotliwość PLLRCLK (ten sygnał nas teraz najbardziej interesuje) o wartości 64 MHz.
Należy pamiętać jeszcze o procedurze konfiguracji PLL. Każda zmiana parametrów bloku PLL wymaga wyłączenia pętli PLL. Po skonfigurowaniu rejestrów odpowiedzialnych za mnożniki i dzielniki należy włączyć blok PLL, a następnie włączyć używane wyjścia PLLRCLK, PLLQCLK i PLLPCLK. Wyjścia mogą być dowolnie włączane i wyłączane w trakcie działania pętli PLL.
Podstawowe rejestry konfiguracyjne – PLLRCLK jako SYSCLK
Rejestr CR – rejestr kontrolny
Bit PLLRDY – flaga włączenia bloku PLL
Bit PLLON – włączenie bloku PLL
Rejestr RCC_CFGR – rejestr konfiguracyjny RCC
Bity SWS[2:0] – status źródła sygnału dla zegara systemowego SYSCLK
Bity SW[2:0] – wybór źródła sygnału dla zegara systemowego SYSCLK
Rejestr PLLCFGR – rejestr konfiguracyjny bloku PLL
Bity PLLR[2:0] – wybór dzielnika dla wyjścia PLLRCLK
Bit PLLREN – włączenie wyjścia PLLRCLK
Bity PLLN[6:0] – mnożnik dla wejścia bloku PLL
Bity PLLM[2:0] – dzielnik dla wejścia bloku PLL
Bity PLLSRC[1:0] – wybór źródła wejścia dla bloku PLL
Opóźnienie dostępu do odczytu FLASH
Przed napisaniem programu z obsługą bloku pętli PLL, muszę wspomnieć jeszcze o jednej rzeczy. Do tej pory korzystaliśmy z małych częstotliwości zegara systemowego, dlatego ta wiedza nie była nam potrzebna. Jednak uruchomienie taktowania na poziomie 64 MHz wymaga poznania dodatkowego aspektu pracy STM32, a konkretnie chodzi o opóźnienie dostępu do odczytu pamięci FLASH (Latency).
Aby odczytać dane z pamięci Flash należy poprawnie zaprogramować ilość stanów oczekiwania (LATENCY). Wartość ta zależy od częstotliwości pracy (konkretnie HCLK, o którym jeszcze powiemy dokładnie w kolejnej części) oraz zakresu napięć pracy. Zależności te dla mikrokontrolera STM32G071RB przedstawia tabela poniżej.
Jeżeli chodzi o VCORE, w normalnym trybie mikrokontroler pracuje w zakresie Range 1 (Range 2 odnosi się do niskiego zużycia energii). Bardziej interesuje nas w tej chwili częstotliwość pracy HCLK (u nas jest będzie taka sama jak SYSCLK, czyli 64 MHz).
Dotychczas pracowaliśmy z częstotliwością 16 MHz, czyli rdzeń potrzebował opóźnienia na poziomie 1 cylku HCLK, aby prawidłowo odczytywać dane z pamięci FLASH, gdzie mamy umieszczony kod programu. Teraz chcemy korzystać z 64 MHz, co wymaga 3 cykli HCLK. Jeżeli nie uwzględnimy tego w konfiguracji, program w momencie ustawienia PLL jako SYSCLK przestanie działać, bo odczyt kolejnych instrukcji z pamięci nie będzie prawidłowa. Z tego względu musimy skonfigurować opóźnienie jako 3 cykle HCLK (2 WS). Najlepiej zrobić to na samym początku konfiguracji.
Jak zatem skonfigurować ilość cykli HCLK jako opóźnienia odczytu Flash? Wystarczy, że ustawimy prawidłową wartość w rejestrze FLASH_ACR w bitach LATENCY[2:0].
Wszystkie projekty z kursu dostępne są w moim repozytorium GitHub.
[PROGRAM] Konfiguracja zegara HSI i pętli PLL (PLLRCLK) jako źródła zegara SYSCLK
Mając już pełna wiedzę, możemy przystąpić do napisania programu i “rozkręcić” częstotliwość pracy mikrokontrolera do 64 MHz.
Na początku konfigurujemy opóźnienie dostępu. Dla 64 MHz ustawiamy 3 cykle HCLK (czyli 2 WS).
LL_FLASH_SetLatency(LL_FLASH_LATENCY_2);
Teraz uruchamiamy rezonator HSI i czekamy, aż się włączy.
LL_RCC_HSI_Enable();
while(LL_RCC_HSI_IsReady() != 1)
;
Następnie konfigurujemy parametry bloku PLL pod kątem użycia go jako źródła zegara systemowego. Ponieważ chcemy uzyskać 64 MHz, a HSI ma wartość 16 MHz, musimy sygnał wejściowy pomnożyć przez 4. Niestety nie mamy jednak możliwości wyboru PLLN jako 4, bo najmniejsza dostępna wartość to 8. Mnożymy więc razy 8 i za pomocą dzielnika PLLR dzielimy przez 2. W ten sposób uzyskujemy 64 MHz.
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSI, LL_RCC_PLLM_DIV_1, 8, LL_RCC_PLLR_DIV_2);
Teraz uruchamiamy blok PLL i czekamy, aż się włączy.
LL_RCC_PLL_Enable();
while(LL_RCC_PLL_IsReady() != 1)
;
Następnie włączamy wyjście PLLR.
LL_RCC_PLL_EnableDomain_SYS();
Na koniec wybieramy źródło SYSCLK jako PLL i czekamy, aż się ustawi.
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
;
Pozostało tylko podać wartość 64 MHz do użytku biblioteki Low Layer.
LL_SetSystemCoreClock(64000000);
LL_Init1msTick(64000000);
Teraz dodajemy analogicznie do poprzednich przykładów konfigurację GPIO, aby sprawdzić, czy dioda będzie migała z poprawną częstotliwością.
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
LL_GPIO_SetPinOutputType(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_MODE_OUTPUT);
while (1)
{
LL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
LL_mDelay(1000);
}
Przypominam, że zgodnie z tym, co przedstawiłem w rozdziale o tworzeniu projektów pliku, w pliku main.h powinny znaleźć się includy oraz definicje pinu.
#define LED_GREEN_Pin LL_GPIO_PIN_5
#define LED_GREEN_GPIO_Port GPIOA
Po skompilowaniu kodu (“Project->Build Project” lub CTRL+B) i wgraniu na płytkę (“Run->Run”), dioda LD4 powinna zmieniać stan co 1 s.
Taktowanie układów peryferyjnych
Do tej pory zajmowaliśmy się głównie zegarem systemowym (SYSCLK) mikrokontrolera. Stanowi on punkt wyjściowy do większości zegarów docelowych dla poszczególnych elementów mikrokontrolera. Jednak aby dostarczyć w odpowiedni sposób sygnał zegarowy do układów peryferyjnych, musimy poznać jeszcze dwa sygnały zegarowe – HCLK i PCLK, które odpowiadają za taktowanie szyn AHB i APB.
Szyny AHB i APB to szyny, które łączą układy peryferyjne z pozostałymi elementami mikrokontrolera. AHB (Advanced High-Performance Bus) odpowiada za komunikację z kontrolerami DMA, EXTI czy RCC. Do szyny APB (Advanced Peripheral Bus) podłączone są wszystkie układy peryferyjne, w tym przetwornik ADC, TIMER-y, interfejsy UART, I2C, SPI czy I2S. Schematycznie zostało to zaznaczone na poniższej grafice.
Każda z szyn ma własny dzielnik, dzięki czemu możemy dostosować zegary do potrzeb aplikacji. Dodatkowo wiele układów peryferyjnych ma możliwość wyboru sygnału zegarowego, co daje programiście dużą elastyczność przy doborze częstotliwości ich pracy. Dostępne sygnały zegarowe dla poszczególnych interfejsów zaznaczone zostały na schemacie poniżej.
Ich dokładniejszą analizą zajmiemy się przy okazji poznawania kolejnych układów peryferyjnych. Teraz najważniejsze jest odpowiednie skonfigurowanie sygnałów HCLK i PCLK. Istotną informacją jest także to, że każdy układ mikrokontrolera ma w rejestrach RCC bity, które odpowiadają za włączenie taktowania portów mikrokontrolera oraz danego peryferium. Bez ustawienia tych bitów nie będzie możliwe ustawianie i odczytywanie rejestrów odpowiedzialnych za konfigurację GPIO, jak również UART, ADC czy TIMER-ów.
Rejestry konfiguracyjne HCLK, PCLK oraz włączenie układów peryferyjnych
Rejestr RCC_CFGR – rejestr konfiguracyjny RCC
Bity PPRE[2:0] – wybór dzielnika dla sygnału PCLK
Bity HPRE[3:0] – wybór dzielnika dla sygnału HCLK
Rejestr RCC_IOPENR – rejestr odpowiedzialny za włączanie/wyłączanie taktowania portów GPIO
RCC_AHBENR, RCC_APBENR1 oraz RCC_APBEN2 – rejestry odpowiedzialne za włączanie/wyłączanie taktowania układów peryferyjnych
[PROGRAM] Konfiguracja HCLK i PCLK
Program będzie bazował na poprzednim przykładzie, gdzie konfigurowaliśmy blok PLL jako źródło dla zegara systemowego SYSCLK. Teraz dodamy jeszcze ustawienie preskalerów dla szyn AHB i APB – obie wartości jako 1, dzięki czemu obie szyny będą korzystały z zegara 64 MHz.
Na początku konfigurujemy opóźnienie dostępu. Dla 64 MHz ustawiamy 3 cykle HCLK (czyli 2 WS).
LL_FLASH_SetLatency(LL_FLASH_LATENCY_2);
Teraz uruchamiamy rezonator HSI i czekamy, aż się włączy.
LL_RCC_HSI_Enable();
while(LL_RCC_HSI_IsReady() != 1)
;
Następnie konfigurujemy parametry bloku PLL pod kątem użycia go jako źródła zegara systemowego. Ponieważ chcemy uzyskać 64 MHz, a HSI ma wartość 16 MHz, musimy sygnał wejściowy pomnożyć przez 4. Niestety nie mamy jednak możliwości wyboru PLLN jako 4, bo najmniejsza dostępna wartość to 8. Mnożymy więc razy 8 i za pomocą dzielnika PLLR dzielimy przez 2. W ten sposób uzyskujemy 64 MHz.
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSI, LL_RCC_PLLM_DIV_1, 8, LL_RCC_PLLR_DIV_2);
Teraz uruchamiamy blok PLL i czekamy, aż się włączy.
LL_RCC_PLL_Enable();
while(LL_RCC_PLL_IsReady() != 1)
;
Następnie włączamy wyjście PLLR.
LL_RCC_PLL_EnableDomain_SYS();
Teraz ustawiamy dzielnik dla szyny AHB i APB.
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
Na koniec wybieramy źródło SYSCLK jako PLL i czekamy, aż się ustawi.
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
;
Pozostało tylko podać wartość 64 MHz do użytku biblioteki Low Layer.
LL_SetSystemCoreClock(64000000);
LL_Init1msTick(64000000);
Teraz dodajemy analogicznie do poprzednich przykładów konfigurację GPIO, aby sprawdzić, czy dioda będzie migała z poprawną częstotliwością. Wiadomo już do czego służy pierwsza linia – włączamy taktowanie dla portu GPIO.
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
LL_GPIO_SetPinOutputType(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_MODE_OUTPUT);
while (1)
{
LL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
LL_mDelay(1000);
}
Przypominam, że zgodnie z tym, co przedstawiłem w rozdziale o tworzeniu projektów pliku, w pliku main.h powinny znaleźć się includy oraz definicje pinu.
#define LED_GREEN_Pin LL_GPIO_PIN_5
#define LED_GREEN_GPIO_Port GPIOA
Po skompilowaniu kodu (“Project->Build Project” lub CTRL+B) i wgraniu na płytkę (“Run->Run”), dioda LD4 powinna zmieniać stan co 1 s. Teraz mając już pełną wiedzę na temat RCC schemat z przepływem sygnałów zegarowych powinien być bardziej przejrzysty.
Pozostaje jeszcze jedna kwestia do wyjaśnienia – dlaczego dotychczas program działał, skoro nie skonfigurowaliśmy dzielników szyn AHB i APB? Są one domyślnie ustawione na wartość 1, dlatego program nam się uruchomił. Wiedzą odnośnie zegarów chciałem wprowadzać stopniowo, aby od razu cała procedura nie wydawała się zbyt skomplikowana. Mam nadzieję, że po przestudiowaniu całego rozdziału o RCC zegary w STM32 nie będą Ci już takie obce. Powyższą konfigurację warto zapamiętać, będziemy z niej korzystali w większości przykładów w kursie.
Repozytorium GitHub
Chciałbyś otrzymywać na bieżąco informacje o nowych artykułach z kursu? Zapisz się do newslettera!
TO NIE TYLKO MAIL Z INFORMACJĄ O NOWEJ LEKCJI, ALE TAKŻE DODATKOWE MATERIAŁY. NIE PRZEGAP NOWEJ TREŚCI I DODATKOWYCH BONUSÓW. PRZEJDŹ DO STRONY KURSU I PODAJ SWÓJ ADRES E-MAIL. NIE ZAPOMNIJ POTWIERDZIĆ CHĘCI DOŁĄCZENIA W PIERWSZEJ WIADOMOŚCI!
No tak, mogłem sprawdzić ile trwa wyjście z delaaya, dzięki 🙂
Wtam. Warto tutaj dopowiedzieć, że GPIO jest kontrolowane przez szynę AHB i tylko jej prescaler będzie miał wpływ na zachowanie diody. Funkcja LL_RCC_SetAPB1Prescaler(); jest tuaj opcjonalna.
Pozdrawiam
Zgadza się, w tym konkretnym przykładzie sterowania diodą taktowanie szyny APB nie ma znaczenia. Jednak przykład był przygotowywany po to, aby przedstawić konfigurację zegara ogólnie, a nie pod diodę LED, dlatego omawiam oba preskalery. Ta konfiguracja przyda się w kolejnych lekcjach, gdzie już szyna APB będzie miała znaczenie.
Ok. Mam jeszcze takie pytanie. Gdy zmienie preskaler tak, że szyna AHB ma 4 razy mniejsze taktowanie to dioda zmienia stan co 4 sekundy przy delay’u jednosekundowym. Niby zrozumiałe – taktowanie sysclk to X , szyny AHB X/4 . Nie mogę jednak odnieść tego do rzeczywistości. Mógłbyś wyjaśnić dlaczego zmiane obserwuje po 4 sekundach? Co tam sie takiego dzieje z tym sygnałem?
Opóźnienie w funkcji LL_mDelay() jest realizowane za pomocą SysTicka, którego działanie jest zależne od szyny AHB. Jeżeli zmniejszasz częstotliwość na szynie, to SysTick też pracuje cztery razy wolniej. Żeby funkcja LL_mDelay() nadal odliczała dobry czas pomimo zmiany taktowania AHB, musimy ponownie skonfigurować SysTick timer podając nasz główny zegar dla rdzenia (HCLK) i konfigurując sam SysTick funkcją LL_Init1msTick(HCLK_CoreFreq). Funkcja ta konfiguruje SysTick tak, aby odliczał czas w ms. Jeśli zajrzysz głębiej do tej funkcji to operuje ona na rejestrach rdzenia odpowiedzialnych za SysTick m.in. SysTick->LOAD, SysTick->VAL, SysTick->CTRL.