Programowanie mikrokontrolerów - o czym należy wiedzieć na początek?

Mikrokontrolery to komponenty elektroniczne, którym udało się zrobić rewolucję w świecie elektroniki. Aby w pełni korzystać z ich możliwości, należy umieć je odpowiednio zaprogramować - bez odpowiednio sporządzonej listy zadań nie potrafią zrobić nic. Jak się do tego zabrać i o czym warto wiedzieć?

Co to jest mikrokontroler?

Na początku warto zastanowić się, czym jest mikrokontroler i dlaczego jest dla nas aż tak ciekawy. Otóż jest nim układ scalony, który zawiera w środku wiele różnych struktur znajdujących się na jednym kawałku krzemu. Wśród nich jest zarówno pamięć, arytmetyczna jednostka logiczna (ALU), porty wejścia/wyjścia, interfejsy komunikacyjne oraz wiele innych użytecznych rzeczy.

Siła mikrokontrolerów tkwi w braku konkretnego zastosowania. Możemy je konfigurować na nieskończenie wiele sposobów, a to wszystko za sprawą możliwości ich programowania. W nieulotnej pamięci programu znajduje się raz wgrana lista rozkazów, według której działa ten podzespół. Mikrokontroler odczytuje je kolejno i wykonuje w ściśle określonej kolejności.

Mikrokontrolery znajdziemy dzisiaj dosłownie wszędzie - od lampek rowerowych, sterowników piekarników i zmywarek począwszy, przez ramki cyfrowe i centrale alarmowe, po telefony komórkowe i telewizory. Oczywiście, w każdym z tych urządzeń znajdziemy inny mikrokontroler, lecz każdy z nich łapie się pod tę definicję. To małe komputery - tak małe, że zostały wepchnięte na mikroskopijnej wielkości strukturę. 

Peryferia

Wbrew pozorom, peryferia mikrokontrolera wcale nie znajdują się poza nim, lecz są wbudowane w sam układ scalony. To różnego rodzaju bloki, które potrafią pełnić określone funkcje. Do najczęściej spotykanych układów peryferyjnych należą:

  • liczniki, zdolne odmierzać czas i liczyć impulsy,
  • przetworniki przekształcające napięcie na postać cyfrową i odwrotnie,
  • interfejsy komunikacyjne (UART, SPI, I2C i wiele innych),
  • komparatory i wzmacniacze.

Producenci wytwarzają zarówno mikrokontrolery ogólnego przeznaczenia, jak i bardziej specjalizowane - na przykład wyposażone w Bluetooth czy WiFi albo interfejsy komunikacyjne do wyświetlaczy graficznych czy magistrali USB. Na potrzeby motoryzacji są produkowane układy wyposażone w obsługę CAN. Do układów kontrolujących silniki bezszczotkowe (BLDC) również są odpowiednie mikrokontrolery. Po prostu każdy znajdzie coś dla siebie.

Przerwania

To, co jeszcze wyróżnia mikrokontrolery na tle innych układów, jest możliwość obsługi tak zwanych przerwań. Wszystkie instrukcje są wykonywane w głównej pętli programu, działającej sekwencyjnie i nieskończenie długo. Ale jej wykonywanie jest czasami przerywane - stąd nazwa - w zupełnie nieoczekiwanych momentach, a cały układ zajmuje się wtedy obsługą danego zdarzenia. 

Przerwania mogą pochodzić zarówno ze świata zewnętrznego (naciśnięcie przycisku, odebranie transmisji radiowej, pojawienie się określonego napięcia), jak i z samego mikrokontrolera. Mogą je generować liczne układy peryferyjne, zawarte w strukturze układu scalonego, jak liczniki, przetworniki, interfejsy komunikacyjne i tak dalej.

Prawidłowe użycie przerwań pozwala na pisanie efektywniejszych programów - wykonujących się zdecydowanie szybciej. Jest to, niestety, znacznie bardziej żmudne, a sam kod trudniejszy do interpretacji przez osoby początkujące. Dlatego na początku nauki z reguły pomija się system przerwań (a potrafi on być naprawdę mocno rozbudowany) i wszystko wykonuje sekwencyjnie, w pętli głównej. Dopiero później zaczyna się odkrywać jego tajniki.

Struktura mikrokontrolera

Uproszczony układ połączeń znajduje się na poniższym rysunku. CPU to Central Processing Unit, czyli jednostka odczytująca rozkazy z pamięci i koordynująca działanie wszystkich bloków. 

