Kurs Arduino – #2 – podstawy programowania, porty I/O

Kurs Arduino – #2 – podstawy programowania, porty I/O

W drugiej części kursu Arduino zaczniemy pisać programy. Na początku oczywiście zajmiemy się kompletnymi podstawami.

Arduino operuje na dostosowanym do platformy języku C. Artykuł ten wprowadzi więc w podstawy programowania w C i przedstawi jego praktyczne wykorzystanie na przykładzie portów I/O.

Podstawowy szkielet programu Arduino

Każdy program komputerowy jest zbiorem rozkazów. Dedykowany układ zwany licznikiem rozkazów wywołuje kolejne rozkazy jeden po drugim. W języku C wszystkie instrukcje, które chcemy wykonać, umieszczamy w funkcji main (o funkcjach dokładniej później), która wygląda tak:

Uwaga! Musisz to zapamiętać!
Symbolami "//" oznaczamy komentarze. Czyli informacje mieszczące się w jednej linijce, które pomagają człowiekowi w zrozumieniu programu. Podczas kompilacji zostają one pominięte. Jeśli chcemy umieścić dłuższy komentarz, to należy zawrzeć go /* w takich symbolach */.

W Arduino pewne sprawy są uproszczone. Otóż w każdym programie najpierw niektóre instrukcje wykonują się jednorazowo, a następnie inne, wykonują się w pętli.

W praktyce pierwsza funkcja będzie zawierała najczęściej pewne ustawienia. Dzięki nim dane piny mikrokontrolera będą ustawione jako wejścia lub wyjścia. Uruchomimy bardziej zaawansowane peryferia oraz wykonamy akcje, które mają dziać się tylko raz, po włączeniu zasilania.

W drugiej funkcji umieścimy właściwy kod aplikacji, który będzie wykonywał się cały czas (w pętli). Dużo łatwiej zrozumiesz to na praktycznych przykładach, które znajdują się dalej.

Funkcje - co oznaczają te zapisy?

Funkcje można pisać samodzielnie, można również korzystać z gotowych, dostarczanych przez producentów lub przez maniaków programowania, którzy zechcieli podzielić się własnym kodem. Na początku kursu skupimy się na wykorzystywaniu funkcji dostarczanych w bibliotekach razem z kompilatorem Arduino. Nie zaszkodzi jednak odrobina szczegółów na ich temat.

W języku C istnieje pojęcie funkcji. Skojarzenie z matematyką jest tutaj trafne. Otóż okazuje się, że funkcja w językach programowania to blok (lista) pewnych wyodrębnionych rozkazów z głównego kodu, których wykonanie zapewnia jakiś wynik.

Każda funkcja może przyjmować pewną ilość argumentów oraz (standardowo) zwracać jeden wynik. Programista może określać, jakimi wartościami będą wynik oraz dane wejściowe. Każda funkcja ma swój typ (czyli rodzaj zwracanego wyniku) - może być liczbą, znakiem lub czymś jeszcze innym. Istnieje też specyficzny rodzaj funkcji – nie zwracający wyniku (ma ona wtedy przedrostek void).

Skupmy się na głównych funkcjach w programach pisanych na Arduino.

Procedura setup ma za zadanie wykonać jednorazowo blok instrukcji, które się w niej znajdują. Jak wskazuje nazwa przeznaczona jest głównie do ustawień. W niej inicjalizowany jest procesor, konfigurowane są peryferia itd.

Funkcja (procedura) loop jest pętlą nieskończoną. Znajdują się w niej instrukcje, które powinny być zapętlone (wykonywać się cały czas). Przejdźmy do przykładów praktycznych.

Zestaw elementów do kursu

Gwarancja pomocy na forum Błyskawiczna wysyłka

Teraz możesz kupić zestaw ponad 70 elementów niezbędnych do przeprowadzenia ćwiczeń z kursu u naszych dystrybutorów!

Kup w Botland.com.pl

Wyprowadzenia ArduinoUNO

Tak jak zostało powiedziane w poprzedniej części, korzystając ze specjalnych złącz, do Arduino można podłączyć elementy zewnętrzne takie jak np.: diody i przyciski. Zanim jednak do tego dojdziemy musimy poznać opis wyprowadzeń. Poniżej widoczny jest rysunek z zaznaczonymi najważniejszymi sygnałami wychodzącymi z Arduino UNO:

Kolorem ciemnozielonym (nr od 0 do 19) zaznaczono uniwersalne, cyfrowe piny wejścia/wyjścia (I/O). Gdy będziemy wykorzystywać je w roli wyjść będziemy mogli na nich ustawić 0V (logiczne 0, stan niski) lub 5V (logiczne 1, stan wysoki). Gdy zostaną skonfigurowane w roli wejść będą mogły wykrywać połączenie pinu z 0V lub 5V.

