Tipkala kako treba
Tipkalo s INPUT_PULLUP, brojanje pritisaka, poskakivanje kontakata, smirivanje s millis() i s bibliotekom Bounce2 te razlika između pritiska i držanja

Ove godine gradimo pinball. Igrač njime upravlja samo tipkama: dvije tipke sa strane pokreću palice koje odbijaju kuglicu (engleski flippers), a tipka Start pokreće igru. Igrač od tipki očekuje da uvijek rade točno onako kako ih pritisne. Palica koja zakasni ili tipka Start koja jedan pritisak broji dvaput pokvarit će svaku igru.
Tipkalo izgleda kao najjednostavniji dio sklopa: pritisneš ga i spoji dvije žice. Ipak, s njim su povezane tri zamke, a danas ćemo upoznati sve tri:
- pin koji nije nigdje spojen čita nasumično;
- program koji samo pita je li tipkalo pritisnuto? jedan pritisak broji tisućama puta;
- metalni kontakti u tipkalu pri svakom pritisku nekoliko puta poskoče.
Prvu i treću zamku pokazuju ova dva dijagrama: kako se mijenja napon na pinu D2 dok tipkalo dvaput zaredom pritisnemo i otpustimo. U žutim prugama tipkalo je pritisnuto.
Pin u zraku. Dok tipkalo nije pritisnuto, pin nije spojen ni s čim, pa njegov napon luta (gornji redak). Iznad 3 V Arduino sigurno čita 1, a ispod 1,5 V sigurno čita 0. Između toga može pročitati bilo što. Zato digitalRead() (donji redak) nasumično daje 1 ili 0. Samo dok je tipkalo pritisnuto, pin je sigurno na 0 V.

Poskakivanje. S pull-up otpornikom, koji ćemo upoznati u Vježbi 1, otpušteno tipkalo drži pin na 5 V, a pritisnuto na 0 V. Pri svakom pritisku i otpuštanju napon nekoliko puta skoči između 5 V i 0 V prije nego što se smiri. Skokovi su nacrtani mnogo širi nego što stvarno jesu: cijelo poskakivanje traje nekoliko milisekundi.

Tri načina čitanja tipkala
Isto tipkalo program može čitati na tri načina. Pinball treba sva tri:
| Što radi pinball | Što program pita | Primjer |
|---|---|---|
| Palica je gore dok držiš tipku. | Je li tipkalo sada pritisnuto? | lijeva i desna palica |
| Jedan pritisak dodaje jednog igrača. | Je li tipkalo upravo pritisnuto i otpušteno? | jedan kratki pritisak na Start |
| Dugo držanje pokreće novu igru. | Koliko dugo je tipkalo pritisnuto? | Start držiš 1 s |
Na mnogim pravim pinballima kratki pritisak na tipku Start dodaje igrača, a dulje držanje iste tipke ponovno pokreće igru. Do kraja ove lekcije naš Arduino znat će to isto.
Materijal
Svaki polaznik dobiva:
- Arduino Uno i USB kabel
- veliki breadboard
- malo tipkalo (6 × 6 mm)
- otpornik 470 Ω (žuto-ljubičasto-smeđe)
- crvenu LED-icu i otpornik 220 Ω (crveno-crveno-smeđe)
- spojne žice
Za dodatni izazov na kraju trebaju još jedno tipkalo i još jedan otpornik od 470 Ω.
Što je program pročitao, pokazat će LED-ica na pinu D8, Serial Monitor i Serial Plotter.
Pravila za spajanje
- Dok spajamo, Arduino je odspojen s USB kabela.
- Na breadboard ne spajamo 5 V. Kao i kod semafora, s Arduina nam treba samo GND.
- GND Arduina spajamo na desnu (–) liniju.
- Sve spajamo u gornjoj polovici ploče, u retke 1 do 30.
- Prije uključivanja voditelj pregleda spoj.
Kako tipkalo stoji na ploči i koje su mu nožice uvijek spojene, piše na stranici Breadboard.
Vježba 1: Tipkalo bez vanjskog otpornika
U lekciji Dva semafora na križanju pin koji čita poruku imao je pull-down otpornik od 10 kΩ. Bez njega bi pin bio u zraku i čitao nasumično HIGH ili LOW. Tipkalu treba isto: dok nije pritisnuto, nešto mora držati pin u poznatom stanju.
Ovaj put taj otpornik nećemo staviti na ploču. Arduino ga već ima unutra, u svom čipu, samo ga treba uključiti. Taj ugrađeni otpornik spaja pin s 5 V, pa je to pull-up otpornik. Zato tipkalo spajamo s pinom na jednoj strani i s GND na drugoj:
| Tipkalo | Što pin vidi | digitalRead() |
|---|---|---|
| otpušteno | ugrađeni otpornik povlači pin prema 5 V | HIGH |
| pritisnuto | tipkalo spaja pin s GND | LOW |

