Sterowanie serwem za pomocą joysticka

Na zajęciach sterowaliśmy serwomechanizmami za pomocą joysticka. Na początku używaliśmy niewielkiego modułu z joystickiem podłączanego bezpośrednio do Arduino. W drugiej części użyliśmy nakładki (shield) na Arduino z joystickiem oraz czterema przyciskami (podobnie jak na gamepadach).

Moduł z joystickiem

Moduł posiada pięć pinów. GND oraz 5V podłączamy do Arduino. VRx i VRy to piny sterujące odpowiednio osią OX (czyli lewo-prawo) oraz OY (czyli góra-dół). Podłączamy je do pinów analogowych. Ostatni pin odpowiada za przycisk, jednak nie używaliśmy go w tym zadaniu.

Prosty program wyświetlający położenie joystika

void setup(){
  Serial.begin(9600);
}
int x,y;
void loop(){
   x = analogRead(A0);
   y = analogRead(A1);
   Serial.print("x=");
   Serial.print(x);
   Serial.print(", y=");
   Serial.println(y);  
  }

Położenie na osi OX (także OY) to liczby z zakresu 0..1023. Położenie spoczynkowe powinno odpowiadać wartości 511 (liczby 0..510 to wychylenie w lewo, liczby 512..1023 to wychylenie w prawo). Użycie tego programu pozwoliło nam sprawdzić, że tak jednak nie jest – u nas joy w położeniu spoczynkowym miał wartości 514, 517 (oś OX i OY).

Podczas działania tego programu możemy postawić sobie następujące zadanie: ustawić pozycję joy-a w takim położeniu, aby odczyty były x=800 y=200. Okazuje się to jednak bardzo trudne! Widzimy więc, że sterowanie joy-em nie jest łatwe i wymaga sporo wprawy.

Wieżyczka

Używana przez nas wieżyczka (do której można przyczepić, np. kamerkę internetową by nią sterować) składała się z dwóch serw na podstawce. Umożliwia to poruszanie się mechanizmu na boki oraz w górę i dół.

Wszystkie potrzebne elementy połączyliśmy za pomocą płytki prototypowej. Zamiast zasilania z Arduino użyliśmy zewnętrznego koszyka na 4 baterie o łącznym napięciu 5V. Dlaczego tak zrobiliśmy? Chodziło nam o oddzielne zasilanie silników aby nie przeciążyć płytki Arduino – pojedyncze serwo może pobrać nawet 200mA prądu (co sprawdzaliśmy na poprzednich zajęciach), a wydajność prądowa Arduino UNO to około 200-400 mA. Dlatego dwa takie serwa mogą uszkodzić naszą płytkę. My użyliśmy oddzielnego zasilania silników aby temu zapobiec. WAŻNE: w przypadku używania kilku źródeł zasilania (u nas Arduino 5V i 4x baterie AAA) musimy uwspólnilić masy (GDN z Arduino, „minus” z bateryjki).

Użyliśmy płytki prototypowej, gdzie na jednej szynie (koloru niebieskiego) wetknęliśmy „-” z koszyka baterii oraz GND Arduino. Do tej „szyny” podłączone były masy serw (przewody koloru brązowego). Druga szyna (czerwona – z drugiej strony płytki, dla naszej wygody) doprowadzone miała przewód „+” z koszyka baterii i tam podłączone były zasilania silników serw (czerwone przewody serw). UWAGA: nie można łączyć pinu 5V Arduino z zewnętrznym zasilaniem baterii – może to spowodować uszkodzenie płytki! Łączymy (=uwspólniamy) jedynie masy. Przewody sterujące serwami (koloru żółtego) podłączyliśmy do pinów PWM Arduino UNO – u nas #3 i #5.

Program sterujący

Na początku standardowo dołączamy do programu bibliotekę umożliwiającą sterowanie serwami. Następnie dodajemy zmienne do obu serw, jedno odpowiedzialne za ruch góra-dół, a drugie lewo-prawo.

#include <Servo.h>
Servo GoraDol;
Servo LewoPrawo;

void setup (){
  Serial.begin(9600);
  GoraDol.attach(3);
  LewoPrawo.attach(5);
}
int x,y;

