Ws2812b oraz potencjometr

Kolejne spotkanie z kolorowymi paskami LEDów WS2812B – tym razem sterowane potencjometrem.

UWAGA: TinkerCAD (czyli „wirtualne Arduino”) został wzbogacony właśnie o omawiane moduły RGB. Trzeba przyznać, że działają całkiem fajnie — dlatego zachęcam do zabawy nawet dla osób, które nie mają ich w realu!

Zadanie do wykonania

Zaświecić LEDem o numerze zgodnym z ustawioną wartością na potencjometrze. Inne LEDy mają być wyłączone. Kręcimy potencjometrem – „przesuwamy” LEDa. Kolor LEDa? Twój ulubiony 😉

Pozycję potencjometru odczytujemy przez funkcję analogRead(A0) – o ile środkowy pin potencjometru został podłączony do pinu A0 właśnie. W potencjometrze podłączone są także piny do GND i 5V w Arduino – zgodnie z powyższym rysunkiem.

Numer LEDa – dzielenie całkowite!

analogRead(a) zwraca wartość z przedziału 0..1023, natomiast my mamy tylko (aż?) 16 LEDów. Należy więc przeliczyć wartość a analogRead() na numer pinu… w prosty sposób:

nr = analogRead(A0)*16/1024;    //dobrze!

Ważna jest tutaj kolejność obliczeń, bo gdy napiszemy

nr = 16/1024*analogRead(A0);   //źle :-(

to choć z matematycznego punktu widzenia oba wyrażenia są równoważne, to jednak w drugim przypadku najpierw wykonuje się dzielenie – a jest tutaj dzielenie całkowite (bo zarówno 16 jak i 1024 są liczbami całkowitymi) więc wynosi zero. A zero mnożone przez cokolwiek da zawsze zero. Pierwszy wzorek jest poprawny, bo najpierw wykonamy mnożenie analogRead(A0)*16 a dopiero potem wynik tego iloczynu dzielimy (dzielenie całkowite, ale teraz nie przeszkadza) – i otrzymujemy liczbę z przedziału 0..15.

Dyskoteka – czyli efekt stroboskopu

Skoro mamy już ustalony numer LEDa, to teraz możemy zgasić wszystkie inne, a potem zaświecić tego konkretnego – o numerze nr właśnie. Ale ale… za chwilkę ponownie ustalimy położenie potencjometru, ponownie wyliczmy nr LEDa i co – ponownie zgasimy wszystkie, aby włączyć ten o numerze nr? Obawiam się, że w związku z wyłączaniem i włączaniem otrzymamy efekt „dyskoteka” (stroboskopu). Poza tym to nieprofesjonalne 😛

#include <Adafruit_NeoPixel.h>
     
#define PIN 7 
#define NDIOD 16 
int delta=1024/NDIOD;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NDIOD, PIN, NEO_GRB + NEO_KHZ800);

int nr,poprzedni;

void setup() {
    Serial.begin(9600);
    pixels.begin(); 

    nr=analogRead(A0)/delta;
    pixels.setPixelColor(nr, 10, 0, 0);
    pixels.show();
    poprzedni = nr;
}

void loop() {
    nr=analogRead(A0)/delta;
//    Serial.println(nr); 
        
    if(nr != poprzedni){
      //AKCJA!
      Serial.print("zmiana! nr="); 
      Serial.println(nr); 
      pixels.setPixelColor(poprzedni, 0, 0, 0);
      poprzedni = nr;
      pixels.setPixelColor(nr, 10, 0, 0);
      pixels.show();
    }
}

Powyższy kod rozwiązuje efekt stroboskopu – dzięki dodatkowej zmiennej poprzedni zapamiętujemy, który LED był ostatnio włączony. W głównej pętli programu ustalamy numer LEDa zgodnego z ustawieniami potencjometru, ale „akcja” dzieje się tylko wówczas, gdy zmieniliśmy ustawienia potencjometra. Dobrze dodać wypisywanie na monitor szeregowy informacji tylko wóczas, gdy dzieje się „akcja” – dzięki temu mamy pewność, że napisy (a tym samym i sama „akcja”) wykonuje się jedynie w przypadku zmian ustawień potencjometru.

Uwaga #1 – int VS float

Proszę zwrócić uwagę, że zarówno zmienna nr jak i zmienna poprzedni są zadeklarowane jako int, co w zupełności wystarczy – nie ma potrzeby używać arytmetyki liczb rzeczywistych (powolnych, szczególnie na Arduino).

Uwaga #2 – być bardziej PRO 😛

