LSI RC - wewnętrzny zegar 32kHz
LSE OSC - zewnętrzny zegar 32.768 kHz
HSI16 RC - wewnętrzny zegar 16 MHz
HSE OSC - zewnętrzny zegar 4-48 MHz
Dla wersji z USB mamy dodatkowo dedykowany wewnętrzny zegar HSI48 RC .
Po resecie core MCU w STM32G0 taktowane jest zegarem wewnętrznym HSI16 RC. Czyli innymi słowy mamy na pokładzie 16 MHz. Aby zwiększyć tę częstotliwość do nominalnej 64 MHz musimy użyć pętli PLL czyli takiego powielacza częstotliwości. W STM32 peryferia są standardowo odłączone od szyny zegarowej. Jeśli chcemy używać jakiegoś peryferium np. GPIO, SPI, I2C etc musimy odpowiednim wpisem w odpowiedni rejestr dokonać włączenia zegara. Taki zabieg ma podobno oszczędzać energię w STM32. Założenie jest słuszne na pierwszy rzut oka, po co taktować peryferium z którego nie korzystamy. Budzi jednak moją wątpliwość fakt, że w produktach firmy Microchip opartych na tym samym rdzeniu czyli Cortex M0+, STM32 bez włączonego zegara na peryferiach żre znacznie więcej prądu niż w np. w ATSAML gdzie większość peryferii jest podłączonych do szyn zegarowych po starcie . Czyli da się zrobić energooszczędnie i z podłączonymi peryferiami no ale nie w STM32.
W STM32G070KBT6 nie mamy możliwości podpięcia równocześnie zewnętrznego kwarca "zegarkowego" czyli LSE OSC i HSE OSC. Jeśli podepniemy HSE OSC i chcemy jednocześnie napędzić zegar RTC to musimy go tak dobrać aby po zastosowaniu podzielnika zbliżyć się do częstotliwości zegarkowej. Nie dostrzegam tutaj specjalnych zalet takiego podejścia.
Z punktu widzenia wydajności w STM32G0 to trzeba zauważyć , że mamy do czynienia z hamulcem w postaci pamięci Flash gdzie dla nominalnej prędkości 64 MHz musimy wprowadzić 3 cykle opóźnienia dla odczytu. Co to może implikować ? ano to, że za wzrostem częstotliwości taktowania nie idzie adekwatny wzrost wydajności . Optymalna częstotliwość z punktu widzenia zwłoki w cyklach w dostępie do flash w STM32G0 to maksymalnie 24 MHz. Powyżej tej częstotliwości musimy wprowadzić już 2 cykle opóźnienia. Czyli może się okazać, że STM32G0 ustawiony na 24 MHz może być w pewnych okolicznościach wydajniejszy niż ustawiony na np. 36 MHz.!! Także z tym dorzucaniem do pieca Mega Herców to z uwagą i ostrożnie :). Jest w necie test w którym wykazano, że 6-krotny wzrost taktowania w STM32 powoduje niecałe 4-krotne przyspieszenie działania.
No dobrze, przyjrzyjmy się jak wygląda schemat modułu zegara w STM32G0 :
Na pierwszy rzut oka to trochę skomplikowane ale przy dłuższym wpatrywaniu się w ten obrazek można się połapać z grubsza o co chodzi. Człowiek to wymyślił to i człowiek to zrozumie :). Wiemy, że na starcie w STM32G0 źródłem zegara jest bloczek HSI16 RC - 16MHz. Ja pokażę jaką drogę trzeba przebyć aby ustawić taktowanie np. na 32 MHz tak nie za dużo i nie za mało ale w sam raz :). Pisząc , że pokażę jak to zrobić nie wiem jeszcze fizycznie jak to zrobić :). Tworząc ten artykuł sam się uczę :)
Poniżej rysunek z moją radosną twórczością gdzie zaznaczyłem drogę jaką musimy przebyć aby dostarczyć zegar 32MHz do szyny AHB na której chodzi m.in core, pamięć, DMA
- upewnić się, że HSI16 RC jest włączony,
- upewnić się, że selektor znajdujący się przed blokiem PLL jest przestawiony na HSI16,
- skonfigurować parametry pętli PLL aby na wyjściu PLLRCLK otrzymać 32 MHz ,
- przestawić selektor SYSCLK na PLLRCLK
- włączyć pętle PLL
Dobra jedziemy z koksem , pierwszy rejestr do którego zajrzymy to RCC_CR.
Teraz tylko wystarczy znaleźć odpowiednie kombinacje dla N , M i R. Ale musimy znać zakresy dla tych stałych. Pomocne będzie zdjęcie poniżej :
I tak poniżej podaję zakresy dla stałych i gdzie dokonujemy ich zmiany :
- M - 1....8 rejestr RCC_PLLCFGR pole PLLM
- N - 8....86 rejestr RCC_PLLCFGR pole PLLN
- R - 2....8 rejestr RCC_PLLCFGR pole PLLR
Moja propozycja dla wzoru to:
fPLLRCLK = 16 MHz * (8/2) / 2 = 32 MHz
Ostatnią czynnością w bloku PLL będzie ustawienia wyjścia na PLLRCLK, bo to będzie nasze wyjście . Robimy to zapisem w rejestrze RCC_PLLCFGR pole PLLREN na wartość 1.
Po ustawieniach pętli PLL nie włączamy jeszcze jej.
Teraz ostatni etap naszego działania konfiguracyjnego czyli przestawienie selektora SYSCLK na źródło PLLRCLK . Ustawienia takiego dokonamy w rejestrze RCC_CFGR w polu bitowym SW na wartość 0b010
Zwracam uwagę, że selektor też po zmianie ustawienia trzeba odpytać czy ustawienie się dokonało a te informację znajdziemy w polu SWS.
No dobra w/g mnie już chyba wszystko i w tym momencie można by było włączyć pętle PLL czyli rejestr RCC_CR pole PLLON na wartość 1. Potem statusem odpytujemy czy PLL się rozpędziła i działa stabilnie , rejestr RCC_CR pole PLLRDY.
Teraz powyższe dywagacje trzeba przekuć na kod i to będzie też fajny przyczynek aby utrwalić sobie metody zapisu do rejestrów wykorzystując plik nagłówkowy od STM-a. Trochę sobie ponarzekałem na ten plik we wpisie poprzednim , że STM nie robi tych plików tak komfortowo jak w przypadku MCU Microchipa, ale może w tej prostocie jest piękno :).
Dla wprawy przed napisaniem kodu prześledźmy np. drogę jaką trzeba pokonać aby ustawić selektor wyboru HSE/HSI16 znajdujący się przed blokiem pętli PLL. Pokażę zatem jak się poruszać po tym wszystkim w praktyce.
Najpierw zaczynamy od obrazka zawartości rejestru RCC_PLLCFGR:
Naszym zamiarem jest ustawienie w rejestrze RCC_PLLCFGR pola bitowego PLLSRC na wartość 0b10 czyli selektor ustawiony na HSI16. Wiemy zatem co chcemy ustawić i jaką wartość docelową.
Teraz zaglądamy do pliku nagłówkowego STM-a dla naszego MCU , plik o nazwie stm32g071xx.h. Skąd wziąć ten plik ?. W IDE SEGGER-a jest on automatycznie dołączany przy każdym nowym projekcie. W pliku odszukujemy sekcję dotyczącą naszego rejestru i pola bitowego , oznaczyłem ten fragment na obrazku poniżej:
Mając przed oczyma powyższe dokonujemy zapisu :
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLSRC ; // zerujemy pole bitowe PLLSRC
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_1 ; // ustaw bit starszy na 1 ,pole bitowe PLLSRC
lubRCC->PLLCFGR &= ~RCC_PLLCFGR_PLLSRC_0 ; // zerujemy bit młodszy, pole PLLSRC
lub
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSI ; // ustaw bit starszy na 1 ,pole bitowe PLLSRC
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLSRC_0 ; // zerujemy bit młodszy, pole PLLSRC
Do wyboru do koloru , jeśli załapiemy te zapisy i tę konwencję zapisu to rozmowa z rejestrami w STM32 staje się prostą czynnością.
Na koniec to co opisałem powyżej przekułem na kod i wydaje mi się, że powinno to zadziałać :). Nie mam jeszcze płytki , żeby to sprawdzić.
Trochę tego kodu nadziergałem , można oczywiście go skrócić , pozbyć się np. zerowań pól bitowych. Ale jak to mówi przysłowie kto chodzi na skróty ten dwa razy ma dłuższa drogę :). Zróbmy to raz a porządnie to nic nam się nie skaszani po drodze. W każdym ustawieniu mamy pełną kontrolę nad każdym bitem w rejestrze, czujecie tę moc :). HAL-owcy stukną się w głowę, tyle zachodu aby ustawić "głupi" zegar. Nasuwa mi się tutaj pewne skojarzenie. Mianowicie :
Zadano pytanie osobie , która skuterem 50 cm jeździ na dalekie wyprawy po Polsce. Czemu się męczysz na takim karakanie zamiast wsadzić tyłek w jakiegoś wygodnego turystyka BMW GS lub samochód. Osoba odpowiedziała w ten sposób , że jadąc tą "marną" 50-tką uczestniczy w wielkiej wyprawie a nie wycieczce, bo wycieczka czyli przemieszczenie się z punktu A do B nie da tylu emocji i nie zobaczy się tyle co podczas jego wyprawy.
Programując na rejestrach uczestniczymy w wyprawie , wielkiej przygodzie i wyzwaniu intelektualnemu :). HAL to wycieczka z punktu A do B :), bez emocji i na czym szybko można się wypalić wpadając w rutynę.
Tak na zakończenie daję link do filmu o człowieku ,który złamał chyba wszystkie bariery jakie mogą człowieka ograniczać w dążeniu do realizacji jednego tylko marzenia. Bardzo zachęcam do obejrzenia tego filmu to tylko 38 min. FILM.
Pozdrawiam
picmajster.blog@gmail.com
świetny artykuł , "dość dyktatury HAL" ;-) ! Tak naprawdę lepiej nauczyć się rejestrów i mam pełną świadomość co się robi oraz działać na lżejszym IDE . Sam mam doświadczenie z MCC , że od pewnego momentu więcej przeszkadzało niż pomagało
OdpowiedzUsuńZ HAL'ami może być dopiero przygoda jak zaczną żyć własnym życiem z powodu baboli w kodzie.
OdpowiedzUsuńWszystko ma swoje wady i zalety, kwestia wyboru czy dana technologia ma dla nas więcej wad czy zalet w danym zamierzeniu. HAL niewątpliwie ma niski próg wejścia dla początkujących i to jest spora zaleta inna sprawa, że nie nauczymy się pływać na płytkiej wodzie. Ale nie wszyscy chcą zostać pływakami :)
OdpowiedzUsuńja ustawiłem to tak , nie ma SWS_PLL , zamieszczam sprawdzoną procedurę dla stm32go70 "void CLK_Internal_32Mhz(void)
OdpowiedzUsuń{
PWR->CR1|=PWR_CR1_VOS_1;
RCC->CR&=~RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY));
while(!(RCC->CR & RCC_CR_HSIRDY));
RCC->PLLCFGR|=RCC_PLLCFGR_PLLSRC_1;
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLSRC_0;
//PLLM set to 2
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLM;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLM_0;
//plln set to 8
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLN;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLN_3;
//pllr to 2
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLR;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLR_0;
//sw set to pllrck
//RCC->PLLCFGR&=~RCC_CFGR_SW;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLREN;
RCC->CR|=RCC_CR_PLLON;
FLASH->ACR |= FLASH_ACR_LATENCY_1 ;
while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_1);
while(!(RCC->CR & RCC_CR_PLLRDY)); //PLL READY ? y/N
//
RCC->CFGR&=~RCC_CFGR_SW;
RCC->CFGR|=RCC_CFGR_SW_1;
//RCC->PLLCFGR|=RCC_PLLCFGR_PLLREN;
while(!(RCC->CFGR & RCC_CFGR_SWS_1));
//*/
}
a tak dla HSE=12MHZ stm32g070 "void CLK_HSE_50MHZ(void)
OdpowiedzUsuń{
//ustawienie SYSCLK na 50MHZ + MCO 25MHz M=3,N=25, R=2
RCC->CR |=RCC_CR_HSEON;
RCC->CR&=~RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY));
RCC->PLLCFGR|=RCC_PLLCFGR_PLLSRC_1|RCC_PLLCFGR_PLLSRC_0;
//PLLM set to 3
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLM;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLM_1;
//plln set to 25
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLN;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLN_0|RCC_PLLCFGR_PLLN_3|RCC_PLLCFGR_PLLN_4 ; // 25 =0b11001
//pllr to 2
RCC->PLLCFGR&=~RCC_PLLCFGR_PLLR;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLR_0;
//MCO config sysclk /2 50mhz/2=25mhz
RCC->CFGR|=RCC_CFGR_MCOSEL_0| RCC_CFGR_MCOSEL_2;
RCC->CFGR|=RCC_CFGR_MCOPRE_0; //|RCC_CFGR_MCOPRE_2;
RCC->PLLCFGR|=RCC_PLLCFGR_PLLREN;
while(!(RCC->CR & RCC_CR_HSERDY));
RCC->CR|=RCC_CR_PLLON;
FLASH->ACR |= FLASH_ACR_LATENCY_2 ;
while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_2);
while(!(RCC->CR & RCC_CR_PLLRDY)); //PLL READY ? y/N
//
RCC->CFGR&=~RCC_CFGR_SW;
RCC->CFGR|=RCC_CFGR_SW_1;
while(!(RCC->CFGR & RCC_CFGR_SWS_1));
RCC->CR&=~RCC_CR_HSION;
}"
a tak MCO output na PA9
OdpowiedzUsuń"
void config_MCO_pin(void)
{
//pa9 pin 28 lqfp48 output
GPIOA->MODER&=~(1U<<18);
GPIOA->MODER|=(2U<<18);
GPIOA->AFR[1]&=~GPIO_AFRH_AFSEL9;
GPIOA->OSPEEDR|=(3U<<18); //very high speed 0b11
}