Wybór języka programowania to kolejny obok wyboru mikrokontrolera najczęściej poruszany temat. Pytanie o język zadają nie tylko zupełnie początkujący. Często na studiach mamy trochę do czynienia z C, C++, MATLABem, FPGA, Asemblerem. Uczymy się wszystkiego po trochu i musimy się na coś zdecydować. Jeżeli siedzimy trochę w branży – słyszymy o zaletach Rusta czy modern C++. Chcemy się dobrze ustawić do wiatru i uczyć się rzeczy jednocześnie popularnych i perspektywicznych. Wybór odpowiednich języków do nauki jest tutaj kluczowy.

Statystyki branżowe

Od lat we wszystkich badaniach rynku embedded niepodzielnie króluje język C. W zależności od metody pomiaru 50-75% projektów wykorzystuje C jako główny język. Tutaj badania z embedded.com z 2023 roku:

Całe badania znajdziesz tu: https://www.embedded.com/embedded-survey/

A tutaj wykres Barr Group z wielu lat:

Źródło: https://embeddedgurus.com/barr-code/2018/02/c-the-immortal-programming-language/

Widzimy więc wyraźnie, że ten trend się utrzymuje od lat. Mieliśmy w tym czasie prawdziwą rewolucję. Procesory są o wiele mocniejsze, projekty dużo większe, zmieniły się narzędzia, doszło IoT, cybersecurity i wiele innych rzeczy. Mieliśmy przystosowanie C++ do embedded, potem modern C++, a ostatnio mamy ogromny hype na Rusta. Jednak żaden z tych czynników nie zachwiał pozycji C. Nie mamy więc podstaw do przypuszczeń, że sytuacja się zmieni w najbliższej przyszłości.

Możesz więc spodziewać się, że:

  • Będziesz się uczyć z materiałów w języku C
  • Prawdopodobnie trafisz do projektu w C (szczególnie na początku)
  • Jeżeli zechcesz zmienić pracę/projekt – znowu pewnie trafisz do C
  • Nawet kiedy będziesz pisać w innym języku, będziesz korzystać z zewnętrznego kodu w C

Dlaczego C ma taką silną pozycję?

Oczywiście dorabianie teorii do istniejących danych zawsze jest prostsze. Ale na pewno jest kilka faktów przemawiających za C.

1. Producenci układów dostarczają kod w C

Dominacja C po stronie producentów jest chyba największa. A jednocześnie najbardziej wpływa na decyzje o wyborze języka w komercyjnych projektach:

  • Wszystkie biblioteki peryferiów od wszystkich producentów procesorów są w C
  • Wszystkie oficjalne przykłady są w C
  • Edytory kodu od producentów są robione pod C – łatwo z nimi wystartować
  • Oficjalna dokumentacja i noty aplikacyjne są w C
  • Oficjalne fora producentów i support skupiają się na C

Producenci olewają inne języki programowania. Społeczności muszą rozwijać biblioteki w Ruscie, czy C++niezależnie. A to ma swoje konsekwencje. Wsparcie na popularniejsze procesory/płytki będzie w miarę ok, ale na mniej popularne już może być problem. Nie ma takiego supportu, na drivery do nowych układów trzeba czekać. Czasem za biblioteki odpowiadają pojedyncze osoby. Takie projekty mogą zostać porzucone, maintainer może nie odpowiadać przez dłuższy czas.

Na przykład biblioteki C++ do obsługi rejestrów z tego artykułu już od dawna nie są rozwijane. Jeżeli nasz projekt osiągnie sukces i utrzymujemy go 5 lat, a potem piszemy nową wersję – możemy obudzić się z ręką w nocniku.

To wszystko są realne ryzyka dla komercyjnych projektów. Dlatego więksi gracze wolą pójść sprawdzoną ścieżką zostawiając nowinki hobbystom i startupom.

3. Istniejący kod od społeczności

Zarówno podczas początkowej nauki, jak i później rozwiązując typowe problemy projektowe często korzystamy z pomocy wujka Google. Liczna, aktywna społeczność działająca od wielu lat jest tutaj nieoceniona. Wszystkie odpowiedzi na forach, przykładowe projekty na GitHubie i blogposty z tutorialami czy rozwiązaniami problemów możemy potem wyszukać w internecie.ji.

