Prepoznavanje kuglice

Arduino prepoznaje kuglicu: mikroprekidač na rampi, udarac mjeren s micros(), zašto Bounce2 propusti kratki udarac, tilt i Hallov senzor s magnetom

Prepoznavanje kuglice

Fliper mora u svakom trenutku znati gdje je kuglica. Kada kuglica prođe stazom, pogodi metu ili padne u rupu, program mora to primijetiti, dodati bodove i pustiti zvuk. Kuglica našeg flipera čelična je, promjera 15 mm, i teška oko 14 grama. Udarac takve kuglice traje vrlo kratko, a program ga ne smije propustiti.

Danas ćemo kuglicu pustiti niz rampu nagnutu kao ploča našeg flipera i izmjeriti koliko traje njezin udarac u mikroprekidač. Vidjet ćemo zašto biblioteka Bounce2, koja je odlična za tipke, kratki udarac kuglice može propustiti. Napravit ćemo i tilt: senzor koji primijeti kada igrač prejako gurne aparat. I u njemu je kuglica. Na kraju ćemo kuglicu prepoznati bez dodira, Hallovim senzorom i magnetom.

Što to znači za fliper?

Tlocrt flipera: plavi krug označava dvije staze pokraj lijeve palice, a u velikom plavom krugu iste su staze uvećane. Ispod svake zelene lampice je blijedi krug, senzor kuglice

Na tlocrtu našeg flipera plavim su krugom označene dvije staze pokraj lijeve palice, a u velikom krugu iste su staze uvećane. Blijedi krugovi ispod zelenih lampica su senzori kuglice: Hallovi senzori s magnetom ispod ploče, iznad kojih kuglica prolazi. Što znače ostali dijelovi crteža, piše na stranici o fliperu.

  • Mete, odbijači i praćke imaju mikroprekidače koje kuglica udari. Program ih čita smirivanjem koje reagira na prvi dodir, nikada s Bounce2 kakvu koristimo za tipke.
  • Senzori u stazama pokraj palica i na vrhu staze za izbacivanje su Hallovi senzori, kao u Vježbi 4. Kuglica ih ne mora pritisnuti, pa prepoznaju i sporu kuglicu. Ploča iznad senzora smije biti debela najviše 2 mm, pa ćemo je ondje odozdo istanjiti.
  • loop() nikada ne čeka. Udarac traje nekoliko milisekundi, a program mora pogledati svaki prekidač mnogo puta u tom vremenu.
  • Tilt su četiri sklopke s kuglicom. Kada zavojnica udari kuglicu, i ona malo protrese aparat, pa glavni Arduino dok zavojnica radi ne broji tilt.
  • Na stolu flipera bit će 10 prekidača i 5 Hallovih senzora, a Arduino Uno ima samo 18 slobodnih pinova. Zato će prekidači biti u matrici, kao u prošloj lekciji. Hallov senzor u matricu ne može, jer svoj izlaz spaja samo s GND. Hallove senzore i tilt zato čita poseban čip, 74HC165, kojemu trebaju samo 3 pina.

Rampa

Rampa je komad kanalice za kabele 25 × 16 mm, dug 40 cm, bez poklopca. Kanalica je dovoljno široka da se kuglica od 15 mm u njoj slobodno kotrlja. Jedan kraj podignut je 4,5 cm, pa je rampa nagnuta oko 6,5°, kao ploča našeg flipera. Na donjem kraju je mikroprekidač, s polugom preko kanalice. Kuglica se kotrlja niz kanalicu i udari u polugu. Kutija ispod donjeg kraja hvata kuglicu ako odskoči.

Na rampi su dvije oznake. S oznake na 30 cm kuglica stiže brzo, a s oznake na 10 cm polako.

Rampa sa strane: kanalica duga 40 cm, gornji kraj podignut 4,5 cm, nagib 6,5 stupnjeva, oznake na 10 i 30 cm, mikroprekidač na donjem kraju i kutija ispod njega

Čelična kuglica koja padne na pod lako se izgubi, a na nju se može i poskliznuti. Ako ti kuglica pobjegne, odmah je podigni.

Materijal

Svaki polaznik dobiva:

  • sve iz lekcije Igra reakcije: Arduino Uno i USB kabel, veliki breadboard, tipkalo, crvenu LED-icu, aktivnu zujalicu, otpornike 470 Ω, 220 Ω i 68 Ω, spojne žice, a na računalu biblioteku Bounce2
  • rampu s mikroprekidačem na donjem kraju; na mikroprekidač su zalemljene dvije žice, na nožice COM i NO
  • čeličnu kuglicu
  • sklopku s kuglicom za tilt
  • modul s Hallovim senzorom i magnetom, koji je pripremio voditelj; na njemu su tri žice, OUT, GND i 5V
  • još tri otpornika od 470 Ω (žuto-ljubičasto-smeđe)

Mikroprekidač

Mikroprekidač je malo tipkalo s polugom. Kada nešto pritisne polugu, ona pritisne mali gumb u kućištu i kontakti se prebace uz tihi klik. Za to je dovoljna vrlo mala sila, zato su mikroprekidači u svakom fliperu: ispod meta, u stazama i u rupi za kuglicu.

Mikroprekidač ima tri nožice:

  • COM (od engleskog common, zajednički) – nožica koja se spaja s jednom od druge dvije;
  • NO (normally open, inače otvoren) – spojen s COM samo dok je poluga pritisnuta;
  • NC (normally closed, inače zatvoren) – spojen s COM dok poluga nije pritisnuta.

Mi koristimo COM i NO, pa mikroprekidač radi kao tipkalo iz lekcije o tipkalima: dok je pritisnut, pin čita LOW.

Spajanje

