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);
}
