Kolejne zajęcia Fibotu za nami!
Tym razem poruszyliśmy tematy, które pozwoliły nam poznać nieco bardziej techniki komunikacji użytkownik – komputer – kontroler.
Po utworzeniu znanego z początkowych zajęć układu równolegle podłączonych i adresowanych LEDów chcieliśmy móc nimi sterować ręcznie, a nie jedynie z pomocą gotowych algorytmów w pętli.
LEDy połączone płytką stykową
By spróbować czegoś zaawansowanego wróciliśmy do podstaw – każdy uczestnik stworzył znany już układ pięciu LEDów z czego każdy był podłączony do innego pinu cyfrowego w płytce Arduino. Ten układ pozwala niezależnie kontrolować każdą diodę.
#define MAX 5 int piny[5]={2,3,4,5,6}; // Tablica z numerami wyjść cyfrowych do których podłączone zostały diody void setup(){ for(i=0;i<MAX;i++) // pętla pozwalająca zdefiniować wyjście każdego z pinów cyfrowych pinMode(piny[i],OUTPUT); }
Monitor szeregowy i komunikacja
Wzbogaciliśmy nasz program o funkcje pozwalające na komunikację przez port szeregowy, a następnie dodaliśmy możliwość wysyłania komend, które będą zapalać i gasić nasze diody.
Sterownik również na bieżąco informuje nas o tym, czy wczytał nasz input – wyświetlał wszystkie znaki które wprowadzimy do monitora szeregowego. By mieć możliwość wczytania więcej niż znaku (char – 1 bajt) zastosowaliśmy funkcję parseInt() pozwalającą na wczytanie ciągu znaków, który zostanie zamienony na liczbę całkowitą. Zmienna „ile” była wprowadzana przez użytkownika i definiowała ile razy lampki mają zamrugać.
#define MAX 5 // liczba diod int piny[MAX]={2,3,4,5,6}; int i,j; //char znak; byte znak; void setup(){ for(i=0;i<MAX;i++) pinMode(piny[i],OUTPUT); Serial.begin(9600); } void loop(){ if(Serial.available()>0){ int ile=Serial.parseInt(); Serial.print("Wczytalam "); Serial.println(ile); mig(ile); }
Warto zwrócić uwagę na linijkę trzynastą Serial.available() zwraca liczbę bajtów czekających na odczytanie (a aktualnie przechowywanych w buforze portu szeregowego), gdy zostanie wprowadzona przez użytkownika jakaś dana wejściowa. Czytając jeden bajt (np. Serial.read()) zabieramy z tego bufora jeden bajt a tym samym zmniejszamy licznik danych (bajtów) czekających na odczytanie.
Adnotacja: podczas zajęć modyfikowaliśmy nasz program na bieżąco. W kodzie możecie znaleźć 'przestarzałe’ metody które wprowadziliśmy w ramach zapoznania się z ideologią zadania. Najczęściej będą one zakomentowane w pełnym kodzie, który znajdziecie na samym dole tego wpisu. Warto zwrócić uwagę, że przy wczytywaniu zmiennej char będącej jednym znakiem musimy stosować tłumaczenie na tablicę ASCII, gdyż właśnie w tym formacie zapisane są zmienne char.
Funkcje: szkoda życia na robienie w kółko tego samego !
Stworzywszy program pozwalający na dwukierunkową komunikację z naszym sterownikiem utworzyliśmy funkcję o wdzięcznej nazwie „mig()”. Tworzenie takich funkcji jest podstawą programowania strukturalnego – chodzi o „zamykanie” logicznych części programu (tu: włączanie/wyłączanie wszystkich LEDów) w pewną całość, którą następnie będziemy wielokrotnie używać.
Funkcja „mig()” przechodziła kolejne stadia swojego rozwoju, od najprostrzej – bezargumentowej:
void mig(){ Serial.println("ON"); for(i=0;i<MAX;i++) digitalWrite(piny[i],HIGH); delay(400); Serial.println("OFF"); for(i=0;i<MAX;i++) digitalWrite(piny[i],LOW); delay(400); }//mig
Zadaniem powyższej jest jednokrotne włączenie/wyłączenie wszystkich LED-ów. Aby zrobić to kilkukrotnie należy wielokrotnie wykonać stworzoną funkcję mig() – lub wykonać ją w pętli n-razy. Dlatego kolejna modyfikacja polegała na dodaniu argumentu do funkcji:
void mig(int ile){ for (int jj=0;jj<ile;jj++){ Serial.println("ON"); for(i=0;i<MAX;i++) digitalWrite(piny[i],HIGH); delay(400); Serial.print("OFF x"); Serial.println(jj+1); for(i=0;i<MAX;i++) digitalWrite(piny[i],LOW); delay(400); }//jj }//mig
jednoargumentową, która umożliwia nam wielokrotne włączenie/wyłączenie LED-ów (dodatkow pętla po zmiennej jj). Mając takią funkcję możemy kazać migać, np. czterokrotnie przez wywołanie mig(4) – wówczas następuję przekazanie liczby 4 dla parametru ile w definicji funkcji mig(int ile). Kolejna modyfikacja polegała na dodaniu dodatkowego, drugiego parametru czas określającego ile ms mają być włączone/wyłączone LED-y.
void mig(int ile, int czas){ for (int jj=0;jj<ile;jj++){ Serial.println("ON"); for(i=0;i<MAX;i++) digitalWrite(piny[i],HIGH); delay(czas); Serial.print("OFF x"); Serial.println(jj+1); for(i=0;i<MAX;i++) digitalWrite(piny[i],LOW); delay(czas); }//jj }//mig
Ten dodatkowy parametr umożliwia nam szybkie miganie (np. czterorkotne) przez wywołanie mig(4, 100) lub wolne przez wywołanie mig(4,2000). Podobnie jak poprzednio wywołując naszą funkcję przypisujemy wartości 4 do zmiennej ile, oraz 100 (lub 2000 w drugim przykładzie) do zmiennej czas. Ostatnia modyfikacja to parametry domyślne w języku C++ (nie ma tego w „czystym” C), czyli zamiana prototypu funkcji (=nagłówka) na następujący:
void mig(int ile, int czas=400){ for (int jj=0;jj<ile;jj++){ Serial.println("ON"); for(i=0;i<MAX;i++) digitalWrite(piny[i],HIGH); delay(czas); Serial.print("OFF x"); Serial.println(jj+1); for(i=0;i<MAX;i++) digitalWrite(piny[i],LOW); delay(czas); }//jj }//mig
Powyższa zmiana umożliwia wywołanie dwuargumentowej funkcji mig(int, int) nie wtylko w postaci mig(4,100) ale także mig(4) – wówczas parametr czas przyjmie domyślną wartość 400.
Funkcja ta robi dokładnie to, o czym wspomniałem przy zmiennej „ile”. Wartość ukryta pod tą zmienną była kierowana do funkcji. Pętla zapalająca (zaznaczona linijka 2) zapala i gasi (linijki 5 i 10) lampki za pomocą znanej już nam pętli wewnętrznej (zapalającej każdą diodę jedną po drugiej w odstępie czasu rzędu milisekund – linijka 4).
Funkcja miała też dodatkową, opcjonalną zmienną wejściową „czas” regulującą odstępy między zapaleniem i zgaszeniem diod za pomocą wbudowanej funkcji „delay()”.
Podsumowanie:
Na tych zajęciach zamiast poznać nowe elementy elektroniczne jak np. znana z poprzednich zajęć czujka szczelinowa, poznaliśmy niezwykle kluczowe możliwości sterownika Arduino – komunikację dwukierunkową przez monitor szeregowy. Możliwość bezpośredniego wysyłania sterownikowi danych wejściowych pozwala na ręczne sterowanie i otwiera nas na nowe możliwości.
Stworzyliśmy swoją własną funkcję istniejącą poza pętlą główną, co zwiększa przejrzystość kodu i daje wygodę stosowania gotowych funkcji.
To nie koniec przygód z komunikacją za pomocą monitora szeregowego. Możliwości implementacji tak kluczowej metody są niezwykle szerokie.
Do zobaczenia na następnych zajęciach!
Maciej (c) 2017 & KG
Pełny kod:
#define MAX 5 int piny[5]={2,3,4,5,6}; int i,j; //char znak; byte znak; void setup(){ for(i=0;i<MAX;i++) pinMode(piny[i],OUTPUT); Serial.begin(9600); } void mig(int ile, int czas=400){ for (int jj=0;jj<ile;jj++){ Serial.println("ON"); for(i=0;i<MAX;i++) digitalWrite(piny[i],HIGH); delay(czas); Serial.print("OFF x"); Serial.println(jj+1); for(i=0;i<MAX;i++) digitalWrite(piny[i],LOW); delay(czas); }//jj }//mig void loop(){ if(Serial.available()>0){ int ile=Serial.parseInt(); //znak=Serial.read(); //przechowuje 1 bajt Serial.print("Wczytalam "); Serial.println(ile); // mig(znak-48); //0 w tabeli ASCII to 48 mig(ile); // if (znak=='3')mig(3); // if (znak=='5')mig(5,500); }