CMSIS — część 2

CMSIS — część 2
Razem głosów: 15 co stanowi: 97.33% całości.

imgres

Poznaliśmy już założenie i cel powstania standardu CMSIS i mimo iż wygląda to chaotycznie i trochę dziwacznie ,naprawdę ma wielki sens i wiele wnosi do programowania  dlatego teraz poznamy nieco więcej szczegółów  naszych bibliotek :)

W prawdzie dotyczy to wszystkich jak już wiemy mikrokontrolerów  opartych o  rdzeń ARM Cortex-M   ja tu dla ułatwienia po części  będę się się odnosił do popularnych na naszym rynku  STM32 . Jedziemy… choć muszę po przynudzać mam nadzieję  że  aż tak źle nie będzie :)

 


Struktura plików CMSIS:

W zasadzie wielkiej filozofii nie ma a nazwy plików znormalizowano tak że jednoznacznie się określają  np:

Jak widać nie jest to tak skomplikowane jak się wydaje i jest to tak skonstruowane że bez problemu można się odnaleźć. Dzięki takiemu podejściu właściwy kod aplikacji w zasadzie będzie zawierał tylko plik nagłówka najwyższego poziomu <device>.h , który niejako nie jawnie pociągnie za sobą wszystkie inne które będą niezbędne.

W zasadzie zależności w plikach nagłówkowych dla przykładowego STM32  możemy przedstawić diagramem. Pliki nagłówkowe stm32.h, core_cm3.h, system_stm32.h są częścią CMSIS  od relejsu  V1P0:

Przechwytywanie111

Dzięki takiemu podejściu  przykładowa konfiguracja  dla procesora z rdzeniem Cortex_M3 i Peryferiów może np wyglądać tak:

Oczywiście pozwoliłem sobie tutaj na małe uproszczenie , ale chciałbym zwrócić uwagę na fakt iż plik <device>.h  jest centralnym plikiem nagłówkowym dostarczanym przez producenta krzemu. Programista poniekąd  powinien go używać w kodzie źródłowym gdyż zawiera on opis danego mikrokontrolera  i jego peryferii , a tym samym ułatwia dostęp do urządzeń peryferyjnych  zawartych w specyfikacji  ARM dla Cortex_M. Oczywiście powinniśmy pamiętać ze Cortex_M3 np ma kilka opcjonalnych funkcji sprzętowych np.  MPU ,  Liczbę przerwań czy tez Priorytetów NVIC. Co widać w przykładzie wyżej. Sprzedawcy krzemu jak STM mogą stosować tu pewną dowolność implementacji. Na powyższym przykładzie widać też na przykładzie STM32 jak można realizować  4 z 8 możliwych bitów priorytetowych.

Macro priorytetowe __NVIC_PRIO_BITS  jest ustawione tutaj na 4.  Ponadto  STM32 nie oferuje  ochrony pamięci (MPU ProtectionUnit) dlatego makro __MPU_PRESENT  ustawiamy na 0. Dlatego też zobaczcie jak wygląda odpowiednia definicja  urządzenia na przykładzie LPC17xx z NXP.  Zawiera on też rdzeń ARM Cortex_M3 , ale NXP wiele rzeczy rozwiązała inaczej  np.  wdrożono 5 bitów priorytetu i ProtectionUnit dla MPU:

Teraz sami zobaczcie .. w zasadzie mamy wszystko tak samo zarówno dla LPC jak i STM. W obu przypadkach ustawiamy __Vendor_SysTickConfig  na domyślna wartość taktowania czyli 0.  Dopowiem w tym miejscu ze jeśli ustawimy makro na wartość 1  to funkcja SysTickConfig() w pliku cm3_core.h zostanie wyłączona, a w takim wypadku plik  <device>.h musi zawierać konkretną realizację tej funki dostarczaną przez producenta.


 

Narzędzia Independence:

CMSIS poniekąd można powiedzieć że istnieje w takiej  jakby trójwymiarowej przestrzeni łańcucha  –>  producent-urządzenie-narzędzia :)  Co ciekawe w celu usunięcia  jednego wymiaru (łańcuch narzędzi), wspólne pliki core_cm3.c i core_cm3.h zawierają wszystkie niezbędne narzędzia, deklaracje i definicje  co można zobrazować przykładem :

 

Jak więc widzicie pozostałe części CMSIS mogą teraz po prostu używać sobie makra __inline w celu zdefiniowania funkcji inline. Obecnie CMSIS wspiera 3 wiodące kompilatory C którymi są zapewne wam dobrze znane:

  1. ARM RealView (armcc)
  2. IAR EWARM (iccarm)
  3. GNU Compiler Collection  (GCC)

Z których ten ostatni nas najbardziej interesuje , ale jak widać takie dobranie kompilatorów poprzez bezpośrednie wsparcie w zasadzie pokrywa większą cześć narzędzi dla ARM.


 

MISRA-C

CMSIS poza definiowaniem API dla Cortex-M  oraz podstawowych wytycznych obsługi urządzenia i dostępu do urządzeń peryferyjnych  określa też wytyczne konwencji kodowania. W czym najważniejsze jest to ze CMSIS bazuje na zgodności z MISRA-C 2004, a to oznacza że każde rozszerzenie powinno być zgodne.

Czym więc jest ta dziwna MISRA-C ??  — Już wyjaśniam  :)  

MISRA-C jest zbiorem zasad bezpieczeństwa ustanowionych przez  „Motor Industry Software Reliability Association” , jest to stowarzyszenie powstałe dla języka programowania C.  Niestety utrzymanie zgodności z wytycznymi MISRA  jest trudne szczególnie podczas wdrażania oprogramowania na poziomie sterowników poszczególnych modułów funkcyjnych. Istnieją też wyjątki w stylu PCLint , które mogą być rozproszone w kodzie źródłowym. Trzeba być też świadomym narzędzi  „thatother”  np.  Pliki w EWARM IAR-a  mogą posiadać błędy wykonania flag, oczywiście każdy z takich wyjątków ma towarzyszący komentarz wyjaśniający , dlaczego jest to wyjątek.


Funkcje CPAL <Core Peripheral Access Layer>:

Wszystkie funkcje warstwy obwodu rdzenia  można powiedzieć że są typu „wklęsłego” to takie może abstrakcyjne stwierdzenie , ale chodzi o to że mogą one być wywoływane w różnych procedurach obsługi przerwań (ISR). Ponadto funkcje CPAL są nie blokujące*  w tym sensie że nie zawierają pętli opóźniających.   Oczywiście większość funkcji  CPAL została wdrożona w pliku nagłówkowym core_cm3.h jako statycznie funkcje inline. Pozwala to kompilatorowi dokonać optymalizacje połączeń funkcyjnych poprzez umieszczanie instrukcji, które tworzą z kolei funkcję z innym kodem z którego funkcja odziedziczyła nazwę :) <trochę chyba zamotałem ,  ale się wyjaśni wszystko z czasem>


 

(*)  Z zasady „non blocking” są zwolnione „bariery pamięci”  pomimo iż one mogą wprowadzać  opóźnienia w działaniu procesora na  kilka cykli. Wynika to ze specyfikacji dostępu do pamięci. 


 

Interrupt Service Routines (ISR):

Tak wiem że wiecie co to ISR , ale mimo wszystko chciałbym wyjaśnić tak króciutko obsługę wyjątków.   Wyjątki zwykle zawierają w nazwie przedrostek  _Handler, podczas gdy przerwania (zewnętrzne)  _IRQHandler . Oczywiście nie jest to domyślne dla każdej procedury obsługi  przerwania, zwłaszcza tego które wykonuje się w nieskończonej pętli. Konfiguracja specyficznych narzędzi musi być na tyle pewna, że domyślnie obsługa będzie nie jako wykorzystana awaryjnie , pod warunkiem że nie jest wywołana przez użytkownika jeśli taka konieczność zaistnieje**.  Biorąc pod uwagę fakt iż NVIC w Cortex-M  zapewnia  tablice i stringi konfiguracji priorytetów przerwań dla źródeł typu en-/disable wyliczanego typu IRQn_t z elementem dla każdego wystąpienia przerwania / wyjątku z przedrostkiem _IRQn  co jest konieczne dla określenia każdego przerwania (<device>.h).

 

