Wprowadzenie
Pamiętam, jak kilkanaście lat temu pewien doświadczony elektronik-programista powiedział mi, że – cytuję – „w przypadku STM32 wystawienie jedynki na port wymaga ukończenia wyższych studiów technicznych”. Choć niewątpliwie w zdaniu tym jest pewna przesada, to jednak wbrew pozorom nie jest ono pozbawione ziarnka prawdy. Faktycznie: skonfigurowanie nawet najprostszych, podstawowych peryferiów nowoczesnego procesora ARM jest zadaniem dalece bardziej złożonym niż wykonanie tego samego zadania np. na poczciwym układzie ATtiny czy MSP430. Niesłusznie byłoby jednak zrzucać całą winę za złożoność programowania 32-bitowców na karb samego tylko rdzenia. Dużo większe znaczenie ma bowiem po prostu konstrukcja wbudowanych bloków peryferyjnych, które znajdujemy w „dużych” mikrokontrolerach – a ta zależy już tylko od decyzji poszczególnych producentów, opracowujących procesory z rdzeniami na licencji ARM.
Wśród programistów pokutuje zatem przekonanie, że programowanie 32-bitowców w rejestrach okazuje się niezwykle trudne, a sam kiedyś spotkałem się z niedowierzaniem, że w ogóle jest to możliwe. Do pisania kodu na najniższym możliwym poziomie (zaraz po asemblerze) zniechęca także powszechne stwierdzenie, że taki sposób pracy byłby zwyczajnie nieopłacalny, ponieważ kod napisany w rejestrach nieszczególnie nadaje się do przenoszenia pomiędzy różnymi rodzinami procesorów tego samego producenta. Po części istotnie tak jest – osoby znające procesory STM32 od podszewki z pewnością spotkały się już z (nierzadko zaskakującymi) różnicami pomiędzy określonymi grupami układów, np. STM32L1, STM32F4, STM32F0 czy też STM32F3. Co ciekawe, bliższe zapoznanie się z obszerną dokumentacją pokazuje dobitnie, że o ile niektóre peryferia są niemal identyczne w większości linii produktowych, o tyle dywersyfikacja nazewnictwa i funkcjonalności rejestrów w innych blokach sprzętowych potrafi przyprawić o ból głowy. Mało tego – producentowi zdarza się (choć rzecz jasna nie w złej wierze) zastawiać na programistów niskopoziomowych pewne pułapki, np. poprzez przesunięcie bitów lub nawet fragmentów pól bitowych w ramach rejestrów o tej samej nazwie i (w przybliżeniu) tożsamym przeznaczeniu.
Czy pisanie kodu w rejestrach ma zatem jakiś sens? Odpowiedź jak najbardziej brzmi TAK! W przypadku rodziny STM32C0 nie należy bowiem spodziewać się szczególnego zróżnicowania modeli układów, gdyż ich znacząca rozbudowa zanadto zbliżyłaby je do „mainstreamowych” linii produktowych, takich jak STM32F0 czy STM32L0. Jeżeli zatem ograniczymy nasz nowy sposób pracy nawet tylko do tej jednej grupy układów, to problemy z przenośnością kodu przestaną mieć znaczenie, zyskamy natomiast szereg innych benefitów. Jakich?
Oto najważniejsze zalety niskopoziomowego podejścia do programowania STM32 (i nie tylko).
- Wydajność obliczeniowa – kod pisany z użyciem gołych rejestrów może być wysoce zoptymalizowany pod względem szybkości wykonywania, co ma znaczenie szczególnie w przypadku procedur realizujących pewne zadania na granicy maksymalnej szybkości sprzętu. Przykładowo – jeżeli procesor musi z zawrotną prędkością obsługiwać krótkie sygnały impulsowe lub np. generować pewne sekwencje sygnałów wyjściowych, to niskopoziomowe programowanie będzie w takim przypadku optymalnym rozwiązaniem: łatwiejszym niż pisanie w asemblerze, a zarazem szybszym i bardziej przewidywalnym od tworzenia kodu np. z użyciem bibliotek HAL.
- Oszczędność pamięci – patrząc na współczesne portfolio procesorów ARM, można odnieść wrażenie, że ograniczenia pod względem dostępnej pamięci Flash stają się coraz mniej dotkliwe, nawet w przypadku rozbudowanych aplikacji. Nie należy jednak zapominać, że wśród mikrokontrolerów 32-bitowych zdarzają się małe, lekkie układy o pamięci na poziomie 16 kB, a nawet 8 kB (czyli zbliżonym do mniejszych przedstawicieli rodzin AVR bądź PIC). Przy tak ograniczonych zasobach sprzętowych oszczędność pamięci jest zaletą niepodważalną.
- Doskonała znajomość procesora – programując wyłącznie z użyciem bibliotek, można dość łatwo wpaść w pułapkę pewnych uproszczeń – biblioteki wiele zadań wykonują niejako za programistę, co sprawia, że często nie ma on potrzeby zaglądania do tego, co dzieje się pod maską wysokopoziomowych funkcji i struktur biblioteki HAL. Tymczasem pisanie kodu niskopoziomowego wymusza dokładne poznanie struktury określonych peryferiów (a także samego rdzenia) na poziomie nie tylko rejestrów, ale wręcz poszczególnych ich bitów. Programiści wyposażeni w tę cenną wiedzę potrafią znacznie lepiej radzić sobie z problemami, wynikającymi np. z braków w dokumentacji bibliotek czy też rozmaitych błędów – i to zarówno w strukturze API, jak i samego sprzętu (tzw. błędy w krzemie).
- Stuprocentowa przejrzystość kodu – kod napisany w rejestrach być może sam w sobie nie należy do najbardziej intuicyjnych dzieł programistycznych (chyba że zostanie odpowiednio opatrzony komentarzami, do czego gorąco zachęcamy!). Ma jednak pewną ogromną zaletę – nie ukrywa żadnych kruczków (np. potencjalnych błędów) pod kolejnymi warstwami abstrakcji i wywołaniami wielokrotnie zagnieżdżonych hierarchicznie funkcji, ułatwia zatem pełną kontrolę przepływu danych i sygnałów sterujących. Może to być istotny atut w przypadku systemów o krytycznym znaczeniu dla bezpieczeństwa, nie musimy bowiem bazować na bibliotekach, w przypadku których (z natury rzeczy) możemy nie mieć 100-procentowego przekonania co do ich stabilności oraz niezawodności.
Niniejszy kurs oparty został na procesorze z najnowszej serii STM32C0, dumnie promowanej przez firmę ST Microelectronics jako produkt, mający na celu zastąpienie (żeby nie powiedzieć – wyparcie z rynku) mikrokontrolerów 8-bitowych. I tutaj ciekawa dygresja – kilkanaście lat temu, gdy pierwsze układy STM32F1 dopiero rozkręcały się na rynku MCU, niemal równolegle z nimi marka ST promowała także własną rodzinę 8-bitowców – STM8S. Nie da się ukryć, że grupa ta nie zrobiła szczególnej furory w świecie systemów embedded, a dziś jej status handlowy to NRND (nierekomendowane do nowych projektów). W międzyczasie ST solidnie zainwestował w rozwój procesorów STM32, stając się jednym z potentatów na tym obszarze. Strategiczne posunięcie, jakim było opracowanie serii lekkich, niewielkich procesorów ARM w niewiarygodnie wręcz niskim przedziale cenowym (najtańsze układy mają kosztować przy zamówieniach hurtowych tylko 24 centy!), wydaje się więc strzałem w dziesiątkę. Firma ST Microelectronics nie kryje się zresztą za bardzo z chęcią wyparcia z rynku kultowych już mikrokontrolerów ATmega328 (stanowiących fundament ekosystemu Arduino), o czym świadczy oficjalny moduł, przeznaczony do… wpinania do podstawki DIL28 w miejsce oryginalnego procesora na Arduino Uno.
Trudno w tym momencie przewidzieć, czy wspomniane posunięcie marketingowe odniesie sukces – z dużą dozą prawdopodobieństwa można jednak stwierdzić, że nie odbędzie się to w takim zakresie, jakiego prawdopodobnie oczekują menedżerowie ST (najnowsze moduły z serii Arduino – np. Arduino Uno R4 – także bazują już wszak na ARM-ach, więc trudno będzie zdominować ten segment rynku). Na pewno jednak najnowsza gałąź drzewa genealogicznego rodziny STM32 znajdzie swoje miejsce w tysiącach aplikacji komercyjnych, zarówno tych prostych i niskobudżetowych (jako główne kontrolery), jak i w rozbudowanych systemach wieloprocesorowych (jako peryferyjne układy do realizacji prostszych zadań). Tym bardziej więc warto poznać bliżej te niezwykle ciekawe procesory.
Niniejszy kurs jest przeznaczony przede wszystkim dla:
- programistów doświadczonych w pracy z STM32 z użyciem bibliotek HAL lub LL, zainteresowanych rozwojem w zakresie niskopoziomowego tworzenia kodu,
- programistów mikrokontrolerów 8-bitowych, którzy dzięki naszym materiałom będą mogli w szybki i przystępny sposób przejść na nową, znacznie wydajniejszą platformę (nie zmieniając przy tym zanadto swoich przyzwyczajeń – tym bardziej że styl nazewnictwa funkcji i struktur, zastosowany zwłaszcza w bibliotekach HAL, może naprawdę przytłoczyć zwolenników czystego, schludnego i zwartego kodu źródłowego).