Zwróć uwagę, że wywołuję funkcję pixels.show() tylko wówczas, gdy coś się zmieniło – niby po co miałbym wołać tą funkcję za każdym razem w loop()-ie, niezależnie od tego, czy coś się zmieniło czy nie? W końcu pixels.show() programuje LEDy zgodnie ze wcześniejszymi ustawieniami pixels.setPixelColor(), więc nie ma senesu wołać ją bez potrzeby.

Zadanie #2 (także tinkercad)

Zmienić kod tak, by świeciły się LEDy od pierwszego do tego konkretnego, zgodnego z ustawieniami potencjometru. Unikać efektu „dysko”.

Zadanie #3

Joystick to dwa potencjometry – można tu go wykorzystać i oprogramować tak, by ruch na osi X zmieniał numer LEDa (jak dotychczas), a ruch na osi Y zmieniał kolor – w jakiś zabawny sposób (może tęcza?). Proszę się pochwalić wynikami!

(c) K.G. 2019

Dni Otwartego Biznesu – oferta PZU

Otrzymaliśmy zaproszenie do uczestnictwa w szkoleniach dla studentów – PZU oferuje bilety PKP oraz jeden ciepły posiłek.

Dni Otwartego Biznesu trwają od 2 do 13 grudnia 2019 r. Każdy dzień poświęcony jest innemu zagadnieniu. Tematy omawiane na szkoleniach to m.in. robotyka, DevOps, sprytne utrzymanie aplikacji i wiele więcej. Obejrzyj załącznik z wszystkimi tematami warsztatów, opisami i datami.

Na zapisy na szkolenie studenci mają czas do 10 listopada 2019 r. Aplikacje zbierane są poprzez stronę https://www.pzu.pl/kariera/studenci/dni-otwartego-biznesu

Każdy ma możliwość zapisania się na wszystkie warsztaty jednak otrzyma zaproszenie tylko na jeden dzień szkoleń.

Szkolenia odbywają się w Warszawie w siedzibie Grupy PZU (około 10-15 min od dworca PKP). Organizator zapewnia każdemu uczestnikowi bilety PKP oraz ciepły posiłek w czasie warsztatów. Szkolenia odbywają się w godzinach 10-17.

Każdy z uczestników otrzymuje certyfikat potwierdzający zdobycie nowych kwalifikacji. Szkolenia prowadzone są przez menadżerów co najmniej średniego szczebla.

(c) K.G. 2019

Ws2812b, potencjometr oraz Monster

Kolejne spotkanie z kolorowymi paskami LEDów WS2812B zapętlonymi w koło – sterowane potencjometrem. Dla nowych studentów to fajna zabawa – odczytujemy napięcie funkcją analogRead() a efekt przekładamy na wędrujący w kole piksel… Prace trwają 😉

Do zapamiętania:

  1. elektronika/fizyka: dzielnik napięć
  2. informatyka: dzielenie całkowite i dzielenie rzeczywiste, rzutowanie typów
  3. Arduino: sprawdź wartość wyjścia 5V i dostosuj formułki do obliczania odczytanego napięcia
  4. Arduino: przetworniki ADC – rozdzielczość pomiaru, błąd odczytu.
W tinkercadzie można też sprawdzić potencjometr!

Efekty w kółku – bez delay()a

Dwie strony naszego kółka wyświetlają różne kolory – rozpoczynamy od góry kółka, gdzie kolejne piksele zapalają się co zadany interwał czasu, a po „dotarciu” do środka kółka – piksele wracają (gaszą się). Ale… lewa strona jest cztery razy szybsza niż prawa strona. Na dodatek nie można używać funkcji delay()!

D E M O

Brawo dla Pana Łukasza!

Monster – moduł sterownika silnika DC

Mocny sterownik do silników DC – prąc pracy 12A (chwilowy 30A), napięcie pracy 16 V – bije na głowę poczciwego L293x i dlatego jest MONSTEREM 😉 12A to brzmi dumnie, ale w postaci BEZ RADIATORA to możemy pracować do 6A.

Układ nazywa się VNH2SP30 i jest warty polecenia, ale uwaga – w wielu sklepach (nawet tych renomowanych) można kupić uszkodzone sterowniki, które kręcą kółkami tylko w jedną stronę! Sprzedawcy wymieniają wadliwe sztuki, ale piszę to w celu zaoszczędzenia nerwów – gdy coś nie działa a powinno.

Moduł Monster dla pojedynczego silnika DC – ze stronki https://protosupplies.com/product/vnh2sp30-single-monster-motor-module/

Warto przeczytać opis użycia modułu oraz jego specyfikację, gdy coś nie idzie tak, jak powinno.