Przez lata w C powstała ogromna liczba projektów, które możemy podpatrzyć. A przede wszystkim bibliotek, które możemy użyć u siebie. Baza bibliotek w C jest dużo większa (chociaż Arduino też ma dużą bazę – a tam piszą w C++).

Jednym z najważniejszych feature’ów innych języków embedded jest łatwe wywoływanie i kompilowanie kodu w C. Dlaczego? Bo zamiast pisać wszystko od nowa, chcemy do naszego projektu w C++, Ruscie czy Adzie dołączyć FreeRTOSa, FatFS, stos TCPIP, czy nawet biblioteki peryferiów od producenta.

Nie ma co kopać się z koniem. Wniosek jest prosty – C to bezpieczny wybór na start. Na pewno nie popełnimy błędu, nie zamkniemy sobie żadnych drzwi. Nauka pójdzie sprawniej dzięki bazie materiałów edukacyjnych i społeczności. Zdobędziemy solidne fundamenty, a potem możemy przeskoczyć na inne języki. To standardowa ścieżka rozwoju.

Czy to źle, że jesteśmy skazani na C?

Nie!

To nie jest tak, że C króluje w embedded ze względów historycznych. Wszyscy chcieli by to zmienić, ale nie mogą. Chociaż faktycznie – możemy się spotkać z taką narracją. Szczególnie od adwokatów innych języków. W końcu to główna oś ich promocji. C ma wiele zalet. Chociaż przyznaję, że przechodzimy nad nimi do porządku dziennego i głośniej mówi się o wadach. Ale C jest idealne na start i świetnie wprowadzi nas w najważniejsze koncepcje programowania embedded.

Po pierwsze C uczy nas prostoty. Wszystko tutaj jest jawnie napisane. Łatwo przeanalizować i przewidzieć działanie kodu. Poszczególne zachowania tak łatwo się przed nami nie ukryją (co jest zmorą w C++).

Uczymy się najważniejszych koncepcji. I widzimy je bezpośrednio w kodzie. Mamy do czynienia od razu z zarządzaniem pamięcią, wskaźnikami, flagami kompilatora, undefined behavior, volatile, kwestiami dotyczącymi wydajności. To wszystko przydaje się przy dalszej nauce innych języków. Tym bardziej, że one zwykle porównują się do C, żeby pokazać swoje zalety.

Ale C ma również swoje pułapki na które musimy uważać podczas nauki. Do C na pewno pasuje maksyma “Easy to learn, hard to master”. Zwykle uczymy się podstawowej składni, to nam pozwala robić pierwsze projekty, skupić się na poznawaniu sprzętu. Ale łatwo ulec przekonaniu, że wszystko już wiemy i potem walczyć z różnymi dziwnymi błędami – z których C również słynie.

Potencjalnie niebezpieczne konstrukcje, drogi na skróty, ręczne “optymalizacje”, sprytny kod, undefined behavior. Różne kruczki w działaniu języka i kompilatora, undefined behavior. Mając już trochę doświadczenia w C widzimy tę kolejną warstwę trudności. Często problemy w C wynikają też z mitów i błędnych założeń.

Dlatego właśnie C jest mocno wałkowane na każdej rozmowie kwalifikacyjnej. Można zadać mnóstwo podchwytliwych pytań. A ciągnąc dalej dyskusję można dużo dowiedzieć się o doświadczeniu kandydata i podejściu do rozwiązywania codziennych problemów w kodzie.

Nie będę tutaj wchodził głębiej w teoretyczne rozważania. Ten wpis i tak jest zatrważająco długi. O mentalności, której uczymy się w C pisałem w tych artykułach:

A jak to jest z innymi językami?

Nie da się ukryć, że programistów embedded ciągnie w kierunku innych języków. Chcielibyśmy zamienić C na Rusta czy C++. Widzimy jak te języki rozwiązują problemy, które znamy z naszych codziennych projektów. W idealnym świecie faktycznie byłyby lepsze. Ale kiedy przychodzi do praktyki, już nie jest tak różowo. Albo języki jeszcze nie są tak mocno rozwinięte (Rust), albo rozwój poszedł w złym kierunku i razem z dobrymi elementami musimy się zgodzić również na wiele złych (C++).