Pravila za spajanje ista su kao u lekciji o tipkalima. Ponovo složi spoj iz igre reakcije: LED-ica je na D8, tipkalo na D2, a zujalica na D5. Tipkalo je sada tipka Start. Dodaj mikroprekidač i sklopku s kuglicom:

DioArduino pinŽica s ArduinaOtpornikDesna strana
LED-icaD8a3220 Ω: e3–f3crvena LED-ica: dulja nožica u j3, kraća u desnu – liniju
Tipkalo (Start)D2a8470 Ω: b8–b12tipkalo u e12, e14, f12, f14; žica iz j14 u desnu – liniju
MikroprekidačD4a18470 Ω: e18–f18žica s nožice COM u j18, žica s nožice NO u desnu – liniju
Sklopka s kuglicomD7a22470 Ω: e22–f22sklopka u h22 i h23; žica iz j23 u desnu – liniju
ZujalicaD5a2768 Ω: e27–f27+ nožica zujalice u j27, druga nožica u desnu – liniju
Zajednički GNDGNDdesna – linija––
Shema: LED-ica na D8, sklopka s kuglicom na D7, zujalica na D5, mikroprekidač na D4 i tipkalo na D2, svaki preko svog otpornika prema GND. Mikroprekidač je spojen nožicama COM i NO, a nožica NC je slobodna
Spajanje: kao u igri reakcije, a uz to D4 u a18, otpornik od e18 do f18, žica s nožice COM mikroprekidača u j18 i žica s nožice NO u desnu minus liniju; D7 u a22, otpornik od e22 do f22, sklopka s kuglicom u h22 i h23 i žica iz j23 u desnu minus liniju
  • Mikroprekidač je na rampi, pa do breadboarda dolazi na žicama. Žice ne natežemo: ako rampu pomaknemo, mogu se otkinuti s mikroprekidača.
  • Otpornici od 470 Ω štite pinove, kao u lekciji o tipkalima. Pinovi su sada ulazi, ali ako je na Arduinu još neki stari program u kojem je D4 ili D7 izlaz, otpornik ograničava struju.
  • Sklopka s kuglicom stoji uspravno, s nožicama prema dolje. Trebat će nam u Vježbi 3.

Vježba 1: Brojimo pogotke

Kuglicu ćemo prepoznati kao pritisak na tipkalo, sa smirivanjem iz Vježbe 3 lekcije o tipkalima. Naš kod reagira na prvi dodir kontakata, a zatim 50 ms ne gleda mikroprekidač. Zašto ovdje ne koristimo biblioteku Bounce2, saznat ćemo u Vježbi 2.

// C++
const int mikroprekidac = 4;
const int zujalica = 5;
const unsigned long SMIRIVANJE = 50;   // nakon promjene 50 ms ne gledamo mikroprekidač

int pogoci = 0;                        // koliko je puta kuglica pogodila mikroprekidač
int zadnjeStanje = HIGH;               // kakav je mikroprekidač bio zadnji put
unsigned long zadnjaPromjena = 0;      // kada se mikroprekidač zadnji put promijenio