Generalnie wszystko jest proste: 5V, GND do odpowiednich na Arduino, INA oraz INB do dowlonych pinów w Arduino, którymi chcemy sterować silniczkiem (tak samo jak w L293x), ale pin PWM musi być podłączony do Arduino – w przeciwnym razie sterowanie odbywa się z ZERO procent mocy. Pin EN (enabled) może być podłączony do 5V w Arduino, choć i tak jest w stanie wysokim, więc układ jest włączony, pin CS to diagnostyczny, na razie pomijamy (odczytuje prąd pracy układu, temperaturę…). Silnik podłączamy do OUTA oraz OUTB, natomiast zasilanie do + (plus) oraz – (minus) po przeciwnej stronie modułu – uwaga na właściwą polaryzację! Tutaj ważne jest ten plus i minus, oby nie odwrotnie!

No i ostatnia ISOTONA kwestia: zasilanie musi być przynajmniej 5.5V, nie może być mniej!

(c) K.G. 2019

Ws2812b oraz millis()

Kolejne spotkanie z kolorowymi paskami LEDów WS2812B – tym razem zapętlone w koło, więc efekt fajny!

Efekty mogą być fajne, ale… programując tylko takie „kółeczka” nie martwimy się używaniem funkcji delay(). To dobrze w tym prostym przypadku. Ale czy zawsze tak można?

Zadanie do wykonania

migać pierwszym LEDem co 1 sek (włączać na 1 sek, wyłączać na 1 sek), migać drugim LEDem co 30 sek — wszystko bez użycia funkcji delay() ! Po co to nam? A chodzi o to, że efekty świetlne mają pojawiać się niezależnie od innych rzeczy dziejących się w „międzyczasie”, nie chcemy ich stopować delay()em. W naszym przypadku zamierzamy jeździć pojazdami, które sterowane będą bezprzewodowo przez moduły radiowe nRF24L więc nie chcemy zatrzymywać odbioru sterowania wyświetlając efekty na kolorowych kółkach. Jak to zrobić?

Liczniki czasu – funkcja millis()

Powiedzmy, że chcemy wykonać pewne instrukcje co interwał czasu (może być 150 ms, może być 3777 ms albo dowolnie).

unsigned long int interwal = 3777;
unsigned long int t1,t2;
unsigned long int licznik = 0;


void loop(){
  t1 = millis();
  //
  //rozne operacje, ktorych nie chcemy zatrzymywac
  //
  t2 = millis();
  licznik += (t2 - t1);

  if (licznik >= interwal){
    //wykonaj zadania co dany interwal
    licznik = 0;
  }
}//loop

Powyższy kod to szkic postępowania w celu uzyskania „wielozadaniowości” na platformie Arduino 😉 Na początku pętli loop() zapisujemy aktualny czasu do zmiennej t1, wykonujemy wszystkie potrzebne instrukcje, których nie chcemy przerywać, na koniec ponownie zapisujemy czas do zmiennej t2. Zwiększamy licznik czasu (zmienna licznik) właśnie o czas wykonania pętli loop(), czyli różnicę t2-t1. Następnie sprawdzamy czy licznik „napełnił się” do zadanego interwału – jeśli tak, wykonujemy zadania i zerujemy licznik. Oczywiście możemy mieć wiele liczników, jak też i różne interwały…

(c) K.G. 2019

Optymalizacja sterowania

Słowem wstępu…

Dzisiejszego dnia pracowaliśmy nad optymalizacją naszego sterowania pojazdem.

Wciąż używamy do tego naszego smartfona, a konkretniej aplikacji „ArduinoRC”, która za pomocą bluetooth w naszym telefonie łączy się z modułem Bluetooth XM-15B podłączonego do Arduino. Więcej o tym w tym artykule.

Na poniższym zdjęciu nasz wypasiony „panel sterowania” 🙂

 

Dotychczas nasz program działał w dosyć prosty sposób – jeżeli Arduino odczytało określony znak z komunikacji szeregowej  bluetooth, to przekazywało do silniczka konkretne stany tak aby koła kręciły się na przykład w przód.

 

Poznaliśmy jednak głębszą teorię sterowania i pojawił się następujący problem:

A więc w aplikacji „ArduinoRC” klikamy strzałkę w górę. Załóżmy że przekazywany znak to literka 'W’, po odczytaniu której kółka mają się kręcić w przód.  Po przekazaniu jej komunikacją szeregową, Arduino posłusznie uruchamia silniczki, po czym je wyłącza. Gdy na telefonie tym razem przytrzymamy strzałkę, zamiast pojedynczego znaku 'W’, Arduino odczyta serie znaków – „WWWWWWWWWW … „. Po każdym z odczytanych znaków silniczki na chwilę staną, po czym po wczytaniu następnego – znowu ruszą. Taki efekt 'szarpania’ to nienajlepsze rozwiązanie ponieważ ciągłe włączanie i wyłączanie silniczka powoduje większe zużycie baterii. Więc zamiast sekwencji:

włącz(1) – wyłącz(1) – włącz(2) – wyłącz(2) – włącz(3) – wyłącz(3)

proponujemy logiczniejsza sekwencje:

włącz(1) – kontynuuj(2) – kontynuuj(3) – wyłącz(3).

Oczywiście nie zobaczymy tego problemu gołym okiem, ponieważ Arduino może odczytywać parę tysięcy takich znaków na sekundę.

Rozwiązanie problemu

Pomysłów na rozwiązanie tego problemu było parę, trzeba było jednak liczyć się z tym że Arduino to nie komputer stacjonarny i warto zrobić to w miarę możliwości optymalnie, aby nie zaśmiecać pamięci mikrokontrolera.

Z pomocą przyszła nam funkcja millis() z wbudowanej biblioteki, która zwraca czas który upłynął od włączenia urządzenia w milisekundach. Utworzyliśmy też parę pomocniczych zmiennych i utworzyliśmy sprytny sposób aby usprawnić pracę silnika.

Znowu użyłem swoich ponadprzeciętnych umiejętności artystycznych oraz zaawansowanego oprogramowania do obróbki graficznej (Paint i Gimp) aby stworzyć kilka schematów blokowych.

Przedstawiają one po kolei:

1. Działanie głównej pętli loop

Jak widać główna pętla programu Arduino będzie składała się z dwóch bloków: Obsługi Bluetootha, a potem Obsługi stopu silnika. Tak, tylko stopu silnika, bo uruchomienie silników zajdzie się w Obsłudze Bluetooth. Raz puszczone koła w ruch zatrzymają się dopiero w drugim bloku, po upływie określonego czasu. 

2. Działanie obsługi Bluetooth

Powyższy schemat koncentruje się tylko na jednej instrukcji – jedz do przodu (wczytanie W). Gdy już wszystko będzie dobrze działać – w podobny sposób trzeba dodać kolejne polecenia (zda do tylu, w lewo i w prawo). 

