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

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