Sterowanie wyświetlaczem siedmiosegmentowym. Programowanie strukturalne.

Wyświetlacze siedmiosegmentowe (sevseg).

Na dzisiejszym spotkaniu poznaliśmy jedno z wielu praktycznych zastosowań LEDów. Siedmiosegmentowe wyświetlacze są układami diod emitujących światło w odpowiednim ustawieniu umożliwiającym wyświetlanie cyfr. Wyświetlacze te, nazywane sevseg (ang. seven sevseg

segment display) są wszechobecne w świecie elektroniki i służą reprezentowaniu danych w przystępny (i niedrogi) sposób. Tego typu wyświetlacz może zastąpić droższe ekrany (np. LCD) w przypadku gdy chcemy zobaczyć tylko wartości liczbowe.

Anoda czy katoda?

Najprostsze układy wyświetlaczy siedmiosegmentowych opierają się na wspólnej katodzie lub wspólnej anodzie, co oznacza (w przypadku wspólnej anody), że z aż 10 pinów dwa środkowe będą otrzymywały napięcie, a 8 pozostałych – uziemienia lub na odwrót (w przypadku wspólnej katody).

My pracowaliśmy na wyświetlaczu ze wspólną anodą. Każdy pin pozwala na przepływ prądu przez inny segment (LED) na obszarze wyświetlania. Segmenty mają swoje oznaczenia od a do g (oraz kropka dziesiętna DP – decimal point).
 

Sterowanie wyświetlaczem przy pomocy arduino

Po podłączeniu 8 pinów wyświetlacza do pinów cyfrowych Arduino oraz doprowadzeniu z Arduino zasilania (dodając po drodze opornik) przyszedł czas na programowanie.

Przed dowolnym programem należało przypisać odpowiednie piny w programie.

int start=6;
int end=13;
int i;

„Start” oznacza pierwszy z użytych pinów Arduino, „end” – ostatni. Zmienna „i” jest pomocniczym licznikiem do tablic.

Na próbę stworzyliśmy prosty program pozwalający zapalić wszystkie segmenty wyświetlacza, co pozwoliło przetestować prawidłowe podłączenie wyświetlacza.

void setup(){
  for(i=start;i<=end;i++){
      pinMode(i,OUTPUT);
      digitalWrite(i,1); //0=LOW, 1=HIGH
  }
}

void loop(){
  for(i=start;i<=end;i++)
      digitalWrite(i,1);
  delay(500);
  for(i=start;i<=end;i++)
      digitalWrite(i,0);
  delay(500);
}

W pętlach for widocznych w bloku funkcji void loop() możemy zobaczyć przypisanie stanów wysokich i niskich każdemu z pinów podłączonych do wyświetlacza. Ze względu na to, że jest to wyświetlacz o wspólnej anodzie, to stan niski powoduje zapalenie się lampek, gdyż następuje wtedy przepływ prądu.

Wyświetlanie cyfr

Następnie rozpoczęliśmy tworzenie programu wyświetlającego konkretne cyfry. Stworzyliśmy prosty program w którym wprowadziliśmy własne funkcje – wyświetlające jedynkę (funkcja void jedynka()) , dwójkę (void dwojka()) oraz „czyszczące” wyświetlacz (funkcja void nic()). Budowanie takich funkcji ma ogromny sens – będą to nasz „cegiełki”, które wielokrotnie można później używać – patrz główna funkcja loop() w Arduino. 

int i;
int ledA =8;
int ledB = 9;
int ledC = 11;
int ledD = 12;
int ledE = 13;
int ledF = 7;
int ledG = 6;
int ledDP = 10;
// Kolejność podpięcia diod a - g,DP to właśnie 8,9,11,12,13,7,6,10
void setup(){
  pinMode(ledA,OUTPUT);
  pinMode(ledB,OUTPUT);
  pinMode(ledC,OUTPUT);
  pinMode(ledD,OUTPUT);
  pinMode(ledE,OUTPUT);
  pinMode(ledF,OUTPUT);
  pinMode(ledG,OUTPUT);
  pinMode(ledDP,OUTPUT);
  digitalWrite(ledDP,1);
  nic();
}

void loop(){
  jedynka();
  delay (500);
  dwojka();
  delay (500);
  dwojka();
  delay (500);
  jedynka();
  delay (500);
  nic();
  delay (500);
}

