Sygnały zegarowe w STM32F1 Connectivity Line
W naszym przypadku sygnał z zewnętrznego oscylatora HSE jest dzielony przez 2 w drugim bloku wstępnego dzielenia sygnału PREDIV2, w ten sposób uzyskiwany jest sygnał o częstotliwości 5 MHz. W następnym kroku włączany jest układ PLL3 z mnożnikiem x10. Wyjście PLL3 może być podłączone przez multiplekser do wyprowadzenia MCO (Microcontroller Clock Output), ale sygnał z PLL3 może być również dzielony przez 2 zanim zostanie doprowadzony do MCO. Właśnie ta druga opcja została wykorzystana w przedstawionym przykładzie, zatem na wyprowadzeniu MCO układu będzie sygnał o częstotliwości 25 MHz, czyli tyle, ile wymaga interfejs MII, który jest stosowany do podłączenia warstwy fizycznej Ethernet.
W założeniach zaznaczono, że rdzeń mikrokontrolera powinien pracować z zegarem 72 MHz. Aby to osiągnąć konfigurowany jest układ PLL2 z mnożnikiem x8, by uzyskane w ten sposób 40 MHz. Następnie częstotliwość dzielona jest przez 5 w bloku PREDIV1, dzięki temu na wejście układu PLL dostarczany jest przebieg o częstotliwości 8 MHz. Teraz wystarczy już tylko ustawić mnożnik PLL na 9, by uzyskać oczekiwane 72 MHz.
Brakuje jeszcze sygnału zegarowego dla interfejsu USB. Częstotliwość przebiegu na wyjściu PLLVCO układu PLL jest w omawianym przypadku powielana 18 razy. W efekcie uzyskuje się 144 MHz, co jest poprawną wartością (maksymalną) dla wyjścia PLLVCO. Ostatecznie sygnał ten dzielony jest przez 3 w preskalerze USB, w wyniku czego uzyskiwane jest wymagane 48 MHz.
Ostatni etap konfiguracji bloku RCC to włączenie układów peryferyjnych: USB, Ethernet i portów wejścia/wyjścia.
Z jaką częstotliwością pracuje mój mikrokontroler?
Pracując z tak zaawansowanym systemem sygnałów zegarowych może niekiedy dochodzić do stanu, że wszystko powinno działać, ale układ wydaje się nie pracować z wymaganą częstotliwością. Bezpośrednią i zarazem najprostszą metodą sprawdzenia szybkości pracy zegara systemowego jest jego wyprowadzenie na zewnątrz. Metoda ta posiada dwa ograniczenia. Pierwsze dotyczy wymogu posiadania miernika częstotliwości o dość dużym zakresie. Drugie ograniczenie daje o sobie znać przy częstotliwościach będących rzędu wielu dziesiątek megaherców. Tutaj ograniczeniem są możliwości układów sterujących wyjściami mikrokontrolera. W przypadku układów STM32, maksymalna częstotliwość przełączania wyprowadzenia MCO wynosi 50 MHz. Z odsieczą przychodzą jednak pośrednie sposoby wyznaczenia częstotliwości pracy jednostki centralnej.
Mikrokontrolery STM32 są wyposażone w systemowy timer SYSTICK. Jest to 24-bitowy układ licznikowy, liczący w dół. Może on być taktowany z taką samą częstotliwością jak rdzeń, lub ośmiokrotnie mniejszą. Pomiar prędkości pracy MCU polega na podzieleniu częstotliwości systemowej za pomocą SYSTICK i zmianie stanu wyprowadzenia w przerwaniu. W ten sposób można uzyskać na wyprowadzeniu sygnał rzędu np. kilku, kilkunastu kHz, a taką wartość można już zmierzyć zwykłym multimetrem posiadającym opcję pomiaru częstotliwości, lub też drugim mikrokontrolerem, który pracuje ze znaną częstotliwością. Jest oczywistym, że taki pomiar nie będzie dokładny, ale pozwala rozstrzygnąć, czy wszystkie dzielniki i mnożniki sygnału zegarowego zostały ustawione w poprawny sposób oraz czy rezonator kwarcowy uruchomił się na odpowiedniej częstotliwości.
Konfiguracja timera SysTICK nie jest zbyt wyrafinowana, wymaga jedynie dwóch linii kodu. Funkcję ustawiającą parametry licznika przedstawiono na listingu 2 Również funkcja obsługi przerwania od timera nie jest skomplikowana, zamieszczono ją na listingu 3. To jeszcze nie wszystko. Wyprowadzenie pomiarowe, w zasadzie dowolne, przykładowo niech będzie to GPIOE7 powinno zostać skonfigurowane, a dla portu GPIOE należy włączyć taktowanie. Wspomniane zadania wykonuje funkcja z listingu 4.
Listing 2. Funkcja konfiguracji licznika SysTick
void SysTick_Configuration(void) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); /* Sygnał zegarowy rdzenia będzie dzielony * przez 10000 */ SysTick_Config(10000); }
Listing 3. Obsługa przerwania od licznika SysTick
void SysTick_Handler(void) { /*funkcja zmieniająca stan linii GPIOE7 na przeciwny*/ GPIO_WriteBit(GPIOE, GPIO_Pin_7, (BitAction) (1 - GPIO_ReadOutputDataBit(GPIOE, GPIO_Pin_7))); }
Listing 4. Funkcja konfigurująca wyprowadzenie GPIOE7
void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; /*włączenie zegara dla portu GPIOE*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); /*konfiguracja linii GPIOE7*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOE, &GPIO_InitStructure); }