Kursy • Poradniki • Inspirujące DIY • Forum
Mam nadzieje, że część ta będzie ciekawa dla konstruktorów robotów oraz wszystkich, którzy chcieliby w swoich projektach wykorzystywać czujnik odległości, taki jak na poniższym zdjęciu.
Zanim jednak do tego dojdzie zajmiemy się opanowaniem nowych sztuczek programistycznych, które sprawią, że nasze programy będą jeszcze lepsze - zarówno dla Arduino, jak i dla osoby odpowiedzialnej za tworzenie programu, czyli dla Ciebie!
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Teraz możesz kupić zestaw ponad 70 elementów niezbędnych do przeprowadzenia ćwiczeń z kursu u naszych dystrybutorów!
Zamów w Botland.com.pl »Popularne pakiety: Mistrz Arduino • Mistrz Robotyki
Własne funkcje bez argumentów
Do tej pory nasz kod umieszczaliśmy w funkcjach setup() {} lub loop() {}. Pierwsza z nich dotyczyła ustawień, a druga była nieskończoną pętlą główną, która wykonywała się cały czas. Przykładowo, aby migać diodą podłączoną do pinu nr 13 należało napisać taki program:
Pamiętaj, że do pinu nr 13 podłączona jest dioda wbudowana na stałe w Arduino!
1 2 3 4 5 6 7 8 9 10 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście } void loop() { digitalWrite(13, HIGH); //Włączenie diody delay(1000); //Odczekanie 1 sekundy digitalWrite(13, LOW); //Wyłączenie diody delay(1000); //Odczekanie 1 sekundy } |
Wyobraźmy sobie sytuację, w której nasz program jest bardzo rozbudowany, a miganie chcemy wykorzystać jako potwierdzanie wybranych operacji. Szybko okaże się, że wielokrotne powielanie fragmentu odpowiedzialnego za włączanie i wyłączanie diody jest czasochłonne dla programisty. Co gorsza, sprawia to również, że cały program jest znacznie trudniejszy w analizowaniu.
Deklarując zmienną możemy odwoływać się do niej wielokrotnie w łatwy sposób. Gdybyśmy mogli zapisywać pod łatwą nazwą całe ciągi operacji, to programy byłyby zdecydowanie czytelniejsze. Również ewentualne zmiany byłyby łatwiejsze. Tutaj pomocne będą właśnie funkcje.
Programując Arduino korzystamy z wielu gotowych funkcji np.: digitalWrite(13, HIGH);. Wiemy, że zapis ten powoduje ustawienie stanu wysokiego (logiczne 1) na pinie numer 13. Jednak nie musimy wiedzieć w jaki sposób jest to realizowane. Analogicznie możemy tworzyć własne funkcje. Przykładowo zajmijmy się przytoczonym już miganiem.
Za tę operacje jest odpowiedzialny następujący fragment kodu:
1 2 3 4 |
digitalWrite(13, HIGH); //Włączenie diody delay(1000); //Odczekanie 1 sekundy digitalWrite(13, LOW); //Wyłączenie diody delay(1000); //Odczekanie jednej sekundy |
Możemy go "wyciągnąć" poza loop() {} i stworzyć z niego osobną funkcję. Jak to zrobić?
Na początku konieczne jest zadeklarowanie nazwy funkcji oraz jej typu. Robimy to np.: pod funkcją loop() {}:
1 2 3 4 5 6 7 8 9 10 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście } void loop() { } void zamigajLED() { //Treść funkcji } |
Jak widać przed nazwą funkcji, czyli w tym wypadku zamigajLED znajduje się typ funkcji. Jest to nic innego jak informacja, czy funkcja po zakończeniu działania zwraca jakąś wartość. Jeśli by tak było, to należałoby wpisać tam odpowiedni typ (zgodnie z typami zmiennych). Mogła to by być np.: liczba całkowita (int), znak (char), liczba niecałkowita (float) itd.
W tym wypadku znajduje się tam słowo void (z ang. pustka). Typ ten oznacza, że funkcja nie zwraca żadnej wartości. Dlaczego taki typ wybrałem? Zadaniem funkcji jest zamiganie diodą, operacja ta nie daje żadnego wyniku (oprócz tego widocznego dla naszego oka).
Jeśli w wyniku działania funkcji nie powstaje żaden wynik,
to poprzedzamy ją słowem void!
(W dalszej części artykułu omówione są inne przypadki)
Za nazwą funkcji pojawia się nawias okrągły. Na ten moment przyjmijmy, że w nawiasie tym nic nie ma. Dalej otwieramy nawias klamrowy i w jego wnętrzu umieszczamy kod, który ma się wykonać w momencie wywołania funkcji. W praktyce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście } void loop() { zamigajLED(); } void zamigajLED() { digitalWrite(13, HIGH); //Włączenie diody delay(500); //Odczekanie 0,5 sekundy digitalWrite(13, LOW); //Wyłączenie diody delay(500); //Odczekanie 0,5 sekundy } |
Zwróć uwagę, że w pętli loop() {} wpisaliśmy nazwę naszej zmiennej (już bez przedrostka void). Działanie takie nazywamy wywołaniem funkcji. Wgraj program i sprawdź, czy działa zgodnie z poniższą animacją:
Pamiętaj:
Nazwy funkcji powinny tłumaczyć, co robi funkcja i muszą być unikalne! Nie mogą np.: pokrywać się z nazwami zmiennych.
Wróćmy do założonej wcześniej sytuacji, w której migania używasz w licznych, bardzo różnych miejscach programu (do potwierdzania jakiejś operacji). Co byłoby, gdybyś nagle zechciał zmienić sekwencję świecenia lub np.: ilość mignięć? Musiałbyś zmieniać ten sam fragment kodu w kilkunastu miejscach. Gdy skorzystasz z własnej funkcji będzie to banalnie proste, wystarczy zmiana w tym jednym miejscu:
1 2 3 4 5 6 7 |
//Szybsze miganie void zamigajLED() { digitalWrite(13, HIGH); //Włączenie diody delay(200); //Odczekanie 0,2 sekundy digitalWrite(13, LOW); //Wyłączenie diody delay(200); //Odczekanie 0,2 sekundy } |
Zadanie domowe 9.1
Napisz funkcję, która sprawia, że dioda będzie pulsować (stopniowe przygaszanie i rozjaśnianie). Skorzystaj oczywiście z wiadomości zdobytych podczas lekcji o sygnale PWM.
Arduino - własne funkcje z argumentami
Pora na odkrycie kolejnych tajemnic funkcji. Pamiętasz jedną z najczęściej wywoływanych przez nas funkcji Arduino? Prawdopodobnie będzie to zmiana stanu na konkretnym pinie, przykładowo:
1 |
digitalWrite(13, HIGH); |
W odróżnieniu od naszej funkcji zamigajLED();, tutaj w nawiasach okrągłych wpisujemy jakieś dane. Może to być numer pinu, stan, który chcemy uzyskać lub wypełnienie sygnału PWM. Te informacje wykorzystywane są później wewnątrz funkcji do wykonania pewnych operacji.
To są właśnie argumenty, które są przekazywane do funkcji.
Zwróć uwagę na użyte słownictwo, zarówno argumenty, jak i przekazywanie nie zostały użyte przypadkowo. Są to określenia stosowane przez wszystkich programistów! Teraz pora napisać własną funkcję, na początek z jednym argumentem.
Załóżmy, że chcemy edytować naszą funkcję zamigajLED w taki sposób, aby podczas wywoływania możliwa była zmiana prędkości migania. W naszej deklaracji funkcji musimy dodać informację, że może ona przyjmować argument. Robimy to w nagłówku funkcji:
1 2 3 4 5 |
//Pierwotna wersja void zamigajLED(){ //Nowa wersja void zamigajLED(int czas){ |
Od tej pory kompilator wie, że w momencie napotkania wywołania funkcji w okrągłych nawiasach ma spodziewać się liczby (typ int). Liczba ta zostanie przekazana do funkcji i będzie tam występowała pod nazwą czas.
Będzie to zmienna widoczna tylko w wewnątrz naszej funkcji, czyli będzie to, tak zwana zmienna lokalna.
W praktyce wnętrze funkcji będzie wyglądało tak:
1 2 3 4 5 6 |
void zamigajLED(int czas){ digitalWrite(13, HIGH); //Włączenie diody delay(czas); //Odczekanie przez jakiś czas digitalWrite(13, LOW); //Wyłączenie diody delay(czas); //Odczekanie przez jakiś czas } |
Pora na ostatni element, czyli nowe wywołanie funkcji. Tutaj chyba nie będzie nic zaskakującego. Zwyczajnie w nawiasie wpisujemy liczbę, która będzie oznaczała czas przez jaki dioda ma być włączona i wyłączona. Cały kod prezentuje się tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście } void loop() { zamigajLED(50); } void zamigajLED(int czas){ digitalWrite(13, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(13, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } |
Sprawdź, że podmieniając wartość w nawiasach możesz wpływać na częstotliwość migania. Pora na kolejny przykład.
Funkcje z argumentem - przykład 2
Do tej pory ciągłe miganie diody było widoczne, ponieważ wywoływaliśmy naszą funkcję w funkcji loop, która jest pętlą. Gdy przeniesiemy wywołanie funkcji do sekcji setup, to po starcie systemu zobaczymy tylko jedno mignięcie. Sprawdź:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście zamigajLED(50); } void loop() { } void zamigajLED(int czas){ digitalWrite(13, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(13, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } |
Dokładnie tak działoby się, gdybyśmy chcieli wykorzystać funkcję np.: do potwierdzeni wciśnięcia przycisku. Wywołanie zamigajLED() powodowałoby jeden błysk.
Pora na przerobienie funkcji w taki sposób, aby oprócz zmiany czasu migania możliwe było również wpływanie na ilość mignięć. Na początku musimy zmienić deklarację funkcji:
1 2 3 4 5 |
//Wersja pierwotna void zamigajLED(int czas){ //Wersja poprawiona void zamigajLED(int czas, int ile){ |
Dodaliśmy argument ile, to on będzie odpowiadał za ilość mignięć. Jak widać, wpisany został po przecinku, poprzedzony oczywiście swoim typem (liczba całkowita, int). Parametr ten mógłby być innego typu, nie ma to żadnej różnicy. Ważne, aby deklaracja była odpowiednia.
Funkcja może przyjmować wiele argumentów o różnych typach!
Jak pewnie się domyślasz wykorzystanie drugiego argumentu jest bardzo proste. Zwyczajnie mamy do dyspozycji teraz drugą zmienną lokalną ile.
Mam nadzieję, że wiesz już, którą pętlę użyć, aby najłatwiej zapanować nad ilością mignięć? Jeśli nie, to odsyłam do poprzedniej części, w której omówiłem pętle for. Zakładam jednak, że materiał ten masz opanowany (lub zaraz nadrobisz braki) i przedstawiam nową funkcję zamigajLED:
1 2 3 4 5 6 7 8 |
void zamigajLED(int ile, int czas){ for (int i=0; i < ile; i++) { digitalWrite(13, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(13, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } } |
Sprawdźmy cały kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 8 jako wyjście zamigajLED(100, 5); } void loop() { } void zamigajLED(int czas, int ile){ for (int i=0; i < ile; i++) { digitalWrite(13, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(13, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } } |
Wszystko powinno działać. Teraz po starcie dioda musi zamigać dokładnie 5 razy. Jednak skąd Arduino wie jak rozróżnić informacje podane w nawiasach, co jest czasem, a co ilością powtórzeń?
Otóż zasada jest banalnie prosta - kolejne wartości oddzielone przecinkami są przypisywane do kolejnych argumentów opisanych w deklaracji funkcji. W powyższym przypadku pierwsza liczba będzie zawsze traktowana jako czas, a druga jako ilość powtórzeń.
Zadanie domowe 9.2
Napisz funkcję, która pozwala osobno regulować ilość mignięć, czas przez jaki dioda jest wyłączona oraz osobno, czas przez jaki dioda jest włączona.
Czy nasza funkcja może zostać jeszcze bardziej rozbudowana? Oczywiście! Czy nie byłoby wygodnie, aby oprócz deklaracji czasu i ilości powtórzeń możliwa była deklaracja pinu, na którym podłączona jest dioda?
Funkcje z argumentami - przykład nr 3
Pin, do którego podłączona jest dioda, to w naszym programie liczba. Wykorzystujemy go w dwóch miejscach podczas deklaracji danego pinu jako wyjście oraz podczas ustawiania stanu wysokiego lub niskiego. Czyli, liczba ta może być kolejnym argumentem naszej funkcji!
Na początku podłączmy drugą diodę do innego pinu, np.: nr 8:
Nie będę się już tutaj rozpisywał, od razu przedstawiam cały przykład:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście zamigajLED(100, 5, 13); zamigajLED(150, 4, 8); } void loop() { } void zamigajLED(int czas, int ile, int pin){ for (int i=0; i < ile; i++) { digitalWrite(pin, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(pin, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } } |
Sprawdźmy, czy działa? Niezbyt, prawda? Miga tylko dioda podłączona do pinu 13, a ta pod 8 nie chce dać znaku życia. Dlaczego? Otóż popełniliśmy bardzo prosty błąd, który łatwo popełnić pisząc takie funkcje. Nie przewidzieliśmy nigdzie, że inne piny (oprócz 13) mogą być wyjściami. Sprawdź sekcje setup:
1 2 3 4 5 |
void setup() { pinMode(13, OUTPUT); //Konfiguracja pinu 13 jako wyjście zamigajLED(100, 5, 13); zamigajLED(150, 4, 8); } |
Możemy to naprawić stosunkowo prosto, wystarczy przenieść konfigurację pinów do naszej funkcji. Wyglądałoby to następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void setup() { zamigajLED(100, 5, 13); zamigajLED(150, 4, 8); } void loop() { } void zamigajLED(int czas, int ile, int pin){ pinMode(pin, OUTPUT); //Konfiguracja jako wyjście for (int i=0; i < ile; i++) { digitalWrite(pin, HIGH); //Włączenie diody delay(czas); //Odczekanie jakiegoś czasu digitalWrite(pin, LOW); //Wyłączenie diody delay(czas); //Odczekanie jakiegoś czasu } } |
Teraz wszystko działa, jednak muszę przyznać, że nie jest to eleganckie rozwiązanie. Dlatego odradzam używania w bardziej profesjonalnych projektach tego typu rozwiązań. Jednak na etapie hobbystycznym nie powinno się nic złego stać i możemy sobie ułatwiać życie taką prostą funkcją.
Pora na ostatnią informację o funkcjach zanim przejdziemy do czujnika odległości.
Funkcje zwracające wynik
Do tej pory wykorzystywaliśmy tylko funkcje, które robiły pewne operacje, ale nie musiały zwracać żadnego wyniku. Zadaniem nowej funkcji będzie... obliczenie pola kwadratu o zadanym boku i zwrócenie wyniku (obliczonego pola). Na początek konieczna jest deklaracja funkcji, tym razem nagłówek będzie wyglądał tak:
1 |
int kwadratPole(int a) { |
Jak widać, oprócz argumentu (bok kwadratu) przed funkcją pojawił się int. Oznacza to, że zwróci ona wynik będący liczbą. W tym przypadku będzie to oczywiście obliczone pole.
Wnętrze funkcji powinno wyglądać natomiast tak:
1 2 3 4 5 6 |
int kwadratPole(int a) { int wynik = 0; wynik = a * a; return wynik; } |
Podświetliłem tutaj linię, ze słowem return. Po nim umieszczamy wartość (zmienną), która jest zwracana jako wynik tej funkcji. Czyli dla argumentu 4, w wyniku działania funkcji powinniśmy otrzymać 16, bo 4*4 = 16. Zastanawiasz się pewnie, co to znaczy, że funkcja zwraca wynik. Gdzie trafia ta wartość?
Sprawdźmy poniższy przykład:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void setup() { Serial.begin(9600); } void loop() { int wynik = kwadratPole(4); Serial.println(wynik); //Wyświetl komunikat delay(500); } int kwadratPole(int a) { int wynik = 0; wynik = a * a; return wynik; } |
Uruchom przykład. W monitorze portu szeregowego powinien pojawiać się wynik operacji, czyli 16. To, że funkcja zwraca wynik oznacza, że po wykonaniu w miejscu jej wywołania "podstawiony zostanie wynik". W tym wypadku zamiast kwadratPole(4); podstawiane jest 16, które zapisywane jest do zmiennej wynik.
Deklaracje osobnej zmiennej jest jednak zbędna. Równie poprawnie (ale trochę mniej czytelnie) sprawdzi się poniższy zapis:
1 2 3 4 5 6 |
//Stara wersja int wynik = kwadratPole(4); Serial.println(wynik); //Wyświetl komunikat //Nowa wersja Serial.println(kwadratPole(4)); //Wyświetl komunikat |
Do pełni szczęścia brakuje tylko, aby bok kwadratu mógł być wysyłany do Arduino z komputera. Wykorzystamy tutaj wiadomości z części kursu o komunikacji przez UART. Program będzie wyglądał następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
String odebraneDane = ""; //Pusty ciąg odebranych danych void setup() { Serial.begin(9600); } void loop() { if(Serial.available() > 0) { //Czy Arduino odebrano dane odebraneDane = Serial.readStringUntil('\n'); //Jeśli tak, to odczytaj je do znaku końca linii i zapisz w zmiennej odebraneDane int wynik = kwadratPole(odebraneDane.toInt()); Serial.println(wynik); //Wyświetl komunikat } } int kwadratPole(int a) { int wynik = 0; wynik = a * a; return wynik; } |
Nowością jest jednak zapis odebraneDane.toInt(). Jak pewnie pamiętasz, podczas komunikacji przez UART wszelkie znaki przesyłane są w formie kodów ASCII. Stąd podczas naszych testów nie mogliśmy w łatwy sposób wysyłać do Arduino liczb.
Można to jednak bardzo łatwo zmienić. Wystarczy do doczytanego ciągu znaków (stringa) dopisać .toInt(). Dzięki temu liczba przesłana jako tekst zostanie przekonwertowana na liczbę typu int.
Operacja zmiany typu zmiennej na inny nazywamy rzutowaniem.
Zadanie domowe 9.1
Napisz funkcje, które obliczają pola prostokąta, koła oraz trójkąta. Wyniki wyślij do PC przez UART!
Ultradźwiękowy czujnik odległości HC-SR04
Pora na opis czujnika odległości, na które czekało wiele osób. Czujnik HC-SR04 składa się z nadajnika i odbiornika ultradźwięków oraz kilku układów scalonych. Dzięki nim, na podstawie nasłuchiwania, jak odbija się sygnał akustyczny (niesłyszalny dla człowieka) możemy całkiem precyzyjnie określić odległość czujnika od przeszkody. Zupełnie jak... nietoperze:
Wróćmy jednak do Arduino i czujnika odległości. Na początek skupmy się na jego czterech pinach sygnałowych. Dwa z nich służą do zasilania układu (Vcc, GND), a drugie dwa (trigger i echo) do wykonywania pomiarów.
Trigger, to wejście wyzwalające. Gdy podamy na nie stan wysoki (przez min. 10 mikrosekund), to rozpocznie się pomiar odległości. Natomiast z wyjścia echo odczytamy zmierzoną odległość. Maksymalny zasięg tego niepozornego układu deklarowany jest przez producenta na 4 m.
- Tył czujnika
- Przód czujnika
Pora na pierwszy, prosty przykład wykorzystania czujnika w praktyce.
Miernik odległości
Podłącz stworzymy układ, który będzie w regularnych odstępach czasu dokonywał pomiarów odległości i będzie wyświetlał je na ekranie komputera. W tym celu konieczne jest złożenie układu zgodnie z poniższym schematem:
Opis wyprowadzeń znajduje się fizycznie na czujniku, jednak dla formalności przedstawiam go poniżej. Patrząc od góry (tak, jak na powyższym schemacie) i od lewej:
- Vcc - 5V
- Trig
- Echo
- Gnd
Zdjęcie złożonego układu umieszczam poniżej. Wybaczcie jeden nadmiarowy przewód (zielony), który zapomniałem odpiąć na czas robienia fotografii:
W naszym przypadku wejście trig łączymy z pinem nr 12, a echo z pinem nr 11. Aby nam się to nie myliło proponuję od razu zrobić odpowiednie definicje:
1 2 |
#define trigPin 12 #define echoPin 11 |
Wiesz mniej więcej jak wyzwolić pomiar odległości. Czas zapisać to w formie programu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście } void loop() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); //Pomiar wykonany } |
Zwróć uwagę na nową funkcję delayMicroseconds();, jest to nic innego jak odpowiednik dobrze nam znanej funkcji delay();. Różnica jest chyba oczywista. Nowa funkcja odlicza czas w mikrosekundach. Przypomnę tylko, że używana do tej pory funkcja delay odmierza milisekundy.
Sekwencja uruchamiająca pomiar jest bardzo prosta. Na początku ustawiamy na pinie połączonym z wejściem wyzwalającym czujnika sygnał niski. Wystarczą 2 mikrosekundy, następnie ustawiamy stan wysoki na 10 mikrosekund. Czujnik wykonuje pomiar i zwraca wyniki na pinie echo.
Pytanie jak go odczytać? Czy jest do tego potrzebny UART lub inny interfejs komunikacyjny? Nie, na szczęście czujnik ten jest bardzo prosty i zmierzona odległość reprezentowana jest przez impuls (stan wysoki) na pinie echo. Jego długość jest proporcjonalna do odległości. Im jest on dłuższy, tym zmierzona odległość jest większa. Tutaj przyda się nam nowa funkcja dostępna w Arduino.
Pomiar czasu trwania impulsu w Arduino
Na szczęście w Arduino zaimplementowano bardzo prostą funkcję, która potrafi zmierzyć czas trwania impulsu podanego na dowolne wejście. Jego długość musi się mieścić w przedziale od 10 mikrosekund do 3 minut. Start pomiaru następuje w momencie wykrycia zmiany stanu na pinie.
Jej składania jest bardzo prosta:
1 2 |
int wynik = 0; wynik = pulseIn(11, HIGH); |
Funkcja, w najprostszej postaci przyjmuje tylko dwa argumenty. Numer pinu, który ma zostać sprawdzony oraz poziom logiczny (niski/wysoki), który ma być mierzony.
Czyli w celu zmierzenia odległości musimy wykonać następującą operację:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście } void loop() { long czas; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); Serial.print(czas); delay(500); } |
Uruchomienie takie programu sprawi, że na ekranie zaczną pojawiać się liczby. Im postawimy przeszkodę bliżej czujnika tym będą one mniejsze. Jednak daleko im do używanych przez nas jednostek. Aby pomiar był czytelny dla człowieka wynik musimy podzielić przez "magiczną liczbę".
Sprawdź jak działa poniższy program - teraz wynik powinny wyświetlać się w centymetrach:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście } void loop() { long czas, dystans; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); dystans = czas / 58; Serial.print(dystans); Serial.println(" cm"); delay(500); } |
Oczywiście wartość, przez którą dzielimy (58) nie jest "magiczna". Wynika ona z czasu przez jaki dźwięk pokonuje odległość 1 cm oraz wzoru na odległość jaką przedstawił producent.
Na ten moment nie musimy się w to zagłębiać.
Jeśli zajdzie taka potrzeba, to opiszę to zagadnienie dokładnie.
Funkcja zwracająca odległość czujnika w cm
Jak widać fragment programu, który pozwala na uzyskanie pomiaru jest stosunkowo długi. Bardzo wygodnie byłoby, gdybyśmy dysponowali funkcją, która robi to wszystko. Napiszmy ją. Nie ma na co czekać, przedstawiam gotowy fragment, w końcu to tylko kopiowanie powyższego kodu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int zmierzOdleglosc() { long czas, dystans; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); dystans = czas / 58; return dystans; } |
Teraz w odpowiednim miejscu programu wystarczy wywołać funkcję:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście } void loop() { Serial.print(zmierzOdleglosc()); Serial.println(" cm"); delay(500); } int zmierzOdleglosc() { long czas, dystans; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); dystans = czas / 58; return dystans; } |
Znacznie czytelniej, prawda?
Sprawdzanie zakresu odległości
Pora na ostatnie ćwiczenie z tej części kursu Arduino. Tym razem dodajmy do naszego układu buzzer, czyli element wydający dźwięk. Wygląda on tak:
- Buzzer zaklejony
- Buzzer bez naklejki
Element ten ma dwa wejścia, gdy na jedno z nich podamy Vcc, a drugie połączymy z masą, to... Buzzer zacznie wydawać głośny dźwięk. Nie jest on zbyt przyjemny, więc proponuję pozostawić na nim naklejkę (dzięki temu wydawany odgłos jest cichszy). Jak łatwo domyśleć się po napisie etykieta ta ochrania wnętrze buzzera podczas mycia płytek drukowanych. Robi się, to głónie w celu usunięcia śladów po lutowaniu.
Buzzer jest elementem biegunowym!
Zwróć uwagę, że jedna z nóżek jest dłuższa, a na obudowie,
obok niej znajdziesz symbol plusa.
W celu wykorzystania buzzera podłączamy go do Arduino zgodnie z poniższym schematem.
Schemat montażowy - buzzer i Arduino.
Symbol użyty przeze mnie do narysowania schematu montażowego odbiega trochę od wyglądu buzzera, więc można się upewnić dodatkowo na zdjęciu mojego układu:
Buzzer dołączany do zestawów Forbota został tak dobrany, że może być podpinany do Arduino bez rezystora. Jeśli używasz swojego buzzera sprawdź, jaki pobiera on prąd!
Tym razem napiszemy funkcję, której zadaniem będzie sprawdzanie, czy obiekt znajduje się w określonej odległości od czujnika. Jeśli tak się stanie, to włączy się alarm (dźwięk buzzera).
Funkcja sprawdzająca zakres będzie korzystała z poprzedniej funkcji zmierzOdleglosc(),
1 2 3 4 5 6 7 8 |
void zakres(int a, int b) { int jakDaleko = zmierzOdleglosc(); if ((jakDaleko > a) && (jakDaleko < b)) { digitalWrite(2, HIGH); //Włączamy buzzer } else { digitalWrite(2, LOW); //Wyłączamy buzzer, gdy obiekt poza zakresem } } |
Jako argumenty funkcji podajemy dwie liczby całkowite. Następnie sprawdzamy czy zmierzona odległość jest większa od początkowej wartości naszego przedziału (a) oraz mniejsza od maksymalnej wartości (b).
Przy okazji możesz zobaczyć, że symbolami && łączymy kilka warunków w jeden.
Cały program wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście pinMode(2, OUTPUT); //Wyjście dla buzzera } void loop() { zakres(10, 25); //Włącz alarm, jeśli w odległości od 10 do 25 cm od czujnika jest przeszkoda delay(100); } int zmierzOdleglosc() { long czas, dystans; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); dystans = czas / 58; return dystans; } void zakres(int a, int b) { int jakDaleko = zmierzOdleglosc(); if ((jakDaleko > a) && (jakDaleko < b)) { digitalWrite(2, HIGH); //Włączamy buzzer } else { digitalWrite(2, LOW); //Wyłączamy buzzer, gdy obiekt poza zakresem } } |
Sprawdź jak program działa w praktyce. Moim zdaniem jest to idealny zalążek do budowy prostej centralki alarmowej, która będzie pilnowała Twoich cennych skarbów!
Zadanie domowe 9.3
Napisz program, który przedstawia odległość przeszkody od czujnika na linijce diod LED. Im przeszkoda jest bliżej czujnika, tym więcej diod powinno się świecić. Przykładowa linijka może być zbudowana jak poniższa:
Zadanie domowe 9.4
Sprawdź jak zmienia się odczyt czujnika w zależności od materiału, z którego zbudowana jest przeszkoda. Czy widzisz różnice w odległości wskazywanej, gdy czujnik kierowany jest np.: na twarde lub miękkie przedmioty? Porównaj takie przeszkody jak ściana, koc kartka papieru.
Podsumowanie kursu Arduino!
Zupełnie nie wiem jak to się stało, ale właśnie poznałeś cały materiał, który był przewidziany na kurs Arduino dla początkujących! Mam nadzieję, że nauka była dla Was owocna. Jeśli będziecie zainteresowani, to obiecuję dodać jeszcze jedną część ekstra oraz kontynuować prace nad kursem dla średniozaawansowanych (kolejne 9 nowych odcinków).
Nawigacja kursu
Nie chcecie przeoczyć kolejnych odcinków? Korzystając z poniższego formularza możecie zapisać się na powiadomienia o nowych publikacjach!
Damian (Treker) Szymański
Powiązane wpisy
arduino, buzzer, czujnik, funkcje, kursArduino
Trwa ładowanie komentarzy...