void nic(){
  digitalWrite(ledA,1);
  digitalWrite(ledB,1);
  digitalWrite(ledC,1);
  digitalWrite(ledD,1);
  digitalWrite(ledE,1);
  digitalWrite(ledF,1);
  digitalWrite(ledG,1);
}//nic

void jedynka(){
  digitalWrite(ledA,1);
  digitalWrite(ledB,0);
  digitalWrite(ledC,0);
  digitalWrite(ledD,1);
  digitalWrite(ledE,1);
  digitalWrite(ledF,1);
  digitalWrite(ledG,1);
}//jedynka

void dwojka(){
  digitalWrite(ledA,0);
  digitalWrite(ledB,0);
  digitalWrite(ledC,1);
  digitalWrite(ledD,0);
  digitalWrite(ledE,0);
  digitalWrite(ledF,1);
  digitalWrite(ledG,0);
}//dwojka

W naszym kodzie, są również widoczne zmienne „ledA”, „ledB” itd (linijki 2 – 9), które były tworzone właśnie w nawiązaniu do nazewnictwa widocznym na rysunkach pokazującym budowę wyświetlacza (widocznym na samym początku wpisu). Warto zauważyć, że opłaca się używać nazw zmiennych mających jakieś konkretne znaczenie – tutaj wybraliśmy nazewnictwo zaczerpnięte z budowy sevseg-a. 

Łatwo zauważyć, że powyższy kod jest bardzo powtarzalny i wizualnie zajmuje sporo miejsca, przez co jest też mniej czytelny i nieprofesjonalny. W/w program jest przykładem programowania strukturalnego, czyli hierarchicznego podziału programu na odpowiednie procedury i bloki kodu. Widoczne są utworzone funkcje „nic()”, „jedynka()” i „dwojka()”. Analogicznie należy tworzyć kolejne funkcje generujące kolejne cyfry i wywoływać je w void loop().

Oczywiście na tym nie poprzestaliśmy.

Program z tablicami

Jak widać każda funkcja kolejno zapala odpowiednie segmenty wyświetlacza. Jest to robione „ręcznie”, tj za każdym razem musieliśmy przypisywać każdemu pinowi stan wysoki i niski. Znacznie bardziej optymalną metodą było stworzenie dwuwymiarowej tablicy. Każdy 8-elementowy wiersz tablicy odpowiadał przypisaniu odpowiedniego stanu logicznego danemu segmentowi (jest ich osiem, gdyż poza 7 segmentami jest również 'kropka dziesiętna’ – decimal point). Ten proces pozwolił nam porzucić metodę nazywania każdej diody po kolei (tj. „ledA”, „ledB”…). Na potrzeby tej metody należało dodać tablicę kontrolną z numerami pinów do których podłączone są piny wyświetlacza (linijka 2).

int i;
int led[8]={8,9,11,12,13,7,6,10};
int digits[5][8]={ //na razie definiuje tylko 5 cyfr
// A B C D E F G DP
  {0,0,0,0,0,0,1,0}, //zero
  {1,0,0,1,1,1,1,0}, //wyswietla 1
  {0,0,1,0,0,1,0,0}, //wyswietla 2
  {0,0,0,0,1,1,0,0}, //wyswietla 3
  {1,0,0,1,1,0,0,0}
  }; //...
   //macierz, 4 tablice, kazda z osmioma elementami

void loop(){
  jedynka();
  delay (500);
  dwojka();
  delay (500);
  trojka();
  delay (500);
  czworka();
  delay (500);
  nic();
  delay (500);
}

  void nic(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[0][i]);
  }//nic

  void jedynka(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[1][i]);
  }//jedynka
  
  void dwojka(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[2][i]);
  }//dwojka

Teraz pozostało jedynie przewidzieć który segment ma dostać stan wysoki albo niski, by całość reprezentowała daną liczbę. Kolejne liczby były reprezentowane przez kolejne wiersze w tablicy dwuwymiarowej.

By zrozumieć działanie tej tablicy należy przyjrzeć się działaniu funkcji wywołujących kolejne liczby. Dla przykładu: „jedynka()” zamiast odwoływać się bezpośrednio do każdego pinu przez zmienne ledA, ledB, ledC… (poprzednio), zastąpiliśmy to teraz pętlą „for” przypisującą kolejne stany logiczne (zera lub jedynki) z wiersza tablicy. Każda kolumna tej tablicy odpowiada kolejnej diodzie w wyświetlaczu, czyli w pierwsza kolumna to (poprzednio) ledA (0=świeci, 1=nie świeci), druga to (poprzednio) ledB (0=świeci, 1=nie swieci) itd.