UWAGA !!!

System nazw jest wspólny dla wszystkich urządzeń i nie może być zmieniany.


 

(**)  w EWARM i RVCT armcc jest taka deklaracja  __weak , w GCC zaś __attribute__((weak)).  [WEAK]  jest eksportem  RVCT/armasm


 

Zobaczmy więc ogólną cześć pliku <device.h>

Jak widzicie wszystkie widoczne handlery mają zanegowane numery wirtualnych slotów po to by można je było odróżnić w funkcjach obsługi systemu i funkcjach obsługi zewnętrznych przerwań. Warto wiedzieć że Zewnętrzne przerwania zaczynają się od indexu = 0 :)

 


 

 

 

Inne konwencje i zalecenia kodowania:

CMSIS zaleca jeszcze stosowanie kilku regulacji dotyczących stosowania identyfikatorów i komentowania kodu.

..:: IDENTYFIKATORY ::..

—– identyfikacja  nazwy RDZENIA i rejestrów peryferyjnych  oraz instrukcji procesora:

np:  NVIC->AIRCR, GPIOB, LDMIAEQ

—– stosowanie „CamekCase” (czyli połączenie dużych i małych liter) w nazwach                    urządzeń peryferyjnych , funkcji , przerwań … itd

np: SysTickConfig(),DebugMonitor_IRQn

zauważcie iż pozwala to jednoznacznie określić z czym mamy doczynienia :)

—– stosowanie przedrostków (<nazwa>_) do identyfikacji funkcji, które należą do                      poszczególnych urządzeń peryferyjnych

np: ITM_SendChar(),NVIC_SystemReset()

Dzięki takiemu systemowi identyfikatorów w CMSIS pisanie kodu staje się łatwe , a czytelność kodu nawet z ograniczonymi komentarzami jest wysoka , bowiem łatwo i precyzyjnie mamy nakreślone co dotyczy czego i jakie funkcje spełnia w kodzie.

..:: Komentarze ::..

Nie jest nowiną zapewne że CMSIS używa komentarzy w stylu Doxygen dla wszystkich definicji i tym samym zachęca programistów do tego samego. Uszczegóławiając komentarz np dla definicji funkcji powinien zawierać przynajmniej:

  • jedną linię krótkiego przeglądu funkcji . (Tag: @brief)
  • szczegółowe objaśnienie parametrów. (Tag: @param)
  • szczegółowe informacje o zwracanych wartościach. (Tag: @return)
  • szczegółowy opis funkcji.

Wiem że to wygląda na dość zagmatwane , ale poniższy przykład powinien wyjaśnić o co chodzi dokładnie a przy okazji ukażą się korzyści jakie niesie za sobą ta regulacja:

Prawda że dzięki takiemu komentarzowi wszystko jest jasne w działaniu funkcji ?? Dlatego też sam namawiam do takiego stylu komentarzy. Ponadto znaczniki mogą być przetwarzane przez Doxy w celu tworzenia auto_dokumentacji.  Składnia znaczników Doxygen jest minimalistyczna bardzo i nie pogarsza czytelności kodu źródłowego. A całość znajdziecie w dokumentacji Doxygena.  Oczywiście nic na siłę i jako alternatywę dla Doxy można stosować standardowe dla języka C bloki komentarzowe /**/. CMSIS nie zabrania tez stosowania komentarzy linii //, ale Jeśli chodzi o zachowanie zgodności z MISRA należy pamiętać, że specyfikacja MISRA-C 2004  nie pozwala na stosowanie komentarzy linii kodu // zgodnie z zasadą 2.2.


