czwartek, 18 października 2018

EFM32TG11B120F128GQ48 ARM Cortex-M0+ od Silicon Labs - mruganie LED-em w środowisku Simplicity Studio 4

W artykule zapoznamy się z dwoma nurtami programowania ARM-a . Z nurtem "natywnym" czyli programowaniem w oparciu o rejestry i pliki nagłówkowe mikrokontrolera oraz z nurtem programowania za pomocą biblioteki typu HAL(Hardware Abstraction Layer) W przypadku Silicon Labs jest to biblioteka emlib. O ile praca i nauka z biblioteką emlib jest lekka łatwa i przyjemna o tyle praca z rejestrami to już inna bajka ale ta bajka gwarantuje  więcej satysfakcji. Z rejestrami najlepiej się poczują odbiorcy , którzy poprzednio działali na AVR-ach lub PIC-ach. Część łatwa , lekka i przyjemna z biblioteką emlib dedykowana jest dla osób przechodzących z Arduino. Czyli każdy znajdzie coś dla siebie. Spróbujmy zatem zamigać diodą LED za pomocą dwóch nurtów programowania MCU z rdzeniem ARM.......

Na początek spróbujemy ogarnąć "nurt natywny" i wyjść od jakiś podstaw. Wiemy , że w nurcie tym posługujemy się wpisami bezpośrednio do rejestru.
Silicon proponuje robić to ogólnie tak :

W przypadku ustawienia konkretnego bitu rejestru na 1 :

register = (register OR mask)

W przypadku ustawienia konkretnego bitu rejestru na 0 :

register = (register AND (NOT mask))

To jest ogólny szablon w/g ,którego należy postępować. W szczególe wygląda to tak, że rejestr jest przedstawiany w postaci zapisu :

module_name -> register

Taki zapis oznacza, że gdzieś jest zadeklarowana struktura module_name a register jest jej elementem do , którego dostajemy się za pomocą operatora wskaźnika -> do elementu struktury. To nic innego jak konwencja języka C.

Skąd mamy wiedzieć jak są opisane nazwy rejestrów, struktur, masek i innych nie znanych nam na razie definicji i deklaracji.
Podstawowe ,źródło tych informacji znajduje się w plikach nagłówkowych stworzonych przez producenta dla konkretnego modelu MCU. W naszym przypadku wszystkie niezbędne pliki nagłówkowe, potrzebne do programowania "natywnego" znajdziemy w każdym utworzonym projekcie w następującej lokalizacji :



Ale samo posługiwanie się tymi plikami nic nam nie da bez wiedzy jak jest zbudowany dany rejestr i do jakiego modułu jest przyporządkowany a tę wiedzę zdobędziemy wertując Reference Manual naszego procka i w ten sposób nabywamy wiedzę jak jest zorganizowane życie wewnętrzne naszego MCU. Programowanie za pomocą bibliotek typu HAL pozbawia nas tej wiedzy.

Przyjrzyjmy się zatem przykładowi jak mniej więcej wygląda droga do np. włączenia zegara peryferyjnego dla GPIO. Z radia i TV wiemy, że w każdym MCU z rdzeniem ARM, musimy taką czynności dokonać manualnie. Tu mała dygresja wiemy, że takie włączanie zegara dla poszczególnych peryferiów jest domeną rdzeni ARM i ma to na celu oszczędzanie energii. W PIC32 Microchipa wszystkie peryferia są dostępne  i taktowane od momentu inicjalizacji MCU. Tak samo jak ARM-y , PIC32 są energooszczędne, różnice są stosunkowo niewielkie jak by to mogło wynikać ze specjalnie oszczędnej energetycznie architektury ARM. Zatem o co chodzi ???

No dobrze chcemy włączyć zegar dla modułu GPIO, musimy zatem pogrzebać w
Reference Manual w cześci dotyczącej zegara czyli modułu CMU. Mnie na początku w oczy rzucił się rysunek blokowy zegara na stronie 295 RM, można patrząc na niego dostać oczopląsu , nawet nie próbujcie tego zrozumieć bo was głowa zaboli ,wszystko w swoim czasie.  Jest tam dla nas jednak dobry trop. Rysunek poniższy jest tylko małym fragmentem całości :