Jeśli wywołujemy jedynkę, pętla „bierze” drugi wiersz w tablicy „digits[]” (uwaga: indeksowanie od zera, więc indeks wiersza=1) i przypisuje diodzie „A” stan wysoki (obszar A nie zaświeci się), diodzie „B” stan niski (dioda zaświeci się). Rysowanie cyfry dwa to branie wiersza numer trzy (indeks wiersza tablicy=2) itd.

Ta operacja zarówno skraca kod, jak i zwiększa jego czytelność i wygodę dodawania kolejnych wyświetlanych liczb lub znaków.

Program wciąż można było ulepszyć.

Ostateczna wersja programu

Optymalizacja kodu dotyczy wywoływania liczb – mamy wiele podobnych funkcji (jedynka(), dwojka() itd). Zamiast tworzyć kolejne funkcje stworzyliśmy jedną funkcję void cyfra(int n), które wywoływały z tablicy dwuwymiarowej kolejne wiersze, czyli kolejne cyfry.

int i;
int led[8]={8,9,11,12,13,7,6,10};
int digits[5][8]={
// A B C D E F G DP
  {0,0,0,0,0,0,1,0}, //zero
  {1,0,0,1,1,1,1,0}, //swieci 1
  {0,0,1,0,0,1,0,0}, //swieci 2
  {0,0,0,0,1,1,0,0}, //swieci 3
  {1,0,0,1,1,0,0,0}
  }; //...
   //macierz, 4 tablice, kazda z osmioma elementami

void setup(){
    for(i=0;i<8;i++)
      pinMode(led[i],OUTPUT);
}

void cyfra(int nr){
  for(i=0;i<8;i++)
    digitalWrite(led[i],digits[nr][i]);
}

void loop(){
  cyfra(1);
  delay (500);
  cyfra(2);
  delay (500);
  cyfra(3);
  delay (500);
  cyfra(4);
  delay (500);
 }

Dodawanie kolejnych liczb stało się niezwykle proste i przejrzyste. Program sprawia wrażenie bardziej profesjonalnego i czytelnego. Uproszczone jest także wyświetlanie. Wystarczy rozszerzyć tablicę o kolejny wiersz i dopisać odpowiednie stany logiczne kolejnym segmentom wyświetlacza.

Podsumowanie

W tym tygodniu poznaliśmy czym jest wyświetlacz 7-segmentowy i w jaki sposób funkcjonuje, odkryliśmy kolejne zastosowania tablic oraz że tablica może być również macierzą, tj. mieć więcej niż jeden wymiar. Ostatecznie zastosowaliśmy metody programowania strukturalnego, które jest obok programowania obiektowego, jedną z najszerzej stosowanych metod w świecie programowania.

Praca domowa

Wykorzystać powyższy kod (np. w wirtualnym arduino) i rozbudować o wczytywanie cyfr z klawiatury (komunikacja szeregowa), czym bawiliśmy się poprzednio.

Do zobaczenia w przyszłym tygodniu!
(c) Maciej 2017

 

Sterowanie ledami za pomocą klawiatury – ASCII i (znowu) tablice

Na ostatnich zajęciach kontynuowaliśmy naukę obsługi ledów przez monitor szeregowy. Tym razem nie ograniczaliśmy się do sterowania wszystkimi ledami jednocześnie, a dążyliśmy do możliwości dowolnego włączania i wyłączania pojedynczych lampek. Podczas tych zajęć uczyliśmy się sprytnego (zaawansowanego?) wykorzystania tablic.

Switch … case

Na zajęciach wykorzystaliśmy układy przygotowane tydzień temu. Podłączyliśmy je w taki sam sposób. Do naszego pierwszego programu wykorzystaliśmy warunek wielokrotnego wyboru, czyli składnię switch … case.

Na początku kodu standardowo  podłączamy każdy z ledów do pinu cyfrowego. Następnie tworzymy pętlę if, która sprawdza, czy są dane (bajty) na porcie szeregowym do odczytania przez Arduino – linia #11. W 12 linii wczytujemy jeden bajt i przypisujemy go do zmiennej znakowej c (omawialiśmy to tydzień temu). W celu umożliwienia sterowania pojedynczymi lampkami za pomocą konkretnych liter w 13 linii tworzymy przełącznik switch zależny właśnie od zmiennej c. W liniach 14-23 tworzymy warunki, czyli przypisujemy do wybranych znaków (u nas: a b c d e A B C D i E)  włączanie lub wyłączanie danego LED-a. Fajne jest to, że do bufora możemy od razu wpisać całe „zdanie” a nie tylko pojedynczy znak. Arduino będzie odczytywać znak-po-znaku (linia 12), a my zobaczymy daną sekwencję włączania/wyłączania LED-ów na płytce. Aby uatrakcyjnić ten fragment etap zabawy z Arduino i LED-ami dodaliśmy specjalny case z wartośćią #, który tworzy przerwę – pauzę (linia 24).