Spajanje
LED-icu spajamo kao pješački semafor u lekciji Semafor za automobile i pješake: žica s D8 ulazi u lijevu polovicu retka 3, otpornik od 220 Ω stoji preko središnjeg utora, a LED-ica u desnoj polovici.
Ispod nje je tipkalo. Žica s D2 ulazi u lijevu polovicu retka 8. Otpornik od 470 Ω leži uz lijevu polovicu ploče, od retka 8 do retka 12. Tipkalo stoji preko središnjeg utora, s nožicama u retcima 12 i 14, kao u primjeru na stranici Breadboard.
| Dio | Arduino pin | Žica s Arduina | Otpornik | Desna strana |
|---|---|---|---|---|
| LED-ica | D8 | a3 | 220 Ω: e3–f3 | crvena LED-ica: dulja nožica u j3, kraća u desnu – liniju |
| Tipkalo | D2 | a8 | 470 Ω: b8–b12 | tipkalo u e12, e14, f12, f14; žica iz j14 u desnu – liniju |
| Zajednički GND | GND | desna – linija | – | – |

Tipkalo koristi dvije dijagonalne nožice: e12 i f14. Pritisak spaja redak 12 s retkom 14, pa put ide ovako: D2 ⇒ redak 8 ⇒ otpornik ⇒ redak 12 ⇒ tipkalo ⇒ redak 14 ⇒ GND.
Čemu služi svaki dio?
- LED-ica s otpornikom od 220 Ω pokazuje što je program pročitao. Dulja nožica (anoda) ide prema otporniku i pinu, a kraća (katoda) prema GND. Kao na semaforu, struja je oko 14 mA.
- Tipkalo prema GND, a ne prema 5 V. Na ploči nema 5 V, pa ni pritisnuto tipkalo ne može spojiti 5 V s GND.
- Otpornik 470 Ω: zaštita. Arduino pamti zadnji program koji smo na njega učitali, i nakon što ga isključimo. Ako je na njemu još program semafora A iz prošle lekcije, pin D2 je izlaz i šalje
HIGH. Pritisak na tipkalo tada bi taj pin spojio izravno s GND. Takav kratki spoj može oštetiti pin. S otpornikom struja je najviše 5 V / 470 Ω ≈ 11 mA, a to pinu ne škodi. Ugrađeni pull-up otpornik ima nekoliko desetaka kΩ, pa je 470 Ω prema njemu malen i pritisnuto tipkalo i dalje dajeLOW.
Prvi program
// C++
const int tipkalo = 2;
const int ledPin = 8;
void setup() {
pinMode(tipkalo, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
int stanje = digitalRead(tipkalo);
digitalWrite(ledPin, stanje);
}
Učitaj program i pritisni tipkalo nekoliko puta. Kada svijetli LED-ica?
Zašto LED-ica svijetli kada tipkalo NIJE pritisnuto?
HIGH, jer ugrađeni pull-up otpornik povlači pin prema 5 V. Program to stanje šalje ravno na LED-icu, pa ona svijetli. Pritisnuto tipkalo spaja pin s GND i daje LOW, pa se LED-ica ugasi.Što je novo u programu?
pinMode(tipkalo, INPUT_PULLUP);– Pin je ulaz, kaoINPUTu prošloj lekciji, ali s uključenim ugrađenim pull-up otpornikom. Na ploči zato ne treba vanjski otpornik prema 5 V.- Pritisnuto je
LOW. S pull-up otpornikom logika je obrnuta:LOWznači pritisnuto, aHIGHotpušteno. Na to ćemo paziti u svakom programu s tipkalima.
Neka LED-ica svijetli dok držimo tipkalo
loop() zamijeni ovim:
// C++
void loop() {
if (digitalRead(tipkalo) == LOW) { // LOW znači: pritisnuto
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
}
Sada LED-ica svijetli dok držiš tipkalo. Točno tako radi palica pinballa: gore je dok držiš tipku. Ovo je prvi način čitanja tipkala: program pita je li tipkalo sada pritisnuto?
Eksperiment: pin u zraku
U setup() promijeni INPUT_PULLUP u INPUT i učitaj program. Sada ugrađeni otpornik nije uključen, pa dok tipkalo nije pritisnuto, pin nije spojen ni s čim. Pogledaj LED-icu: može svijetliti, biti ugašena, slabije svijetliti ili se mijenjati kada prstom dotakneš otpornik ili žicu. Pin hvata smetnje iz okoline. To je zabluda 10 na stranici Zablude: nespojeni ulaz ne čita 0, nego nasumično.
Vrati INPUT_PULLUP i ponovo učitaj program.
Zašto pull-up, a ne pull-down?
U prošloj lekciji koristili smo vanjski pull-down otpornik, pa je HIGH značio poruku. Zašto tipkalo sada spajamo obrnuto?
Čip Arduina Uno ima ugrađene samo pull-up otpornike. Kada tipkalo spojimo prema GND, ne treba nam nijedan dodatni dio, a na ploču ne moramo dovesti 5 V. Zato gotovo svi uređaji s tipkalima, od daljinskog upravljača do pinballa, tipkala spajaju prema GND. Obrnutu logiku program lako popravi: == LOW umjesto == HIGH.
Vježba 2: Brojimo pritiske
Sada želimo brojati pritiske, kao tipka Start koja broji igrače. Prvi pokušaj izgleda jednostavno: kada je tipkalo pritisnuto, brojač povećamo za jedan. Da vidimo što se događa, program svakih 100 ms ispiše dvije vrijednosti: što pin D2 upravo čita i koliko je brojač.
// C++
const int tipkalo = 2;
const unsigned long ISPIS = 100; // ispisujemo svakih 100 ms
unsigned long brojac = 0; // unsigned long, jer će brojač rasti jako brzo
unsigned long zadnjiIspis = 0; // kada smo zadnji put ispisali
void setup() {
pinMode(tipkalo, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
if (digitalRead(tipkalo) == LOW) {
brojac = brojac + 1;
}
if (millis() - zadnjiIspis >= ISPIS) { // je li vrijeme za ispis?
zadnjiIspis = millis();
Serial.print("Tipkalo:");
Serial.print(digitalRead(tipkalo));
Serial.print(", Brojac:");
Serial.println(brojac);
}
}
Otvori Serial Monitor (9600 baud) i pritisni tipkalo jednom, kratko.
Zašto brojač nakon jednog pritiska pokazuje desetke tisuća?
loop() se vrti desetke tisuća puta u sekundi. Dok držiš tipkalo, svaki prolaz vidi LOW i povećava brojač. Program je brojao koliko je puta pogledao tipkalo dok je bilo pritisnuto, a ne koliko si ga puta pritisnuo.Što je novo u programu?
Serial.print("Tipkalo:");– U jednom retku ispisujemo dvije vrijednosti: što pin čita (1 jeHIGH, 0 jeLOW) i brojač. Oznake s dvotočkom daju linijama imena u Serial Plotteru, kao u lekciji Djelilo napona, Arduino i PWM.if (millis() - zadnjiIspis >= ISPIS)– Ispisujemo svakih 100 ms, a ne u svakom prolazu krozloop(). Ispis je spor: pri 9600 baud jedan ovakav redak putuje do računala više od 20 ms. Da ispisujemo u svakom prolazu,loop()bi čekao ispis i tipkalo bi čitao manje od 50 puta u sekundi. Ovako ga čita u svakom prolazu, a ispisuje 10 puta u sekundi. Isti oblik smillis()upoznali smo na semaforima.unsigned long brojac– Brojač će narasti veći nego što stane uint(najviše 32767). Uunsigned longstanu mnogo veći brojevi, kao vrijeme izmillis().
Pritisak je promjena
Pritisak nije tipkalo je pritisnuto, nego trenutak u kojem se tipkalo promijeni iz otpuštenog u pritisnuto. Da bi ga primijetio, program mora pamtiti kakvo je tipkalo bilo u prošlom prolazu.
Iznad setup() sada je i varijabla zadnjeStanje, a dio s ispisom ostaje isti:
// C++
const int tipkalo = 2;
const unsigned long ISPIS = 100; // ispisujemo svakih 100 ms
int brojac = 0;
int zadnjeStanje = HIGH; // kakvo je tipkalo bilo zadnji put: na početku otpušteno
unsigned long zadnjiIspis = 0; // kada smo zadnji put ispisali
void setup() {
pinMode(tipkalo, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
int procitano = digitalRead(tipkalo);
if (procitano != zadnjeStanje) { // tipkalo se promijenilo
zadnjeStanje = procitano;
if (procitano == LOW) { // promjena u pritisnuto: to je pritisak
brojac = brojac + 1;
}
}
if (millis() - zadnjiIspis >= ISPIS) { // je li vrijeme za ispis?
zadnjiIspis = millis();
Serial.print("Tipkalo:");
Serial.print(digitalRead(tipkalo));
Serial.print(", Brojac:");
Serial.println(brojac);
}
}
Učitaj program i otvori Serial Plotter (izbornik Tools, 9600 baud). Pritisni tipkalo nekoliko puta, polako. Linija Tipkalo je na 1 dok je tipkalo otpušteno i padne na 0 dok ga držiš. Linija Brojac pri svakom pritisku naraste za jednu stepenicu.
Što je novo u programu?
int zadnjeStanje = HIGH;– Pamti stanje tipkala iz prošlog prolaza. Napisana je iznadsetup(), pa vrijednost ostaje zapamćena između dva prolaza krozloop()(globalna varijabla iz prošle lekcije).!=– Nije jednako. Suprotno od==. Uvjet je istinit kada se pročitano stanje razlikuje od zapamćenog, dakle kada se tipkalo upravo promijenilo.if (procitano == LOW)– Tipkalo se može promijeniti na dva načina: kada ga pritisnemo i kada ga otpustimo. Brojimo samo prvu promjenu.int brojac = 0;– Brojač je opetint. Sada broji samo pritiske, pa neće narasti velik.
Poskakivanje kontakata
Pritisni tipkalo 20 puta, polako, i prati brojač. Je li stigao točno do 20? Kod većine tipkala brojač ponekad skoči za 2 ili 3, iako si pritisnuo samo jednom. U Serial Plotteru to se vidi kao jedan pad linije Tipkalo uz koji Brojac naraste za dvije ili tri stepenice odjednom. Ako se to tvom tipkalu ne događa, pokušaj pritiskati lagano ili ga brzo otpuštati.
Program nije pogriješio. Tipkalo stvarno nekoliko puta spoji i odspoji. Kada se metalni kontakti u tipkalu udare jedan o drugi, odskoče, kao lopta koja padne na pod, i tek se nakon nekoliko milisekundi smire. Isto se događa i kada tipkalo otpustimo. Čovjek to ne može primijetiti, ali Arduino tipkalo pročita mnogo puta u svakoj milisekundi i vidi svaki skok. Ta pojava zove se poskakivanje kontakata (engleski contact bounce).
Same skokove Plotter ne pokazuje. Program ispisuje svakih 100 ms, a kontakti se smire za nekoliko milisekundi, pa ispis gotovo uvijek uhvati tipkalo koje je već mirno. Vidimo samo posljedicu: brojač koji poskoči.

Na dijagramu je poskakivanje nacrtano sporije nego što stvarno traje, da bi se vidio svaki skok. Gornji redak pokazuje što Arduino čita na pinu D2. Program iz ove vježbe vidi tri pritiska dok se tipkalo smiruje, a pri otpuštanju još jedan. Brojač je zato nakon jednog pritiska na 4. Donji redak pokazuje brojač iz sljedeće vježbe.
Vježba 3: Smirivanje
Kontakti se smire za nekoliko milisekundi, a čovjek ne može pritisnuti tipkalo dvaput u 50 ms. Zato uvodimo jednostavno pravilo: nakon svake promjene 50 ms ne gledamo tipkalo. Dok se kontakti smiruju, program ih ne čita, pa skokove ne vidi. To se zove smirivanje tipkala (engleski debounce).
Žute pruge na dijagramu su tih 50 ms. Prva promjena odmah se broji, a skokovi nakon nje padnu u žutu prugu i program ih ne vidi.
Ovo je isti oblik s millis() koji već koristimo za ispis: je li već prošlo dovoljno vremena? Sada ga program koristi dvaput, za smirivanje i za ispis.
// C++
const int tipkalo = 2;
const unsigned long SMIRIVANJE = 50; // nakon promjene 50 ms ne gledamo tipkalo
const unsigned long ISPIS = 100; // ispisujemo svakih 100 ms
int brojac = 0;
int zadnjeStanje = HIGH; // kakvo je tipkalo bilo zadnji put
unsigned long zadnjaPromjena = 0; // kada se tipkalo zadnji put promijenilo
unsigned long zadnjiIspis = 0; // kada smo zadnji put ispisali
void setup() {
pinMode(tipkalo, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
if (millis() - zadnjaPromjena >= SMIRIVANJE) { // jesu li se kontakti smirili?
int procitano = digitalRead(tipkalo);
if (procitano != zadnjeStanje) { // tipkalo se promijenilo
zadnjaPromjena = millis();
zadnjeStanje = procitano;
if (procitano == LOW) { // promjena u pritisnuto: to je pritisak
brojac = brojac + 1;
}
}
}
if (millis() - zadnjiIspis >= ISPIS) { // je li vrijeme za ispis?
zadnjiIspis = millis();
Serial.print("Tipkalo:");
Serial.print(digitalRead(tipkalo));
Serial.print(", Brojac:");
Serial.println(brojac);
}
}
Ponovo pritisni tipkalo 20 puta. Sada brojač mora stići točno do 20. U Serial Plotteru Brojac pri svakom padu linije Tipkalo naraste za točno jednu stepenicu.
Što je novo u programu?
const unsigned long SMIRIVANJE = 50;– Koliko dugo nakon promjene ne gledamo tipkalo. Tip jeunsigned longjer ga uspoređujemo s vremenom izmillis(), kaoNAJDULJE_CEKANJEna semaforu.if (millis() - zadnjaPromjena >= SMIRIVANJE)– Tipkalo čitamo tek kada je od zadnje promjene prošlo barem 50 ms. Dok to vrijeme ne prođe,loop()samo pogleda na sat i odmah ide dalje.zadnjaPromjena = millis();– Pamtimo trenutak promjene, kod pritiska i kod otpuštanja. Od njega brojimo sljedećih 50 ms.Serial.print(digitalRead(tipkalo));– Varijablaprocitanopostoji samo unutar vitičastih zagrada{ }u kojima je napisana, pa za ispis tipkalo čitamo ponovno.- Prva promjena broji se odmah. Program ne čeka da se kontakti smire, nego reagira na prvi dodir, a smirivanje vrijedi tek nakon njega. Zato bi i palica pinballa s ovakvim programom krenula istog trenutka kada igrač pritisne tipku.
Zašto ne delay(50)?
Smirivanje bismo mogli napisati i s delay(50) iza svakog pritiska. Brojač bi radio, ali Arduino bi za vrijeme tih 50 ms bio gluh i slijep: ne bi čitao drugo tipkalo, ne bi pomaknuo palicu i ne bi osvježio zaslon. Pinball ima dvije palice, tipku Start i mnoge prekidače na stolu. S millis() program ne čeka, nego samo pogleda na sat, pa može istodobno paziti na sve njih.
Eksperimenti
1. Bez smirivanja
Promijeni SMIRIVANJE u 0 i učitaj program. Brojač opet ponekad skoči za 2 ili 3, kao u Vježbi 2.
2. Predugo smirivanje
Promijeni SMIRIVANJE u 500 i pritiskaj tipkalo što brže možeš, 10 puta. Koliko je pritisaka program izbrojio? Vrati SMIRIVANJE na 50.
Zašto program u eksperimentu 2 izgubi neke pritiske?
Vježba 4: Pritisak ili držanje?
Sada ćemo napraviti tipku Start pravog pinballa:
- kratki pritisak dodaje jednog igrača, najviše do 4;
- držanje dulje od 1 s pokreće novu igru s jednim igračem;
- LED-ica svijetli dok držimo tipkalo, kao palica u Vježbi 1.
Kada program zna je li pritisak bio kratak? Dok je tipkalo pritisnuto, to još ne zna: možda ga igrač upravo drži za novu igru. Zato kratki pritisak prepoznajemo tek kada se tipkalo otpusti. Držanje prepoznajemo dok je tipkalo još pritisnuto, čim prođe 1 s. Tada moramo zapamtiti da smo držanje već javili, inače bi otpuštanje nakon držanja dodalo još jednog igrača.
Cijeli program
// C++
const int tipkalo = 2;
const int ledPin = 8;
const unsigned long SMIRIVANJE = 50; // nakon promjene 50 ms ne gledamo tipkalo
const unsigned long DRZANJE = 1000; // toliko dugo držimo tipkalo za novu igru
int zadnjeStanje = HIGH; // kakvo je tipkalo bilo zadnji put
unsigned long zadnjaPromjena = 0; // kada se tipkalo zadnji put promijenilo
unsigned long pocetakPritiska = 0; // kada je tipkalo pritisnuto
bool drzanjeJavljeno = false; // jesmo li već javili držanje
int igraci = 0; // broj igrača, od 0 do 4
void setup() {
pinMode(tipkalo, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
if (millis() - zadnjaPromjena >= SMIRIVANJE) { // jesu li se kontakti smirili?
int procitano = digitalRead(tipkalo);
if (procitano != zadnjeStanje) { // tipkalo se promijenilo
zadnjaPromjena = millis();
zadnjeStanje = procitano;
if (procitano == LOW) { // upravo pritisnuto
pocetakPritiska = millis();
drzanjeJavljeno = false;
} else if (!drzanjeJavljeno) { // upravo otpušteno nakon kratkog pritiska
if (igraci < 4) {
igraci = igraci + 1;
}
Serial.print("Igrača: ");
Serial.println(igraci);
}
}
}
// Držimo li tipkalo već dovoljno dugo?
if (zadnjeStanje == LOW && !drzanjeJavljeno && millis() - pocetakPritiska >= DRZANJE) {
drzanjeJavljeno = true;
igraci = 1;
Serial.println("Nova igra! Igrača: 1");
}
digitalWrite(ledPin, zadnjeStanje == LOW); // LED-ica svijetli dok je tipkalo pritisnuto
}
Otvori Serial Monitor i isprobaj:
- Pritisni tipkalo kratko tri puta. Program ispisuje 1, 2 i 3 igrača, svaki put tek kada otpustiš tipkalo.
- Pritisni ga još tri puta. Igrača ima najviše 4.
- Drži tipkalo. Nakon jedne sekunde program ispiše Nova igra!, iako tipkalo još držiš. Kada ga otpustiš, ne dodaje se nijedan igrač.
Što je novo u programu?
const unsigned long DRZANJE = 1000;– Koliko dugo treba držati tipkalo za novu igru: 1000 ms je jedna sekunda.pocetakPritiska = millis();– Pamtimo trenutak pritiska. Iz njega kasnije računamo koliko dugo tipkalo držimo:millis() - pocetakPritiska.else if (!drzanjeJavljeno)–else ifznači inače, ako. Prvi uvjet bio je upravo pritisnuto, pa ovaj dio radi kada je tipkalo upravo otpušteno, ali samo ako držanje nije javljeno. Tada je pritisak bio kratak.if (igraci < 4)– Igrača može biti najviše četiri. Peti kratki pritisak ne mijenja ništa.bool drzanjeJavljeno– Bez nje program bi novu igru javljao u svakom prolazu krozloop()dok držimo tipkalo, a nakon otpuštanja još bi i dodao igrača. Ova varijabla pamti da je držanje već obavljeno, pa se ono javi samo jednom.- Provjera držanja stoji izvan smirivanja. Dok tipkalo držimo, ono se ne mijenja, pa unutarnji
ifništa ne radi. Koliko dugo ga držimo, program mora provjeravati u svakom prolazu. digitalWrite(ledPin, zadnjeStanje == LOW);– UsporedbazadnjeStanje == LOWdajetrueilifalse, a za Arduino je to isto što iHIGHiLOW. Jednim retkom LED-ica prati tipkalo.- U
loop()nema nijednogdelay(). Program istodobno smiruje tipkalo, mjeri koliko ga dugo držimo i pali LED-icu.
Igrač drži tipku Start pola sekunde i otpusti je. Što ispiše program?
Vježba 5: Tipka Start s bibliotekom Bounce2
Smirivanje treba gotovo svaki program s tipkalima, pa su ga drugi programeri već napisali i objavili kao biblioteku: gotov kod koji uključimo u svoj program. Jedna od najpoznatijih biblioteka za tipkala zove se Bounce2. Napravit ćemo istu tipku Start kao u Vježbi 4, ali smirivanje i mjerenje vremena prepustit ćemo biblioteci. Spoj ostaje isti.
Instaliranje biblioteke
- U Arduino IDE-u otvori Library Manager: ikona s knjigama na lijevoj strani ili izbornik Sketch ⇒ Include Library ⇒ Manage Libraries…
- U tražilicu upiši Bounce2.
- Instaliraj biblioteku Bounce2 autora Thomas O Fredericks.
Za instaliranje treba internet. Ako ga u radionici nema, voditelj biblioteku unaprijed instalira na svako računalo.
Plan je gotovo isti kao u Vježbi 4. Nema samo dijela zapamti kada je pritisnuto, jer to pamti biblioteka.
Program s bibliotekom
// C++
#include <Bounce2.h> // biblioteka za smirivanje tipkala
const int tipkalo = 2;
const int ledPin = 8;
const unsigned long DRZANJE = 1000; // toliko dugo držimo tipkalo za novu igru
Bounce2::Button start; // tipka Start
bool drzanjeJavljeno = false; // jesmo li već javili držanje
int igraci = 0; // broj igrača, od 0 do 4
void setup() {
start.attach(tipkalo, INPUT_PULLUP); // tipkalo je na D2, s ugrađenim pull-up otpornikom
start.interval(10); // promjena vrijedi kada je tipkalo mirno 10 ms
start.setPressedState(LOW); // pritisnuto je LOW
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
start.update(); // pročitaj i smiri tipkalo
if (start.pressed()) { // upravo pritisnuto
drzanjeJavljeno = false;
}
if (start.released() && !drzanjeJavljeno) { // upravo otpušteno nakon kratkog pritiska
if (igraci < 4) {
igraci = igraci + 1;
}
Serial.print("Igrača: ");
Serial.println(igraci);
}
// Držimo li tipkalo već dovoljno dugo?
if (start.isPressed() && !drzanjeJavljeno && start.currentDuration() >= DRZANJE) {
drzanjeJavljeno = true;
igraci = 1;
Serial.println("Nova igra! Igrača: 1");
}
digitalWrite(ledPin, start.isPressed()); // LED-ica svijetli dok je tipkalo pritisnuto
}
Učitaj program i ponovi sva tri pokusa iz Vježbe 4. Program mora raditi isto.
Što je novo u programu?
#include <Bounce2.h>– Uključuje biblioteku u program. Ako biblioteka nije instalirana, prevođenje javi greškuBounce2.h: No such file or directory.Bounce2::Button start;–startje objekt: varijabla koja uz svoje podatke ima i svoje funkcije. Funkcije objekta pozivamo s točkom, na primjerstart.update(), kao što već pozivamoSerial.begin()iSerial.print().Bounce2::znači da tipButtondolazi iz biblioteke Bounce2. Objekt sam pamti ono što smo u Vježbi 4 pamtili u varijablamazadnjeStanje,zadnjaPromjenaipocetakPritiska.start.attach(tipkalo, INPUT_PULLUP);– Veže objekt uz pin D2 i postavlja pin kao ulaz s pull-up otpornikom, papinMode()za tipkalo ne pišemo.start.interval(10);– Koliko dugo tipkalo mora biti mirno da bi biblioteka prihvatila promjenu: 10 ms. Zašto je to manje od 50 ms u Vježbi 3, piše niže.start.setPressedState(LOW);– Biblioteci jednom kažemo da pritisnuto značiLOW. Dalje u programu obrnutu logiku više ne pišemo:start.isPressed()dajetruekada je tipkalo pritisnuto.start.update();– Biblioteka pročita pin i smiri ga. Zato mora biti u svakom prolazu krozloop(), jednom, prije svih pitanja o tipkalu.start.pressed()istart.released()– Dajutruesamo u prolazu u kojem je tipkalo upravo pritisnuto ili otpušteno.start.isPressed()– Dajetruedok je tipkalo pritisnuto.start.currentDuration()– Koliko je milisekundi tipkalo u sadašnjem stanju. Dok ga držimo, to je koliko ga dugo držimo, pa nam ne trebapocetakPritiska.
Tri načina čitanja tipkala s početka lekcije u biblioteci imaju svoje funkcije: sada je isPressed(), upravo su pressed() i released(), a koliko dugo je currentDuration().
Što je drukčije?
Obje vježbe smiruju tipkalo, ali na različite načine:
- Vježba 3 i 4: program reagira na prvu promjenu, a zatim 50 ms ne gleda tipkalo.
- Bounce2: promjena vrijedi tek kada je tipkalo mirno 10 ms. Svaki skok kontakata ponovno pokrene tih 10 ms, pa biblioteka pričeka da se kontakti smire i tek tada javi pritisak.
Zato je biblioteci dovoljno 10 ms: ne mora čekati dulje od cijelog poskakivanja, nego samo malo dulje od razmaka između dva skoka.
| Vježba 4: naš kod | Vježba 5: Bounce2 | |
|---|---|---|
| Kada program vidi pritisak? | odmah, pri prvom dodiru kontakata | kada se kontakti smire i ostanu mirni 10 ms |
| Kratka smetnja, kraća od 10 ms | broji se kao pritisak | ne broji se |
| Što program pamti za tipkalo | tri varijable | jedan objekt |
Obrnuta logika (LOW je pritisnuto) | == LOW na više mjesta | jednom, u setPressedState(LOW) |
| Drugo tipkalo | još tri varijable i sav kod još jednom | još jedan objekt |
| Što treba instalirati | ništa | biblioteku Bounce2 |
Prednosti biblioteke:
- Program je kraći i lakše se čita. Funkcije imaju imena koja kažu što pitaju:
pressed(),released(),isPressed(). - Manje je mjesta za greške. Ne može se dogoditi da zaboravimo zapamtiti vrijeme promjene ili da negdje napišemo
== HIGHumjesto== LOW. - Više tipkala je lako: svako tipkalo dobije svoj objekt, na primjer
lijevoidesnoza palice. Dodatni izazov 1 s bibliotekom postaje mnogo kraći. - Biblioteku koriste mnogi ljudi u mnogim projektima, pa su njezine greške većinom već pronađene i popravljene.
- Kratka smetnja na žici, na primjer kada prstom dotakneš žicu, ne broji se kao pritisak.
Mane biblioteke:
- Treba je instalirati na svako računalo, a za to treba internet. Program koji nekome pošalješ ne prevodi se dok on ne instalira istu biblioteku.
- Ne vidimo kako radi. Da nismo prvo sami napisali smirivanje, ne bismo znali što znači
interval(10)ni zaštoupdate()mora biti u svakom prolazu. - Pritisak stiže malo kasnije: tek kada se kontakti smire i prođe još 10 ms. Za tipku Start to nitko neće primijetiti. Kod palica pinballa važna je svaka milisekunda, pa je ondje bolje smirivanje koje reagira odmah, kao naše.
- Biblioteka se s vremenom mijenja. Funkcija
currentDuration()u starijim se verzijama zvaladuration(), pa primjeri s interneta ne odgovaraju uvijek verziji koju imaš. - Zauzima malo više memorije: ovaj program je oko 270 bajtova veći od programa iz Vježbe 4. Arduino Uno ima 32 KB za programe, pa nam to danas ne smeta.
Kada razumiješ kako nešto radi, biblioteka štedi vrijeme i smanjuje broj grešaka. Zato smo smirivanje prvo napisali sami: sada znamo što biblioteka radi umjesto nas.
Zašto Bounce2 ne broji smetnju kraću od 10 ms, a naš program iz Vježbe 3 je broji?
Naš program reagira na prvu promjenu, pa mu je i kratka smetnja pritisak. Tek nakon nje 50 ms ne gleda tipkalo. Bounce2 promjenu prihvati tek kada tipkalo ostane mirno 10 ms. Smetnja nestane prije toga, pa je biblioteka nikada ne javi.
Bounce2 zna smirivati i kao naš program: reagirati odmah i zatim neko vrijeme ne gledati tipkalo (engleski lock-out). Taj se način ne uključuje u našem programu, nego u datoteci same biblioteke, Bounce2.h, pa ga ovdje ne koristimo.
Ako nešto ne radi
- LED-ica nikada ne svijetli ⇒ Je li dulja nožica LED-ice u j3, a kraća u desnoj – liniji? Okrenuta LED-ica ne svijetli. Je li žica s D8 u a3, a otpornik od e3 do f3?
- LED-ica ne reagira na tipkalo ili brojač stoji ⇒ Je li tipkalo preko središnjeg utora, s nožicama u retcima 12 i 14? Je li žica iz j14 u desnoj – liniji, a GND Arduina u istoj liniji? Je li žica s D2 u a8, a otpornik od b8 do b12?
- LED-ica svijetli i kada tipkalo ne diramo, a gasi se kada ga pritisnemo ⇒ Tako radi prvi program u Vježbi 1. Popravak je drugi
loop(), s== LOW. - LED-ica ili brojač mijenja se sam od sebe ⇒ U
setup()pišeINPUTumjestoINPUT_PULLUP. - Brojač stalno raste i kada tipkalo ne diramo ⇒ Tipkalo je okrenuto za 90 stupnjeva, pa je pin stalno spojen s GND. Okreni ga i pogledaj nožice na stranici Breadboard.
- Serial Monitor ili Serial Plotter ne pokazuje ništa ili pokazuje čudne znakove ⇒ Odaberi 9600 baud.
- Brojač i sa smirivanjem ponekad skoči za 2 ⇒ Neka tipkala poskakuju dulje. Povećaj
SMIRIVANJEna 100. - Greška
Bounce2.h: No such file or directory⇒ Biblioteka Bounce2 nije instalirana. Instaliraj je u Library Manageru, kako piše u Vježbi 5. - U Vježbi 5 program ne reagira na tipkalo ⇒ Je li
start.update();na početkuloop()? Bez njega biblioteka tipkalo nikada ne pročita. - Program se ne može učitati ⇒ Provjeri jesu li u Arduino IDE-u odabrani ploča Arduino Uno i pravi port.
Dodatni izazovi
- Dvije palice. Dodaj drugo tipkalo na D4, spojeno na isti način kao prvo: žica s D4 u a18, otpornik od b18 do b22, tipkalo u e22, e24, f22 i f24, žica iz j24 u desnu – liniju. Neka program u Serial Monitoru ispiše lijevo ili desno pri svakom pritisku. Program možeš napisati na dva načina. Bez biblioteke, kao u Vježbi 3, svako tipkalo treba svoje varijable
zadnjeStanjeizadnjaPromjena. S bibliotekom Bounce2, kao u Vježbi 5, svako tipkalo dobije svoj objekt, na primjerlijevoidesno. Napiši oba programa: koji je kraći? Radi li smirivanje kada pritisneš oba tipkala istodobno? - Koliko dugo? Neka program pri svakom otpuštanju ispiše koliko je milisekundi tipkalo bilo pritisnuto. Koliko traje tvoj najkraći pritisak?
- Servisni način. Na pravim pinballima voditelj ima tipke kojima ulazi u servisni način i provjerava dijelove stola. Neka držanje dulje od 3 s ispiše Servis, a držanje od 1 do 3 s i dalje pokreće novu igru. Savjet: o tome koje je držanje bilo, program može odlučiti tek kada se tipkalo otpusti.
Što smo naučili?
pinMode(pin, INPUT_PULLUP)uključuje ugrađeni pull-up otpornik, pa tipkalo spajamo samo između pina i GND, bez vanjskog otpornika.- S pull-up otpornikom pritisnuto tipkalo daje
LOW, a otpuštenoHIGH. - Pin koji nije nigdje spojen čita nasumično. Tipkalu uvijek treba pull-up ili pull-down otpornik.
- Otpornik od 470 Ω prema tipkalu štiti pin ako je greškom izlaz.
- Tipkalo čitamo na tri načina: je li sada pritisnuto, je li upravo pritisnuto i otpušteno te koliko dugo je pritisnuto.
- Pritisak je promjena: program mora pamtiti prijašnje stanje i usporediti ga s novim.
!=znači nije jednako. - Ispis je spor. Zato ispisujemo svakih 100 ms s
millis(), a ne u svakom prolazu, pa program tipkalo i dalje čita brzo. Serial Plotter od ispisanih vrijednosti crta grafikon. - Metalni kontakti pri pritisku i otpuštanju nekoliko puta poskoče. Zato tipkalo smirujemo: nakon svake promjene neko vrijeme ga ne gledamo.
- Smirivanje s
millis()ne zaustavlja program, pa Arduino može istodobno paziti na više tipkala. - Kratki pritisak prepoznajemo tek kada se tipkalo otpusti, a držanje dok je još pritisnuto. Varijabla tipa
boolpamti je li nešto već javljeno. - Biblioteka je gotov kod koji uključimo s
#include. Bounce2 smiruje tipkalo i mjeri koliko ga dugo držimo, a prije prvog korištenja treba je instalirati. - Bounce2 promjenu prihvati tek kada je tipkalo mirno, pa ne broji kratke smetnje, ali pritisak javi malo kasnije od našeg programa.
Mali kviz
1. Zašto s INPUT_PULLUP pritisnuto tipkalo daje LOW?
HIGH. Pritisnuto tipkalo spaja pin izravno s GND, pa pin čita LOW.2. Zašto tipkalo spajamo prema GND, a ne prema 5 V?
3. Čemu služi otpornik od 470 Ω uz tipkalo?
HIGH, pritisak na tipkalo spojio bi ga izravno s GND. Otpornik tada ograničava struju na oko 11 mA, što pinu ne škodi.4. Zašto je prvi brojač u Vježbi 2 brojao desetke tisuća?
loop() u kojem je tipkalo bilo pritisnuto, a loop() se vrti desetke tisuća puta u sekundi. Pritisak je promjena iz otpuštenog u pritisnuto, a ne samo stanje pritisnuto.5. Što je poskakivanje kontakata?
6. Zašto smirivanje pišemo s millis(), a ne s delay(50)?
delay(50) Arduino ne radi ništa drugo: ne čita druga tipkala i ne upravlja ničim drugim. S millis() program samo pogleda je li prošlo 50 ms i odmah ide dalje, pa istodobno može paziti na više tipkala.7. Zašto smirivanje od 500 ms nije dobro?
8. Zašto kratki pritisak na tipku Start prepoznajemo tek kada je otpustimo?
9. Čemu služi varijabla drzanjeJavljeno?
loop() dok držimo tipkalo, a nakon otpuštanja još bi i dodao igrača.