Z rysunku powyższego możemy wyczytać m.in jak z punktu widzenia zegara powstaje gałąź do taktowania modułu GPIO. Nas interesuje ten fragmencik gdzie na wyjściu jest HFBUSCLK GPIO. Widzimy , że w tej gałęzi stoi mały bloczek o nazwie Clock Gate. Takich bloczków jest więcej w innych gałęziach powyżej i poniżej ale nas interesuje dokładnie ten w gałęzi dla GPIO. Jak sama nazwa wskazuje jest to brama zegarowa, więc idąc dalej tym tropem widzimy jedną dyndającą nóżkę przy tej bramie, opisaną jako CMU_HFBUSCLKEN0.GPIO. Domyślamy się zatem , że tym czymś sterujemy bramą  on/off. Dobra postępujemy zatem jak Sherlock Holmes i podążamy tym tropem, sprawdzamy czy jest rejestr w module CMU o nazwie HFBUSCLKEN0.
Rejestru szukamy w tabelce zbiorczej rejestrów dla modułu CMU , znajdziemy ją w RM na stronie 323 :




Widzimy , że w tabelce jest rejestr o nazwie CMU_HFBUSCLKEN0 (High Frequency Bus Clock Enable Register 0). Robimy podgląd jak ten rejestr wygląda od środka :



Fajnie, rejestr jest prosty , pola bitowe są ładnie i zrozumiale opisane. Czego chcieć więcej. Nas interesuje konkretnie 3 bit w tym rejestrze nazwany GPIO i opisany jako Set to enable the clock for GPIO. No dobrze wiemy w jakim rejestrze i jaki bit musimy ustawić teraz jak to fizycznie zrobić w programie.
W pierwszej kolejności musimy poszukać pliku nagłówkowego w którym producent opisał nasz rejestr bo musimy jakoś się do niego odwołać w programie. Poniżej ścieżka do tego pliku i jego zawartość :



W sumie są dwa pliki nagłówkowe gdzie są opisane rejestry, jednym z nich jest plik nagłówkowyo o nazwie efm32tg11b120f128gq48.h a drugi plik o nazwie efm32tg11b_cmu.h. Obojętnie , którym plikiem się posłużymy w naszych poszukiwaniach znajdziemy to samo. A szukamy opisu dla rejestru CMU_HFBUSCLKEN0. Na zdjęciu powyżej widać zawartość tego opisu przyporządkowaną do naszego rejestru. Są to jak widzimy definicje, nas interesuje to co ma na końcu GPIO a w szczególności dwie definicje :

/**< General purpose Input/Output Clock Enable */ 
#define CMU_HFBUSCLKEN0_GPIO                  (0x1UL << 2)   

 /**< Bit mask for CMU_GPIO */
 #define _CMU_HFBUSCLKEN0_GPIO_MASK  0x4UL

Pierwsza definicja to przesunięcie dwa razy jedynki  w lewo w efekcie otrzymamy bitowo wyrażenie .....100
Druga definicja reprezentuje wartość  z ustawionym trzecim bitem  czyli 4.

Teraz musimy sobie przypomnieć szablon w/g ,którego producent naszego procka zaleca  dokonywać ustawienia konkretnego bitu rejestru na 1 :


W przypadku ustawienia konkretnego bitu rejestru na 1 :

register = (register OR mask)
gdzie register = module_name -> register

Spróbujmy zatem zbudować fizycznie wyrażenie, które dokona ustawienia bitu GPIO w rejestrze CMU_HFBUSCLKEN0 posługując się szablonem zalecanym przez producenta:

CMU->HFBUSCLKEN0 = CMU->HFBUSCLKEN0 | _CMU_HFBUSCLKEN0_GPIO_MASK ; // enable clock for GPIO module 

Wykorzystując konwencje języka C możemy ten zapis skrócić do postaci jak poniżej :

CMU->HFBUSCLKEN0 |= _CMU_HFBUSCLKEN0_GPIO_MASK ; // enable clock for GPIO module

dla porównania jak to samo uzyskamy za pomocą biblioteki emlib :

CMU_ClockEnable(cmuClock_GPIO ,1); //gdzie 1 oznacza ON