Kolorem jasnozielonym zaznaczone zostały wejścia analogowe (A0-A5). Są to wyjątkowe piny, które pozwalają na pomiar napięcia (w zakresie 0-5V). Jak widać numeracja tych wejść pokrywa się z pinami uniwersalnymi (numery od 14 do 19).  Praca w trybie analogowym to dodatkowa funkcja tych pinów.

Na niebiesko zaznaczone zostały alternatywne funkcje dla poszczególnych sygnałów. Oznacza to, że oprócz bycia standardowym wejściem lub wyjściem mogą one pełnić bardziej skomplikowane funkcje. Zajmiemy się nimi później, na ten moment wystarczy jedynie podstawowe wyjaśnienie:

  • SDA, SCL - wyprowadzenia magistrali I²C wykorzystywanej np.: do komunikacji z bardziej zaawansowanymi czujnikami, wyprowadzenia tych pinów są zdublowane (znajdują się w lewym dolnym i prawym górnym rogu płytki - to są dokładnie te same sygnały),
  • TX, RX - interfejs UART, wykorzystywany głównie do komunikacji z komputerem,
  • PWM - wyprowadzenia, na których możliwe jest generowanie sygnału prostokątnego o zmiennym wypełnieniu. Bardzo przydatna funkcja np.: przy sterowaniu serwomechanizmami,
  • LED - dioda świecąca, wbudowana na stałe w Arduino, która połączona jest z pinem nr 13.

Kolor pomarańczowy to wyprowadzenia które nie są programowalne. Odpowiadają one głównie za zasilanie układu. Zostaną one dokładniej omówione, gdy przyjdzie pora na ich wykorzystanie.

Złącza opisane na powyższej grafice jako ICSP służą do bezpośredniego programowania dwóch mikrokontrolerów, które znajdują się na płytce Arduino UNO. Złącza te wykorzystywane są w bardzo specyficznych przypadkach i na tym etapie nie ma najmniejszej potrzeby, aby się nimi zajmować.

Wyjścia w praktyce - dioda LED

Teraz zajmiemy się rzeczą najprostszą, włączymy diodę LED. Zgodnie z powyższym opisem możemy do tego wykorzystać dowolny pin I/O. Na początek wybierzmy wyjście cyfrowe nr 8. Wyjście cyfrowe - jest to wyjście, które możemy ustawić w jeden z dwóch stanów. Niski lub wysoki. W przypadku Arduino będzie to 5V lub 0V.

Układ należy podłączyć zgodnie z poniższym rysunkiem. Diodę łączymy szeregowo z rezystorem (330R). Następnie dłuższą nóżkę diody (anodę) łączymy z wyprowadzeniem nr 8. Drugą, przez rezystor z masą, którą znajdziemy w złączu zasilania (opisaną jako GND). Na płytce znajdują się 3 wyprowadzenia opisane jako GND. Możemy wybrać dowolne. Więcej informacji na temat płytek stykowych znaleźć można w artykule: Jak działa płytka stykowa?

Podłączenie diody do Arduino.

Podłączenie diody do Arduino.

Programowe włączenie diody jest bardzo proste. Podłącz Arduino do komputera za pomocą kabla USB. Uruchom Arduino IDE i przepisz poniższy kod. Następnie wgraj go na płytkę. Opis tej czynności znajdziesz w pierwszej części kursu.

Funkcja pinMode(Pin, Tryb) umożliwia wybranie, czy dany pin jest wejściem, czy wyjściem. Pin może być liczbą całkowitą z zakresu od 0 do 13, zaś Tryb to:

  • INPUT,
  • OUTPUT,
  • INPUT_PULLUP.

Jeżeli chcemy sterować wyjściem, to zawsze używamy trybu Output. Pozostałe tryby zostaną omówione później.

Dzięki takiej konfiguracji możemy ustawić stan logiczny na wyjściu (i dzięki temu włączyć diodę). Do tego celu służy funkcja digitalWrite(Pin, Stan). Stan jest stanem logicznym, który może być HIGH bądź LOW (wysoki bądź niski).

W naszym przykładzie dioda została już podłączona do masy, dlatego Arduino musi doprowadzić do niej stan wysoki stąd digitalWrite(8, HIGH);.

Po jednorazowym ustawieniu pinu w stan wysoki jego wartość nie zmieni się do momentu, gdy sami ustawimy mu inną wartość. W związku z tym, program taki jak powyższy sprawi, że dioda będzie świeciła się cały czas.

Opóźnienia w programie - miganie diodą

Tym razem będziemy diodą migać. Do tego potrzebna jest nowa funkcja, której zadaniem będzie wprowadzanie opóźnienia - delay. Schemat połączeń jest dokładnie taki sam jak w pierwszym przypadku. Natomiast kod będzie wyglądał jak poniżej:

Stan wyjścia zmienia się tu cały czas w pętli nieskończonej. W programie zostały dodane opóźnienia z pomocą funkcji delay(Czas) (dzięki temu miganie jest widoczne). Funkcja ta jako argument przyjmuje liczbę milisekund, jakie mają zostać przeczekane.

Zadanie domowe nr 1.1

Sprawdź przy jakiej najmniejszej wartości opóźnień będziesz w stanie zauważyć miganie diody! Co stanie się, gdy dioda będzie migała zbyt szybko? Swoimi spostrzeżeniami podziel się w komentarzu!

Zadanie domowe nr 1.2

Wybierz wolny pin i podłącz do niego drugą diodę. Napisz program, który będzie włączał obie diody LED. Następnie napisz program, w który sprawi, że obie diody będą migały na zmianę.

Wejścia układu w praktyce - instrukcja warunkowa

Bardzo często chcemy, aby zaprogramowany układ mógł reagować na sygnały z zewnątrz. Tym razem do Arduino podłączymy oprócz diody przycisk.

Należy zrobić to zgodnie z poniższym przykładem. Jedna strona przycisku została podłączona do masy (minusa), druga do wyprowadzenia nr 7.

Podłączenie przycisku do Arduino.

Zadanie będzie proste, ale musimy wykorzystać coś nowego - instrukcje warunkowe. Naszym celem jest stworzenie programu, który w momencie wciśnięcia przycisku włączy diodę.

Przejdźmy do realizacji zadania. Oczekujemy, że program będzie nieustannie w jednym z dwóch stanów - dioda włączona lub wyłączona. Na początek konieczne jest odczytanie stanu logicznego, który występuje na wejściu z przyciskiem.

Z konfiguracja wejścia spotkasz się tutaj pierwszy raz. Zwróć uwagę, na wcześniej wspomniany tryb INPUT_PULLUP. Będziemy wykorzystywać go za każdym razem, gdy do Arduino podłączymy przełącznik.

Teraz musimy odczytać stan wejścia. Do tego konieczna jest funkcja digitalRead(pin), która zwraca wartość HIGH bądź LOW, zależnie od stanu. W naszym układzie przycisk zwiera wejście Arduino z masą (LOW).  Samo odczytanie wejścia nic nam nie da, musimy umieć uzależnić od tej informacji działanie programu.

Stąd instrukcja warunkowa (potocznie if lub warunek). Instrukcja ta jest bardzo popularna. Dzięki niej możemy wykonać daną część kodu, jeśli zaszły pewne okoliczności. Czyli np.: jeśli wciśnięto przycisk.

Instrukcja ta może zostać bardzo łatwo rozbudowana o część kodu, która zostanie wykonana tylko, gdy warunek nie zostanie spełniony. Służy do tego instrukcja else.

Łącząc zdobytą wiedzę możemy stworzyć program realizujący nasze zadanie. Przeanalizuj go, a następnie wgraj do Arduino.

Program taki jest jednak mało użyteczny. Po co nam włącznik światła, który działa tylko, gdy go trzymamy palcem? Czy nie lepiej byłoby, gdyby po wciśnięciu przycisku dioda świeciła przez określony czas?

Przykład - Włącznik światła z "czasomierzem"

Załóżmy, że chcemy przerobić powyższy przykład tak, aby dioda świeciła przez 10 sekund od wciśnięcia przycisku. Później ma byc wyłączona, oczywiście tylko do czasu ponownego wciśnięcia przycisku.

Czy jesteś w stanie napisać już taki program samodzielnie? Mam nadzieje, że tak! W razie problemów możesz "podejrzeć" mój kod:

Przykład - Światła drogowe

Nasz kolejny układ będzie układem przełączanych świateł drogowych. Naszym głównym celem będzie napisanie programu, który po wciśnięciu przycisku wyświetli kolejną, poprawną sekwencję świateł. Przyjmijmy taki cykl pracy świateł:

[...] -> Zielone -> Pomarańczowe -> Czerwone -> Czerwone Pomarańczowe[...]

Kiedy naciśniemy przycisk, układ powinien przełączyć światła w ich kolejną sekwencję. Dojdziemy do tego kilkoma etapami. Na początku podłącz 3 diody oraz przełącznik zgodnie z poniższym rysunkiem.

Podłączenie "sygnalizacji" drogowej.

Przygotujmy szkielet programu, na którym będziemy działać. Jego zadanie to tylko konfiguracja wejść i wyjść.

Teraz zapomnijmy o przełączniku i napiszmy program, który będzie zmieniał światła samoczynnie, co 1 sekundę. Powinien on wyglądać następująco:

