Jednym ze schematów, za którym podążają osoby chcące rozpocząć swoją karierę w świecie systemów wbudowanych, jest przygotowanie do pracy oparte na kilku podstawach. Są to m.in. lektura popularnych książek poruszających tematykę programowania w języku C mikrokontrolerów z rodziny AVR lub STM32, znajomość podstaw elektroniki cyfrowej i analogowej, wykształcenie techniczne skorelowane z tematyką embedded oraz wykorzystanie tej wiedzy w praktyce poprzez wykonanie, chociaż kilku działających układów opartych o wymienione wcześniej platformy. Jest to niewątpliwie słuszne podejście. Warto jednak móc dodatkowo wyróżnić się czymś więcej na tle innych kandydatów – czymś, czym można zaskoczyć swojego przyszłego pracodawcę podczas rozmowy rekrutacyjnej.
Poza umiejętnościami i narzędziami, które stanowią rdzeń pracy programisty systemów wbudowanych, jak np. znajomość C/C++, znajomość architektury i układów peryferyjnych stosowanego mikrokontrolera), w profesjonalnym wytwarzaniu oprogramowania wykorzystywane jest dodatkowo wiele narzędzi, mających usprawnić lub zautomatyzować proces. Do takich rozwiązań zaliczyć możemy recenzję kodu (ang. code review), testowanie, kompilację projektu lub analizę statyczną kodu – czyli wszystkie te procesy, którymi nie zajmuje się hobbysta chcący po prostu tworzyć ciekawe projekty. Wiele z narzędzi jest powszechnych w produkcji oprogramowania niezależnie od jego specyfiki – np. narzędzia kontroli wersji takie jak Git lub systemy zarządzania rozwojem projektu, np. Jira, ale często używane są też narzędzia dedykowane i przystosowane do specyfiki inżynierii systemów wbudowanych. Prędzej czy później zetkniesz się z nimi podczas swojej kariery. Opanowanie ich wcześniej przyniesie Ci wiele pożytku, nie tylko podczas rozmowy rekrutacyjnej, ale też podczas wdrażania się do nowego projektu.
CMake
Jak skompilować kod? W najprostszym przypadku ograniczać się to będzie do skorzystania z przycisku w oknie środowiska programistycznego (IDE). W analogiczny sposób możemy załadować program do pamięci i nie przejmować się całą tą złożoną sekwencją zdarzeń, w wyniku której otrzymujemy działający na mikrokontrolerze program. W praktyce jednak zachodzi potrzeba uniezależnienia się od środowiska programistycznego oraz dostosowania procesu budowania projektu do własnych potrzeb. Ponadto proces budowania projektu przez IDE, choć wygodny dla programistów, zwykle nie jest łatwy do automatyzacji. Odpowiedzmy sobie zatem na pytanie – co tak naprawdę trzeba zrobić, aby skompilować kod źródłowy do pliku binarnego? W pierwszym podejściu, budowanie projektu ogranicza się do długiej listy wywołań szeregu aplikacji toolchaina z odpowiednimi parametrami – trzeba jedynie wskazać gdzie znajdują się pliki źródłowe, gdzie znajdują się pliki nagłówkowe, jak ma wyglądać plik wynikowy (czy to w postaci binarnej, Intel hex, czy bardziej uniwersalnej postaci pliku .elf), wskazać opcje kompilacji takie jak optymalizacja, wybrać standard języka, flagi manipulujące listą wyświetlanych przez kompilator ostrzeżeń i sprawdzanych błędów… no właśnie. O ile taka metoda bezpośredniego wywołania kompilatora i ręczne wpisywanie parametrów za każdym razem może się sprawdzić w przypadku kompilacji kilku plików źródłowych (chociaż i to jest mocno naciągane – nikt tak nie robi), o tyle w przypadku odrobinę poważniejszych projektów, plików i wariantów tworzenia pliku wynikowego przybywa tyle, że trudno jest zapanować nad prostą czynnością, jaką jest kompilacja kodu.
Pierwszym krokiem, aby uporządkować i uprościć tę czynność jest tworzenie plików Makefile – w dużym skrócie jest to plik zawierający przepis na plik wynikowy. Zawiera dane niezbędne dla kompilatora takie jak ścieżki do plików źródłowych i nagłówkowych.
Właśnie w ten sposób popularne w środowiskach embedded IDE kompilują projekty. Każdy nowo utworzony plik uwzględniany jest w tworzonych na bieżąco przez IDE plikach Makefile. Aby jednak nie było potrzeby polegania na konkretnym środowisku oraz aby nasz projekt zawierał wszystkie niezbędne do kompilacji informacje zapisane w postaci kodu (zgodnie z IaC, Infrastructure as Code), należy uzyskać możliwość samodzielnego tworzenia plików Makefile. Opanowanie składni plików Makefile, chociaż w podstawowym zakresie, jest bardzo przydatne w zrozumieniu procesu tworzenia pliku wynikowego.
Problem jednak w tym, że analogicznie jak przy poprzedniej metodzie, przy większych projektach i złożonych wariantach kompilacji, tworzenie i utrzymywanie plików Makefile staje się czasochłonne i obarczone ryzykiem błędów. Rozwiązaniem takich problemów jest narzędzie CMake.
CMake to narzędzie pozwalające w prosty sposób tworzyć przepis na plik z regułami dla kompilacji (np. Makefile) – analogicznie do tego, że Makefile jest przepisem na plik binarny). Składnia CMake’a jest prostsza i bardziej przejrzysta niż składnia plików Makefile. Dzięki temu aktualizacja plików CMake’a podczas rozwoju projektu, uwzględnianie nowych wersji platformy docelowej, praca z różnymi wersjami kompilatorów oraz jakakolwiek parametryzacja procesu budowania staje się dużo szybsza i łatwiejsza. Jednak najważniejszą zaletą użycia CMake względem Makefile jest przenośność – projekty budowane przy pomocy CMake można zbudować na szeregu systemów operacyjnych i szeregu systemów budowania, niekoniecznie z użyciem Makefile.
Valgrind
Duża grupę błędów w oprogramowaniu stanowią błędy w zarządzaniu pamięcią. Może być to m.in. brak zwolnienia zaalokowanej wcześniej pamięci (wyciek) lub nieuprawniony dostęp do pamięci. Problemy tego typu zyskują na znaczeniu w systemach wbudowanych, gdzie często ilość dostępnej pamięci RAM jest bardzo ograniczona, a skutki błędu zarządzania pamięcią mogą nieść ryzyko utraty funkcjonalności całego urządzenia. Dodatkowo, nie wszystkie mikrokontrolery posiadają MPU (Memory Protection Unit, sprzętowa ochrona pamięci) a jeśli je mają, funkcjonalność MPU jest często nieprawidłowo skonfigurowana lub ograniczająca rozwój oprogramowania na tyle, że programiści celowo nie korzystają z niej wcale. To zdecydowanie ogranicza możliwość wczesnej detekcji w dostępie czy zapisie pamięci.
Aby wykrywać tego rodzaju błędy już na etapie rozwoju oprogramowania, stosuje się narzędzia do kontroli przydziału pamięci dynamicznej. Jednym z najpopularniejszych jest Valgrind – aplikacja analizująca dynamicznie proces zarządzania pamięcią. W systemach wbudowanych narzędzie to bardzo często wykorzystuje się podczas tworzenia testów jednostkowych (co pozwala używać Valgrinda nie na platformie docelowej, lecz na serwerze CI).
Jak widać, droga od ukończenia pracy nad samym kodem, do momentu faktycznego zakończenia zadania jest długa i skomplikowana. Jest to jednak niezbędne, aby zapewnić maksymalną niezawodność tworzonego oprogramowania oraz uczynić proces możliwie płynnym i zautomatyzowanym – a co za tym idzie – szybszym. Wszystkie wymienione narzędzia, z pewnością przydadzą Ci się w pracy zawodowej – nawet sama świadomość ich istnienia oraz zrozumienie powodów, dla których się je stosuje – są dobrym wstępem do poznania praktyk stosowanych o inżynierii oprogramowania wbudowanego. Jednak żadna teoria, nie zastąpi przygotowania praktycznego. Dlatego dobrym pomysłem na rozpoczęcie kariery jest staż o profilu embedded, organizowany co roku przez firmę Comarch, a którego sam byłem uczestnikiem. Pod okiem specjalistów z wieloletnim doświadczeniem studenci mają możliwość uczenia się i realizowania projektów, co jest bardzo ważne, w praktyce. Dzięki temu mogą skonfrontować swoje oczekiwania już na początku kariery, zobaczyć, jak wygląda praca programistów w praktyce oraz „doszlifować” kompetencje miękkie. Jeżeli chcesz rozwijać się w obszarze elektroniki i hardware oraz tworzyć, integrować i weryfikować oprogramowanie, jest to idealna ścieżka, by rozpocząć przygodę z IT!
Opublikowany fragment pochodzi z artykułu "5 narzędzi używanych w inżynierii systemów wbudowanych, których będziesz musiał się nauczyć" autorstwa Michała Łokcewicza. Pełen artykuł można przeczytać na portalu Geek Just Join IT.
A jak wygląda zgłaszanie błędów w projektach webowych? Tego dowiesz się z innego artykułu.