Cześć! W poprzedniej części kursu dowiedzieliśmy się czym jest Arduino, NodeMCU oraz ESP8266, wybraliśmy płytkę oraz zebraliśmy listę potrzebnych modułów. Jesteśmy gotowi, żeby rozpocząć naszą wspólną przygodę z programowaniem! 🙂 Kolejnym krokiem jest instalacja i konfiguracja Arduino – środowiska programistycznego, które posłuży nam do pisania aplikacji, wgrywania ich na urządzenie oraz sprawdzania ich działania. Napiszemy też nasz pierwszy program!
Instalacja Arduino
Program, który umożliwia wykonywanie tych czynności nazywany jest zintegrowanym środowiskiem programistycznym (z ang. Integrated Development Environment – IDE) i w naszym przypadku nosi on po prostu nazwę Arduino. Instalacja jest bardzo prosta, wystarczy pobrać instalator ze strony Arduino. Na stronie znajdziemy kilka zachęt do wpłaty dobrowolnego datku na cel rozwoju Arduino. Wybór należy do Was, ale wszyscy domyślamy się jak to się skończy ;). Ważne są tutaj dwie rzeczy: jeśli pobieramy Arduino na system Windows, nie wybierajmy wersji do pobrania ze sklepu z aplikacjami Windows – powód wyjaśnimy w jednym z kolejnych spotkań. Przy pobieraniu wybierz więc odnośnik nazwany „Windows Installer”, nie „Windows App”. Drugą istotną kwestią jest to, aby w miarę możliwości zostawić domyślną ścieżkę instalacji Arduino. Ograniczy to nieco ilość możliwych problemów oraz uprości rozwiązywanie ich.
Instalacja sterowników NodeMCU
Po instalacji Arduino musimy wykonać jeszcze jedną rzecz. Jest nią instalacja sterowników umożliwiających komunikację komputera z płytką NodeMCU. Sterowniki muszą być dopasowane zarówno do posiadanej płytki oraz systemu operacyjnego na którym pracujemy. Jak wspominaliśmy w poprzedniej części kursu, na płytkach stosowane są dwa typy interfejsu umożliwiającego programowanie płytki przez USB. Wspomniane układy scalone znajdują się w okolicy złącza microUSB oraz noszą nazwy CH340G – można poznać go po podłużnym, prostokątnym kształcie obudowy. Drugi układ, który możesz spotkać w NodeMCU nosi nazwę CP2102 i jest on zamknięty w obudowie o kwadratowym kształcie. Lokalizację obu układów zaznaczyłem na zdjęciach. Jeżeli posiadamy płytkę z układem CH340G sterowniki znajdziemy na repozytorium twórców NodeMCU. Sterowniki dla układu CP2102 są dostępne na stronie producenta układu. Instalacja w obu przypadkach sprowadza się do rozpakowania archiwum oraz przejścia przez standardowy proces instalacji.
Konfiguracja IDE
Wreszcie możemy otworzyć środowisko programistyczne Arduino. Po uruchomieniu, przywita nas niewielkie okienko, które dzieli się na kilka części (patrz rysunek poniżej). W najwyższej części okna (1) znajdziemy pasek z przyciskami umożliwiającymi szybki dostęp do najważniejszych funkcji środowiska programistycznego: kompilowanie, wgranie, zapis programu oraz otwarcie okna monitora portu szeregowego. Nieco niżej znajduje się pasek (2) na którym otwierają się kolejne karty zawierające poszczególne moduły projektu. Dzięki nim możemy podzielić nasz kod na wiele połączonych ze sobą plików, co umożliwi łatwiejsze przeglądanie go, jeśli napiszemy wiele linijek kodu. Największą część okna zajmuje edytor kodu (3) – zobaczymy w nim kilka linijek kodu, który jest konieczny do uruchomienia jakiegokolwiek programu na Arduino. Na samym dole znajduje się okno (4), za pomocą którego komunikuje się z nami środowisko programistyczne. W oknie tym znajdziecie komunikaty o błędach kompilacji kodu i wszelkie informacje o procesie wgrywania kodu na płytkę.
Żeby zacząć programować nasze NodeMCU musimy dodać naszą płytkę do menedżera płytek. Aby to zrobić, musimy wejść w menu Arduino -> Preferencje. W oknie które się otworzy, musimy odnaleźć pole „Dodatkowe adresy URL do menedżera płytek” oraz wkleić następujący URL: http://arduino.esp8266.com/stable/package_esp8266com_index.json. Po zatwierdzeniu zmian, przejdźmy do menu Narzędzia -> Płytka oraz wybierzmy pozycję Menedżer płytek. To tutaj możemy znaleźć oprogramowanie umożliwiające tworzenie programów na wszelkie płytki kompatybilne z Arduino. NodeMCU nie jest jedną z oficjalnych płytek Arduino, dlatego w poprzednim kroku poinformowaliśmy nasze środowisko programistyczne skąd może pobrać informacje o wykorzystywanej przez nas płytce. W wyszukiwarkę wpiszmy „esp8266„, na liście powinna pojawić się pozycja „esp8266 by ESP8266 Community„. Wybierz wersję 2.4.2, naciśnij przycisk „Instaluj” i zamknij okno po zakończeniu instalacji. Pamiętaj, że poszczególne wersje oprogramowania płytki mogą wprowadzać zmiany w wykorzystywanych przez nie funkcjach, co może doprowadzić do problemów w uruchamianiu programów prezentowanych w czasie kursu.
Nasz pierwszy program! 🙂
Nareszcie nastał czas programowania! W świecie informatyki, pierwszy program, którego uczymy się, żeby poznać i przetestować środowisko programistyczne i nowy język programowania nazywa się zwykle ^. Nazwa bierze się od słów wypisywanych w okienku debuggera/konsoli, które są symbolicznym przywitaniem się programu ze światem :). W przypadku programowania mikrokontrolerów zwykle nazywa się on jednak „Blinky”, ponieważ mruganie diodą LED jest zazwyczaj najprostszą możliwą do zrealizowania operacją. Nasz program również będzie na tym polegał.
Zacznijmy od otwarcia przykładowego programu. W menu Plik -> Przykłady -> 01. Basics wybierz pozycję Blink. Twoim oczom ukaże się skrypt – czyli plik zawierający kod programu. Zawiera on sporą ilość tekstu w różnych kolorach, co może wydać Ci się na początku przytłaczające. Pewnie nie możesz się już doczekać, aż uruchomisz swój pierwszy program, zajmijmy się tym więc, a potem omówimy znaczenie poszczególnych linii kodu! 🙂 Aby uruchomić program, musimy dokonać niewielkiej modyfikacji – odszukaj w kodzie wszystkie wystąpienia stałej LED_BUILTIN i wpisz zamiast nich D4 (koniecznie użyj wielkiej litery!). Teraz pozostaje nam tylko wgrać program na płytkę. W tym celu musimy odpowiednio skonfigurować programator Arduino.
Konfiguracja programatora
Wybierz menu Narzędzia i w odpowiednich pozycjach ustaw:
- Płytka: NodeMCU 1.0 ESP-12E Module (jeśli korzystasz z rekomendowanej przeze mnie płytki, w przeciwnym wypadku postaraj się znaleźć pozycję odpowiadającą nazwie Twojej płytki),
- Upload speed: 115200,
- CPU Frequency: 80MHz,
- Flash size: 4M (1M SPIFFS),
- Port: wybierz z listy rozwijanej pozycje które zawierają napis „wchusb” i numer – w przypadku posiadania płytki z układem CH340G oraz „SLAB_USBtoUART” dla płytek z układem CP2102 (nazwy portów mogą się nieznacznie różnić w zależności od systemu operacyjnego i wersji sterowników).
Jeżeli w zakładce Port nie widzisz swojego urządzenia, oznacza to, że najprawdopodobniej wystąpił problem podczas instalacji sterowników lub kabel USB źle styka i uniemożliwia wykrycie płytki. Jako pierwszy krok do rozwiązania problemu spróbuj połączyć płytkę innym kablem oraz spróbuj innych gniazd USB w swoim komputerze. Po modyfikacji ustawień naciśnij przycisk „Wgraj” (strzałka w prawo na górnym pasku okna IDE) i obserwuj dolną część okna zawierającą tekst na czarnym tle. Powinien pojawić się pomarańczowy tekst informujący o stanie kompilacji oraz wgrywania programu. Po prawidłowym zaprogramowaniu układu powinieneś ujrzeć widok podobny do tego na poniższym rysunku, a niebieska dioda wbudowana w płytkę powinna migać z sekundowymi przerwami. Brawo, to Twój pierwszy program uruchomiony na Arduino! 🙂 Jeśli pojawiły się jakieś błędy – nie martw się, w dalszej części posta znajdziesz podpowiedzi jak radzić sobie z najczęstszymi problemami.
Omówienie kodu
Dobrze, udało nam się uruchomić pierwszy program, ale jak on właściwie działa? Co oznacza cały ten kolorowy tekst znajdujący się oknie środowiska programistycznego? Zajmijmy się poszczególnymi blokami kodu.
Komentarze
Początek programu stanowią dwa bloki tekstu o szarym kolorze. Są to komentarze – wiadomości, które stanowią swego rodzaju dokumentację zamieszczoną w kodzie programu. Komentarze mogą zawierać wyjaśnienia bardziej zawiłych fragmentów programu. Zazwyczaj dobrze napisany kod jest jednak łatwo zrozumiały i nie powinien ich wymagać. Mogą one zawierać również pomocne notatki. W komentarzach można też spotkać informacje o autorze programu oraz klauzule dotyczące praw autorskich. Obecnie jest to jednak mało sensowne rozwiązanie, ponieważ programy przechowuje się w tzw. repozytoriach, które udostępniają informację o autorze poszczególnych linii kodu. Z tego powodu powinno się też unikać „zakomentowywania” fragmentów kodu „na później”. Świat programowania mikrokontrolerów różni się jednak nieco od programowania na smartfony lub tworzenia stron internetowych. Znacznie częściej można tutaj spotkać stosowanie komentarzy, jest to związane ze specyfiką programowania na mikrokontrolery, które często wykorzystuje skomplikowane operacje na bitach.
Komentarze blokowe
Wiemy już w jakim celu stosuje się komentarze, ale jak właściwie je pisać? W Arduino, jak i większości języków programowania stosuje się dwa typy komentarzy. Na samym początku kodu znajduje się tzw. komentarz blokowy. Tworzy się go poprzez umieszczenie tekstu pomiędzy znakami /* i */ na przykład: /* To jest treść komentarza. */. Komentarz taki może obejmować wiele linii, dlatego często jest stosowany na początku pliku do umieszczenia informacji o programie i jego autorze. Tego typu komentarz może być umieszczony w obrębie linii zawierających kod ponieważ, tak jak wszystkie inne typy komentarzy jest on zupełnie ignorowany przez kompilator. Z tego też powodu nie musimy się martwić o ilość linii jakie on zajmuje – nie zwiększy on rozmiaru programu wgrywanego na nasz mikrokontroler.
Komentarze liniowe
Drugim typem komentarzy są komentarze liniowe. Tworzy się je poprzez dodanie przed treścią komentarza znaków // (na przykład //To jest treść komentarza). Wszystkie znaki od wystąpienia ciągu „//” do momentu znaku końca linii będą ignorowane przez kompilator. Zwykle używa się tego typu komentarzy do objaśniania określonej linii kodu lub do zostawiania notatek „na później”. W przypadku takich notatek stosuje się też ustalane według standardów określonych w dokumentacji IDE znaczników takich jak //TODO:, które są potem grupowane przez środowisko programistyczne jako zadania do wykonania w przyszłości.
Podstawowe funkcje szkicu
Każdy program Arduino jest nazywany szkicem (z ang. Sketch) i musi zawierać dwie podstawowe funkcje: setup oraz loop. Wyjaśnię wkrótce na czym polega ich działanie, zaczniemy jednak od omówienia tego, czym właściwie jest funkcja oraz jak wygląda składnia funkcji w Arduino.
Funkcje w Arduino
Każdy program Arduino składa się z przynajmniej dwóch funkcji. Koniecznie musimy więc zrozumieć czym są funkcje i jak one działają. Jeśli słowo „funkcja” kojarzy Ci się z lekcjami matematyki, to Twoja intuicja bardzo dobrze Ci podpowiada. Każda funkcja (inaczej zwana metodą) przyjmuje bowiem pewne argumenty, na których dokonuje pewnych operacji, aby finalnie zwrócić pewien wynik. Z funkcjami możemy robić dwie podstawowe rzeczy: definiować je oraz je wywoływać. Zacznijmy od tego pierwszego działania. Definiowanie funkcji oznacza po prostu pisanie jej – określanie jakie zmienne wejściowe przyjmuje, co z nimi robi i jaki wynik zwraca. Symboliczna składnia definiowania metod w Arduino prezentuje się następująco:
typ_zwracany nazwaFunkcji(typ_argumentu1 nazwaArgumentu1, typ_argumentu2 nazwaArgumentu2) { //Ciało funkcji return wartosc_zwracana; }
Omówmy teraz elementy tej struktury.
Typ zwracanej wartości
Jako pierwszy określamy typ zwracanego wyniku – każda funkcja może zwracać dowolną wartość całkowitą, zmiennoprzecinkową, łańcuch znaków itp. Konieczne jest jednak poinformowanie kompilatora o tym, jaki jest oczekiwany przez nas zwracany typ danych. Funkcje mogą też nie zwracać żadnej wartości – jako typ wpisujemy wtedy void, co można przetłumaczyć na „pusty”. Wbrew pozorom funkcje takie są bardzo często stosowane, przykładowo do gromadzenia kilku instrukcji, które są często wykonywane w tej samej kolejności, lub z różnymi argumentami.
Nazwa funkcji
Po określeniu typu zwracanej wartości podajemy nazwę funkcji. Powinna ona być zwięzła, ale musi zawierać w sobie wystarczające informacje, żeby zrozumieć jej działanie bez konieczności dalszego jej analizowania. W zależności od języka programowania przyjmuje się też różne zasady pisania nazw funkcji. W przypadku Arduino obowiązuje konwencja zwana „camelCase” np. mojaFunkcja. Będę też stosował angielskie nazwy funkcji, starając się je objaśniać, ponieważ nieformalnie angielski jest uniwersalnym językiem programistów. Nazwy funkcji muszą spełniać kilka zasad: nie mogą zaczynać się od cyfry, zawierać polskich znaków oraz znaków specjalnych (np. „!,.”). Taka sama nazwa funkcji może być stosowana wielokrotnie, jeżeli dwie funkcje będą miały inne argumenty – nazywa się to przeciążaniem nazw funkcji. Można na przykład napisać dwie funkcje pole, z których jedna będzie przyjmowała promień koła, a druga długości boków prostokąta. Nie możemy jednak napisać dwóch funkcji o takich samach nazwach, które będą różniły się wyłącznie zwracanym typem danych. Wyobraź sobie, że napisałbyś dwie funkcje pole, z których jedna zwraca wartość liczbową, a druga tekst. Gdybyśmy próbowali użyć takiej metody, kompilator nie wiedziałby o którą z nich nam chodzi!
Argumenty funkcji
Pisane przez nas metody, oprócz zwracania wartości, mogą przyjmować też argumenty. W celu zdefiniowania argumentów funkcji podajemy w okrągłych nawiasach typ wartości oraz nazwę argumentu. Jeżeli chcemy napisać metodę przyjmującą kilka danych wejściowych, oddzielamy je przecinkami. Kiedy chcemy natomiast napisać funkcję, która nie przyjmuje żadnych wartości wejściowych musimy pozostawić puste nawiasy okrągłe. Oczywiście możemy też napisać metodę, która nie przyjmuje żadnych argumentów, a zwraca jakąś wartość.
Ciało funkcji
Ostatnią częścią definiowanej funkcji jest fragment, który zawiera wykonywane przez daną metodę instrukcje, czyli tak zwane ciało funkcji. Może ono zawierać właściwie dowolną ilość kodu, który realizuje określone działanie. Dobrą praktyką programowania jest jednak pisanie funkcji krótkich, wykonujących jedną, jasno określoną czynność. W dalszej części zobaczymy kilka przykładów funkcji, na razie zapamiętaj tylko, że każda linia kodu wykonywanego w ciele funkcji musi być zakończona średnikiem „;”. W przeciwnym wypadku dojdzie do tzw. błędu kompilacji i nasz program się nie uruchomi. Takie błędy są stosunkowo proste do dostrzeżenia i poprawienia. Niekiedy zdarza się jednak, że program uruchomi się, lecz będzie działał niepoprawnie. Znalezienie brakującego średnika może przysporzyć wtedy wiele nerwów. Ważną informacją jest również to, że ciało funkcji zwracającej jakąś wartość musi kończyć się instrukcją return, np. return 1; . Po napotkaniu takiej instrukcji program „wychodzi” z ciała funkcji i zwraca wynik jej działania w miejscu wywołania funkcji.
Wywoływanie funkcji
Wywoływanie funkcji oznacza po prostu wykorzystywanie ich. Aby to zrobić, wystarczy, że napiszesz nazwę funkcji, a w nawiasach okrągłych wpiszesz argumenty wejściowe z którymi metoda ta ma zostać wykonana. Natomiast o tym co można zrobić ze zwracaną przez funkcję wartością porozmawiamy sobie w jednej z dalszych części kursu. 😉
Funkcja setup
Działanie każdego programu Arduino rozpoczyna się od wywołania funkcji setup. Zgodnie z jej nazwą, wykorzystuje się ją do ustawienia początkowych parametrów programu, skonfigurowania i zainicjalizowania wbudowanych w mikrokontroler modułów oraz podłączonych peryferiów. Instrukcje zawarte w tej funkcji wykonywane są tylko raz, przy każdym ponownym uruchomieniu mikrokontrolera (również po wybudzaniu z uśpienia). W naszym przypadku ciałem funkcji jest komentarz ułatwiający zrozumienie działania przykładu oraz jedna linia kodu odpowiedzialna za konfigurację jednego z pinów mikrokontrolera: pinMode(D4, OUTPUT);. Jak pewnie się domyślasz, jest to przykład wywołania metody z dwoma argumentami, która nie zwraca żadnej wartości. Pełni ona jednak bardzo ważną funkcję.
Funkcja loop
Ostatnim, ale nie mniej ważnym zagadnieniem, które dziś omówimy jest funkcja loop, czyli główna pętla programu. Jest ona wykonywana zaraz po zakończeniu działania funkcji setup, a następnie powtarzana jest w nieskończoność… Pomijając oczywiście sytuacje w których wystąpi jakieś fatalne zdarzenie jak błąd programu, brak zasilania itp. :). To właśnie w ciele tej funkcji umieszczać będziemy większość kodu, który będzie odpowiedzialny za wprowadzanie naszych projektów w życie. W przypadku naszego przykładu widzimy cztery wywołania metod. digitalWrite(D4, HIGH); oznacza ustawienie przygotowanego wcześniej wyjścia w stan wysoki. delay(1000); oznacza pauzę w wykonywaniu programu trwającą 1000ms. Następnie ustawiamy wyjście w stan niski, co jest równoznaczne z wyłączeniem diody oraz ponownie czekamy sekundę. Cykliczne wykonywanie pętli powoduje zatem zapalanie diody na sekundę a potem gaszenie jej również na sekundę.
Ćwiczenie
Omówiliśmy dość dokładnie budowę funkcji w Arduino oraz działanie kodu w funkcji loop. Spróbuj zatem przenieść kod, który odpowiada za zapalanie i gaszenie diody do oddzielnych funkcji. Podpowiem, że nie muszą one zwracać żadnych wartości. Możesz również spróbować napisać funkcję, która przyjmuje wartość czasu opóźnienia jako argument. Tutaj uchylę rąbka tajemnicy, że typ danych całkowitoliczbowych nazywa się int, od angielskiego słowa integer.
Rozwiązywanie problemów
Jeżeli z jakichś względów nie udało Ci się zaprogramować układu nie panikuj, na pewno uda nam się znaleźć rozwiązanie. Podstawowymi typami błędów są błędy w czasie wgrywania programu oraz błędy kompilacji.
Problemy przy wgrywaniu programu
Jeżeli w okienku debuggera pojawił się komunikat error: espcomm_upload_mem failed, oznacza to błąd przy wgrywaniu programu. Upewnij się wtedy, że ustawienia w zakładce Narzędzia są zgodne z tymi prezentowanymi w kursie. Najczęstszym problemem jest brak widoczności płytki w zakładce „Port”. Zazwyczaj przyczyną takich problemów jest kabel USB, który źle styka i rozłącza się lub utrudnia transmisję danych. Spróbuj podłączyć płytkę za pomocą innego kabla, lub używając innego portu USB w komputerze. Innym potencjalnym źródłem problemów jest użycie nieprawidłowych sterowników lub błędy w czasie ich instalacji. Możesz próbować ponownego uruchomienia komputera i reinstalacji sterowników. Nie powinno być to jednak problemem, jeśli już wcześniej widziałeś sterownik płytki w zakładce „Port”.
Błędy kompilacji
Błędy kompilacji pojawiają się, kiedy w kodzie programu zawrzesz instrukcje, których nie jest w stanie zrozumieć kompilator. Kompilator jest narzędziem, które zamienia kod, który piszesz w informacje zrozumiałe dla programowanego przez nas układu. Jeśli więc popełnisz w kodzie literówkę, przykładowo wywołasz metodę pinMode z argumentem d4 zamiast D4 lub zapomnisz średnika na końcu linii, pojawi się błąd. Arduino zakreśli linijkę zawierającą błąd i wyświetli opis przyczyny błędu w oknie debuggera. Koniecznie popróbuj zmusić środowisko programistyczne do „wyplucia” różnego typu błędów psując kod przykładowego programu.
Podsumowanie
Wow, nawet nie zauważyłem kiedy przekroczyliśmy próg 2500 słów. 🙂 Niestety, zdaję sobie sprawę, że to naprawdę sporo informacji jak na pierwszy raz, masz prawo czuć się przytłoczony. Odwaliliśmy dziś jednak kawał dobrej roboty. Udało nam się zainstalować Arduino i skonfigurować je do pracy z NodeMCU. Napisaliśmy nasz pierwszy program i poznaliśmy podstawową budowę programu Arduino. Dowiedzieliśmy się jakie są typy komentarzy w kodzie i w jakich sytuacjach możemy je stosować. Poznaliśmy też masę ciekawostek o funkcjach. W następnej części dowiemy się co oznacza tajemniczy skrót UART. Poznamy też niezastąpioną sztuczkę, która pomoże nam sprawdzać co dzieje się w naszym układzie. Jeśli starczy nam czasu, omówimy też szerzej temat obsługi wejść i wyjść naszego układu. Do zobaczenia wkrótce! 🙂