Sama obsługa instrukcji jazdy do przodu (obsługa 'W’) będzie obszerniej wytłumaczona za chwilę, ale już sam schemat algorytmu dużo może wyjaśnić:

3. Działanie obsługi odczytania znaku 'W’

Na początku, utwórzmy zmienną pomocniczą P i na razie przypiszmy jej liczbę 0. Będzie to zmienna przechowująca czas działania Arduino w milisekundach, po którego przekroczeniu nasze kółka przestaną się kręcić.

Zmienna DeltaT przechowywuje czas w milisekundach, przez jaki nasze kółka mają się kręcić po jednorazowym odczytaniu znaku. My akurat ustaliliśmy wartość 100 milisekund, jest to optymalna wartość przy której w praktyce nasza maszyna śmiga jak należy.

Dla lepszego wyobrażenia sobie tego o czym mowa, poniżej przedstawiłem oś przedstawiającą upływ czasu od włączenia Arduino. Zaznaczyłem zupełnie przypadkową wartość, akurat w tym przypadku oznacza, że minęło 10234 milisekund (czyli jakieś 10,2 sekund od włączenia urządzenia).

Kolejny krok w naszym schemacie to instrukcja warunkowa:

Czy P<milis() ?

W tej chwili Arduino wywołuje funkcje millis() i porównuje ją ze zmienną P. Między uruchomieniem urządzenia a wczytaniem znaku z naszego telefonu musiała minąć przynajmniej 1/1000 sekundy. Warunek zostaje więc spełniony – upływ czasu w milisekundach jest większy od 0. Przechodzimy więc do następnego bloku zgodnie ze schematem.

  1. P = millis() + DeltaT
  2. włączSilnik()

1.Zmienna P przyjmuje wartość sumy aktualnego upływu czasu   i   czasu przez jaki nasze kółka mają się kręcić po jednorazowym odczytaniu znaku.

2.Nasz silnik uruchamia się a wehikuł jedzie do przodu.

 

Tak jak wspominałem, zmienna P będzie przetrzymywała czas działania Arduino po którym silnik przestaje działać – teraz widzimy to na osi. Działa to mniej więcej w ten sposób:

  1. Wczytany został sygnał 'W’.
  2. Aktualnie minęło 10234 milisekund od czasu uruchomienia urządzenia.
  3. Silnik uruchamia się.
  4. Silnik ma pracować dopóki czas działania Arduino nie przekroczy 10334 milisekund.

Całkiem proste prawda? 🙂

To co opisałem do tej pory, to reakcja na pojedynczy znak 'W’. Jednak ja chcę aby mój samochodzik podjechał dwa metry do przodu, a co mi tam! Zobaczmy więc co zdarzy się jeśli przytrzymam strzałkę w aplikacji sterującej, a Arduino dostanie serie znaków „WWWWWWWWWWW … „.

Ok, analogiczna do powyższej sytuacja:

  1. Arduino odczytuje pojedynczy znak 'W’
  2. zmienna P przyjmuje odpowiednią wartość
  3. silniczek rusza

Jednak zanim minie nasze DeltaT czyli 100 milisekund, Arduino dostaje kolejny znak – kolejne 'W’. I co teraz? Wróćmy do naszego schematu blokowego nr 2 – tym razem zacznijmy działanie od instrukcji warunkowej (zmienne przypisuje tylko na początku działania programu) – i spójrzmy na poniższy wykres:

Aktualnie nasze P jest większe od czasu który upłynął dotychczas – rozpatrując więc działanie schematu blokowego, pierwszy warunek nie zostaje spełniony – wykonuje się instrukcja:

P = P + DeltaT

Czyli zmienna P która dotychczas przyjmowała konkretną wartość, powiększy się o kolejne DeltaT – czyli o 100. Oznacza to że czas działania urządzenia w którym silnik ma się wyłączyć wydłuża się o 100 milisekund.

Po odczytywaniu kolejnych znaków – analogicznie czas ten będzie się wydłużał. Będzie tym bardziej dłuższy, im dłużej przytrzymam strzałkę i im więcej znaków w jednej sekwencji odczyta Arduino. Przy tym warto zauważyć że w takiej sytuacji nasz silnik nie zatrzyma się, będzie działał dopóki trzymam strzałkę. Tak więc nasz problem został rozwiązany.

Na koniec ostatni blok programu – sprawdzenie, czy trzeba zatrzymać silniki.

4. Działanie obsługi stopu silników

 

PROGRAM i obiekty

Teraz zapoznajmy się z praktycznym kodem na Arduino. Naturalnie jest on obszerniejszy niż schematy blokowe, ponieważ obsługuje On całe działanie samochodu. W tym przykładzie zdecydowałem się na stworzenie klasy – jest to forma programowania obiektowego,  co umożliwia język C++. Różni się więc od poprzednich programów z fi-bot-a – gdzie programowaliśmy strukturalnie (język C). Opiszę więc komentarzem kluczowe linijki kodu których działanie opisałem wcześniej.

#include <SoftwareSerial.h>
#define RxD 2
#define TxD 3
#define IN1 4
#define IN2 5
#define IN3 6
#define IN4 7

SoftwareSerial btSerial(RxD,TxD);
unsigned long int przod = millis();                           
unsigned int delta_t=100;               

class Silnik      //w tym przykladzie zdecydowalem sie na stworzenie klasy - jest to forma 
{                 //programowania obiektowego co umozliwia język C++, a nie jak w poprzednich
  public:         //artykulach - tylko programowania strukturalnego a'la język C

  void wylaczSilnik()
  {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,HIGH);
    digitalWrite(IN4,HIGH);
  }
 
  void doPrzodu()                        
  {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,HIGH);
    digitalWrite(IN4,LOW);
  }

  void doTylu()
  {
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,LOW);
    digitalWrite(IN4,HIGH);
  }

  void wPrawo()
  {
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,HIGH);
    digitalWrite(IN4,HIGH);
  }

  void wLewo()
  {
     digitalWrite(IN1,HIGH);
     digitalWrite(IN2,HIGH);
     digitalWrite(IN3,HIGH);
     digitalWrite(IN4,LOW);
  }
};

  Silnik Maria;

void setup() {
  Serial.begin(9600);
  Serial.println("start!");
  btSerial.begin(9600);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);
 
}
              
void loop() {                          //schemat blokowy nr 1
  if(btSerial.available())             //schemat blokowy nr 3
  {
    char zn=btSerial.read();           
    Serial.println(zn);

   
    switch(zn)
    {
      case 'W':                        //schemat blokowy nr 2
      if(przod<millis())               
      {
        przod=millis()+delta_t;        
        Maria.doPrzodu();
      }

      else                              
      {
        przod+=delta_t;
      }

      break;

      case 'S':
      if(przod<millis())
      {
        przod=millis()+delta_t;
        Maria.doTylu();
      }

      else
      {
        przod+=delta_t;
      }
     
      break;

      case 'A':
      if(przod<millis())
      {
        przod=millis()+delta_t;
        Maria.wLewo();
      }

      else
      {
        przod+=delta_t;
      }
      break;
     
      case 'D':
      if(przod<millis())
      {
        przod=millis()+delta_t;
        Maria.wPrawo();
      }

      else
      {
        przod+=delta_t;
      }
      break;
    }
  }

  if(millis()>przod)                    //schemat blokowy nr 4
    {
      Maria.wylaczSilnik();             
    }
}