Ostatecznie dochodzimy do wniosku, że inne języki też mają swoje problemy. Wracamy więc do C (albo trafiamy do innego projektu, gdzie C było już wcześniej). Ale jesteśmy bogatsi o nowe doświadczenia z innych języków, które wpływają na to jak podchodzimy do pisania kodu i myślenia o projekcie.

Za każdym językiem kryje się jakaś myśl przewodnia. Kiedy lepiej ją poznamy dostrzegamy sposoby na jej stosowanie nawet we wcześniej poznanym języku. To również element nauki.

Ok, pora na omówienie kilku popularnych języków.

C++

Będę tu mówić tylko o C++ w embedded. Można się również uczyć od początku na programistę C++. To otwiera trochę inną ścieżkę kariery, z której później również można przejść do embedded. A można pisać zupełnie inne aplikacje.

C++ jest tak dobry jak zespół, który go używa. Można w nim osiągnąć lepszy projekt niż w C. Ale trzeba w to włożyć sporo wysiłku. Ale równie dobrze wszystko może pójść nie tak i nasz projekt okaże się koszmarem. Tutaj idealnie pasuje słynny cytat:

“C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”

Bjarne Stroustrup, Twórca C++

Wchodząc w temat C++ widzimy na początku szklankę do połowy pełną.C++ daje dużo fajnych opcji, których nie mieliśmy w zwykłym C. To rozwiązuje wiele problemów i kod pisze się przyjemniej. Odsyłam tutaj do artykułu pokazującego najważniejsze osiągnięcia modern C++ z punktu widzenia programisty embedded.

Później jednak okazuje się, że jest szklanka jest również do połowy pusta. C++ poza wieloma ułatwieniami dodaję również całą masę przestarzałych konstrukcji pozostawionych dla kompatybilności wstecznej. Dzięki temu możemy każdą rzecz napisać na kilka sposobów. A co za tym idzie – możemy łatwiej wprowadzać złe praktyki.

W C++ poszczególne operacje są dużo mniej jawne. Mogą na przykład dziać się w konstruktorach i destruktorach, możemy przeciążać operatory. Mamy cały wielki temat template metaprogramming. To wszystko się świetnie sprawdza w rękach doświadczonego zespołu z dobrymi narzędziami i procesami. Ale równie dobrze może być źródłem wielu problemów. Szczególnie kiedy wdrażamy C++ po raz pierwszy.

Ok. Wiemy już, że aby projekt w C++ był lepszy musimy przestrzegać dobrych praktyk. A co to za dobre praktyki? No i tutaj zaczynają się schody. Bo w każdym projekcie okazuje się, że są inne. Nie ma jednego zestawu dobrych praktyk co do którego jest konsensus społeczności C++ embedded. A niektóre dobre praktyki ze zwykłego C++ do embedded się nie nadają. Na przykład ze względu na dynamiczną alokację pamięci. A jeszcze co 3 lata wychodzi nowy standard C++. W efekcie coś, co w jednym projekcie jest uznawane za dobrą praktykę, w innym już nie. Nie jesteśmy w stanie uczyć się w spójny sposób.

Także C++ z jednej strony rozwiązuje problemy C, ale z drugiej dodaje masę kolejnych. Na pewno warto w którymś momencie zainteresować się C++. Poznać dobre praktyki, poczytać różne materiały od społeczności. Społeczność to moim zdaniem jeden z większych atutów C++. Produkują oddolnie ogromną ilość materiałów pokazujących jak bezpiecznie pisać w C++ i unikać strzelenia sobie w stopę. Wiele z tych mechanizmów możemy również przenieść do C.

Rust

To dużo nowszy język od C, czy C++ i nie ma całego bagażu związanego z kompatybilnością wsteczną. Większość dobrych praktyk z C++ jest po prostu wbudowana w język jako domyślne rozwiązanie. Są też dodatkowe elementy, z których Rust słynie. Rust nastawia się na bezpieczeństwo pamięci, obiekty niemutowalne, jasną odpowiedzialność poszczególnych modułów za modyfikację zmiennych. Dzięki temu jesteśmy w stanie wyeliminować wiele przyczyn błędów w naszych programach.