Charakterystycznym elementem dla układów w architekturze harwardzkiej jest rozdzielenie pamięci danych i pamięci rozkazów. Dodatkowo tutaj jest widoczny jeszcze jeden podział: na szynę danych i szynę adresową. Pierwsza służy do wymiany informacji między wszystkimi blokami, lecz kierunkiem przepływu i tak zarządza CPU. Natomiast szyna adresowa służy do zaadresowania przez CPU bloku, który ma nadawać dane na szynę adresową oraz tego, który ma je odbierać.

Wszystkie operacje odbywają się synchronicznie, czyli w takt sygnału zegarowego, który otrzymuje każdy z bloków. Może on pochodzić z zewnątrz lub być generowany przez sam układ. Niemal każdy współczesny mikrokontroler ma do wyboru kilka źródeł sygnału zegarowego, a niektóre z nich mogą pracować równocześnie i służyć do taktowania różnych peryferiów. Na przykład, oddzielny rezonator kwarcowy może taktować licznik odmierzający czas, a precyzyjny generator sterować próbkowaniem przez przetwornik analogowo-cyfrowy.

Języki programowania

Pisanie programów na mikrokontrolery odbywa się w ustandaryzowany sposób. Służą do tego języki programowania, które pełnią funkcję komend, poleceń i odpowiednich instrukcji wyrażoną w sposób zrozumiały dla ludzi

Po napisaniu programu, kod jest kompilowany przez kompilator, który ewentualnie sygnalizuje różnorakie błędy. Jeżeli w kodzie nie ma już żadnych usterek, efektem działania kompilatora jest plik binarny zawierający listę rozkazów w postaci binarnej, zrozumiałej dla samego mikrokontrolera.

Na przestrzeni lat powstało wiele różnych języków programowania. Niektóre z nich są dzisiaj na wymarciu, inne powstały jako zwykły dowcip, jeszcze inne dopiero się rozwijają lub znajduje się dla nich coraz to nowe zastosowania. Oto kilka najpopularniejszych języków programowania mikrokontrolerów.

Asembler

Ten sposób programowania jest najbardziej efektywny i najtrudniejszy zarazem. Jest językiem niskiego poziomu co oznacza, że pisząc program operujemy wprost na poszczególnych rejestrach mikrokontrolera i odwołujemy się bezpośrednio do jego zasobów. Charakterystycznym elementem asemblera są mnemoniki, czyli kilkuliterowe wyrażenia oznaczające daną czynność do wykonania.

Każdy mikrokontroler ma swój asembler, inną architekturę oraz inną organizację pamięci i przestrzeni adresowej. To oznacza, że chcąc użyć innego układu, trzeba się uczyć na nowo jego budowy. 

Wbrew tym niedogodnościom, asembler ma wielu zwolenników na całym świecie, ponieważ pozwala pisać programy zajmujące mało miejsca w pamięci oraz realizujące się możliwie szybko. Dlatego często są na nim pisane jedynie krótkie wstawki w dłuższym kodzie, który jest już tworzony w języku wyższego poziomu.

Przekształcenie kodu asemblera na maszynowy nazywa się asemblacją.

C

To najpopularniejszy język wysokiego poziomu, jaki znajdziemy na mikrokontrolery. Zawiera już sporą dozę abstrakcji, czyli nie operujemy wprost na rejestrach, a możemy skupić się bardziej na obsłudze poszczególnych zasobów. Na przykład ma zaimplementowane działania matematyczne (dodawanie, odejmowanie, mnożenie itd.) na różnych typach zmiennych, które służą do przechowywania danych.

Kod napisany w C jest kompilowany najczęściej dwuetapowo. Na początku zostaje przekształcony na asembler, a dopiero potem dokonuje się jego asemblacji. W ten sposób łatwiej programistom pisać kolejne funkcje danego języka oraz udoskonalać te już istniejące.

Wielką zaletą języka C jest jego powszechność, ponieważ spotkać go można również na zwykłych komputerach. Poza tym, jego składnia jest tak skonstruowana, że do zrozumienia większości kodu wystarczy podstawowa znajomość języka angielskiego. Dlatego warto go znać choćby w stopniu podstawowym, nawet jeżeli planujemy uczyć się czegoś innego.

Ten język powstał na początku lat siedemdziesiątych, lecz jest nadal rozwijany. Doczekał się wielu modyfikacji, a najpopularniejsze z nich to C++ czy C#. To kolejny powód, dla którego warto go znać.