Typy DANYCH:

Wszystkie typy danych do jakich CMSIS się odwołuje są określone w pliku nagłówkowym stdint.h.  Natomiast struktury danych dla rejestrów podstawowych są zdefiniowane w nagłówku core_cm3.h wraz z makrami   kwalifikującymi rejestry zgodnie z ich uprawnieniami dostępu. Uzasadnieniem dla takiego sposobu jest to że narzędzia mogą być w stanie wydobyć automatycznie te informacje w celu debugowania.

 


 

Debugowanie:

Wspólnym podczas rozwoju oprogramowania jest sposób przekazywania informacji  debugowych na jakieś wyjście terminalowe :)  Tak terminalowe bowiem, stosowanie Wyświetlaczy textowych czy graficznych np nie może być brane pod uwagę z faktu częstego „nie posiadania pod ręką” czy też z powodu nie używania w projekcie tego rozwiązania interakcji z użytkownikiem. I dlatego dopuszczamy w sumie 2 możliwości:

  1.  Użycie jednego z obecnych w strukturze UARTU i połączenia terminalowego. Trzeba jednak mieć na uwadze iż wszystkie dostępne UARTy mogą być używane lub dostęp do UART może być nie możliwy ze względu na podział funkcji pinów u układzie np: używamy funkcji alternatywnych dostępnych na pinie.
  2. Użycie mechanizmu semihosting.                                                                                  Jednak mogą tu wystąpić problemy np:  znaczne obciążenie procesora przez oprogramowanie docelowe, różnice w obsłudze urządzeń przez dostępne narzędzia programowe i środowiska sprzętowe, a także wpływ jaki może mieć połączenie debugowe na zachowanie ustroju sprzętowo-programowego.

W przypadku rdzenia Cortex-M3 jako preferowaną metodę stosuje się Instrumentację śledzenia ITM <Instrumentation Trace Macrocell >, która jest częścią  fizyczną procesora, a tym samym zawsze obecną.

UWAGA!

—- ITM jest zawsze obecny w rdzeniach Cortex-M3 Rev1 natomiast w Cortex_M3 rev2 jest już funkcją opcjonalną. Żeby nie było  że nie wspomniałem :)

Serial Wire Viewer (SWV) może odbierać dane debugowe poprzez ITM  na pinie SWO (Serial Wire Output) będącym notabene takim pinem debugowym :)

Narzędzia ITM mogą posiadać 32 kanały danych ogólnego przeznaczenia. CMSIS niejako opiera się na „wierzchu” i stwierdza czy kanał 0 jest stosowany czy nie jako wyjście terminalowe wraz z funkcją ITM_SendChar() , która może być użyta jako sterownik niskiego poziomu zapewniający funkcjonalność  podobną do  funkcji printf . Kanał 31 jest zarezerwowany dla „świadomego” debugowania w systemie, co oznacza że jądro może go używać do przesyłania danych z jądra, które mogą być następnie interpretowane przez narzędzia debugowe.

Dzięki takiej normalizacji producenci narzędzi mają znacznie ułatwione wdrażanie konkretnych rozwiązań wspomagających debugowanie oprogramowania w fazie tworzenia i prototypowania co znacznie przyspiesza wyszukiwanie i eliminację błędów a to wpływa na redukcje kosztów powstawania rozwiązań konsumenckich.

Ciekawą cechą jest emulacja terminala na podstawie danych otrzymywanych przez itm na kanale 0, dzięki czemu Programiści mogą polegać na tej funkcji np w celu uzyskania zrzutu informacji o stanie ustroju bez konieczności konfigurowania UART-a i stosowania zewnętrznych emulatorów terminali. (opiszę działanie  i używanie przy okazji).

I to by było na tyle w tej części. Następnym razem już podziałamy na przykładach :)

 

 

 

 

 

 

 

Podziel się na:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay