Witaj w kolejnej części naszego kursu Arduino! Dzisiaj dowiemy się co oznacza tajemniczy skrót UART oraz czym są zmienne globalne i lokalne. Poznamy też podstawowe operatory arytmetyczne. Do dzisiejszych zajęć wciąż wystarczy nam jedynie płytka NodeMCU (lub tak naprawdę dowolna inna płytka zgodna z Arduino). Bez zbędnej zwłoki zaczynamy więc omawianie dzisiejszych zagadnień, które są szczególnie ważne, ponieważ ułatwią nam dalszą pracę z Arduino w ogromnym stopniu!
UART
Skrót UART oznacza z angielskiego Universal Asynchronous Receiver-Transmitter, czyli Uniwersalny Asynchroniczny Odbiornik-Nadajnik. Jest to moduł sprzętowy wbudowany w mikrokontroler, który umożliwia komunikację pomiędzy dwoma urządzeniami. W przypadku Arduino moduł ten wykorzystujemy na przykład do wgrywania programu do pamięci mikrokontrolera. Bardzo często UART pozwoli nam też „podejrzeć” co dzieje się w trakcie wykonywania programu i pomoże nam szukać błędów. Myślę, że czujesz już, jak istotny jest to temat. Przybliżę więc podstawowe cechy komunikacji UART i przejdziemy do przykładów.
Komunikacja UART odbywa się w sposób asynchroniczny. Oznacza to, że w przeciwieństwie do komunikacji synchronicznej, do transmisji danych nie jest wykorzystywany wspólny sygnał zegarowy, który umożliwiałby synchronizację dwóch urządzeń. Zamiast tego wykorzystywane są tak zwane sygnały synchronizacyjne określające początek i koniec nadawanej wiadomości. Upraszcza to zatem realizację sprzętową takiej komunikacji.
Transmisja szeregowa oznacza, że dane przesyłane są bit po bicie. Układ UART posiada specjalny bufor, który umożliwia zmagazynowanie kilku transmitowanych bitów i „sklejenie” ich w bajty danych po stronie odbiornika. Ten typ transmisji danych posiada tę zaletę, że wystarczą dwa przewody (masa – wspólny punkt odniesienia i sygnał), aby umożliwić komunikację dwóch urządzeń w jedną stronę. Jest to przeciwieństwo komunikacji równoległej, do której wykorzystuje się wiele przewodów, aby jednocześnie przesyłać kilka bitów danych.
Komunikacja z wykorzystaniem UART wymaga, aby oba urządzenia były skonfigurowane identycznie. Konfiguracja taka dotyczy innymi na przykład szybkości transmisji, jednak pod uwagę należy wziąć nieco więcej czynników. W przypadku Arduino, a zwłaszcza komunikacji między płytką a środowiskiem programistycznym sprawa jest znacznie prostsza. Szczegóły standardu UART omówimy w przyszłości w oddzielnym artykule, natomiast teraz nauczymy się jak wykorzystywać ten użyteczny moduł.
Inicjalizacja UART
Zanim zaczniemy wykorzystywać port szeregowy, musimy go najpierw zainicjalizować. Do wykonywania wszelkich operacji z wykorzystaniem portu szeregowego służy klasa Serial. O klasach i obiektach porozmawiamy w dalszej przyszłości, ponieważ jest to bardzo rozległy temat. Na tę chwilę ważne jest tylko, żebyś pamiętał, że żeby wywołać metody związane z portem szeregowym musimy napisać najpierw „Serial.” (kropka oznacza wywołanie funkcji z danej klasy). Przyjrzyjmy się więc przykładowej funkcji setup, która inicjalizuje złącze szeregowe i powoduje wypisanie informację o gotowości modułu.
void setup() { Serial.begin(115200); Serial.println("Arduino UART is ready!"); }
Do inicjalizacji portu szeregowego wykorzystywana jest funkcja begin. Przyjmuje ona jako parametr liczbę całkowitą, która określa prędkość transmisji. W przypadku płytek NodeMCU będziemy wykorzystywać wartość 115200 bitów na sekundę, co jest taką samą wartością jak wybrana przez nas w poprzedniej części prędkość wgrywania programu przez ten sam port.
Kiedy moduł komunikacji szeregowej zostanie poprawnie zainicjalizowany, wykorzystujemy metodę println (skrót od print line – wypisz linię). Funkcja ta ma wiele odmian, dzięki czemu możemy wywoływać ją z różnymi typami argumentów np. liczbami całkowitymi, znakami ASCII lub tak jak w naszym przykładzie z łańcuchami tekstowymi, czyli obiektami typu String. Funkcja ta wysyła komunikat poprzez port szeregowy i kończy go przejściem do nowej linii. Podobną funkcją jest print, różni się ona jedynie tym, że nie wysyła znaku nowej linii na końcu wiadomości.
Monitor portu szeregowego
Wiesz już mniej więcej jak zainicjalizować UART i wysłać z jego pomocą wiadomość. Jednak jak sprawdzić działanie tego portu? Do tego celu służy narzędzie wbudowane w środowisko programistyczne Arduino nazwane Monitor portu szeregowego. Jak się domyślasz, aby je uruchomić musisz wejść w menu Narzędzia i wybrać pozycję o wspomnianej nazwie. Twoim oczom ukaże się okno jak na obrazku poniżej. Omówmy teraz rolę poszczególnych pól.
W najwyższej części okna znajduje się pole tekstowe (1), za pomocą którego możemy wysyłać komunikaty do połączonej z komputerem płytki. Biały obszar oznaczony (2) to okno, w którym pojawiają się komunikaty odebrane od płytki wykonującej nasz program. Powinniśmy w nim zobaczyć nasz komunikat „Arduino UART is ready!”.
Na samym dole okna znajduje się kilka kontrolek ułatwiających przeglądanie danych. Większość z nich jest całkiem oczywista, ciekawsze są właściwie tylko listy z opcjami do wyboru (3) i (4). Pole (3) oznacza sposób zakończenia wiadomości wysyłanej do płytki. Upewnij się, że zaznaczoną wartością jest „Nowa linia”, będziemy wykorzystywać to ustawienie w dalszej części dzisiejszego odcinka. Lista (4) zawiera listę obsługiwanych przez Arduino prędkości transmisji, upewnij się, że zaznaczona jest wartość 115200.
Mam nadzieję, że widzisz wysłany przez Twoją płytkę komunikat. Jeśli coś działa nie prawidłowo sprawdź porady na końcu tego postu. W przeciwnym wypadku lecimy dalej i przechodzimy do omówienia zmiennych globalnych.
Zmienne globalne
Czym właściwie są zmienne? Dosyć popularnym porównaniem obrazującym ich działanie jest nawiązanie do szufladek. Każda zmienna jest jak szufladka, do której możesz włożyć tylko jeden typ przedmiotu (typ danych). Oznacza to, że do jednej szufladki (zmiennej) możesz włożyć tylko śrubki (liczby całkowite), a do innej tylko nakrętki (łańcuchy tekstowe). Wartości przechowywane w szufladkach mogą być stałe (np. w szufladce stale przechowujemy pudełko zapałek), lub zmienne, gdy do szufladki wkładamy lub wyjmujemy śrubki. O wartościach stałych opowiemy nieco później. Teraz dowiemy się co oznacza „globalność” zmiennych.
Zmienne mogą być zadeklarowane w obrębie pisanych przez nas funkcji (np. setup, loop i dowolnej innej). Deklarowanie oznacza informowanie kompilator, że taka szufladka kiedyś nam się przyda, ale nie umieszczamy w niej jeszcze żadnych przedmiotów. Możemy wtedy używać takiej szufladki jedynie w obrębie danej funkcji i tylko od tej linii, w której została dokonana inicjalizacja (czyli przypisanie wartości początkowej). Mówi się, że jest to jej zakres widoczności. O zmiennych lokalnych porozmawiamy omawiając przykład z odczytem danych przez port szeregowy.
Zmienne globalne są natomiast widoczne w każdym miejscu pisanego przez nas programu. Jak się domyślasz, jest to całkiem wygodne – możesz sięgnąć do szufladki z każdego miejsca w pokoju. Często ta wygoda jest jednak zdradziecka, bo wszystkie metody (współlokatorzy) mogą zacząć w takiej szufladce „grzebać” i zmieniać jej zawartość. Z tego powodu należy szczególnie uważać stosując zmienne globalne – powinno ich być jak najmniej i nie powinny być współdzielone przez wiele metod. Stałe i zmienne globalne piszemy zazwyczaj na samej górze pliku zawierającego kod – zwiększa to przejrzystość i ułatwia orientację w kodzie. Dodaj więc na górze pliku dwie zmienne globalne:
int powerOfTwo = 1; int exponent = 0;
Będziemy w nich przechowywać wyniki kolejnych potęg liczby 2 oraz wykładnik potęgi. Jak widzisz int powerOfTwo; oznacza deklarację i inicjalizację zmiennej globalnej o nazwie powerOfTwo typu int, czyli w „szufladce” przechowywać będziemy zmienne całkowitoliczbowe. int powerOfTwo = 1; oznacza inicjalizację zmiennej globalnej, czyli deklarację oraz przypisanie do niej wartości początkowej. Przejdźmy do omówienia pierwszego przykładu i napiszmy zawartość funkcji loop!
Przykład 1 – Obliczanie potęgi liczby 2
W przykładzie tym będziemy wykorzystywali kod, który do tej pory napisaliśmy. Dopisz metodę loop. Na jej początku wypisz komunikaty, które wyświetlą tekst „2 do potęgi (wartość wykładnika) równa się (wartość potęgi)”. Pamiętaj, że funkcje print i println mogą jednocześnie przyjmować tylko jeden typ danych. Jeśli brakuje Ci pewności zajrzyj do mojego repozytorium, w którym znajduje się całość kodu omawianego w dzisiejszym przykładzie (w języku angielskim!). Na koniec funkcji loop dopisz opóźnienie (delay) o wartości 1000ms, aby ograniczyć nieco częstotliwość pojawiania się komunikatów. Uruchom program i sprawdź jak wyświetlane są Twoje komunikaty. Popracuj nad formatowaniem tekstu, o ile to konieczne używając białych znaków oraz odpowiednio używając metod print oraz println (która, jak przypominam, przechodzi do nowej linii).
Jak widzisz nasz program jest strasznie nudny – wypisuje w kółko ten sam tekst! Czas go trochę ożywić i zacząć „grzebać w naszych szufladkach”. Do tego posłuży nam wiedza o podstawowych operatorach arytmetycznych!
Operatory arytmetyczne
Operatory arytmetyczne, jak wskazuje nazwa, umożliwiają wykonywanie działań matematycznych z wykorzystaniem wartości zawartych w stałych i zmiennych oraz zapisywanie wyników tych operacji w zmiennych. Podstawowymi operatorami arytmetycznymi w C++ i w Arduino są:
- „+” – dodawanie
- „–” – odejmowanie
- „*” – mnożenie
- „/” – dzielenie
- „%” – modulo, reszta z dzielenia
- „=” – operator przypisania, wykorzystywany do zmodyfikowania wartości przechowywanej w zmiennej będącej po lewej stronie operatora.
Operacje arytmetyczne odczytywać należy od prawej do lewej strony. Oznacza to, że zapis:
powerOfTwo = exponent + 3;
Należy odczytać jako: „do wartości zmiennej exponent dodaj 3 i przypisz wynik do zmiennej o nazwie powerOfTwo” (pomijam fakt, że ten przykład nie ma sensu ze względu na nazwy zmiennych).
Inkrementacja / Dekrementacja
Oprócz podstawowych operatorów bardzo często będziemy się spotykać z operatorami „++„, czyli inkrementacją oraz „—„, czyli dekrementacją. Powodują one odpowiednio zwiększenie lub zmniejszenie wartości przechowywanej w zmiennej o 1, wraz z jednoczesnym przypisaniem nowej wartości do tej zmiennej. Jest to po prostu skrócenie zapisu, który jest bardzo często wykorzystywany. Oznacza to, że zapisy
exponent++;
oraz
exponent = exponent + 1;
są sobie równoważne. Zwróć uwagę, że ta sama zmienna może występować po obu stronach działania. Oznacza to, że najpierw jej stara wartość będzie wykorzystywana w działaniu umieszczonym po prawej stronie operatora „=„, a następnie wynik zostanie w niej zapisany.
Operatory jednoargumentowe
Inkrementacja i dekrementacja nie są jedynymi skróconymi operatorami arytmetycznymi. Wszystkie podstawowe operatory arytmetyczne można połączyć z operatorem przypisania, na przykład:
exponent += 1;
oznacza dokładnie to samo, co dwa wcześniejsze zapisy i powoduje zwiększenie wartości zmiennej exponent o 1 i zapisanie nowej wartości.
Pamiętać należy, że operacje arytmetyczne należy wykonywać na zmiennych tego samego typu, inaczej zacznie dochodzić do sytuacji, w których wyniki przestaną być zgodne z naszymi oczekiwaniami. W szczególności należy uważać na dzielenie, które powinno być wykonywane na typach zmiennoprzecinkowych. W przeciwnym wypadku kompilator sam zastosuje zaokrąglenia do wartości całkowitych, co zazwyczaj przyniesie nam wartość działania odmienną od oczekiwanej. Weź też pod uwagę, że nie wszystkie działania są tak samo trudne dla naszego mikroprocesora – dzielenie jest dla niego znacznie bardziej złożone od dodawania. Nieprzypadkowo użyłem tutaj słowa „złożone”, ten temat omówimy dokładniej w lekcji o złożoności obliczeniowej i optymalizowaniu programów.
Obliczanie potęgi liczby 2
To na razie wszystko, co powinniśmy wiedzieć o operatorach arytmetycznych. Wróćmy do naszego przykładu z obliczaniem potęgi liczby 2. Zastanówmy się co musimy zrobić z naszymi zmiennymi globalnymi, żeby wykonać zadanie. Pierwsze wartości zawarte w naszych zmiennych są zgodne z prawdą: 2 do potęgi 0 wynosi 1. Operacje na zmiennych muszą więc znaleźć się za instrukcjami wysyłającymi komunikaty przez port szeregowy.
Zmienna exponent oznacza wykładnik potęgi, w każdym obiegu pętli powinna być zatem zwiększana o 1, znamy już na szczęście aż 3 sposoby zapisu takiego działania. 🙂
powerOfTwo przechowywać będzie wartość potęgi liczby 2. Przyjrzyjmy się kilku kolejnym wartościom: 1, 2, 4, 8… Można zauważyć, że każda następna wartość, to wartość poprzednia pomnożona razy 2. Istnieje też alternatywny zapis, który możesz podejrzeć w repozytorium. Zastanów się chwilę nad tym jak odczytać tę nietrywialną linijkę 😉
Po dopisaniu linijek wykonujących obliczenia uruchom program i poobserwuj chwilę jego działanie. Radość z działania programu będzie trwała ok. 30 sekund. Wtedy nastąpi bowiem coś strasznego – przepełnienie zakresu typu zmiennej int. Monitor portu szeregowego jako wynik pokaże najpierw dużą wartość ujemną, a następnie będzie stale wskazywał 0 jako wynik potęgi. Jest to kolejne cenne doświadczenie – zmienna każdego typu ma swoją minimalną oraz maksymalną wartość. Nazywa się to zakresem wartości danego typu i zależy od kilku czynników – m.in. architektury procesora (np. 8bitowa lub 64bitowa). Często celowo będziemy wprowadzali takie ograniczenia w celu optymalizacji wykorzystywanej przez program ilości pamięci. Będziemy musieli jednak nauczyć się opanowywać takie sytuacje i nie dopuszczać do przypadkowego przekraczania zakresu. Instrukcje, które mogą nam w tym pomóc poznamy w następnej części kursu, dziś nie będziemy się jednak przejmować tym zjawiskiem. 😉
Przykład 2 – Obliczanie obwodu koła. Odczytywanie danych poprzez UART.
Czas przejść do drugiego przykładu, w którym zapoznamy się z odczytywaniem danych przez moduł UART. Dane wykorzystamy do obliczania obwodu koła. Przy okazji poćwiczymy wykorzystywanie operatorów arytmetycznych i przekonamy się o ograniczeniach zmiennych lokalnych. Zacznij od utworzenia nowego Szkicu, a następnie napisz funkcję setup inicjalizującą moduł UART i wypisującą komunikat o gotowości. Dopisz do niej linię zachęcającą użytkownika do wpisania wartości promienia koła (mała podpowiedź – po komunikacie nie powinien być dodany znak nowej linii). Następnie zajmiemy się funkcją loop. Pojawi się w niej sporo nowych elementów, zaprezentuję więc ją poniżej, a następnie po kolei omówimy nowości.
void loop() { if(Serial.available()) { String serialInput = Serial.readStringUntil('\n'); Serial.print(serialInput); Serial.print('\t'); Serial.print("Enter radius value: "); } }
Odczytywanie danych z UART
Najważniejszym elementem głównej pętli programu jest tzw. instrukcja warunkowa if, czyli po polsku „jeżeli”. Instrukcja ta sprawdza prawdziwość warunku logicznego zawartego w nawiasach okrągłych. W naszym przypadku jest tam wywołana metoda Serial.available(), która zwraca ilość bajtów danych dostępnych do odczytania. Jeśli zwrócona wartość jest większa od zera, wykonane zostaną instrukcje zawarte w nawiasach klamrowych, w przeciwnym wypadku program będzie ponawiał wykonywanie pętli programu, czyli będzie kontynuował sprawdzanie zawartości bufora modułu UART. Instrukcje warunkowe omówimy dokładniej w następnej części kursu, na razie wystarczy nam podstawowe zrozumienie działania naszego programu.
Kolejna linia przynosi kilka kolejnych ciekawostek. Zacznijmy od najważniejszego elementu – wykorzystania metody Serial.readStringUntil(). Funkcja ta powoduje odczytanie danych zawartych w buforze UART aż do momentu wystąpienia znaku, który ustalamy jako symboliczny koniec wiadomości. W naszym przypadku znakiem tym jest ’\n’, poświęćmy jednak chwilę, żeby zrozumieć co to oznacza.
Typ danych char
Poznajemy właśnie nowy typ danych – char (z ang. character – znak [w druku]), który służy do reprezentowania znaków ASCII. Typ ten zajmuje jeden bajt danych, ponieważ to wystarczy do przedstawienia wszystkich podstawowych znaków ASCII, które są reprezentowane jako wartość liczbowa z zakresu od 0 do 127. Tabela przedstawiająca najpopularniejsze dostępne znaki i odpowiadające im wartości liczbowe znajduje się tutaj. Oznacza to, że zmienną typu char, możemy zainicjalizować zarówno używając wartości liczbowej, jak i znaku ASCII, który musimy zawrzeć pomiędzy dwoma średnikami (np. 'A’). Poniżej znajdziesz przykład dwóch równoważnych sposobów inicjalizacji zmiennej char:
char literaA = 'A'; char literaA = 65;
W naszym przypadku wykorzystujemy znak ’\n’, który oznacza znak przejścia do nowej linii (z ang. newline). W dalszej części kodu widzimy też znak specjalny '\t’, który oznacza tabulator – dodaje tzw. „biały znak”, który zwiększa czytelność tekstu.
Zmienne lokalne
Kolejną nowością w omawianej linii jest inicjalizacja zmiennej lokalnej typu String o nazwie serialInput. Jako wartość jest w niej zapisywany łańcuch tekstowy zwracany przez funkcję Serial.readStringUntil(’\n’). Zakres życia zmiennej lokalnej ograniczany jest zazwyczaj przez nawiasy klamrowe, w tym przypadku te, które są przypisane do instrukcji if. Linia Serial.print(serialInput) wypisuje zawartość zmiennej serialInput przez port szeregowy. Spróbuj dodać ją za nawiasem klamrowym zamykającym ciało instrukcji warunkowej if. Możesz również spróbować napisać własną funkcję (nie musi nic zwracać, ani przyjmować żadnych argumentów), która będzie próbowała wykonać tę linię kodu. Zwróć uwagę jaki błąd kompilacji pojawi się w dolnej części okienka Arduino, możesz zobaczyć efekt jaki pojawił się u mnie na obrazku poniżej.
Obliczanie obwodu koła
Reszta instrukcji znajdujących się w pętli loop powinna być już w tym momencie zrozumiała. Znajdują się tam już tylko wypisania komunikatów tekstowych przez UART i zachęta do wpisania kolejnej wartości promienia koła. Zwróć uwagę na „pustą” linię – znalazła się tu ona nieprzypadkowo. W tym miejscu dopiszemy instrukcję wyświetlającą obwód okręgu, kiedy już napiszemy funkcję, która go oblicza. Zajmijmy się tym zatem!
Spróbuj napisać tę funkcję samodzielnie, w razie problemów rzuć okiem na kod w repozytorium. Funkcja musi przyjmować argument typu float, a zwracać wartość typu String. Podpowiem Ci też, że wyliczona wartość obwodu również będzie typu float. Aby zawrzeć ją w łańcuchu tekstowym możesz posłużyć się operatorem rzutowania String(nazwa_zmiennej). Musisz też wiedzieć, że dwie zmienne typu String mogą zostać „sklejone”, za pomocą operatora +, aby utworzyć nową zmienną typu String. We wzorze na obwód koła pojawia się też pewna stała matematyczna – pi. Wspomnimy więc teraz o tym jak deklarować wartości stałe.
Wartości stałe
Wartości stałe, zarówno zmienne globalne jak i lokalne, możemy definiować podobnie jak wartości zmienne. Przed typem danych musimy zawrzeć jednak słowo kluczowe const (z ang. constant – stały), przykładowo:
const double pi = 3.14;
Wartości stałe muszą być zainicjalizowane w momencie deklaracji (w przeciwieństwie do zmiennych, których wartość możemy ustawić w dowolnym momencie). Jest to całkiem zrozumiałe, ponieważ zaraz po utworzeniu stałej nie możemy zmienić w żaden sposób przypisanej do niej wartości. Z tego względu możemy śmiało tworzyć stałe globalne – ich wartość nie zostanie naruszona przez żadną funkcję i nie wprowadzi to zamieszania w kodzie.
Obliczanie obwodu koła – ciąg dalszy
Mam nadzieję, że udało Ci się napisać funkcję obliczającą obwód koła i zwracającą wynik w postaci tekstu. Możesz wywołać ją w obrębie metody Serial.println() żeby wyświetlić wynik w Monitorze portu szeregowego. Musimy jednak wywołać Twoją metodą z argumentem – wartością promienia koła. Żeby to zrobić, możesz wywołać na zmiennej serialInput metodę toFloat() (czyli serialInput.toFloat()), która zwraca łańcuch tekstowy skonwertowany do zmiennej typu float. Należy zauważyć, że żeby konwersja się powiodła łańcuch tekstowy musi zaczynać się od ciągu cyfr, a jeżeli chcemy zamieścić część dziesiętną, musimy użyć kropki (np. 123.4). Pozostałe znaki niebędące cyframi lub niekompatybilne łańcuchy tekstowe nie zostaną skonwertowane lub wynikiem konwersji będzie 0. W moim przypadku linia ta wygląda następująco:
Serial.println(circleCircumference(serialInput.toFloat()));
Zwróć uwagę, że jest to wywołanie funkcji toFloat() jako argument funkcji circleCircumference (z ang. obwód okręgu), która jest wywoływana jako argument funkcji Serial.println 😀 Nie jest to najbardziej czytelny zapis, chciałem Ci jednak pokazać, że takie cuda są możliwe i czasem można się na nie natknąć. Przypominam, że całość kodu możesz znaleźć tutaj, jeżeli masz jakieś problemy.
Jeżeli wszystko poszło ok, to w Monitorze portu szeregowego, będziemy mogli w najwyższym polu wpisać wartość liczbową, a po kliknięciu przycisku Wyślij z prawej strony, wyświetli nam się wynik stanowiący obwód okręgu. Brawo, to już był kawał programowania! 🙂
Podsumowanie
Uff, kolejny solidny kawał materiału za nami! 🙂 Nauczyliśmy się dziś czym jest i do czego możemy wykorzystać moduł UART, czym się różnią zmienne lokalne od globalnych i jak zamienić je w wartości stałe. Poznaliśmy też nowe typy danych i nauczyliśmy się „podglądać działanie programu” w Monitorze portu szeregowego. To naprawdę fundamentalna wiedza, konieczna do dalszej pracy z kodem. Być może omówione dziś przykłady nie były zbyt fascynujące, jednak już wkrótce przejdziemy do wykonywania ciekawszych projektów, obiecuję!
W następnej części kursu omówimy kolejne niesamowicie użyteczne elementy składni języka C++ – instrukcje sterujące oraz pętle i nierozłączne z nimi warunki logiczne. Powiemy też sobie nieco więcej o wykorzystywaniu wejść i wyjść cyfrowych mikrokontrolera i o poziomach logicznych. Jeszcze raz zachęcam Cię do samodzielnych eksperymentów z kodem. Spróbuj dopisać inne funkcje, które będą obliczały inne rzeczy, na przykład pole koła lub objętość kuli. Daj mi znać w komentarzu jak poradziłeś sobie z częściami, które sugerowałem jako zadanie do samodzielnego wykonania i jakie pomysły na własne funkcje udało Ci się zakodzić! 😉
Rozwiązywanie problemów
Najczęstszym problemem z jakim możesz się spotkać jest pojawienie się w oknie Monitora portu szeregowego ciągu niezrozumiałych znaków. Zazwyczaj oznacza to zły dobór prędkości komunikacji przy wywołaniu funkcji Serial.begin(). Upewnij się, że w dolnej części Monitora portu jest ustawiona taka sama wartość jak w Serial.begin(). Jeśli problemy wciąż się pojawiają spróbuj obniżyć tę wartość do jednej z tych dostępnych w rozwijanym menu w oknie Monitora. Jeśli wykonujesz inne eksperymenty ze swoją płytką mogłeś też próbować podłączać zewnętrzne moduły do pinów oznaczonych RXD oraz TDX. Są to piny wykorzystywane przez UART, jeśli podłączysz do nich elementy zewnętrzne, może to zakłócić transmisję danych. Inną potencjalną przyczyną jest źle stykający kabel USB lub problemy ze sterownikami.
Innym typem błędów na jakie możesz się dziś natknąć są błędy kompilacji. Mogą one wynikać z zapominania o średniku na końcu linii, z popełniania literówek oraz z prób wykonywania niedozwolonych operacji (używanie niezgodnych typów danych itp.). Spróbuj odczytać pomarańczowe komunikaty na dole środowiska programistycznego, zrozumieć je (pomóż sobie Google Translate w razie problemów z językiem). Jeżeli nie będziesz w stanie samodzielnie zlokalizować problemu, napisz komentarz, postaram się pomóc. 🙂