W kolejnej części deklarujemy zmienne odpowiadające za położenie obu osi joysticka – dokładnie tak, jak w pierwszym programie odczytującym położenia joy-a. Wyświetlamy wartości x oraz y w monitorze szeregowym. Joystick zwykle nie jest idealnie skalibrowany, jednak nie ma to znaczenia przy niewielkiej precyzji.

Funkcja map pozwala na łatwe proporcjonalne przeliczenie wartości. Używamy jej, ponieważ położenia joysticka są z zakresiu 0-1023, natomiast sterowanie serwem chcemy wyrażamy w stopniach 0-180. Wynik przypisujemy od razu do zmiennych x oraz y (używamy tych samych zmiennych, niszcząc ich poprzednie wartości). W argumentach funkcji wpisujemy zmienną do przeliczenia, następnie jej obecny zakres i na końcu zakres po zamianie. Po przeliczeniu wartości możemy je przekazać do serwomechanizmów.

   x=map(x,0,1023,0,180); //funkcja map
   LewoPrawo.write(x);
   y=map(y,0,1023,0,180);
   GoraDol.write(y);
   delay(10);

Nakładka (shield) na Arduino

Fajnym rozwiązaniem jest wykorzystanie specjalnej nakładki (shield), która poszerza możliwości Arduino. Piny obu komponentów pasują do siebie, więc nie da się pomylić przy wpinaniu nakładki. Joystick oraz przyciski są fabrycznie podłączone do pinów, więc nie musimy tego robić w programie. Na nakładce wszystkie piny są podpisane, więc później będziemy tylko operować odpowiednimi oznaczeniami pinów.

Modyfikacja programu – przycisk „zamrażający” położenie wieżyczki

Program sterujący jest bardzo podobny do poprzedniego. Jedyną różnicą jest brak podłączenia pinów cyfrowych. Dodatkową funkcją, którą wprowadzamy do naszego programu, jest zatrzymywanie serwa w ustawionej pozycji. Chcemy aby wciśnięcie wybranego przycisku (np. koloru czerwonego) zablokowywało dalsze sterowanie wieżyczką. Kolejne wciśnięcie tego przycisku powoduje odblokowanie sterowania. W tym celu do kodu wprowadzamy zmienną typu logicznego bool (nazwaną tryb) i ustawiamy ją na wartość true. Następnie wybieramy przycisk (np. ten czerwony), który ma sterować zatrzymaniem serwa i  sprawdzamy, który numer pinu u odpowiada. W naszym programie jest to pin 5.  Tworzymy instrukcję sterującą if, która wykona się po wciśnięciu przycisku. Do wartości zmiennej tryb, przypisujemy jej odwrotność. Czyli jeśli tryb jest true, zostanie zmieniony na false i odwrotnie. Funkcja delay() wprowadzamy aby zarejestrować tylko jedną zmianę ustawienia przycisku (przyciski lubią „drgać” co powoduje nie jeden „klik” a wiele takich „klików” – opóźnienie je zniweluje). Działa to w taki sposób, że jeśli tryb jest wartością false, czyli zostanie raz zmieniony we wcześniejszym if’ie, sterowanie joyem zostaje zablokowane. Natomiast po kolejnym użyciu przycisku, tryb zmieni się z false na true, a sterowanie zostanie odblokowane.

bool tryb=true;
void loop(){
  x=analogRead(A0);
  y=analogRead(A1); 
   if (digitalRead(5)==HIGH){
      tryb=!tryb;
      delay(50);
   }

   if(tryb==true){ 
   x=map(x,0,1023,0,180); //funkcja map
   LewoPrawo.write(x);
   y=map(y,0,1023,0,180);
   GoraDol.write(y);
  }
}

Podsumowanie

Jak można zauważyć programy umożliwiające sterowanie servami nie są skomplikowane ani długie. Można sprytnie wykorzystać komponenty posiadające więcej niż jedną oś ruchu, dzięki czemu nasze możliwości się poszerzają. Jednak precyzyjne sterowanie takimi joystikami nie jest łatwe…

(c) Ewelina, KG 2017

Sterowanie servem za pomocą potencjometru