Programowanie mikrokontrolerów w języku C nie zwalnia od choćby podstawowej znajomości architektury danego układu, ale programy można znacznie łatwiej przenosić między różnymi rodzinami. W większości wypadków modyfikacje ograniczają się do zmiany nazw charakterystycznych rejestrów i adresów, do których się odwołujemy.

BASCOM

Ten język wysokiego poziomu powstał jako rozwinięcie języka Pascal dla mikrokontrolerów. Został stworzony na układy z rodziny AVR i 8051, więc obszar jego zastosowań jest dosyć ograniczony. W odróżnieniu od poprzednich, środowisko do programowania w tym języku jest płatne, a darmowa wersja testowa pozwala na pisanie jedynie prostych programów.

W odróżnieniu od C, BASCOM nie wymaga znajomości architektury mikrokontrolera, który stosujemy. Ma to pewne wady, a najpoważniejszymi z nich są objętościowe kody wynikowe (zajmują dużo pamięci programu) oraz ich powolne wykonywanie. Te same programy napisane w C zajmują mniej miejsca i wykonują się szybciej. Przedsiębiorstwa informatyczne, poszukujące programistów mikrokontrolerów, bardzo rzadko pracują na BASCOM.

Z wyżej wymienionych powodów, ten język, pomimo swojej prostoty, nie jest polecany osobom początkującym, które chcą rozpocząć naukę programowania a potem rozwijać swoją pasję. Konieczność zakupu środowiska, niewielka liczba obsługiwanych układów, trudności w pisaniu zoptymalizowanych programów to jego główne wady.

Rodzina mikrokontrolerów

Aby programować, trzeba również wiedzieć, co chcemy obsłużyć. Każda firma produkująca mikrokontrolery ma swoją rodzinę układów, która różni się od innych.

AVR

To najpopularniejsza dzisiaj grupa mikrokontrolerów, którą wprowadziła na rynek firma Atmel, wykupiona później przez Microchip. Ma 8-bitową szynę danych, co jest wystarczające w wielu prostych zastosowaniach. Układy z tej rodziny są przede wszystkim tanie i łatwo dostępne, a na ich temat powstało wiele opracowań.

Pomimo prostoty, są stosowane w wielu urządzeniach produkowanych seryjnie, ponieważ są bardzo tanie. Realizacja sygnalizatorów, małych sterowników czy układów zasilania jest możliwa właśnie przy ich pomocy. Osoby początkujące cenią je również ze względu na występowanie w obudowach przewlekanych (THT), a nie tylko montowanych powierzchniowo (SMD).

W dzisiejszym świecie te układy nieco “trącą myszką”, chociaż są nadal udoskonalane i stosowane również w nowych urządzeniach. Przykładem platformy, która je wykorzystuje, jest Arduino. Wiele płytek Arduino wykorzystuje właśnie mikrokontrolery AVR.

W rodzinie AVR możemy znaleźć dwie główne serie: tinyAVR (układy ATtiny) oraz megaAVR (układy ATmega). Różnią się między sobą liczbą i możliwościami peryferiów, wielkością dostępnej pamięci Flash, RAM i EEPROM oraz maksymalną częstotliwością zegara. Na przestrzeni lat powstała również seria XMEGA (z 16-bitową szyną danych) i AVR32 (układy 32-bitowe). Nie znajdują dzisiaj szerszego zastosowania.

Do układów ATtiny i ATmega (czyli najpopularniejszych) mamy do dyspozycji wiele darmowych środowisk programistycznych do pisania w asemblerze i C. Bazują na - również darmowym - kompilatorze AVR-GCC, który był przez lata rozwijany i obecnie osiągnął stadium niemal pozbawionego błędów

PIC

Ta rodzina jest od wielu lat rozwijana przez firmę Microchip. Początkowo w ofercie były tylko układy 8-bitowe, potem powstały również z 16- i 32-bitową szyną danych. Podobnie jak AVR, występują w obudowach THT i SMD, jednak nie stały się tak popularne jak one.

ARM

To nie jest rodzina, lecz rodzaj architektury. W niej produkowane są nowoczesne mikrokontrolery 32- i 64-bitowe. Jedną z rodzin układów wykonanych właśnie w tej architekturze jest STM32 od firmy ST, choć jest ich znacznie więcej. Wiele osób wybiera je na pierwsze spotkanie z układami właśnie w tej architekturze, a to za sprawą dobrze opracowanych not katalogowych i wielu źródeł informacji oraz publikacji na ich temat. 