void setup() {
  pinMode(mikroprekidac, INPUT_PULLUP);
  pinMode(zujalica, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (millis() - zadnjaPromjena >= SMIRIVANJE) {   // jesu li se kontakti smirili?
    int procitano = digitalRead(mikroprekidac);

    if (procitano != zadnjeStanje) {               // mikroprekidač se promijenio
      zadnjaPromjena = millis();
      zadnjeStanje = procitano;
      if (procitano == LOW) {                      // kontakti su se spojili: pogodak
        pogoci = pogoci + 1;
        tone(zujalica, 1000, 30);
        Serial.print("Pogodaka: ");
        Serial.println(pogoci);
      }
    }
  }
}

Učitaj program, otvori Serial Monitor (9600 baud) i isprobaj:

  1. Pusti kuglicu 10 puta s oznake na 30 cm. Koliko je pogodaka program izbrojio?
  2. Pusti kuglicu 10 puta s oznake na 10 cm.
  3. Stavi kuglicu u kanalicu tik do poluge i pusti je. Pritisne li polugu dovoljno da mikroprekidač klikne?

Kuglicu samo pusti, ne guraj je. Zapiši rezultate, trebat će ti u Vježbi 2.

Što je novo u programu?

Ništa! To je program za brojanje pritisaka iz lekcije o tipkalima, samo s mikroprekidačem na pinu D4 i kratkim zvukom pri svakom pogotku. Arduinu je mikroprekidač samo još jedno tipkalo.

Zašto spora kuglica ponekad ne izbroji pogodak?
Mikroprekidaču treba neka najmanja sila da klikne. Lagana kuglica koja polako stigne do poluge pritisne je preslabo. Tada se kontakti ne spoje i program nema što izbrojiti. Program tu ne može pomoći: mikroprekidač mora biti osjetljiviji, poluga dulja ili kuglica teža.

Na fliperu kuglica neke prekidače udari (mete), a preko nekih se samo prokotrlja (staze). Prekidač u stazi je najteži: kuglica ga pritisne samo svojom težinom, dok prolazi. Zato u stazama našeg flipera neće biti mikroprekidači, nego senzori koji kuglicu prepoznaju bez dodira, kao u Vježbi 4.

Vježba 2: Koliko traje udarac?

Kada pritisneš tipkalo, obično ga držiš barem desetinku sekunde. Kuglica polugu samo udari i odskoči. Koliko dugo su tada kontakti spojeni? Za to nam millis() nije dovoljno precizan, jer broji cijele milisekunde. Koristit ćemo micros(), koji broji mikrosekunde: 1000 µs je jedna milisekunda.

Ovaj program namjerno ne smiruje kontakte, jer želimo vidjeti svaki dodir. Udarac počinje prvim dodirom kontakata, a gotov je kada su kontakti razdvojeni već 20 ms. Tada program ispiše tri broja: koliko je trajao cijeli udarac, koliko su se puta kontakti spojili i koliko je trajao najdulji dodir.

// C++
const int mikroprekidac = 4;
const unsigned long KRAJ_UDARCA = 20000;   // 20 000 µs (20 ms) bez dodira: udarac je gotov

int zadnjeStanje = HIGH;               // kakav je mikroprekidač bio zadnji put
bool udarac = false;                   // traje li sada udarac
unsigned long pocetak = 0;             // kada su se kontakti prvi put spojili (µs)
unsigned long pocetakDodira = 0;       // kada su se kontakti zadnji put spojili (µs)
unsigned long kraj = 0;                // kada su se kontakti zadnji put razdvojili (µs)
unsigned long najdulji = 0;            // najdulji dodir u ovom udarcu (µs)
int dodiri = 0;                        // koliko su se puta kontakti spojili

void setup() {
  pinMode(mikroprekidac, INPUT_PULLUP);
  Serial.begin(9600);
  Serial.println("Pusti kuglicu niz rampu.");
}

void loop() {
  int procitano = digitalRead(mikroprekidac);
  unsigned long sada = micros();

  if (procitano != zadnjeStanje) {     // ovdje namjerno NE smirujemo kontakte
    zadnjeStanje = procitano;
    if (procitano == LOW) {            // kontakti su se spojili
      if (!udarac) {                   // prvi dodir: počinje novi udarac
        udarac = true;
        pocetak = sada;
        dodiri = 0;
        najdulji = 0;
      }
      dodiri = dodiri + 1;
      pocetakDodira = sada;
    } else {                           // kontakti su se razdvojili
      kraj = sada;
      if (kraj - pocetakDodira > najdulji) {
        najdulji = kraj - pocetakDodira;
      }
    }
  }

  // Udarac je gotov kada su kontakti razdvojeni već 20 ms.
  if (udarac && procitano == HIGH && sada - kraj >= KRAJ_UDARCA) {
    udarac = false;
    Serial.print("Udarac: ");
    Serial.print(kraj - pocetak);
    Serial.print(" µs, dodira: ");
    Serial.print(dodiri);
    Serial.print(", najdulji dodir: ");
    Serial.print(najdulji);
    Serial.println(" µs");
  }
}

Pusti kuglicu nekoliko puta s obje oznake i pogledaj brojeve u Serial Monitoru.

Što je novo u programu?

  • unsigned long sada = micros(); – micros() je kao millis(), ali broji mikrosekunde od pokretanja Arduina. Vrijeme računamo na isti način, oduzimanjem: kraj - pocetak.
  • const unsigned long KRAJ_UDARCA = 20000; – 20 000 µs je 20 ms. Toliko dugo kontakti moraju biti razdvojeni da bi program zaključio da je udarac gotov.
  • bool udarac – Pamti traje li udarac. Prvi dodir pokrene udarac, a sljedeći dodiri u istom udarcu samo se broje.
  • Ispis tek na kraju udarca. Dok udarac traje, program samo pamti brojeve. Ispisuje ih kada je udarac gotov, pa ispis ne smeta mjerenju.

Kod većine mikroprekidača udarac traje samo nekoliko milisekundi, a kontakti se pri tome spoje više puta, jer poskakuju kao u tipkalu. Najdulji dodir često je kraći od 10 ms.

Zašto Bounce2 propusti udarac

U lekciji o tipkalima vidjeli smo da Bounce2 promjenu prihvati tek kada je tipkalo mirno 10 ms, pa ne broji kratke smetnje. Za tipke je to odlično. Ali udarac kuglice izgleda baš kao kratka smetnja: kontakti se nekoliko puta spoje i razdvoje, a nijednom ne ostanu spojeni punih 10 ms. Bounce2 zato udarac uopće ne javi. Naš kod reagira na prvi dodir, pa ga izbroji.

Vremenski dijagram udarca: kontakti mikroprekidača tri se puta kratko spoje unutar 7 ms. Naš kod pogodak izbroji pri prvom dodiru, a brojač biblioteke Bounce2 ostaje na nuli

Isprobajmo to. Ovaj program isti mikroprekidač broji na oba načina odjednom:

// C++
#include <Bounce2.h>                   // biblioteka za smirivanje iz prijašnjih lekcija

const int mikroprekidac = 4;
const unsigned long SMIRIVANJE = 50;   // naše smirivanje: nakon promjene 50 ms ne gledamo

Bounce2::Button biblioteka;            // isti mikroprekidač, smiren bibliotekom
int pogociNas = 0;                     // pogoci koje broji naš kod
int pogociBounce2 = 0;                 // pogoci koje broji Bounce2
int zadnjeStanje = HIGH;
unsigned long zadnjaPromjena = 0;
bool ispisi = false;                   // treba li ispisati nove brojeve

void setup() {
  biblioteka.attach(mikroprekidac, INPUT_PULLUP);
  biblioteka.interval(10);             // promjena vrijedi kada je kontakt miran 10 ms
  biblioteka.setPressedState(LOW);
  Serial.begin(9600);
}

void loop() {
  // 1. Naše smirivanje iz lekcije 3.0: prva promjena odmah vrijedi.
  if (millis() - zadnjaPromjena >= SMIRIVANJE) {
    int procitano = digitalRead(mikroprekidac);
    if (procitano != zadnjeStanje) {
      zadnjaPromjena = millis();
      zadnjeStanje = procitano;
      if (procitano == LOW) {
        pogociNas = pogociNas + 1;
        ispisi = true;
      }
    }
  }

  // 2. Bounce2: promjena vrijedi tek kada je kontakt miran 10 ms.
  biblioteka.update();
  if (biblioteka.pressed()) {
    pogociBounce2 = pogociBounce2 + 1;
    ispisi = true;
  }

  // 3. Ispis tek kada je mikroprekidač 100 ms miran, da ispis ne smeta brojanju.
  if (ispisi && millis() - zadnjaPromjena >= 100) {
    ispisi = false;
    Serial.print("Naš kod: ");
    Serial.print(pogociNas);
    Serial.print(", Bounce2: ");
    Serial.println(pogociBounce2);
  }
}

Pusti kuglicu 10 puta s oznake na 30 cm i 10 puta s oznake na 10 cm. Koji brojač ima više pogodaka?

Što je novo u programu?

  • Dva brojača za isti pin. Naš kod čita pin s digitalRead(), a biblioteka sama, u update(). Oba dijela rade u istom prolazu kroz loop() i ne smetaju jedan drugome.
  • bool ispisi – Program ne ispisuje odmah nakon pogotka, nego tek kada je mikroprekidač miran 100 ms. Tako ispis ne smeta brojanju dok udarac još traje.
Bounce2 je kod tebe izbrojio jednako pogodaka kao naš kod. Što to znači?
Tvoj mikroprekidač pri udarcu drži kontakte spojene dulje od 10 ms, pa ga i Bounce2 vidi. Ovisi to o mikroprekidaču, o poluzi i o brzini kuglice. Na fliperu ne smijemo na to računati: kuglica može doći brže, a mikroprekidač može biti drukčiji. Zato za kuglicu koristimo smirivanje koje reagira na prvi dodir.

Biblioteka za tipke, naš kod za kuglicu

Čovjek tipku drži barem desetinku sekunde, pa Bounce2 ima dovoljno vremena da pričeka da se kontakti smire. Zato tipke na našem fliperu čitamo s bibliotekom. Kuglica kontakte spoji samo na nekoliko milisekundi, pa za nju koristimo smirivanje iz lekcije o tipkalima: prvi dodir odmah vrijedi, a nakon njega neko vrijeme ne gledamo mikroprekidač.

Biblioteka nije loša, samo je napravljena za drugi posao. Dobar programer zna što biblioteka radi i kada mu treba nešto drugo.

Eksperiment: spori loop()

Učitaj ponovo program iz Vježbe 1 i na kraj funkcije loop(), ispred zadnje vitičaste zagrade, dodaj redak delay(20);. Pusti kuglicu 10 puta s oznake na 30 cm. Koliko je pogodaka program izbrojio?

Zašto delay(20) gubi pogotke?
Za vrijeme delay(20) program ne čita mikroprekidač. Ako cijeli udarac, od prvog do zadnjeg dodira, padne u tih 20 ms, program ga ne vidi. Udarac traje samo nekoliko milisekundi, pa se to događa često. Zato na fliperu loop() nikada ne smije čekati: mora proći mnogo puta u svakoj milisekundi.

Izbriši delay(20); prije sljedeće vježbe.

Vježba 3: Tilt

Igrači ponekad guraju ili podižu fliper da bi pomogli kuglici. Zato svaki fliper ima tilt: senzor koji primijeti kada je igrač pretjerao. Na našem fliperu igrač dobije dva upozorenja, a treći put igra uzima kuglicu.

Naš tilt je sklopka s kuglicom: mali valjak s dvije nožice, a unutra je metalna kuglica. Dok sklopka stoji uspravno, s nožicama prema dolje, kuglica leži na dva kontakta i spaja ih, pa pin čita LOW. Kada sklopku nagnemo ili naglo pomaknemo, kuglica se otkotrlja ili poskoči i na trenutak razdvoji kontakte. Pin tada čita HIGH.

U našem fliperu bit će četiri takve sklopke, gotovo vodoravne i okrenute na četiri strane, kao kompas, kako piše na stranici o fliperu. Spojene su jedna za drugom, pa je dovoljno da se otvori jedna. Danas radimo s jednom.

Kod tilta pazimo na dvije stvari:

  • Kratko otvaranje je ono što tražimo. Guranje sklopku otvori samo na trenutak, a kuglica u njoj još neko vrijeme zvecka i otvara je mnogo puta. Ne smijemo ga smiriti i baciti, kao Bounce2 udarac kuglice. Brojimo prvo otvaranje, a zatim 1 s ne brojimo nova. To je isto smirivanje kao u lekciji o tipkalima, samo s 1000 ms umjesto 50 ms.
  • Predugo otvorena sklopka je odmah TILT. Ako je sklopka otvorena dulje od 1 s, igrač je aparat podigao ili nagnuo. Isto se dogodi ako se žica do sklopke prekine. Tada igra odmah ide u TILT, pa prekinutu žicu primijetimo odmah. Tilt koji bi zbog prekinute žice tiho prestao raditi bilo bi mnogo teže primijetiti.

Igra ima dva stanja, kao automat stanja u igri reakcije: IGRA i TILT. Iz stanja TILT izlazi se tipkom Start.

Planiraj prije nego što programiraš:
ponavljaj zauvijek:
pročitaj tipku Start i sklopku
ako je stanje IGRA:
ako se sklopka upravo otvorila:
zapamti kada
ako je od zadnjeg upozorenja prošlo barem 1 s:
još jedno upozorenje
ako su bila najviše dva: kratki zvuk
inače: TILT
ako je sklopka otvorena dulje od 1 s: TILT
ako je stanje TILT:
ako je pritisnut Start: nova igra, upozorenja na nulu
zapamti kakva je sklopka bila
// C++
#include <Bounce2.h>                   // za tipku Start

const int tipkalo = 2;                 // tipka Start: nova igra nakon TILT-a
const int zujalica = 5;
const int senzorTilt = 7;              // sklopka s kuglicom
const int ledPin = 8;
const unsigned long MIR = 1000;        // nakon upozorenja 1 s ne brojimo nova otvaranja
const unsigned long PODIGNUT = 1000;   // otvorena dulje od 1 s: aparat je podignut

// Stanja igre
const int IGRA = 0;                    // igra teče, pazimo na tilt
const int TILT = 1;                    // igrač je pretjerao: igra stoji

int stanje = IGRA;
int upozorenja = 0;                    // koliko je puta igrač gurnuo aparat
unsigned long zadnjeUpozorenje = 0;    // kada smo zadnji put izbrojili guranje
unsigned long otvorenaOd = 0;          // kada se sklopka zadnji put otvorila
int zadnjiTilt = LOW;                  // kakva je sklopka bila zadnji put: na početku zatvorena

Bounce2::Button start;                 // tipka Start, kao u igri reakcije

void setup() {
  start.attach(tipkalo, INPUT_PULLUP);
  start.interval(10);
  start.setPressedState(LOW);
  pinMode(senzorTilt, INPUT_PULLUP);
  pinMode(zujalica, OUTPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("Igra teče. Ne guraj aparat!");
}

void loop() {
  start.update();
  int procitano = digitalRead(senzorTilt);   // LOW: zatvorena, HIGH: otvorena

  switch (stanje) {
    case IGRA:
      if (procitano == HIGH && zadnjiTilt == LOW) {    // sklopka se upravo otvorila
        otvorenaOd = millis();
        if (millis() - zadnjeUpozorenje >= MIR) {      // novo guranje, ne isto
          zadnjeUpozorenje = millis();
          upozorenja = upozorenja + 1;
          if (upozorenja <= 2) {
            tone(zujalica, 600, 100);
            Serial.print("Upozorenje ");
            Serial.println(upozorenja);
          } else {
            naTilt();
          }
        }
      }
      if (stanje == IGRA && procitano == HIGH && millis() - otvorenaOd >= PODIGNUT) {
        naTilt();                      // otvorena predugo: aparat je podignut ili je žica prekinuta
      }
      break;

    case TILT:
      if (start.pressed()) {
        upozorenja = 0;
        digitalWrite(ledPin, LOW);
        Serial.println("Nova igra. Ne guraj aparat!");
        stanje = IGRA;
      }
      break;
  }

  zadnjiTilt = procitano;
}

void naTilt() {
  stanje = TILT;
  digitalWrite(ledPin, HIGH);
  tone(zujalica, 150, 1000);           // dugi niski zvuk
  Serial.println("TILT! Pritisni Start za novu igru.");
}

Učitaj program i otvori Serial Monitor. Lagano udari dlanom po stolu pokraj breadboarda ili lagano pomakni breadboard. Kada program javi upozorenje? Kada TILT?

Ako sklopka ne reagira ni na jače guranje, malo je nagni u smjeru u kojem guraš: kuglica će lakše skočiti s kontakata. Ako javlja upozorenja i kada stol ne diraš, uspravi je.

Eksperiment: izvuci žicu iz j23 i pričekaj. Što se dogodi nakon jedne sekunde?

Što je novo u programu?

  • procitano == HIGH && zadnjiTilt == LOW – Sklopka se upravo otvorila: sada je otvorena, a u prošlom prolazu bila je zatvorena. Varijabla zadnjiTilt pamti kakva je sklopka bila u prošlom prolazu, a na kraju loop() dobije novu vrijednost.
  • millis() - zadnjeUpozorenje >= MIR – Novo otvaranje broji se kao novo guranje samo ako je od zadnjeg upozorenja prošlo barem 1 s. Sva zveckanja kuglice nakon jednog guranja tako su jedno upozorenje.
  • millis() - otvorenaOd >= PODIGNUT – Sklopka je otvorena dulje od 1 s: aparat je podignut ili je žica prekinuta.
  • stanje == IGRA && – Ako je treće guranje upravo uključilo TILT, igra nije više u stanju IGRA, pa ga ne uključujemo još jednom.
  • void naTilt() – Sve što se događa pri prelasku u TILT na jednom je mjestu: stanje, LED-ica, zvuk i ispis. Funkciju pozivamo s dva mjesta.
  • const int TILT = 1; i senzorTilt – TILT je stanje igre, a senzorTilt pin na koji je spojena sklopka. Imena se razlikuju da ih ne bismo pomiješali.

Kuglica bez dodira

Mikroprekidač kuglica mora pritisnuti, a u Vježbi 1 vidjeli smo da ga spora kuglica ponekad ne pritisne dovoljno. Kuglicu možemo prepoznati i bez dodira: magnetom ili svjetlom. Na našem fliperu kuglicu u stazama pokraj palica i na vrhu staze za izbacivanje prepoznat će Hallovi senzori.

Hallov senzor

Hallov senzor osjeća magnetsko polje. Naš senzor, A3144, mali je crni čip s tri nožice. Kada je polje kroz njega dovoljno jako, senzor spoji svoj izlaz s GND, pa pin čita LOW. Kada polje oslabi, senzor izlaz odspoji, a otpornik prema 5 V u Arduinu (INPUT_PULLUP) vrati pin na HIGH. Arduinu je to isto kao tipkalo koje pritišće magnet. Senzor treba i napajanje, zato modul ima tri žice: 5V, GND i OUT (izlaz).

Čelična kuglica nije magnet, pa je senzor sam ne bi primijetio. Zato je u modulu ispod senzora magnet. Njegovo polje prolazi kroz senzor, ali voditelj ga je namjestio tako da je za senzor malo preslabo. Čelik privlači silnice magnetskog polja: kada kuglica dođe iznad senzora, silnice se skupe prema njoj i polje kroz senzor postane jače. Senzor se uključi.

Lijevo: magnet ispod senzora, a silnice polja šire se prema gore. Stupac pokazuje da je polje kroz senzor slabije od crtkane granice, pa je lampica ugašena. Desno: kuglica iznad senzora skupi silnice prema sebi, polje kroz senzor jače je od granice i lampica svijetli

Senzor se uključi pri jačem polju nego što se isključi. Kada je kuglica na rubu, senzor zato ne treperi, nego ostane uključen dok kuglica ne ode. Hallov senzor nema ni kontakte koji bi poskakivali, pa jedan prolaz kuglice napravi samo jednu promjenu na pinu.

Jaki magnet

U modulu je mali, ali vrlo jak magnet. Ne rastavljaj modul. Drži ga dalje od mobitela i kartica s magnetskom trakom. Dva takva magneta naglo skoče jedan prema drugome i mogu priklještiti prst.

Odakle ime

Senzor je ime dobio po američkom fizičaru Edwinu Hallu, koji je 1879. otkrio da magnetsko polje malo skrene struju u vodiču. Hallovi senzori danas su u prijenosnim računalima, koja tako znaju da je poklopac zatvoren, i u mnogim automobilima, gdje mjere koliko se brzo okreću kotači.

Vježba 4: Hallov senzor

Modul pričvrsti s donje strane rampe, kako ti pokaže voditelj: senzor mora biti točno ispod sredine kanalice, nekoliko centimetara prije mikroprekidača. Mikroprekidač, tipkalo, LED-ica, zujalica i sklopka s kuglicom ostaju spojeni. Dodaj modul:

DioArduino pinŽica s ArduinaOtpornikDesna strana
Hallov senzorD9a7470 Ω: e7–f7žica OUT s modula u j7, žica GND u desnu – liniju, žica 5V u desnu + liniju
Napajanje5Vdesna + linija––
Shema: sve iz Vježbi 1 do 3, a uz to Hallov senzor A3144 (U2): izlaz OUT preko otpornika od 470 oma na D9, napajanje 5V s pina 5V Arduina i GND na zajedničku liniju
Spajanje: kao u Vježbama 1 do 3, a uz to žica s pina 5V Arduina preko vrha breadboarda u desnu plus liniju; D9 u a7, otpornik od e7 do f7, žica OUT s modula u j7, žica GND u desnu minus liniju i žica 5V u desnu plus liniju
  • Do sada nijedan dio nije trebao 5 V s breadboarda. Hallov senzor treba napajanje, pa žica s pina 5V Arduina ide u desnu + liniju.
  • Žice 5V i GND ne zamijeni. Prije nego što spojiš USB kabel, provjeri ih još jednom.
// C++
const int mikroprekidac = 4;
const int zujalica = 5;
const int hallSenzor = 9;
const unsigned long SMIRIVANJE = 50;   // nakon promjene 50 ms ne gledamo mikroprekidač

int pogoci = 0;                        // koliko je puta kuglica pogodila mikroprekidač
int prolazi = 0;                       // koliko je puta kuglica prošla iznad Hallova senzora
int zadnjeStanje = HIGH;               // kakav je mikroprekidač bio zadnji put
int zadnjiHall = HIGH;                 // kakav je Hallov senzor bio u prošlom prolazu
unsigned long zadnjaPromjena = 0;      // kada se mikroprekidač zadnji put promijenio

void setup() {
  pinMode(mikroprekidac, INPUT_PULLUP);
  pinMode(hallSenzor, INPUT_PULLUP);
  pinMode(zujalica, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // Hallov senzor: nema kontakte, pa ga ne smirujemo
  int hall = digitalRead(hallSenzor);
  if (hall == LOW && zadnjiHall == HIGH) {         // kuglica je upravo stigla iznad senzora
    prolazi = prolazi + 1;
    tone(zujalica, 1000, 30);
    ispisi();
  }
  zadnjiHall = hall;

  // mikroprekidač: kao u Vježbi 1
  if (millis() - zadnjaPromjena >= SMIRIVANJE) {   // jesu li se kontakti smirili?
    int procitano = digitalRead(mikroprekidac);

    if (procitano != zadnjeStanje) {               // mikroprekidač se promijenio
      zadnjaPromjena = millis();
      zadnjeStanje = procitano;
      if (procitano == LOW) {                      // kontakti su se spojili: pogodak
        pogoci = pogoci + 1;
        tone(zujalica, 1000, 30);
        ispisi();
      }
    }
  }
}

void ispisi() {
  Serial.print("Hallov senzor: ");
  Serial.print(prolazi);
  Serial.print(", mikroprekidač: ");
  Serial.println(pogoci);
}

Učitaj program, otvori Serial Monitor i isprobaj:

  1. Pusti kuglicu 10 puta s oznake na 30 cm, a zatim 10 puta s oznake na 10 cm. Koliko je prolaza izbrojio Hallov senzor, a koliko pogodaka mikroprekidač? Usporedi to s rezultatima iz Vježbe 1.
  2. Učitaj program iz Vježbe 2 i u retku const int mikroprekidac = 4; promijeni broj pina u 9. Program sada mjeri Hallov senzor. Koliko dodira ispiše za jedan prolaz kuglice? Koliko je dugo kuglica iznad senzora kada stigne s oznake na 30 cm, a koliko kada stigne s oznake na 10 cm?
  3. Podmetni kovanicu pod jednu dugu stranu rampe, tako da se kuglica kotrlja uz rub kanalice. Broji li Hallov senzor i sada svaki prolaz?

Što je novo u programu?

  • pinMode(hallSenzor, INPUT_PULLUP); – Hallov senzor čitamo kao tipkalo. Dok kuglica nije iznad senzora, otpornik u Arduinu drži pin na HIGH, a kada jest, senzor ga spoji s GND.
  • hall == LOW && zadnjiHall == HIGH – Senzor se upravo uključio: sada je LOW, a u prošlom prolazu kroz loop() bio je HIGH. Tako jedan prolaz kuglice brojimo samo jednom, koliko god dugo ona bila iznad senzora. Isto smo napravili sa sklopkom za tilt u Vježbi 3.
  • Hallov senzor ne smirujemo. Nema kontakte koji bi poskakivali, pa ga program čita bez čekanja. Mikroprekidač i dalje smirujemo kao u Vježbi 1.
  • void ispisi() – Ispis je u funkciji, jer ga pozivamo s dva mjesta: kada se uključi Hallov senzor i kada kuglica pogodi mikroprekidač.

Hallov senzor obično izbroji svaki prolaz, i one spore koje mikroprekidač propusti. Program iz Vježbe 2 za njega ispiše samo jedan dodir po prolazu.

Što bi napravio kada bi kuglica prolazila previše sa strane senzora, pa je senzor ne bi primijetio?
Senzor osjeti kuglicu samo dok je ona gotovo točno iznad njega. Pokraj prvog senzora možemo staviti drugi, sa svojim magnetom, tako da zajedno pokriju cijelu širinu staze. Program broji prolaz kada se uključi bilo koji od njih. Kuglica koja prođe između njih može uključiti oba, pa nakon prvog uključenja kratko ne brojimo nova, kao kod tilta. Druga je mogućnost suziti stazu vodilicama, tako da kuglica mora proći iznad senzora.

Svjetlosna barijera

Kuglicu bez dodira možemo prepoznati i svjetlom. Preko staze svijetli uska zraka svjetla, a s druge strane staze je senzor svjetla. Kada kuglica prođe, zakloni zraku i senzor na trenutak dobije manje svjetla. Takav senzor zove se svjetlosna barijera. Ništa ne mora pritisnuti, pa radi i s laganom kuglicom koja se polako kotrlja.

Fotootpornik iz lekcije Arduino – korak po korak za to je prespor: na promjenu svjetla odgovori za nekoliko desetaka milisekundi, a kuglica zraku zakloni samo na desetak milisekundi. Zato svjetlosne barijere koriste infracrvenu svjetlost, koju ljudsko oko ne vidi, i senzore koji na nju reagiraju za manje od milisekunde. Isto svjetlo koristi daljinski upravljač za televizor. Kako rade takvi senzori, objašnjava stranica Infracrveni senzori.

Vidi nevidljivo svjetlo

Kamera mobitela vidi dio infracrvenog svjetla koje mi ne vidimo. Uperi daljinski upravljač za televizor u kameru mobitela i pritisni neku tipku. Na zaslonu mobitela vidjet ćeš kako LED-ica na vrhu daljinskog trepće. Ako je ne vidiš, pokušaj s prednjom kamerom mobitela.

Ako nešto ne radi

  • Program ne broji nijedan pogodak ⇒ Klikne li mikroprekidač kada polugu pritisneš prstom? Je li žica s nožice COM u j18, a žica s nožice NO u desnoj – liniji? Je li žica s D4 u a18, a otpornik od e18 do f18?
  • Brojač raste i kada kuglica ne dira polugu ⇒ Je li druga žica možda na nožici NC umjesto NO? S nožicom NC mikroprekidač je spojen dok poluga nije pritisnuta.
  • U Vježbi 2 program ne ispisuje ništa ⇒ Ispis dolazi tek 20 ms nakon zadnjeg dodira. Ako poluga ostane pritisnuta, jer kuglica stoji na njoj, udarac još traje. Makni kuglicu.
  • Greška Bounce2.h: No such file or directory ⇒ Biblioteka Bounce2 nije instalirana. Instaliraj je kao u lekciji o tipkalima.
  • Tilt odmah nakon pokretanja javi TILT ⇒ Sklopka je otvorena: stoji li uspravno, s nožicama prema dolje? Je li u h22 i h23, a žica iz j23 u desnoj – liniji? Je li žica s D7 u a22?
  • Tilt ne reagira ni na jako guranje ⇒ Malo nagni sklopku. Ako ni tada ne reagira, izvuci je i provjeri multimetrom spaja li nožice dok stoji uspravno.
  • Hallov senzor ne broji nijedan prolaz ⇒ Je li žica OUT s modula u j7, žica GND u desnoj – liniji, a žica 5V u desnoj + liniji? Ide li žica s pina 5V Arduina u desnu + liniju? Je li žica s D9 u a7, a otpornik od e7 do f7? Je li senzor točno ispod sredine kanalice?
  • Hallov senzor broji i kada kuglice nema ⇒ Je li blizu modula nešto čelično, na primjer škare, odvijač ili druga kuglica? Makni to. Ako ne pomogne, magnet u modulu se možda pomaknuo: javi voditelju.
  • Serial Monitor ne pokazuje ništa ili pokazuje čudne znakove ⇒ Odaberi 9600 baud.
  • Program se ne može učitati ⇒ Provjeri jesu li u Arduino IDE-u odabrani ploča Arduino Uno i pravi port.

Dodatni izazovi

  1. Mali fliper. Spoji Vježbe 1 i 3 u jednu igru. Tipka Start pokreće igru. Svaki pogodak u mikroprekidač donosi 10 bodova, a program ispisuje rezultat. TILT završava igru i ispisuje konačni rezultat. Za vrijeme TILT-a pogoci se ne broje. Savjet: stanja CEKA, IGRA i TILT.
  2. Upozorenja se zaborave. Neka program vrati upozorenja na nulu ako igrač 10 s nije gurnuo aparat. Je li igra tada lakša ili teža?
  3. Najkraći dodir. Dopuni program iz Vježbe 2 tako da ispisuje i koliko je trajao najkraći dodir u udarcu. Koliko je kratak najkraći dodir koji si izmjerio?
  4. Brzina kuglice. Izmjeri udaljenost od Hallova senzora do poluge mikroprekidača. Neka program iz Vježbe 4 s micros() izmjeri koliko prođe od uključenja Hallova senzora do pogotka u mikroprekidač i iz toga izračuna brzinu kuglice u metrima u sekundi. Koliko je kuglica s oznake na 30 cm brža od kuglice s oznake na 10 cm?

Što smo naučili?

  • Mikroprekidač ima nožice COM, NO i NC. S nožicama COM i NO radi kao tipkalo: dok je poluga pritisnuta, pin čita LOW.
  • Udarac kuglice traje samo nekoliko milisekundi, a kontakti se pri tome spoje više puta.
  • micros() broji mikrosekunde: 1000 µs je 1 ms. Koristimo ga za vrlo kratka vremena.
  • Bounce2 promjenu prihvati tek kada je kontakt miran 10 ms. Za tipke je to dobro, ali kratki udarac kuglice može propustiti.
  • Za kuglicu koristimo smirivanje koje reagira na prvi dodir, a zatim neko vrijeme ne gleda prekidač.
  • delay() u loop() može sakriti cijeli udarac. Program koji mora primijetiti kuglicu nikada ne čeka.
  • Sklopka s kuglicom je zatvorena dok stoji uspravno, a guranje je na trenutak otvori.
  • Kod tilta brojimo prvo otvaranje, a zatim neko vrijeme ne brojimo nova. Predugo otvorena sklopka znači TILT, pa primijetimo i prekinutu žicu.
  • Hallov senzor osjeća magnetsko polje. Kada je polje dovoljno jako, spoji pin s GND, pa ga čitamo kao tipkalo.
  • Čeličnu kuglicu Hallov senzor primijeti uz pomoć magneta ispod sebe: kuglica skupi silnice polja prema sebi i polje kroz senzor postane jače.
  • Hallov senzor nema kontakte, pa ne poskakuje i ne treba smirivanje. Prepoznaje i sporu kuglicu koja mikroprekidač ne bi pritisnula.
  • Svjetlosna barijera prepoznaje kuglicu bez dodira, a koristi infracrveno svjetlo koje ne vidimo.

Mali kviz

1. Što znače oznake COM, NO i NC na mikroprekidaču?
COM je zajednička nožica. NO (normally open) spojen je s COM samo dok je poluga pritisnuta, a NC (normally closed) dok poluga nije pritisnuta.
2. Zašto u Vježbi 2 koristimo micros(), a ne millis()?
Udarac traje samo nekoliko milisekundi, a pojedini dodir još kraće. millis() broji cijele milisekunde, pa bi kratki dodir izmjerio kao 0 ili 1 ms. micros() broji mikrosekunde, tisuću puta preciznije.
3. Zašto Bounce2 ne izbroji kratki udarac kuglice?
Bounce2 promjenu prihvati tek kada kontakt ostane miran 10 ms. Pri udarcu se kontakti nekoliko puta spoje i razdvoje, ali nijednom ne ostanu spojeni 10 ms. Biblioteka zato udarac smatra smetnjom i ne javi ga.
4. Zašto tipke flipera ipak čitamo s Bounce2?
Čovjek tipku drži barem desetinku sekunde, pa biblioteka stigne pričekati da se kontakti smire. Kratke smetnje na žicama pri tome ne broji, a program je kraći i jasniji.
5. Zašto program iz Vježbe 2 ispisuje tek kada je udarac gotov?
Dok udarac traje, program mora vidjeti svaki dodir kontakata. Ispis traje dugo, pa bi program za vrijeme ispisa mogao propustiti dodir. Zato brojeve samo pamti i ispiše ih kada je udarac gotov.
6. Zašto tilt nakon upozorenja 1 s ne broji nova otvaranja?
Nakon jednog guranja kuglica u sklopki još neko vrijeme zvecka i otvara je mnogo puta. Kada bismo brojali svako otvaranje, jedno guranje bilo bi odmah TILT. Ovako je jedno guranje jedno upozorenje.
7. Što tilt napravi ako se prekine žica do sklopke, i zašto je to dobro?
Pin s prekinutom žicom čita HIGH, kao otvorena sklopka. Nakon jedne sekunde program to shvati kao podignut aparat i uključi TILT. Tako kvar primijetimo odmah. Tilt koji bi zbog kvara tiho prestao raditi nitko ne bi primijetio.
8. Zašto fotootpornik nije dobar za svjetlosnu barijeru na fliperu?
Fotootpornik na promjenu svjetla odgovori za nekoliko desetaka milisekundi, a kuglica zraku zakloni samo na desetak milisekundi. Kuglica bi prošla prije nego što bi fotootpornik reagirao.
9. Zašto Hallov senzor ispod rampe treba magnet?
Čelična kuglica nije magnet, pa sama nema magnetsko polje koje bi senzor osjetio. Magnet ispod senzora daje polje, a kuglica ga, kada dođe iznad senzora, pojača toliko da se senzor uključi.
10. Zašto Hallov senzor u Vježbi 4 ne smirujemo, a mikroprekidač smirujemo?
Hallov senzor nema kontakte, pa njegov izlaz ne poskakuje: jedan prolaz kuglice jedna je promjena na pinu. Kontakti mikroprekidača pri udarcu poskakuju i spoje se nekoliko puta, pa bez smirivanja jedan udarac izbrojimo više puta.