#define MAX 5
int piny[5]={2,3,4,5,6};
int i;
char c;
void setup(){
  for(i=0;i<MAX;i++)
  pinMode(piny[i],OUTPUT);
  Serial.begin(9600);
}
void loop(){
  if(Serial.available()>0){
  c=Serial.read();  
   switch(c){
      case 'a': digitalWrite(piny[0],HIGH);break;
      case 'A': digitalWrite(piny[0],LOW);break;
      case 'b': digitalWrite(piny[1],HIGH);break;
      case 'B': digitalWrite(piny[1],LOW);break;
      case 'c': digitalWrite(piny[2],HIGH);break;
      case 'C': digitalWrite(piny[2],LOW);break;
      case 'd': digitalWrite(piny[3],HIGH);break;
      case 'D': digitalWrite(piny[3],LOW);break;
      case 'e': digitalWrite(piny[4],HIGH);break;
      case 'E': digitalWrite(piny[4],LOW);break;
      case '#': delay(200);break; 
}}}

Właśnie dzięki nowemu symbolowi # (pauza) było możliwe wpisywanie sekwencji (=”zdań”) typu abcde#####A#B#C#D#E (jednoczesne włączenie wszystkich 5-ciu LEDów, odczekanie sekundy a następnie wyłączenie, z krótkimi przerwami, po kolei LED-ów). Inne sekwencje to, np. abcde##E##D##C##B##A##abcde###ABCDE###abcde###ABCDE (włączenie wszystkich, wyłączenie po kolei wszystkich w odwrotnej kolejności a na koniec dwukrotne „mrygnięcie” wszystkimi na raz LED-ami).

Obserwacje/uwagi