Na standardowym wyposażeniu układów STM32 są rzeczy, których w niewielkich układach AVR próżno szukać. Można do nich zaliczyć DMA (pozwalające na bezpośredni transfer danych między peryferiami lub blokami pamięci, bez angażowania rdzenia układu) i NVIC (rozbudowany kontroler przerwań, w którym można nadać im różne priorytety).

Inni producenci też mają swoje rodziny mikrokontrolerów z rdzeniem ARM. Są to, między innymi:

  • LPC od firmy NXP (Philips),
  • MSP od Texas Instruments,
  • MAX od Maxim Integrated

Coraz większa liczba elektroników sięga od razu po układy w architekturze ARM ze względu na jej przewagę nad innymi. Układy wykonane na tym rdzeniu są bardzo efektywne (wykonują wiele działań w pojedynczym cyklu maszynowym) oraz energooszczędne - co w dzisiejszym świecie ma niebagatelne znaczenie. Mimo, iż początki tej architektury sięgają lat osiemdziesiątych, jest ona rozwijana do dzisiaj. I nic nie wróży jej szybkiego końca.

Czy warto ją poznać? Zdecydowanie tak! Mnogość płytek ewaluacyjnych z różnymi mikrokontrolerami oraz duża dostępność darmowych narzędzi powodują, że te układy stają się coraz powszechniejsze. Złożone zadania, typu przetwarzanie dźwięku czy wyświetlanie obrazu, przestały być domeną wyłącznie drogich układów, ponieważ coraz tańsze mikrokontrolery mają na pokładzie sporą ilość pamięci oraz peryferia wspomagające te procesy.

Programatory

Nawet najlepszy program nie zda się na nic, dopóki nie znajdzie się w pamięci Flash mikrokontrolera. Do jej zaprogramowania - czyli przesłania wsadu - służy urządzenie zwane programatorem. Jest pośrednikiem między komputerem, na którym piszemy kod i dokonujemy kompilacji a mikrokontrolerem. Każda rodzina układów ma swoje programatory.

Przykładowo, dla AVR kiedyś popularne były programatory podłączane do portu drukarkowego (LPT) komputera. Niestety, to złącze wymarło jakiś czas temu, więc musiały pojawić się nowe rozwiązania, bazujące na popularnym złączu USB. Jednym z nich jest USBasp - programator zbudowany na mikrokontrolerze ATmega8, który doskonale spełnia swoją funkcję

Są też inne rozwiązania, na przykład programatory serii STK, dedykowane mikrokontrolerom AVR. Programatory do zastosowań przemysłowych wyglądają jeszcze inaczej, ponieważ w nich liczy się czas potrzebny na zaprogramowanie każdego układu.

Dla układów z rodziny PIC firma Microchip wypuściła dedykowane programatory PICkit. Natomiast dla układów STM32 są dostępne niedrogie układy ST-Link, które umożliwiają zarówno programowanie, jak i debugowanie - czyli podgląd zawartości pamięci w trakcie wykonywania programu. Wiele mikrokontrolerów można też obsługiwać za pośrednictwem JTAG.

W płytkach ewaluacyjnych zazwyczaj mamy wbudowany programator, więc nie musimy się nim przejmować. Co lepsze, zazwyczaj istnieje możliwość przełączenia odpowiednich linii tegoż wbudowanego programatora, aby móc go używać do obsługi innych układów, a wbudowany w płytkę mikrokontroler czasowo odłączyć. To bardzo przydatna opcja dla tych, którzy chcą zbudować własne urządzenie z użyciem mikrokontrolera z tej samej rodziny, ale nie chcą kupować oddzielnie programatora.

Podsumowanie

Chcąc poznać świat mikrokontrolerów, należy podjąć zawczasu kilka decyzji. Najważniejszą sprawą jest wybór języka programowania, w którym będziemy pisać kod dla naszych układów. Potem trzeba wybrać rodzinę układów, którą chcemy poznać. Dopiero na końcu trzeba znaleźć dedykowane środowisko programistyczne oraz programator i/lub płytkę ewaluacyjną.

Na szczęście mamy do wyboru mnóstwo opracowań na ten temat, dostępnych zarówno w internecie, jak i w literaturze. Nauka programowania mikrokontrolerów może być prawdziwą frajdą, a na dodatek możemy w ten sposób pozyskać cenne umiejętności!