niedziela, 28 marca 2021

STM32G0 - ustawienie zegara.

W STM32G0 mamy do dyspozycji cztery podstawowe źródła taktowania. 

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

 

Tu przy okazji jedna uwaga konia z rzędem kto dopatrzy się na schemacie zegara dla modułu GPIO , no psia mać "zapomnieli" domalować jedno z najważniejszych peryferium w MCU :). Tu wspomnę tylko, że moduł GPIO jest sprzęgnięty zegarowo z core MCU ale mogli to zaznaczyć na schemacie zegara, żeby potem klient się nie denerwował, że kupił MCU bez możliwości podpięcia zegara do GPIO :).

Przed tym jak zajrzę do rejestrów zegarowych muszę sobie w głowie uporządkować jakie ogólne czynności powinienem wykonać bazując na samym schemacie zegara i kierując się zaznaczoną na czerwono drogą.
  • 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
Ej nie jest tak źle, jeśli chodzi o ilość ogólnych czynności do wykonania. Teraz czas na wertowanie rejestrów modułu RCC.


Dobra jedziemy z koksem , pierwszy rejestr do którego zajrzymy to RCC_CR.




W rejestrze RCC_CR wybrałem te bity , które w procesie konfiguracji będą przydatne, pozostałe usunąłem z opisu. Na bicie nr 8 mamy HSION, ten bit musi być ustawiony na 1 czyli HSI16 oscillator ON. Musimy się upewnić ,że tak jest . Pod nazwą rejestru RCC_CR mamy informację, że po resecie MCU rejestr ten ma wartość : 0x0000 0500 stąd mamy pewność , że nasz generator HSI RC16 działa. Ale tu uwaga , warto po uruchomieniu MCU lub po resecie poczekać na flagę HSIRDY: HSI16 clock ready flag. Jak dojdziemy do napisania kodu konfiguracyjnego pokażę jak to fizycznie zrobić.


Na tym mamy zakończony pierwszy punkt programu czyli upewnić się, że HSI16 RC jest włączony.
Kolejnym etapem jaki sobie założyłem ogólnie to upewnić się, że selektor znajdujący się przed blokiem PLL jest przestawiony na HSI16. Stosowne ustawienie znalazłem w rejestrze RCC_PLLCFGR w polu bitowym PLLSRC pole to musimy ustawić na wartość 0b10:

                                      


Przechodzimy zatem do kolejnego punktu naszego planu czyli skonfigurować parametry pętli PLL aby na wyjściu PLLRCLK otrzymać 32 MHz . Intuicyjnie zakładam , że tutaj będzie już ciekawie :). Przyjrzyjmy się dokładniej bloczkowi PLL :

                                             

Widzimy takie tam oznaczenia jak fPLLIN to częstotliwość na wejściu do PLL czyli nasze 16 MHz. Potem jakieś podzielniki, mnożniki literki M , N. No dobra nie wiem co z tym zrobić , więc szukam rejestru dla PLL , który by mi to wytłumaczył. Stosownym rejestrem jest RCC_PLLCFGR


Na dzień dobry otrzymujemy wzorki z których trzeba skorzystać aby wyliczyć potrzebne zależności. wiemy, że na wyjściu PLL a konkretnie na wyjściu oznaczonym PLLRCLK chcemy uzyskać 32 MHz. Zatem na podstawie powyższych wzorków prawdziwy będzie zapis :

fPLLRCLK = 16 MHz * (N/M) / R = 32 MHz

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:


i ustawienia jakimi się zajmiemy :


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

lub 
 
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_1 ; // ustaw bit starszy na 1 ,pole bitowe PLLSRC

RCC->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
picmajster.blog@gmail.com

6 komentarzy:

  1. ś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ń
  2. Z HAL'ami może być dopiero przygoda jak zaczną żyć własnym życiem z powodu baboli w kodzie.

    OdpowiedzUsuń
  3. 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ń
  4. ja ustawiłem to tak , nie ma SWS_PLL , zamieszczam sprawdzoną procedurę dla stm32go70 "void CLK_Internal_32Mhz(void)
    {
    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));
    //*/
    }

    OdpowiedzUsuń
  5. a tak dla HSE=12MHZ stm32g070 "void CLK_HSE_50MHZ(void)
    {
    //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;
    }"

    OdpowiedzUsuń
  6. a tak MCO output na PA9
    "
    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

    }

    OdpowiedzUsuń