Powyżej pokazałem drogę jaką mniej więcej trzeba się poruszać w programowaniu "natywnym" ,trochę jak widać jest przy tym roboty, bo trzeba szperać w dokumentacji w plikach nagłówkowych etc. Ale to podejście zapremiuje w przyszłości bo to nauczy nasz mózg jak szybko rozgryźć inny model procka z rdzeniem ARM a w sumie i nie tylko. Dla porównania podejście wygodne w postaci używania biblioteki typu HAL ograniczy wybitnie pracę naszego mózgu. 

Ponieważ naszym celem jest zamruganie diodą LED. Musimy zatem rozpoznać aspekt związany ze sterowaniem pinów. Na mojej płytce developerskiej dla MCU Silicona mamy diodę LED podłączoną do pinu nr 8 na porcie PC.

Najbardziej przydatnym dokumentem do rozpracowania zagadnień związanych z GPIO  jest  EFM32TG11 - AN0012: General Purpose Input Output
Podstawowe rejestry jakie mamy do dyspozycji odnośnie sterowania pinami są nastepujące :

GPIO_Px_MODEL (for port pins 0 to 7) - tu ustawiamy tryby pracy pinów
GPIO_Px_MODEH (for port pins 8 to 15) - tu ustawiamy tryby pracy pinów
GPIO_Px_DOUT - tu ustawimy stan pinu ustawionego jako wyjście
- GPIO_Px_CTRL - mniej istotne dla nas obecnie aspekty konfiguracyjne
- GPIO_Px_DOUTTGL - tu mamy możliwość togglowania pinem
- GPIO_Px_DIN - tu odczytamy stan pinu ustawionego jako wejście


Wszystkie rejestry dostępne w module GPIO są ujęte w RM na stronie 1132
Mamy tu jednak małą dezinformację, z tabelki wynika , że np jeśli chcemy odwołać się do rejestru GPIO_PC_MODEH (nasz pin  PC8 jest opisany w tym rejestrze) to z tego co wiemy dotychczas odwołanie się do tego rejestru powinno wyglądać tak :


CMU->GPIO_PC_MODEH = ...........

niestety taki zapis nie zadziała i jest to dosyć spora zmyłka. Sytuację ratuje dokument AN0012 i przykład zawarty wniej na stronie 5, z którego dowiemy się jak w tym przypadku odwoływać się do np. rejstru GPIO_PC_MODEH . A robimy to w ten sposób :

 CMU->P[2].MODEH = ...........

Zastosowano tu tablicę struktur P[x] gdzie x oznacza odpowiednio P[0] - struktura dla port A, P[1] - struktura dla port B, P[2] - struktura dla port C itd.... Trochę to mniej czytelnie wygląda niż jawna nazwa portu typu PA, PB w nazwie rejestru ale ciort tam, ważne , że doszliśmy o co biega. Jeśli dokładnie chcemy poznać drogę jak powstaje powyższy zapis to znajdziemy te informację w pliku nagłówkowym : efm32tg11b120f128gq48.h i efm32tg11b_gpio.h

Przyjrzyjmy się jak z bliska wygląda rejestr GPIO_Px_MODEH :


Poszczególne piny na porcie Px opisane są jako MODEx. Nas konkretnie interesuje MODE8 bo to reprezentuje nasz pin nr 8 na porcie PC.
W rejestrze tym ustawiamy tryb pracy pinu, jak widzimy jest ich 16 trochę sporawo. My chcemy aby nasz pin był wyjściem i tylko tyle. Po długim doktoryzowaniu się z funkcji poszczególnych trybów dochodzimy do wniosku , że tryb PUSHPULL będzie tym czego szukamy. Pole bitowe opisujące tryb dla poszczególnych MODEx ma rozmiar 4 bitów. W pole te musimy wpisać cyferkę oznaczającą konkretny tryb. Dla ustawienia trybu PUSHPULL musimy wpisać cyfrę 4.

Zatem podsumujmy , chcemy w rejestrze GPIO_PC_MODEH dokonać wpisu ustawiającego pin PC8 w tryb PUSHPULL (OUTPUT) czyli w pole MODE8 rejestru trzeba wpisać wartość 4. Żeby nie przedłużać nadmiernie części opisowej poniżej pokazuję prawidłowy zapis do rejestru :

/*Config pin PC9 PUSHPULL mode*/ 
GPIO->P[2].MODEH = (GPIO->P[2].MODEH & (~ _GPIO_P_MODEH_MODE8_MASK)) |  GPIO_P_MODEH_MODE8_PUSHPULL ;