To by było na tyle, bądźcie na bieżąco 🙂

Pozdrawiam!

(c) Przemysław Baj 2018

Ekranik LCD 16×2 oraz komunikacja I2C

Kontynuujemy projekt zabawki mierzącej refleks (także pamięć – „memory”). Potrzebujemy sposobu komunikacji z użytkownikiem (innego niż podłączony komputer PC do Arduino) – wybór padł na ekranik LCD 16×2.

LCD 16×2

Układ ten to szesnaście znaków w dwóch wierszach – stąd nazwa 16×2. Są także inne, obejrzyjcie dla przykładu magazyny botland.com.pl

Schemat podłączenia ładnie opisany jest na oficjalnych stronach Arduino – zapraszam do lektury. Nasze układy po złożeniu wyglądały tak:

Program polega na użyciu wbudowanej bibliotyki LiquidCrystal.h — poniżej program:

//LCD16x2 sterowany przez Arduino

#include <LiquidCrystalC.h> // dolaczenie pobranej biblioteki dla LCD

LiquidCrystal lcd(, 2, 11, 12, 4, 5, 6, 7);

void setup(){
  lcd.begin(16,2);   // Inicjalizacja LCD 2x16
  
  lcd.setCursor(0,0); // Ustawienie kursora w pozycji 0,0 (pierwszy wiersz, pierwsza kolumna)
  lcd.print("pomidor!");
  delay(500);
  lcd.setCursor(0,1); //Ustawienie kursora w pozycji 0,0 (drugi wiersz, pierwsza kolumna)
  lcd.print("LCD 16x2");
}
void loop() {
}

Warte podkreślenia jest tutaj fakt wykorzystania aż 6-ciu cyfrowych pinów do obsługi tego wyświetlacza. To dużo! Nie ma sprawy, gdy tylko „bawimy” się modułem ekraniku, ale gdy już coś budujemy, podłączamy LED-y czy przyciski – to wówczas spotykamy się z deficytem pinów w Arduino UNO. Ale są lepsze sposoby na podłączenie takiego wyświetlacza.

Komunikacja I2C (IIC, TWI)

To bardzo popularny interface komunikacyjny, obsługujący za pomocą tylko 2 linii aż 127 urządzeń! tymi pinami są SDA (w Arduino pin A4) oraz SDC (w Arduino pin A5). Oznacza to, że gdy podłączamy coś na I2C to łączamy to coś dwoma przewodami z Arduino, podłączając do wejść A4 i A5 – jednocześnie „tracimy” te piny (A4 i A5) – nie możemy z nich korzystać. Trudno – coś za coś. Zresztą, to nic nowego – podobnie jest z komunikacją UART – Serial.begin(xxx) – „zabiera” nam cyfrowe piny #o (TX) i #1 (RX). Przy czym UART to tylko komunikacja z jednym urządzeniem – a tutaj 2 piny i możliwość obsługi do 127 urządzeń!

Moduł hd44780 (i2c)

Jako przykład komunikacji I2C użyliśmy wyświetlacza LCD 16×2 z dodatkowym sterownikiem hd44780. Taki sterownik jest tani a bardzo użyteczny. 

Całe podłączenie polega na połączeniu VCC i GND ze steronika do VCC i GND z Arduino, oraz pinów SDA, SDC ze sternika – do SDA i SDC w Arduino. Przy okazji dowiedzieliśmy się, że piny A4 i A5 w Arduino mają swoje „klony” w szeregu pinów cyfrowych, powyżej #13, GDN, ARFE.

Musimy użyć nowej biblioteki do obsługi tego modułu – ja zdecydowałem się na LiquidCrystal_I2C.h autorstwa Franka de Brabandera. Nie jest standardowo zainstalowana więc trzeba ją samodzielnie doinstalować. Przykładowy program:

//LCD16x2 sterowany przez I2C Arduino

include <Wire.h>   // standardowa biblioteka Arduino
#include <LiquidCrystal_I2C.h> // dolaczenie pobranej biblioteki I2C dla LCD

LiquidCrystal_I2C lcd(0x27, 16, 2);
//LiquidCrystal_I2C lcd(0x3f, 16, 2);