Powyższy program jest prosty ale ciągle efektowny – dzięki wprowadzeniu pauzy (#). Jednak z informatycznego punktu widzenia cierpi na następujące problemy:

  • chcąc dodać więcej LED-ów musimy jak „małpa” skopiować linie 14-15 dodając nowe literki do sterowania. Zauważamy jednak pewną regularność w oprogramowaniu każdej literki (linie 14-23 niewiele różnią się od siebie). Może nie ma w tym nic złego, ale czy nie da się tego jakoś lepiej zaprogramować? 
  • jak rozbudować program o możliwość sterowania literkami w ten sposób, że dana literka włącza LED-a gdy był on wyłączony, a wyłącza gdy był on włączony? Jak na razie do włączania używamy małych liter a do wyłączania dużych – to chyba zbyteczna rozrzutność.

Zaczynamy od rozwiązania pierwszego problemu i przechodzimy do wykorzystania tablic. Tablice pojawiają się tu w sposób naturalny – przyglądając się liniom 14-23 zauważamy, że włączamy/wyłaczamy LED-y podpięte do portów cyfrowych Arduino zapisanych w tablicy piny[0,1,2,3,4]. Przy czym pierwszy LED podłączony jest do portu piny[0], drugi do portu piny[1] i tak dalej. Jak więc dobrać odpowiedni indeks tablicy do konkretnego LED-a?

ASCII

Dobranie indeksu tablicy piny[] będzie bazować na kodowaniu znaków ASCII. Dane wczytywane przez Serial.read() to w rzeczywistości bajty, które możemy interpretować jako literki (typ char) lub jako liczby (typ int). Możemy więc patrzeć na literkę d jak znak 'd’ (typ char) lub jak na kod ASCII wynoszący 100 (liczba całkowita, typ int). Dlaczego liczba 100? Przypatrz się uważnie tablicy kodów ASCII z poprzedniego linku (kolumna DEC) lub tutaj. Kolejna literka po d to e – czyli kod ASCII 101 i tak dalej. Co więcej, możemy odejmować literki od siebie, bo to będzie zrozumiałe jako… odejmowanie liczb całkowitych! Tak więc d-a oznacza 100-97, czyli 3. Jesli więc umówimy się, że pod literką a mamy sterowanie pierwszego LEDa, pod b drugiego i tak dalej – to właśnie różnica wczytanej literki i znaku a da nam dobrze określenie indeksu tablicy dla konkretnego LED-a! Zapisane jest to w linii #6 poniższego kodu:

void loop(){
  if(Serial.available()>0){
    c=Serial.read();
    Serial.print("Wczytałem znak = ");
    Serial.println(c);
    int idx=c-'a';
    Serial.print("indeks =");
    Serial.print(idx);
    if(idx>=0&&idx<5){ //gdy mamy 5 LEDow
      Serial.print("Włączam / wyłączam LED-a nr");
      Serial.print(idx);
      //dalsza część kodu
    }
}

Na małą uwagę zasługuje jeszcze linia #9 – sprawdzenie, czy indeks nie jest większy niż liczba podłączonych LED-ów (oczywiste) oraz czy indeks jest większy od zera. To drugie może nastąpić  gdy, np. omyłkowo wpiszem z klawiatury znak [ (o numerze ASCII 91), więc w wyniku odejmowania mamy nieistnieący element tablicy o indeksie 91-97=-6.

Tablica – zapamiętanie stanów pinów

Drugi problem na naszej liście to zapamiętanie stanów portów cyfrowych Arduino. Chodzi o to, aby po odczytaniu danej literki włączyć LED-a gdy był on w stanie wyłączonym, i wyłączyć – gdy  był on włączony. Arduino nie ma jakiejś specjalnej funkcji do „zapytania się” o aktualny stan portu, dlatego więc musimy zrobić do samodzielnie. Wykorzystamy pamięć operacyjną płytki Arduino, czyli stany portów będziemy zapisywać w zmiennych. Może do tego służyć zmienna typu logicznego bool. Przechowuje ona tylko dwie wartości: 0, czyli fałsz (false), oraz liczbę różną od zera, czyli prawdę (true). Pewnie nie ma nic złego w utworzeniu pięciu takich zmiennych dla naszych pięciu LED-ów, ale gdy podłączymy ich 20? 30? Dlatego ponownie używamy tablice:

bool stan[]={false,false,false,false,false};
void loop(){
  if(Serial.available()>0){
  c=Serial.read();
  int idx=c-'a';
  if(idx>=0&&idx<5)//gdy mamy 5 LEDow
    if(stan[idx]==true){
      digitalWrite(piny[idx],HIGH);
      stan[idx]=false;
    }
    else{
      digitalWrite(piny[idx],LOW);
      stan[idx]=true;
    }
  else//jesli idx rozny od zera to moze... nasza pauza?
  if(c=='#') delay(200);

Na początku (linia #1) wprowadzamy zmienną tablicową stan z informacją o wyłączeniu wszystkich LED-ów (pięć false-ów). Kluczowe są linie #7-14, gdzie sprawdzamy stan portu i jeśli jest on włączony (wartość true) to wyłączamy LED-a i zmieniamy stan na false (linia #9), a gdy jest wyłączony (wartość false – u nas „w przeciwnym przypadku” linia #11) to włączamy LED-a i także zmieniamy stan portu – tym razem na true (linia #13). 

Program ponownie akceptuje całe sekwencje rozkazów („zdania”) a dodatkowo nie potrzebuje już oddzielnej literki dla włączenia (poprzednio mała literka) i wyłączenia (poprzednio duża literka) LED-a. Piszemy więc zdania typu   abcde######edcba##a##a##b##b##c##c##b##b  i obserwujemy płytkę stykową.

Podsumowanie

Wszystko dało się tak prosto zapisać dzięki tablicom oraz sprytnie wyliczonemu indeksowi tablicy (kodowanie ASCII). Jak widać indeks wykorzystaliśmy dwukrotnie – raz w odniesieniu do włączania/wyłączania LED-a (funkcja digitalWrite i LEDy podłączone do portów zapisanych w tablicy piny) a drugi raz do zapisu stanu portu cyffrowego Arduino (tablica stan). Siłę tego programy docenimy wówczas, gdy podłączymy dużo LEDów (np. 20) i jedyne rozbudowanie tego kodu polegać będzie na… zmienie stałej MAX w pierwszej linii programu – a nie dopisywaniu prawie identycznych linii kodu dla każdego nowego LED-a (i jego stanu!).

Proszę przemyśleć dzisiejszą lekcję, bo za tydzień ponownie spotykamy się z tablicami (no i z wyświetlaczem siedmiosegmentowym).

(c) Ewelina & KG

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

 

Czujka szczelinowa — pomiar wartości przyspieszenia ziemskiego

Czujka szczelinowa

Na ostatnich zajęciach za pomocą czujki szczelinowej i wahadła wyznaczaliśmy wartość g (przyspieszenia ziemskiego). Podzieliliśmy się na dwie grupy, z których każda miała inny pomysł na stworzenie mechanizmu oraz napisanie odpowiedniego programu.

Zasada działania czujki jest bardzo prosta. Na wyjściu cyfrowym OUT z modułu czujki pojawia się sygnał wysoki, jeśli szczelina jest przesłonięta, oraz sygnał niski, gdy szczelina jest odsłonięta. Wahadło wykonując swoje ruchy harmoniczne będzie przechodziło przez szczelinę zasłaniając ją – my musimy zmierzyć czas pomiędzy tymi sygnałami.

Wahadła

Jedna grupa korzystała ze specjalnego statywu, na którym przymocowała cały mechanizm składający się ze sznurka i nakrętki na śrubkę. Ułatwiło to pracę i zminimalizowało niepewności pomiarowe.

Druga grupa musiała wykazać się inwencją twórczą i improwizacją.

Podłączanie czujki

Czujnik łączymy z Arduino za pomocą trzech kabelków. Vcc podłączamy do napięcia 5V, GND do GND, a OUT to dowolny pin cyfrowy, który jest niezbędny do przekazywania informacji.

Kod programu

int czujka=5;
unsigned long t1,t2;

void setup(){
 Serial.begin(9600);
 pinMode(czujka, INPUT);
 Serial.println("jestem gotowy! ");
 Serial.print(millis());
  t1=millis();
  Serial.println(sizeof(t1));
}
int i=1;

void loop(){
 if(digitalRead(czujka)==1)
 {t2=millis();
 Serial.print(i);
 Serial.print(" ");

 Serial.println(t2-t1);
 t1=t2;
 i++;

 delay(200);
}
}

Czujnik podłączyliśmy do cyfrowego pinu nr 5, można to zmienić w pierwszej linijce kodu.

Dążąc do wyznacznia wartości przyspieszenia ziemskiego, musimy poznać okres drgań wahadła. W tym celu napisaliśmy program korzystający z funkcji millis (więcej informacji znajduje się w jej manualu). Używamy jej dwa razy: za pierwszym razem w linii #9 zapamiętujemy jej wartość do zmiennej t1 (czyli czas włączenia programu Arduino), natomiast w linii #16 przypisujemy jej wartość do zmiennej t2 w celu późniejszego obliczenia różnicy pomiędzy nimi. Różnica ta oznacza czas pomiędzy dwoma następującymi po sobie przesłonięciami czujki, czyli momentem przejścia przez „bramkę” i ponownego powrotu. Pamiętajmy, że nie jest jeszcze okres wahadła (okres to czas pełenego ruchu „tam i spowrotem”) dlatego obrabiając dane pamiętamy o mnożeniu tych czasów przez dwa. W linii #21 równamy wartości zmiennych t1 i t2, dzięki czemu w następnej iteracji pętli zostanie obliczona różnica pomiędzy kolejnymi dwoma ruchami wahadła. W przypadku braku tej linii, otrzymywane wyniki byłyby różnicą czasu danego ruchu wahadła i czasu od włączenia Arduino (uruchomienia programu). Wypisane wartości sprawdzaliśmy w Monitorze szeregowym, podobnie jak w przypadku przycisku.

Istotna jest linia #24 powyższego kodu – te opóźnienie (dobrane metodą prób-i-błędów) potrzebne jest po to, aby otrzymać tylko jedną informację o przesłonięciu czujki naszym wahadłem. Gdy nie ma tej linii, to w czasie przejścia przez szczelinę wahadła czujka „produkuje” setki zdarzeń, które są nam zbyteczne (a nawet przeszkadzają).

Wyznaczanie wartości g

Po otrzymaniu wielu pomiarów, w celu dalszych obliczeń użyliśmy arkusza kalkulacyjnego. Skorzystaliśmy ze wzoru: G=(4π²)*L/T², przy czym G to przyspieszenie ziemskie, L to długość wahadła, a T to okres drgań. Wykonaliśmy obliczenia (w programie LibreOffice Calc), a wyniki wyszły nam dość imponujące. Średnia wartość pomiarów dała nam wynik 9,73(±0,12)m/s²,  przy czym ogólnie przyjęta wartość to 9,81m/s².

 (c) Ewelina 2017

Przycisk (modeuł) + ekran

Dziś poznalismy trzy rzeczy: wypisywanie komunikatów przez Arduino na ekran PC-ta, funkcję „zegara” millis() oraz obsługę przycisku (a właściwie modułu z przyciskiem, niedługo poznamy różnice).

Komunickacja z PC-tem

Chodzi o komunikację szeregową UART z wykorzystaniem dwóch pinów cyfrowych Rx/Tx (piny numer zero i jeden). Uruchamiamy komunikację poleceniem Serial.begin(szybkość). W tym momencie tracimy dwa wspomniane piny(zero i jeden) – no cóż, coś za coś. Parametr szybkość musi być na obu urządzeniach taki sam, w przeciwnym przypadku urządzenia się „nie dogadają”. W środowisku Arduino IDE wybieramy Monitor szeregowy (lub w angielskiej wersji: Serial Monitor) i nasłuchujemy to, co nadaje do nas Arduino – to jest właśnie drugie urządzenie, do którego Arduino nadaje (prawy, górny róg okienka z kodem programu)

Jak wspomniałem należy sprawdzić, czy w okienku Monitora (portu) szeregowego parametry tramisji (szybkość) jest taka sama, jak w programie Arduino – prawy, dolny róg tego okienka.

W przyszłości opowiem więcej o tych parametrach transmisji oraz jak wysyłać dane do Arduino – ale to później.

Trzeba zapamiętać dwie funkcje wysyłające napisy z Arduino:

  • Serial.print(„napis”) — wysyłamy tekst napis do urządzenia nasłuchującego; tekst musi być w cudzymsłowiu (aka. psie uszy)
  • Serial.println(„napis”) — wysyłamy napis i znak przejścia do nowej linii – dzięki temu kolejny napis pojawi się w nowej linii.

Te dwie funkcje w zupełności wystarczają, aby Arduino coś do nas „mówiło” 😉

millis()

Bardzo przydatna funkcja zwracająca liczbę milisekund od czasu rucuhomienia programu (włączenia/zrestartowania Arduino). Do zapisywania danych zwracanych przez tą funkcję należy użyć typu unsigned long – wystarczy na około 50 dni. Więcej info tutaj.

Dzięki tej funkcji możemy mierzyć czas wykonania jakiejś czynności. Aby to zrobić, musimy zapisać „zegar” (aktualną wartość milisekund, która sama w sobie nie jest istotna) do jednej zmiennej, powiedzmy t1, wykonać czasochłonne czynności, a po nich ponownie zapytać o „zegar” – i zapisać do drugiej zmiennej, np. t2. Różnica t2 i t1 jest właśnie czasem wykonania konkretnej czynności.

Przycisk (moduł).

Dzięki temu, że to jest przycisk z modułem, to jego obsługa jest bardzo prosta – musimy podłączyć zasilanie do modułu (Vcc do Arduino 5V, GND do Arduino GND, a pin sygnałowy S – do dowolnego, wybranego przez nas pinu cyfrowego w Arduino – np. 7).

Następnie pamiętajmy o poleceniu pinMode(7, INPUT), które spowoduje, że będziemy mogli odczytywac stan przycisku za pomocą digitalRead(7). Jeśli przycisk jest wciśnięty, to funkcja ta zwróci wartość 1, w przeciwnym przypadku – wartość 0 (zero).

Koszt takiego modułu to około 3 zł, należy szukać hasła w stylu Moduł przycisku tack switch z LED ARDUINO (np. w serwisie Allegro).

Fale… z ledów… także wirtualnych…

Fala… z LED-ów…. prawdziwych! Oprócz fal pojawiła się TABLICA i pętla FOR. To sporo – kto nadążał – gratuluję!

Prezentacja Pana Macieja – proste, ale… całkiem efektowne! Gratuluję. Bez pętli zaprogramować coś takiego to koszmar.

Następnie zachęcałem do wirtualnego Arduino ze stronki tinkercad.com (jest to znany projekt cicuits.io, który przejęła firma Autodesk):

Stronka ta umożliwia zabawę w Arduino bez posiadania płytki Arduino – wystarczy się zarejestrować (za darmo!) i można działać.

Jak zacząć? Już po rejestracji wybieramy CIRCUITS:

a następnie zielony przycisk Create new circuits:

Kolejnym krokiem jest już dodawanie elementów po naciśnięciu +Components (prawy pasek, na górze)

gdzie zaczynamy od płytki stykówki, LED-a, rezystorka i bateryjki. Uruchamiamy symulację przyciskiem Start Simulation (potem ją zatrzymujemy Stop). Gdy już wszystko rozumiemy (tyle-o-ile) to czas na dodanie płytki Arduino i… tu się zaczyna zabawa!

Kto zrobi pracę domową z „odbijającą się” falą? Ze zmienną czas? Na następnych zajęciach wgramy programiki na prawdziwe płytki i zobaczymy kolorowe efekty! Proszę śmiało działać!

 

Pierwsze spotkanie 2017 r.

No i rozpoczęliśmy nowy rok akademicki 2017/2018 a tym samym i Fi-BOTa. Na pierwszych zajęciach pojawiło się 9 śmiałków – zobaczymy, ilu będzie kontynuować…

Co robiliśmy? Dla większości była to pierwsza przygoda z Arduino więc było sporo gadania, ale finalnie udało nam się błyskać jednym LED-em podłączonym na płytce stykowej. To dużo jak na godzinkę z hakiem i moją skłonność do gadania 😉 Ale pokazałem też motywację – ta sama płytka Arduino podłączona do żarówki 230V – i działa!

Spotkania w roku 2017/18 będą odbywać się zawsze we wtorki o godz. 16:00. Zapraszam!

Przerwa wakacyjna

Ponownie spotykamy się w październiku 2017 r.

Podsumowując rok akademicki 2016/17: przeprowadziliśmy 31 spotkań w ramach Fi-BOTa, w tym jedno festiwalowe (XV Podlaski Festiwal nauki i Sztuki). Dziękuję za wytrwałość i zapraszam w roku akademickim 2017/18 (termin będzie ustalony w październiku). Zachęcam do samodzielnej pracy z Arduino i… pochwaleniem się własnymi projektami w nowym semestrze!

K.G.

Followliner (FL) – starcie 1

  1. Prosty FL – zbudowany na 2 czujnikach TCRT5000 ustawionych blisko siebie tak, by oba leżały nad czarną linią. Położenie czujek – z przodu, przy napędzie (wiem wiem…).
  2. Instalacja modułu w pojeździe.
  3. Pierwszy program – jedź prosto, gdy oba czujniki „widzą” linię.
  4. Modyfikacja – skręcaj (zatrzymując jedno koło) gdy jedna czujka „gubi” linię.
  5. Modyfikacja – silniki STOP, gdy obie czujki zgubiły linię.

Do zrobienia:

  1. (chwilowo) zostaniemy przy 2 czujkach, za to dodamy regulację szybkości kół w zależności od sygnału na czujce (aby nie było szarpania, a ruch był płynny).
  2. gdy pojazd wyjechał poza linię, to ma się cofnąć (może nawet kilka razy, a jeśli po tych kilku razach dalej nie widać linii – to dopiero wówczas STOP).
  3. zmodyfikujemy tor testowy – łagodniejsze łuki (łatwiejsze, ale z czasem zmienimy).

Liczymy obroty (TCRT5000) – sukces!

Liczymy obroty silniczka D65:

Znalezione obrazy dla zapytania d65 zestaw napedowytutaj nasz program:

 

bool stan, poprz;
unsigned long int t1;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  stan=false;
}

int odczyt;
int licz=0;
void loop() {
  poprz=stan;
  odczyt=analogRead(A0);
  if (odczyt<50)
    stan=true;
  else
    stan=false;
//  Serial.print(odczyt);
//  Serial.print("   ");
//  Serial.print(stan?"jasne":"ciemne");
//  Serial.print("   ");
  if (stan!=poprz){
    licz++;
    Serial.print(" KLIK ");
    Serial.print(licz);
    Serial.print(" ");
    if (licz%2==0){
      Serial.print(1000./(millis() - t1)*60);
      Serial.println(" rpm");
      t1= millis();
    }
  }
//  else
//    Serial.println();
}

I działa bardzo fajnie, nawet z tylko jednym znacznikiem! Wyniki są bardzo powtarzalne (przy zasilaniu około 7V – jeden obrót w 230-240ms, czyli 4 obroty w 1 sekundę, a to daje 4*60=240 obrotów na minutę – rpm).

Rozbudowa?

  1. więcej znaczników (już 2 polepszają sprawę, 4 to zdecydowanie OK).
  2. inny algorytm – zliczanie liczny obrotów i aktualizowanie wartości rpm co określony czas, np. 2 sek