Pojazd (autonomiczny), refleks (LEDy + przyciski) oraz line follower

Ciągle dwa/trzy niezależne projekty. Wygląda na to, że będą jednak trzy, bo Pan Bartek „atakuje” temat line followera (świetnie!)

1) zespół od pojazdu po sukcesie sterowania pojazdem przez człowieka (bluetooth) poznawał temat czujki odległości HC-SR04. Zamontowali już nawet „trzymak” w pojeździe – bez użycia wkrętaki! zuchy! 😉 Tak, tak – to „oklepany” układ, ale każdy od czegoś zaczyna – prawda? Trzymam więc kciuki za poprawne oprogramowanie go w pojeździe i zrobienie autonomicznego robota (poruszającego się bez ingerencji człowieka, w tym przypadku: wykrywającego kolizje i zmieniającego kierunek ruchu).

2) „refleks” przeszedł w stan rozbudowany i teraz składa się z 3 losowo zapalanych LEDów, i łapaniu reakcji przez 3 przyciski. Układ:

oraz program (+dużo komentarzy):

//piny z podlaczonymi przyciskami
int btn[]={2,4,6};
//piny z podlaczonymi ledami
int led[]={3,5,7};

void setup() {
  Serial.begin(9600);
  for (int i=0; i<3; i++){
    pinMode(btn[i],INPUT_PULLUP);
    pinMode(led[i], OUTPUT);
  }
}

unsigned int i;
unsigned int cr,tstart,tstop;
byte bt1, bt2, bt3, LOS;

void loop() {
  //"dysko-mode" informujace o poczatku rozgrywki
  for(i=0;i<3;i++){
        digitalWrite(led[0], HIGH);
        delay(100);
        digitalWrite(led[1], HIGH);
        digitalWrite(led[0], LOW);
        delay(100);
        digitalWrite(led[2], HIGH);
        digitalWrite(led[1], LOW);
        delay(100);
        digitalWrite(led[2], LOW);
        delay(100);
    }//"dysko-mode"
      
   //(pseudo)losowe czekanie: od 1s do 5s
   delay(random(1000,5000));

   //wybor LEDa do zaswiecenia: 1,2 lub 3
   LOS=random(1,4);
   
   //wersja tylko do testow: wypisywanie na ekran losowania
   Serial.print("LOS=");
   Serial.println(LOS);

   //wlaczenie wylosowanego LEDa     
   //dzieki zapisaniu numerow pin-ow do tablicy jest to teraz bardzo proste!
   digitalWrite(led[LOS-1], HIGH);

   //rozpoczecie mierzenia czasu
   tstart = millis();
       
   //czekanie na rekacje uzytkownika - wcisniecie jednego z trzech pyrzycsikow
   //zapamietujemy w zmiennych, ktore przyciski sa wcisniete
   do{                
       bt1 = digitalRead(btn[0]);
       bt2 = digitalRead(btn[1]);
       bt3 = digitalRead(btn[2]);                
   }while(bt1+bt2+bt3==0); //petla wykonuje sie tak dlugo, az przynajmniej jeden z przyciskow zostanie wcisniety                          

   //zatrzymanie "stopera" skoro cos zostalo wcisniete
   tstop = millis();
   digitalWrite(led[LOS-1], LOW);     

   //okreslenie poprawnosci wcisniecia przycisku:
   //zakladamy, ze zle wcisniety przycisk (tak wygodniej)
   bool ok=false;   //czy wygralem? 

   //sprawdzenie, czy moze jednak zostal wcisniety odpowieni przycisk
   switch(LOS){
      case 1: if ((bt1==1)&&(bt2==0)&&(bt3==0)) ok=true;break;
      case 2: if ((bt2==1)&&(bt1==0)&&(bt3==0)) ok=true;break;
      case 3: if ((bt3==1)&&(bt1==0)&&(bt2==0)) ok=true;break;
    }//swicth

    //jesli wygrales, to stosowny komunikat
    if (ok){    
      cr = tstop-tstart; //cr = "czas rekacji"
      Serial.print("brako! gratulacje! ");    
      Serial.print("czas reakcji:");
      Serial.println(cr);
    }//ok==true
    else{    
      Serial.println("pudło! nie ten przycisk!");    
    }     
    delay(1000);
}//loop
         
        



Można jeszcze popracować nad tym kodem – można dodać kolejny (inny) „dysko-mode” informujące, że się poprawnie wybrało przycisk, albo zaświecić wszystkimi LED-ami, gdy użytkownik „spudłował”. W przyszłości dodamy do układu wyświetlacz LCD aby tam pojawił się stosowny komunikat, a nie na ekranie podłączonego komputera.

3) na bazie TSOP5000 (kiedyś już ją poznawaliśmy) powstanie czujnik do pojazdu typu line follower – praca się właśnie rozpoczyna. Działa już (podwójny) układ czujników, więc teraz rozpoczął się drugi etap: układ 3/4/5? czujek na własnoręcznej płytce prototypowej + pojazd na sterowniku L298.

Dlaczego własnoręcznie robiona czujka, a nie jedna z „kupnych”, jak te ze zdjęć poniżej?

Odpowiedź jest prosta: bo to fajniejsze i mamy więcej zabawy podczas pracy, a o to właśnie chodzi!

 

KG, 2018

Pojazd (Bluetooth) + podstawy (3xLEDy + 3x przycisk)

Rozwija się projekt POJAZDU, a jednocześnie nauczane są podstawy Arduino (i programowania)…

  1. POJAZD
    1. Sterownik Monster vnh2p30 okazał się uszkodzony! Trudno to było wyczuć, bo z jednej strony działał poprawnie kręcąc silniczkiem w jedną stronę, natomiast w stronę przeciwną – albo wcale, albo baaaardzo powoli. Dlatego odsyłamy sterowniki do sklepu i wracamy do L298 i programujemy sterowanie przez Bluetooth. Pamiętamy o tym, aby nie jeździć na maksa bo ten sterownik przeznaczony jest tylko do pracy z prądami <1A. Na kolejnych zajęciach wrócimy do Monstera – bo mamy ich kilka sztuk a nie wszystkie są niesprawne.

  1. Sterowanie Bluetooth przez aplikację  Android RC działa! gratulacje.
  2. Podstawy Arduino/C/C++ pracujemy….

KG, 2018

Pojazd (pierwsze uruchomienie) + podstawy (LEDy + przycisk)

Rozwija się projekt POJAZDU, a jednocześnie nauczane są podstawy Arduino (i programowania)…

  1. POJAZD
    1. praca nad sterowaniem pojazdem: wykorzystujemy sterownik Monster vnh2p30 o bardzo dobrych parametrach (maksymalny prąd nawet 30A! zasilanie 6-18V) i świetnej cenie (~25 zł). Sterowanie podobne jak w przypadku L298 (za pomocą dwóch sygnałów) – konieczne jest podłączenie sygnału PWM (lub innego pinu z Arduino i włączenie go na stałe na HIGH, czyli PWM=100%). Fajna stronka opisująca co i jak podłączyć – polecam.
    2. Można kupić PODWÓJNY sterownik, ale cena (o dziwo) nie jest już aż tak atrakcyjnaZnalezione obrazy dla zapytania monster vnh2p30
  2. PODSTAWY: LEDy + przycisk = REFLEKS. Poznajemy jak odczytać stan przycisku – małe wyzwanie: program REFLEKS: LED zapali się po losowym czasie, naszym zadaniem jest jak najszybsze wciśnięcie przycisku od razu po zaświeceniu LEDa. Mierzymy czas reakcji człowieka.  Ciągle polecam wirtualne Arduino do zabawy w domu!
  3. Poznaliśmy OPTYCZNY czujnik odległości E18-D80NK (5V, zakres 3-80cm, nie dźwiękowy!) – prosty w użyciu, a niedługo do wykorzystania w projekcie Pojazdu.

KG, 2018

Prototyp – gra zręcznościowa + (ponownie) podstawy