Samemu nie mam większego doświadczenia w Ruscie. Robiłem tylko kilka tutoriali. Ale słyszałem, że te opcje zwiększające bezpieczeństwo mogą np. utrudniać prototypowanie. Nie wiemy do końca co chcemy osiągnąć i zamiast się dowiedzieć – walczymy z kompilatorem. Podobny problem występuje w Adzie, o której parę słów dalej.

Rust jest również niesamowicie lubiany. Każdy chce się go nauczyć, każdy chce w nim pisać, ale mało komu się udaje. Raczej to jest język hobbystyczny, w którym robimy projekty do szuflady. W projektach komercyjnych zwykle firmy nie chcą podejmować ryzyka. Wybierają sprawdzony język C, gdzie od razu dostają społeczność, bazę kodu i wsparcie od producentów układów. Jeszcze minie wiele lat, zanim to się zmieni. Jednak warto odnotować pewne wyjątki – na przykład używanie Rusta w projektach kosmicznych.

Rust jest również ryzykownym wyborem pod kątem ścieżki kariery. Dużo łatwiej znaleźć go w małych firmach i startupach. A to nie jest stabilne zatrudnienie. Startupy upadają, możemy nie znaleźć kolejnego. Pewniej będzie i tak nauczyć się C, a potem też C++ i wskoczyć do Rusta później, kiedy nadarzy się okazja.

Asembler

Kiedyś asembler królował w embedded. Ale te czasy dawno się skończyły. Dzisiaj jest to język wspomagający. Używamy go do pisania wstawek, procedur startowych, korzystania ze specjalnych instrukcji procesora, czy do analizy kodu wynikowego z kompilatora.

Na pewno warto go znać. Daje głębokie zrozumienie wewnętrznego działania procesora. Nabieramy wtedy lepszej intuicji co da się zrobić, a co nie. Dlaczego niektóre operacje są kosztowne. Widzimy też jasny związek między składnią C a instrukcjami asemblerowymi.

Analizę kodu asemblerowego możesz często znaleźć w moich materiałach. Na przykład jak sprawdzić czy na pewno korzystamy z koprocesora FPU:

Jednak zaczynanie nauki od asemblera jest dosyć karkołomnym pomysłem. Dużo bardziej nam pomoże, kiedy znamy już C i mamy trochę doświadczenia z mikrokontrolerami.

Czy asembler jest wymagany do pracy? Bezpośrednio nie. A na pewno nie na stanowiskach juniorskich. Warto o nim pomyśleć później. Kiedy go umiesz – zwiększasz mocno swoją wartość. Posiadasz potrzebne, rzadkie umiejętności i możesz robić rzeczy w projekcie, których wszyscy się boją. Masz również zrozumienie, które pomoże Ci w innych językach.

Ada

Język wykorzystywany w wojsku, w lotnictwie, w branży kosmicznej. Bardzo niszowy i raczej na wymarciu. Na pewno nie opłaca się go uczyć zbyt wcześnie. Raczej warto go poznać później jako ciekawostka. Kiedy będziesz aplikować do firmy wykorzystującej Adę – raczej i tak otrzymasz solidne przeszkolenie na start.

Ada w założeniach jest językiem dla branży safety-critical. Ma podobne mechanizmy jak Rust, tylko zapewnia dużo więcej bezpieczeństwa i kontroli. To bardzo ciekawe, że Rust jest na topie, a Ada jest wymieniana jednym tchem z COBOLem jako najbardziej znienawidzony język. Ale to temat na odrębny wpis.

Z Adą się trochę bawiłem – można znaleźć serię artykułów o tym języku. I tutaj na własnej skórze doświadczyłem opisanego przy okazji Rusta problemu z prototypowaniem. Albo znasz docelowe rozwiązanie, albo program się nie skompiluje.

Języki do FPGA – VHDL i Verilog

FPGA to kolejna nisza. Jeżeli uda Ci się do niej załapać to masz super ciekawą i dobrze płatną pracę. Jednak takiej pracy jest bardzo mało. FPGA jest drogie i ciężko się go nauczyć. W ostatnim czasie widoczny jest trend uczenia programistów embedded w firmach pod FPGA. Więc po raz kolejny pewniejszą drogą będzie najpierw wyrobienie fundamentów i nauka C, a dopiero potem przejście na FPGA.

Inne języki