Na ostatnich zajęciach łączyliśmy wiedzę nabytą na dwóch poprzednich spotkaniach, czyli działanie potencjometru oraz serva. Chcieliśmy wykorzystać możliwość zmiany nastawy potencjometru, w celu sterowania ramionami serwomechanizmu. Podobnie jak na zajęciach przedświątecznych, wykorzystamy do tego komendy biblioteki sterującej servami.

Sterowanie serva

#include <Servo.h>
Servo silnik;

void setup (){
  Serial.begin(9600);
  silnik.attach(3);
}

Servo podłączamy tak samo jak na przedświątecznych zajęciach, jednak w tym przypadku używamy 3 pinu, a sam mechanizm nazywamy silnikiem (linia #2 i #6).

Wykorzystanie potencjometru

int pot;
void loop(){
   pot=analogRead(A0);//odczytujemy liczby z zakresu od 0 do 1023
   pot=pot*180.0/1023;//zamienimy na liczby od 0 do 180
   Serial.print(pot);
   silnik.write(pot);
}

Na początku deklarujemy zmienną zapisującą stan potencjometru. W ciele funkcji void loop() podłączamy potencjometr do portu analogowego A0. Linia #4 przelicza zakres potencjometru. Jak pamiętamy, wynosi on od 0 do 1023, natomiast stopnie wychylenia serva chcemy wyrażać w zakresie 0-180. 180 konwertujemy na zmienną typu float dopisując do niej część dziesiętną w celu uniknięcia dzielenia całkowitego. W innym wypadku otrzymywalibyśmy nieprawdziwe wyniki, ponieważ wynikiem dzielenia 180/1023 zawsze będzie 0, przez co całe działanie również wyniesie 0. Przy zapisie 180.0/1023 mamy do czynienia z dzieleniem rzeczywistym, którego wynikiem będzie liczba rzeczywista. Następnie przy przemnożeniu przez zmienną całkowitą pot dostaniemy również liczbę rzeczywistą. Ostateczny wynik jest rzutowany na liczbę całkowitą w momencie przypisania operatorem równości.

Ulepszenie – dodatkowy if

W celu ulepszenia naszego kodu, chcieliśmy aby położenie ramion serwa było aktualizowane jedynie wtedy, kiedy zmienimy położenie potencjometru. Tym samym chcemy uniknąć sytuacji wydawania polecenia „ustaw serwo na pozycję XX” jeśli właśnie aktualną pozycją jest XX (nie ma to sensu, mimo tego, że to działa – jak w naszym pierwszym, prostym programie). Użyliśmy do tego instrukcji sterującej if. W linii #1 dopisaliśmy kolejną zmienną nazwaną old, która ma za zadanie zapisywać poprzedni stan położenia potencjometru (odczytanego napięcia). W warunkach if-a sprawdzamy, czy zmienna pot (czyli aktualny stan potencjometru), różni się od zmiennej old (czyli jego poprzedni stan).  Jeśli nie, funkcja Serial.print() nic nie wypisze i nie zmieniamy położenia serwa. W przeciwnym przypadku zostanie wypisane nowe napięcie, a ramiona serwa zmienią położenie. Na końcu przypisujemy zmienną old do pot, aby móc dokonać nowego porównania w kolejnej iteracji pętli. 

int pot, old; //old - zmienna zapisujaca poprzedni odczyt

void loop(){
  pot=analogRead(A0);
  pot=pot*180.0/1023.0; 
  if (pot!=old){ //aby zmieniac polozenie tylko wtedy, kiedy sie zmienilo, a nie wyswietlac polozenie ciagle
   Serial.print(pot);
   silnik.write(pot);
   old=pot; 
  }
}

Podsumowanie

Dzięki naszemu programowi możemy sterować ramionami serwa kręcąc potencjometrem. Przeliczenie wartości napięcia na stopnie umożliwia dość precyzyjne ustawienie serva.

2018, Ewelina (c)

Rejestr przesuwny 74HC595

Na ostatnich zajęciach nadal zajmowaliśmy się programowaniem wyświetlaczy siedmiosegmentowych. W przypadku podłączania wyświetlacza bezpośrednio do Arduino, zajmujemy aż osiem pinów cyfrowych. To sprawia, że płytka Arduino UNO (wyposażona w 14 wejść/wyjść cyfrowych) może sterować tylko jednym wyświetlaczem. Można co prawda wykorzystać piny analogowe (A0..A5) jako cyfrowe, ale to da możliwość podłączenia jedynie drugiego wyświetlacza. To mało. Jest jednak sposób, aby sterować wieloma wyświetlaczami bez straty wszystkich pinów cyfrowych. Umożliwia nam to rejestr przesuwny 74HC595. Dzięki temu małemu układowi scalonemu mamy możliwość sterowania osmioma wyjściami za pomocą jedynie trzech pinów cyfrowych z Arduino. Bardziej szczegółowe informacje o tym układzie znajdują się w jego specyfikacji technicznej (ang. datasheet).

Podłączanie rejestru przesuwnego

Rejestr przesuwny podłączamy używając schematu pokazanego poniżej. Należy zwrócić uwagę na wyżłobienie po jednej stronie układu, które oznacza górną część.

Wyjście 8 (GND) podłączamy standardowo do pinu o tej samej nazwie na Arduino. Układ wymaga zasilania (to oczywiste) – które podłączamy do pinu 16 (VCC). Rejstr akceptuje zasilanie z przedziału od 2 do 6V – dlatego pin 16 śmiało łączymy z pinem 5V w Arduino UNO. Dodatkowo wyjścia 10 i 16 rejestru łączymy ze sobą (10 to pin odpowiedzialny za czyszczenie stanów rejestru – my to będziemy robić „ręcznie”, więc ustawiamy go w stan wysoki). Kluczowe są wyjście 14, 12 i 11 rejestru, które sterują jego pracą – łączymy je z wybranymi pinami cyfrowymi Arduino (ja wybrałam piny 8,9 i 10 na mojej płytce Arduino). Osiem wyjść rejestru (QA..QH, czyli piny 15, 1..7) podłączamy do kolejnych ledów na wyświetlaczu według tego schematu:

Oczywiście aby układ działał prawidłowo, do wyświetlacza siedmiosegmentowego również należy podłączyć zasilanie i odpowiedni rezystor (poprzednie zajęcia).

Opis pinów i sposób działania rejestru

Do pinów cyfrowych Arduino podłączamy trzy wyjścia rejestru. Są one kluczowe we wzajemnej komunikacji.

  • Pin 11 SRCLK to pin przesuwający wszystkie osiem stanów rejestru – przesuwanie odbywa się zawsze do przodu. Praca rejestru polega właśnie na cyklicznym przesuwania swoich stanów. Aby wykonać przesunięcie ustawiamy SRCLK w stanie LOW, a potem w stanie HIGH – ta sekwencja to komenda „przesuń stany rejestru”.
  • Pin 14 SER to pin ustawiający pierwszy rejest w stanie wysokim (gdy SER= HIGH) lub niskim (gdy SER=LOW).
  • Pin 12 RCLK (latch pin) – ustawienie go w stan niski (LOW) powoduje zablokowanie wyjść rejestru QA..QH, przez co nie widać zmian na tych pinach a tym samym nie widać etapów przejściowych przesuwania (które są wymagane do uzyskania końcowej konfigurajci ośmiu pinów). Ustawienie go w stan wysoki (HIGH) powoduje aktywację pinów QA..QH i to, co jest do nich podłączone – działa tak, jak chcieliśmy. 

Rejestr przesuwny działa w taki sposób, że ustawia (bądź pozostawia je w stanie zerowym) pierwsze wyjście (SER), po czym stan wszystkich wejść przesuwa o jedno miejsce do przodu (SRCLK). Następnie powtarza te czynności tyle razy ile tego zarządamy, aż osiągniemy oczekiwany ciąg zer i jedynek.  Wynika z tego, że zapisując docelową konfigurację wyjść QA..QH w tablicy (gdzie na pierwszym miejscu tablicy powinien znajdować się pierwszy led itd) to odczyt takiej tablicy (i związane z tym ustawianie pinu SER) powinien odbywać się „od tyłu” – aby po ośmiu przesunięciach osiągnąć naszą konfigurację.

Kod programu

Na początku programu definiujemy dwie stałe (dyrektywy #define). Pierwsza stała oznacza liczbę  rejestrów przesuwnych (u nas na razie 1), natomiast druga całkowitą liczbę pinów podłączonych do wyświetlaczy ledowych (liczbę ileScalaków mnożymy razy osiem, ponieważ każdy wyświetlacz ma osiem ledów). Następnie deklarujemy trzy piny cyfrowe łączące Arduino z rejestrem. Należy zwrócić na to uwagę, gdyż w przypadku nieprawidłowego podłączenia, nasz program może zachowywać się nieprzewidywalnie, bądź nie działać. Tablica rejestr odnosi się do wcześniejszej stałej ilePinow, czyli oznacza całkowitą liczbę pinów, którymi będziemy sterować.

#define ileScalakow 1
#define ilePinow ileScalakow * 8
int SER=8; 
int RCLK=9;
int SRCLK=10; 
int rejestr[ilePinow];

Funkcja void czyscRejestr() pozwala na wyczyszczenie tablicy rejestr, aby umożliwić jej ponowne zapełnienie. Robimy to używając pętli for, ustawiając kolejno każdą wartość na zqero (LOW).

Funkcja void zapiszRejestr() to główna funkcja programująca rejest przesuwny – ustawia piny QA..QH w takich stanach, jakie ma tablica rejestr w pamięci Arduino. Ustawienie pinu RCLK na pozycję LOW zamraża stany QA..QH – nie widać przełączania (=przesuwania) stanów rejestru. Pętlę for wykonujemy od końca, od liczby pinów odejmujemy 1, ze względu na numerowanie tablic od 0. Dzięki włączeniu pinu RCLK w stan HIGH zobaczymy dokonane zmiany.

W funkcji void ustawPin(int ktory, int wartosc) zmieniamy tablicę rejestr na pozycji ktory, nadającj jej wartość. Można się jej pozbyć – programując bezpośrednio tablicę rejestr – ale funkcja ta powstała aby ćwiczyć programowanie strukturalne.

void setup(){
  pinMode(SER, OUTPUT);
  pinMode(RCLK, OUTPUT);
  pinMode(SRCLK, OUTPUT);
  czyscRejestr();
  zapiszRejestr();
}
void czyscRejestr(){
  for(int i=0; i<ilePinow; i++)
    rejestr[i]=LOW;
}
void zapiszRejestr(){
  digitalWrite(RCLK, LOW); 
  for(int i=ilePinow-1; i>=0; i--){
    digitalWrite(SRCLK, LOW);
    digitalWrite(SER, rejestr[i]);
    digitalWrite(SRCLK, HIGH); 
  }
  digitalWrite(RCLK, HIGH); 
}
void ustawPin(int ktory, int wartosc){
  rejestr[ktory]=wartosc;
}

W funkcji głównej void loop() tworzymy pętlę, która najpierw ustawi wszystkie piny w pozycję low, abyśmy mogli zobaczyć przyszłe zmiany. Program ten najpierw zapali wszystkie ledy, poczeka pół sekundy, po czym po kolei je zgasi i ponownie zapali po półsekundowej przerwie.

void loop(){
  int i;
  for(i=0;i<ilePinow;i++)
    ustawPin(i, LOW);
    zapiszRejestr();
    delay(500);
   for(i=0;i<ilePinow;i++){
    ustawPin(i, HIGH);
    zapiszRejestr();
    delay(500);
}
}

Nie jest to program wyświetlający cyferki 0,1,2,…9 a jedynie program testujący działanie ośmiu wyjść QA..QH rejestru – naszą pracą domową jest zamiana powyższego kodu aby wyświetlała cyferki.

Podsumowanie

W tym tygodniu poznaliśmy prosty sposób na sterowanie wyświetlaczem siedmiosegmentowym za pomocą jedynie trzech pinów cyfrowych. Stwarza to duże możliwości, ponieważ rejestry przesuwne można ze sobą łączyć – a ciągle do ich sterowania będą potrzebne tylko 3 piny cyfrowe  z Arduino! Ale o tym za tydzień.

Praca domowa

Wykorzystując powyższy kod, wyświetlić cyfry na wyświetlaczu.

(c) Ewelina, 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

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