Komunikacja z naszym Arduino – monitor szeregowy oraz funkcje.

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