void setup(){
  lcd.init();
  lcd.begin(16,2);   // Inicjalizacja LCD 2x16
  
  lcd.backlight(); // zalaczenie podwietlenia 
  lcd.setCursor(0,0); // Ustawienie kursora w pozycji 0,0 (pierwszy wiersz, pierwsza kolumna)
  lcd.print("pomidor!");
  delay(500);
  lcd.setCursor(0,1); //Ustawienie kursora w pozycji 0,0 (drugi wiersz, pierwsza kolumna)
  lcd.print("LCD 16x2 I2C -- hd44780");
}
void loop() {
}

i2cScanner

To bardzo użyteczny program (aż mnie dziw bierze, że nie jest standardowo dodany do Arduino IDE!) więc trzeba go ręcznie zgrać z internetu i uruchomić. Dzięki niemu poznajemy adres swojego urządzenia – bo skoro magistrala i2c obsługuje aż do 127 urządzeń, to jak je rozpoznaje? które urządzenie jest które? a jeśli chcemy mieć 2, 3 lub 4 wyświetlacze LCD w jednym projekcie? Właśnie po to są adresy!

W naszej pracowni występują dwa rodzaje urządzeń – o adresach 0x27 oraz o 0x3f. Koniecznie sprawdź u siebie! W naszym przykładowym kodzie adres zapisany jest w linii 6 (kolejna linia przygotowuje na inny adresik).

Pomiar prądu

Warto zdawać sobie sprawę z użycia prądu – jak widać z poniższych zdjęć wykonaliśmy pomiar i odnotowaliśmy różnicę w poborze prądu w zależności od trybu działania ekraniku LCD – bez podświetlenia (około 6 mA) oraz z podświetleniem (około 26 mA).

A tutaj pomiar prądu z włączonym podświetleniem:

Projektując swój układ warto brać pod uwagę „prądożerność” każdego urządzenia. Ale to na przyszłość. My przećwiczyliśmy mierzenie prądu 😉

(c) KG 2018

Przycisk + dioda + random = REFLEKS!

Na zajęciach wykorzystaliśmy proste elementy do zbudowania prototypu maszyny badającej nasz refleks. Ale od początku.

Moduł z przyciskiem

Moduł posiada trzy piny – GND oraz Vcc to zalsilanie, S to stygnał wysyłany przez płytkę (na innych płytkach często oznaczony OUT). My używamy modułów firmy RobotDyn (o nazwie Button Switch module) i trzeba przyznać im dobrą jakość wykonania. Na dodatek układy te mają wbudowanego LED-a informującego o wciśnięciu przycisku.

Najpierw sprawdzamy, czy po wciśnięciu przycisku Arduino „zobaczy” jedynkę (stan HIGH) czy „zero” (stan LOW). Prosty programik poniżej:

#define gdzie 7
void setup(){
  pinMode(gdzie, INPUT);
  Serial.begin(9600);
}
void loop(){
   Serial.println(digitalRead(gdzie));
   delay(100);
}

Liczby losowe – rand()

Bibliotyki Arduino wyposażone są w funkcje pseudo losowe – czyli takie, które generują liczby „udające” prawdziwe liczby losowe. Mowa tu o funkcji rand() – aby to sprawdzić piszemy poniższy kod:

void setup(){
  Serial.begin(9600);
}
void loop(){
   Serial.print("czas= ");
   Serial.print(millis());
   Serial.print(" los=");
   Serial.printtln(rand());
   delay(100);
}

Widzimy więc duuuuuże (i losowe!) liczby całkowite, które wyświetlają się co 100ms. Aby z takich liczb zbudować coś konkretnego, np. typowy rzut kostki do gry – trzeba to lekko zmodyfikować przez użycie funkcji reszta z dzielenia całkowitego (tzw. modulo, symbol % w języku C/C++):

void setup(){
  Serial.begin(9600);
}
void loop(){
   Serial.print("czas= ");
   Serial.print(millis());
   Serial.print(" kostka=");
   Serial.printtln(1 + rand()%6);
   delay(100);
}

Jak to działa? Reszta z dzielenia całkowitego przez 6 zwraca liczby z przedziału 0..5, ale my dodajemy jeszcze jedynkę – otrzymujemy liczby 1…6 – czyli naszą kostkę do gry. W ten właśnie sposób możemy modyfikować wynik funkcji rand() i dopasowywać ją do naszych potrzeb.

Zapalenie LED-a co losowy czas