Jest jeszcze cała masa języków wykorzystywanych w jakiś niszach, albo wspomagających development. Programista embedded mocno skorzysta na znajomości Pythona. Nie do pisania w microPythonie. Raczej do skryptów wspomagających development, generujących dane itp.

Cały czas są również rozwijane nowe języki, które marzą o zastąpieniu w przyszłości C. Ostatnio trafiłem na język Zig, który ma wiele ciekawych opcji. Jednak to tylko ciekawostka. Miną jeszcze lata zanim Zig nabierze jakiegokolwiek znaczenia komercyjnego.

Jak podejść do nauki C? Kiedy dodać kolejny język?

Ok, ustaliliśmy już, że najbardziej optymalnie najpierw nauczyć się C, a potem dodać do tego inne języki. Teraz zastanówmy się jak to zadanie rozłożyć w czasie. A także kiedy jest dobry czas, aby przejść na inne języki.

Ja widzę to tak:

Krok 1: Opanujemy składnię i podstawy C, robimy proste ćwiczenia
Krok 2: Skupiamy się bardziej na innych aspektach embedded i traktuemy C jako narzędzie. Uczymy się elektroniki, mikrokontrolerów, gita, robimy projekty
Krok 3: Przy okazji trafiamy na problemy w sofcie i zdobywamy wiedzę o różnych kruczkach w C. Poznajemy dobre praktyki, wzorce, pułapki, zasady czystego kodu
Krok 4: Mając trochę doświadczenia praktycznego możemy uzupełnić luki i nauczyć się C solidnie. Tym bardziej, że to nam się zwróci na rozmowach kwalifikacyjnych
Krok 5: Mocniej skupiamy się na ekosystemie – kompilator, toolchain, build system, narzędzia wspomagające pracę

Te 5 kroków już nam daje naprawdę dobrą wiedzę o języku. Gdzieś w okolicy kroku 4 warto również solidniej przysiąść do innych języków. Aby efektywnie się ich uczyć musimy już mieć pojęcie o mikrokontrolerach, kompilatorze, debugowaniu i wszystkich elementach dookoła. Chcemy skupić się na języku. A nie uczyć się kilku rzeczy na raz, które nawzajem zaciemniają obraz.

Warto tutaj wspomnieć, że firmy nieraz wspomagają programistów w nauce nowych technologii pod konkretne projekty. Wspominałem wcześniej o takiej praktyce w FPGA, czy w Adzie. Nawet projekty w C++ wymagają połączenia dwóch światów – znajomości mikrokontrolerów i znajomości C++. Dlatego specjalista embedded po przyspieszonym kursie C++ jest wartością w projekcie.

Doświadczeni programiści szukają nowych wyzwań, nie chcą robić cały czas tego samego. Poza tym każdego ciągnie do innych języków. Dlatego nauka nowych języków z pomocą pracodawcy jest optymalnym rozwiązaniem dla obu stron. A żeby znaleźć się w tym miejscu najłatwiej po prostu zacząć od C.

Podsumowanie

Jak widzisz – C trzyma się bardzo dobrze. Mamy jakieś alternatywy, ale nie wygląda aby miały stać się bardziej popularne. Na pewno w pełni nie zastąpią C. Raczej będą koegzystować. Dodatkowo C ma dużo większą bazę istniejącego kodu i projekty w C++, Ruście, Adzie, wywołują nieraz biblioteki napisane w C.

Dlatego w embedded od C nie uciekniemy. Warto potraktować naukę C jako solidny fundament, który później może nam posłużyć do rozwoju w różnych kierunkach. Samą naukę C warto również rozłożyć na kilka faz, ponieważ język ma prostą składnię, ale dużo niuansów. Żeby wszystko odpowiednio zrozumieć i docenić potrzebujemy doświadczenia z różnymi projektami.

Mam nadzieję, że ten przegląd języków w embedded był dla Ciebie pomocny i dzięki niemu będziesz lepiej wiedzieć jak poprowadzić swoją ścieżkę rozwoju. A więcej o karierze embedded i ścieżce nauki w środę 13 marca na webinarze Jak wejść do branży embedded w 2024 roku?”.

Nie będziemy się skupiać jedynie na ścieżce rozwoju prowadzącej do pierwszej pracy, ale również na kolejnych krokach. Dlatego warto przyjść nawet jeśli jesteś już w branży.

Zapisz się tutaj, aby nie przegapić!