Opisy rejestrów , które nas interesują znajdziemy w pliku nagłówkowym efm32tg11b_gpio.h :


Rozbijmy ten zapis na atomy  :
 
GPIO->P[2].MODEH  reprezentuje rejestr GPIO_PC_MODEH

_GPIO_P_MODEH_MODE8_MASK ustawia wartość 0xF inaczej 00...1111

 GPIO_P_MODEH_MODE8_PUSHPULL ustawia wartość 4<<0

Co się dzieje fizycznie w wyrażeniu na bitach, w pierwszej kolejności zawartość rejestru GPIO_PC_MODEH jest mnożona przez zanegowaną maskę w efekcie otrzymujemy wyrażenie gdzie wszystkie bity z wyjątkiem pola 4 bitowego  są mnożone przez 1 co w efekcie powoduje , że nie zmieniamy tych wartości w rejestrze jakie były takie pozostaną, nasze 4 bity na których będziemy dokonywać modyfikacji w rejestrze są zerowane (mnożone przez 0) i do tak utworzonego wyrażenia dodawana jest wartość 4.
Takie operacje bitowe zapewniają, że zmieniamy w rejestrze tylko te bity , które nas interesują bez wpływu na pozostałe bity rejestru. Proponuję  sobie to rozpisać i zrozumieć, bo operacje bitowe to fundament programowania na rejestrach.

Do migania diodą LED wykorzystam "tykacza" zaimplementowanego w każdym rdzeniu ARM czyli SysTick. Jest to taki niezależny dodatek do puli Timerów występujących w MCU. Dzięki niemu nie będzie trzeba korzystać z ohydnych delay-i. Do zmiany stanu pinu wykorzystam rejestr GPIO_PC_DOUTTGL , wpisując 1 w pozycję nr 8 reprezentującą nasz pin PC8 uzyskamy efekt toglowania stanem pinu. Zapis do tego rejestru i ustawienie bitu nr 8 wygląda tak :

GPIO->P[2].DOUTTGL |= (1<<8) ; 

Ponownie widzimy kolejny przykład wykorzystania przesunięcia bitowego, bez tego ani rusz w programowaniu "natywnym" .

Poniżej kompletny kod z przykładem użycia programowania "natywnego" :


Jedynym wyjątkiem w tym kodzie jest ustawienie zegara wewnętrznego RC na 26MHz, bo tu skorzystaliśmy z funkcji bibliotecznej emlib.

Co robi program ?
1. Ustawia zegar RC na 26MHz
2. Włączamy zegar dla modułu GPIO
3. Ustawiamy pin PC8 w tryb PUSHPULL (OUTPUT)
4. Ustawiamy SysTick-a aby co 0.5 s generował przerwanie po zliczeniu do 0. 
5. Funkcja obsługi przerwania w której toglujemy pinem PC8.

Teraz zerkniemy jak nasze mruganie diodą LED wyglądałoby z użyciem biblioteki emlib. Tu już sprawa będzie bardzo prosta, nasze działania poznawcze ograniczą się do wyszukiwania funkcji w dokumentacji interaktywnej biblioteki emlib czytania opisów i tyle. Jest to droga bardzo kusząca i jest to bardzo duży skrót do rozpoczęcia przygody z naszym MCU.

Poniżej program do migania diodą LED na pinie PC8 z użyciem biblioteki emlib Silicona. Nad funkcjami emlib-a zakomentowana postać zapisu w trybie "natywnym" czyli wpisy bezpośrednie do rejestru. Napisanie programu z użyciem biblioteki emlib zajęło mi dosłownie , krótką chwilę. Poruszanie się po interaktywnej dokumentacji tej biblioteki jest przyznam przyjemnością.




Dla porównania zerknijmy ile trzeba naklepać kodu w przypadku STM32 za pomocą ichniej biblioteki HAL aby skonfigurować pin :

// Enable clock for GPIOC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

// Configure PC8 as push-pull output
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStruct);

Jak widzimy HAL od STM-a jest mało zachęcający i wygląda na trochę przekombinowany. To co robimy w emlib Silicona jedną funkcją w STM32 rozbite jest na kilka wpisów w struktury. Zdecydowanie przyjemniej się używa emliba niż HAL-a STM-owego.


Pozdrawiam
picmajster.blog@gmail.com

Linki :


Brak komentarzy:

Prześlij komentarz