Podłączyliśmy niebieskiego LED-a bezpośrednio do pinu 13 Arduino i GND – bez dodatkowego, wymaganego opornika. Nie jest to poprawne połączenie (brak opornika = uszkodzenie LED-a), ALE niebieskie LEDy mają (wysokie) napięcie przewodzenia, około 3V. Arduino zasili je jednak 5V – co jest za dużo – i uszkadzamy naszego LED-a, ale go nie zniszczymy (celowo wybrałem niebieski LED, a nie inny – inne LEDy pracuą na napięciu ~2V, więc 5V by je zniszczyło). Zależy mi tutaj na prostocie budowy układu więc darowałem sobie niezbędny opornik (no i nie chiałem korzystać z wbudowanego LEDa #13 – bo jest mały i niewyraźny).

Chwilowo odłożyliśmy moduł z przyciskiem i zaprogramowaliśmy włączenei LED-a po losowym czasie od 5s, do 15s:

#define LED 13
void setup(){
  pinMode(LED, OUTPUT);
}
int i;
void loop(){
   //odczekanie 5..15 sekund
   delay(5000+ rand()%10000);
   //wlaczenie LED-a
   digitalWrite(LED, HIGH);
   delay(1000);
   digitalWrite(LED, LOW);
   //miganie - znak, ze za chwile powtarzamy zabawe
   for (i=0; i<4; i++){
       digitalWrite(LED, HIGH); 
       delay(200); 
       digitalWrite(LED, LOW);      
       delay(200);
   }//miganie
}

Warto pobawić się z tym programem, uzupełniając go o dodatkowe informacje wyświetlane przez monitor portu szeregowego – informujące, że trwa losowe czekanie, a potem, że włączono LEDa i na koniec – że zabawa od początku się zaczyna.

Program „badamy refleks”

Wracamy do przycisku – rozbudowujemy poprzedni program o odczytanie momentu wciśnięcia przycisku. Użytkownik ma to zrobić w momencie zapalenia się niebieskiego LEDa — tylko, że nie wiadomo, kiedy to dokładnie nastąpi (losowy czas z poprzedniego programu). Na koniec wyświetlimy czas jego reakcji – jego refleksu 😉

#define LED 13
#define gdzie 7
void setup(){
  pinMode(gdzie, INPUT);   
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}
int i;
unsigned long int t1,t2;
void loop(){
   Serial.println("START!");
   //odczekanie 5..15 sekund
   delay(5000+ rand()%10000);
   t1=millis();
   //wlaczenie LED-a
   digitalWrite(LED, HIGH);
   while (digitalRead(gdzie)== HIGH);
   t2=millis();
   digitalWrite(LED, LOW);
   Serial.print("Rekacja (refleks)=");
   Serial.print(t2-t1);
   Serial.println(" ms");
   delay(500);
   //miganie - znak, ze za chwile powtarzamy zabawe
   for (i=0; i<4; i++){
       digitalWrite(LED, HIGH); 
       delay(200); 
       digitalWrite(LED, LOW);      
       delay(200);
   }//miganie
}

Kluczowa jest linia #17 – to w niej następuje zatrzymanie działania programu i oczekiwanie na rekację użytkownika. Zrealizowałem to za pomocą pętli podczytującej przycisk – w moim module naciśnięcie przycisku powoduje odczyt stanu LOW, natomiast stan HIGH oznacza brak wciśnięcia. Jak widać ta pętla NIC nie robi. Właśnie o to mi tu chodziło – pętla nic nie robi, więc ponownie wracamy do sprawdzenia warunku pętli while – bez straty czasu. I tak w kółko, aż w końcu naciśnięty zostanie przycisk. 

A jak mierzę czas? Za pomocą funkcji millis() – która zwraca czas (w milisekundach) od uruchomienia Arduino. Robię to dwukrotnie – przed odczytaniem przycisku zapisuję do zmiennej t1, a po naciśnięciu przycisku – do zmiennej t2. Różnica tych czasów jest właśnie Twoim czesem reakcji – Twoim refleksem.

Pomysły

Program należy rozbudować – o dwa przyciski, dwa LEDy. To wzbogaci zabawę, bo losowo zapali się albo jeden LED, albo drugi. Warto wybrać dwa kolory LED-ów i dwa kolory przycisków. To będzie dodatkowe utrudnienie dla użytkownika – ma on bowiem wcisnąć odpowiedni przycisk (np. LED żółty – to i przycisk żółty, a nie niebieski. Niebieski to dyskwalifikacja! I na odwrót).

Inna modyfikacja polega na wychwyceniu falstartu – zapobiegnięciu sytuacji, że użytkownik bezmyślnie „klika” przyciskiem w nadziei, że gdy LED się zaświeci – on właśnie wcisnął przycisk i otrzymał bardzo krótki czas reakcji. Trzeba tak zrobić, aby wciśnięcie przycisku PRZED zaświeceniem kończyło zabawę. Podpowiedź: zamiast funkcji delay() w linii #13 trzeba sprytnie wykorzystać pętlę while…

(c) KG 2018

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).