Pam Maksymilian rozbudował program o karanie za zbyt wczesne wciśnięcie przycisku – gratulacje! Wgrał owoce swojej pracy z wirtualnego Arduino do naszego prototypu i… działa! 

Mamy też nowych zainteresowanych Fi-BOTem- z Wydziału Mat-Inf – dlatego przy tej okazji wróciliśmy do podstaw (pinMode, digitalWrite). 

Za tydzień (10 kwietnia) będą ponownie podstawy – piny PWM – ale… z diodami RGB, oraz (popularnymi) listwami RBG. Wszystko to ma rozbudzić w nas twórczość nowego propotypu gry zręcznościowej. Zapraszam!

(c) KG 2018

Projekt — wirtualny, choć całkiem realny ;-)

Całe zajęcia poświęciliśmy wirtualnemu Arduino – aby każdy z nas miał swoją kopię układu nad którym pracujemy oraz… aby popracować samodzielnie w domu i zabłysnąć na kolejnych zajęciach 😉

Niestety – okazało się, że tinkercad.com nie oferuje sterownika HD44780 do obsługi popularnego LCD16x2 – musieliśmy wykorzystać komunikację UART (obiekt Serial). Troszkę szkoda…

W realu wygląda to tak:

Prawie ostateczna wersja oprogramowania (z kilkoma udziwnieniami – potencjometrami do konfigurowania trudności (szybkości) losowych zdarzeń) ale bez liczenia RUND i licznika czasu do końca gry:

byte btns[6]={2,3,4,5,6,7};
byte leds[5]={12,11,10,9,8};
byte i;
int czas_staly, czas_los;

void setup()
{
  Serial.begin(9600);
  for (i=0;i<6; i++)
    pinMode(btns[i], INPUT_PULLUP);
  for (i=0;i<5; i++)
    pinMode(leds[i], OUTPUT);
  Serial.println(analogRead(A5));
  srand(analogRead(A5));
  
  czas_staly=2*analogRead(A0);
  czas_los=2*analogRead(A1);
  Serial.print("KONFIG:");
  Serial.print("czas stały=");
  Serial.println(czas_staly);
  Serial.print("czas los=");
  Serial.println(czas_los);
  delay(1000);
}

byte los;
unsigned long int t1,t2;
void loop()
{
  delay(czas_staly+rand()%czas_los);
  los=rand()%5;
  Serial.print("los=");
  Serial.println(los);

  digitalWrite(leds[los], HIGH);
  
  t1=millis();
  while(digitalRead(btns[los])==HIGH);//nic nie rób, czekaj na klik!
  t2=millis();
  digitalWrite(leds[los], LOW);
  
  Serial.print("refleks=");
  Serial.println(t2-t1);
  delay(2000);
  czas_staly=2*analogRead(A0);
  czas_los=2*analogRead(A1);
}

Warte podkreślenia jest użycie tablic – to zdecydowanie ułatwiło losowanie LED-a i sprawdzenie, czy odpowiedni przycisk został wciśnięty czy nie. 

Do zrobienia:

  • rozbudowa softu o monitorowanie czasu reakcji – jeśli przekroczymy pozostały czas, to gra powinna się automatycznie zakończyć (i wyświetlić jakieś podsumowanie, a potem – rozpocząć się od początku)
  • rozbudowa softu o zabezpieczenie przez „strategią małpy” – klikanie wszystkich przycisków, aż któryś się trafi i będzie bardzo krótki czas reakcji. To już troszkę trudniejsze zadanie
  • niby kto powiedział, że ma to być tylko gra refleks? warto pomyśleć także o grze memory – i zaprogramować sekwencje błysków (przygotowane wcześniej lub może losowo?), które należy powtarzać i zdobywać punkty. Można w setupie() zadać pytanie o wybór gry: reflex czy memory (wybór odczytujemy klikając jeden albo drugi przycisk) a wówczas loop() będzie uruchamiał odpowiedni kod…

(c) KG 2018

P.S. Radek (pracowity gimnazjalisto) – skontaktuj się ze mną emailem, czekam! KG

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