Wgraj program do Arduino i sprawdź czy działa. Musimy mieć pewność, że wszystko jest poprawnie podłączone zanim przejdziemy do dalszej pracy.

Pętla While

Do tej pory korzystaliśmy tylko z głównej, obowiązkowej "pętli" w kodzie Arduino loop(). Teraz pora na poznanie pętli, którą będziemy mogli używać wewnątrz naszych programów.

Teraz omówimy pętlę while(), która działa, do momentu, gdy warunek jest spełniony. Jej praca została przedstawiona w poniższym kodzie:

Dla jasności, pętle while() wykonuje cały czas tylko ten kod, który mieści się między jej nawiasami klamrowymi (zaznaczony u góry na pomarańczowo). Cała reszta kodu nie jest wtedy wykonywana.

Wykorzystajmy złożony w tej chwili układ ze światłami drogowymi i napiszmy program, który będzie migał jedną diodą, tylko gdy mamy wciśnięty przycisk.

Prawdopodobnie pomyślałeś tutaj o instrukcji warunkowej if. Jednak jak chciałbyś zrealizować miganie diodą? To wbrew pozorom trudniejsze zadanie od napisanego wcześniej ciągłego świecenia.

Program ten najlepiej zrealizować z użycie pętli while(), będzie to wyglądało następująco:

Jeśli rozumiesz powyższy kod możemy przejść do zrealizowania naszego pierwotnego zadania. Czyli automatycznego przełączania świateł.

Tym razem sekwencje mają być wyświetlane do momentu, gdy wciśniemy przycisk (wtedy ma nastąpić zmiana). Zakładamy na razie, że przycisk wciskamy i puszczamy bardzo szybko. Gotowy program powinien wyglądać jak poniżej:

W tym wypadku pętla została wykorzystana w dość dziwny sposób. Otóż jak możecie zobaczyć w nawiasach klamrowych nie znajduje się nic! Dlaczego więc program działa? Dzieje się tak, bo program wykorzystuje pętle do tego, aby się zatrzymać.

Jak to działa?

  1. Uruchamiamy świecenie diodami zgodnie z pewną sekwencją,
  2. wchodzimy w pętlę while(), która jest tuż poniżej,
  3. pętla jest pusta, więc program cały czas kręci się w koło i...  nic nie robi,
  4. dopiero po wciśnięciu przycisku (niespełnienie warunku) program wychodzi z pętli,
  5. zapalana jest kolejne sekwencja i sytuacja się powtarza.

Sprawdźmy działanie programu w praktyce!

Co się dzieje? Czy wszystko działa tak jak powinno? Otóż nie! Pomimo, że przycisk wciskamy nawet na bardzo krótko, to czasami program działa poprawnie, a czasami przeskakuje o kilka pozycji. Dlaczego tak się stało?

Jak powinieneś pamiętać z pierwszej części, mówiąc w uproszczeniu, procesor wykonuje około 16 milionów operacji na sekundę. W związku z tym, podczas wciśnięcia przycisku zdąży obiec wszystkie stany naszej sygnalizacji (i to nie jeden raz...). Różne efekty po zwolnieniu przycisku, to tylko efekt losowego "wstrzelenia się" w daną sekwencję.

Jak rozwiązać ten problem? Bardzo prosto. Wystarczy przerobić program w taki sposób, aby zmiana świateł nie następowała częściej niż np.: co sekundę. Możemy wykorzystać do tego znaną już funkcję opóźniającą delay().

Warto wspomnieć, że warunki w pętli while() mogą być łączone oraz znacznie bardziej rozbudowane, wrócimy jednak do tego tematu, gdy poznamy zmienne.

Pamiętaj, że komplet elementów niezbędnych do przeprowadzenia wszystkich ćwiczeń jest dostępny w Botlandzie. Zakup zestawów wspiera kolejne publikacje na Forbocie!

Podsumowanie

Po sprawdzeniu i zrozumieniu programów z tej części nie powinniście mieć problemów z najważniejszymi peryferiami mikrokontrolerów portami I/O. Mam nadzieję, że zauważyłeś również, że często warto rozbić program na kilka etapów zamiast od razu pisać "pełną funkcjonalność". Pytajcie w komentarzach jeśli coś jest niejasne.

W kolejnej części zajmiemy się komunikacją z komputerem przez port USB. Dzięki temu ułatwione będzie testowanie późniejszych, rozbudowanych programów.

Nawigacja kursu

Autorzy: Sławomir Kozok
Damian (Treker) Szymański

P.S. Nie chcesz przeoczyć kolejnych części naszego darmowego kursu programowania Arduino? Skorzystaj z poniższego formularza i zapisz się na powiadomienia o nowych publikacjach!

arduino, diody, kursArduino, piny, porty, programowanie, przełączniki, wejścia, wyjścia