Skip to main content

2.2 Debounce tipke

Cilj ovog primjera je pokazati ti kako pritiske tipke učiniti pouzdanijima i dosljednijima pomoću tehnike koja se zove debounce. Kada pritisnemo fizičku tipku, njezini kontakti mogu brzo odskakivati, zbog čega mikrokontroler detektira više pritisaka umjesto jednog. U ovom primjeru koristit ćemo softverski debounce pomoću time.ticks_ms() kako bismo osigurali da jedan pritisak znači jedno prebacivanje LED-ice.

U ovoj dokumentaciji naučit ćeš:

  • Kako pročitati stanje tipke pomoću value().
  • Što je debounce tipke i kako ga riješiti pomoću time.ticks_ms().
  • Zašto time.ticks_diff() postoji i kada ga moraš koristiti.
  • Kako prebacivati LED-icu pri svakom pritisku tipke.

Potreban hardver:

  • 1x Soldered NULA MINI pločica
  • 1x Breadboard
  • 1x Tipka
  • 1x LED-ica (bilo koje boje)
  • 1x otpornik od 330 ohma
  • 5x Jumper žice
  • 1x USB-C kabel
ℹ️
Dio kruga s tipkom isti je kao u 2.1 Brojač pritisaka tipke. Ako ti je taj primjer još sastavljen, ostavi ga spojenog i preskoči na korak 5. Sve prije toga je ponavljanje.

Spajanje komponenti

Prati osam koraka ispod. Svaka fotografija snimljena je s istog položaja, pa je možeš usporediti s prethodnom i vidjeti točno što se promijenilo.

1. Umetni NULA MINI pločicu na breadboard

ℹ️
Ovaj korak pretpostavlja da znaš kako je breadboard spojen iznutra i što su njegove naponske vodilice (power rails). Za uvod pogledaj stranicu dokumentacije Osnove breadboarda.

Utisni pločicu u jedan kraj breadboarda tako da njezina dva reda pinova sjednu s obje strane središnjeg kanala, s čipom okrenutim prema dolje. Pritisni je ravnomjerno dok svi pinovi ne sjednu.

NULA MINI pločica postavljena na breadboard
Korak 1: pločica postavljena na breadboard

Ovaj primjer koristi tri pina. Imena su ispisana duž oba ruba pločice, a svaki pin sjedi u svom numeriranom redu. Na fotografiji pločica zauzima redove 25 do 30, što stavlja IO19 u red 27 na a–e strani, te IO4 u red 27 i GND u red 30 na f–j strani.

ℹ️
Tvoja pločica može sjediti u drugim redovima od one na fotografiji. Važno je u koji red sleti koji pin. Čitaj imena ispisana na pločici, a ne prepisuj brojeve.

2. Postavi tipku

Utisni tipku u sredinu breadboarda tako da premosti središnji kanal, nekoliko redova dalje od pločice. Na fotografiji su njezine nožice u redovima 15 i 17.

Tipka postavljena preko središnjeg kanala
Korak 2: tipka premošćuje središnji kanal
⚠️
Tipka ima četiri nožice, ali samo dvije su važne. Dvije nožice na istoj strani tipke trajno su spojene unutar nje. Pritisak tipke spaja jednu stranu s drugom. Zato uvijek koristi jednu nožicu s obje strane središnjeg kanala, jednu u pojasu a–e i jednu u pojasu f–j. Ako uzmeš obje žice s iste strane, te dvije su već spojene i pločica će se ponašati kao da je tipka zauvijek pritisnuta.

3. Spoji GND na tipku

Masa ide u dva skoka, koristeći plavu vodilicu duž ruba breadboarda kao dijeljenu liniju mase.

  • Prva jumper žica: iz reda u kojem je GND (red 30 na fotografiji) do plave vodilice.
  • Druga jumper žica: s te iste plave vodilice natrag u red 15, na f–j strani tipke.
Dvije jumper žice spajaju GND na tipku preko negativne vodilice
Korak 3: GND dolazi do tipke preko plave − vodilice
ℹ️
Obje žice moraju ići u isti stupac vodilice, onaj koji ide uz plavu liniju. Vodilica uz crvenu liniju je zaseban pojas i nije spojena s njom.

4. Spoji IO19 na tipku

Povuci jednu jumper žicu iz reda u kojem je IO19 (red 27 na a–e strani) u red 17, na a–e strani tipke.

Jumper žica spaja IO19 na lijevu stranu tipke
Korak 4: IO19 spojen na drugu stranu tipke

Dvije žice dolaze do tipke s suprotnih strana: red 15 na f–j strani, red 17 na a–e strani. To je dijagonalni par, i to je ono što tipku čini stvarnim prekidačem. Tipka je sada gotova; ostatak ove stranice je LED-ica.

5. Izvedi IO4 i dodaj otpornik

LED-icu pobuđuje IO4, koji sjedi u redu 27 na f–j strani pločice, istoj strani kao GND i suprotnoj strani od IO19.

⚠️
IO4 i IO19 su oba u redu 27, na suprotnim stranama središnjeg kanala. Potpuno su odvojeni. Sve spajanje LED-ice odvija se na f–j strani.
  • Pločica prekriva većinu tog reda, pa koristi krajnju rupicu, j27, i povuci jumper žicu do reda 7.
  • Zatim postavi otpornik od 330 Ω tako da premosti red 7 i red 5.
Jumper žica s IO4 i otpornik od 330 ohma na mjestu
Korak 5: IO4 izveden u red 7, s otpornikom od 330 Ω koji premošćuje redove 7 i 5
ℹ️
Otpornik nema polaritet, pa nije važno kojim je krajem okrenut. Redovi su samo ono što fotografija slučajno koristi. Bilo koji slobodni redovi rade, dokle god otpornik i LED-ica završe serijski spojeni između IO4 i mase.

6. Dodaj LED-icu

LED-ica ide sljedeća, nastavljajući lanac: duga nožica (anoda) u red 5, isti red u kojem otpornik završava, i kratka nožica (katoda) u red 3.

LED-ica dodana, anoda prema otporniku i katoda prema masi
Korak 6: LED-ica na mjestu, duga nožica prema otporniku
⚠️
Postavi LED-icu na pravu stranu. LED-ica propušta struju samo u jednom smjeru. Duga nožica mora biti okrenuta prema otporniku i IO4, a kratka nožica prema masi. Okrenuta obrnuto, jednostavno se neće upaliti. Ništa se neće pokvariti, ali se ni ništa neće dogoditi.

7. Spoji LED-icu na masu

Jedna jumper žica zatvara krug: iz reda 3, gdje sjedi kratka nožica LED-ice, do plave vodilice, iste vodilice koju tipka već koristi za masu.

Jumper žica spaja katodu LED-ice na negativnu vodilicu
Korak 7: kratka nožica LED-ice spojena na plavu − vodilicu

Cijeli put je sada IO4 → 330 Ω → LED-ica → − vodilica → GND. Budući da je vodilica dijeljena, tipka i LED-ica masu uzimaju s istog mjesta.

8. Spoji pločicu na računalo

Gotov krug napajan preko USB-C
Korak 8: gotov krug, napajan preko USB-C

Stvaranje dvaju pinova

Ovaj primjer treba po jedan pin u svakom smjeru, i oba se stvaraju na istom mjestu na vrhu datoteke. Tipka čita, pa dobiva Pin.IN zajedno s Pin.PULL_UP koji si sreo u prethodnom primjeru. LED-ica piše, pa dobiva Pin.OUT.

btn = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)
led = Pin(LED_PIN, Pin.OUT)

LED-icu također izričito gasimo prije nego petlja počne:

led.value(0)
ℹ️
Ta linija je važnija nego što izgleda. Program pamti je li LED-ica upaljena u varijabli led_state, koja počinje kao False. Ako je pin slučajno ostavljen u visokom stanju od prethodno pokrenute skripte, ono što vidiš i ono što program vjeruje ne bi se poklapalo, i prvi pritisak bi izgledao kao da ne radi ništa.

Čitanje stanja tipke

value() čita pin i vraća ili 1 (visoko) ili 0 (nisko). Čitamo ga jednom po prolazu kroz petlju i odgovor držimo u varijabli:

reading = btn.value()
ℹ️
Vrijednost je obrnuta od onoga što bi mogao očekivati. Pin.PULL_UP uključuje otpornik unutar čipa koji drži pin na 3.3 V, pa pin čita 1 dok je tipka otpuštena. Pritisak tipke spaja pin na GND, pa čita 0. Tipka spojena na ovaj način naziva se active low, i zato kod ispod pazi na promjenu s 1 na 0.

Debounce tipke

Kada pritisneš tipku, ona se mijenja iz otvorenog u zatvoreni krug, što tretiramo kao jedan pritisak. Nama se čini kao jedna čista radnja, jer reagiramo sporo u odnosu na elektroniku. Ali mikrokontroleru, koji stanje tipke provjerava tisućama puta u tom kratkom trenutku, izgleda vrlo drugačije.

Dok se tipka pritišće, dva metalna kontakta unutra dotiču se međusobno. Budući da nisu savršeno glatki ni poravnati, mogu se brzo spojiti i razdvojiti nekoliko puta prije nego se umire. Mikrokontroleru to izgleda kao da je tipka pritisnuta nekoliko puta u nizu, iako si je pritisnuo samo jednom.

Debounce tipke vizualiziran
Vizualizacija debounce tehnike

Najjednostavnija softverska strategija za rješavanje ovoga je debouncer zasnovan na kašnjenju. Cijela mu je svrha pričekati da prozor odskakivanja prođe, a da bismo to mogli, moramo znati koliko je vremena prošlo.

time.ticks_ms() vraća brojač milisekundi koji pločica vodi dok je upaljena. Usporedba dvije takve vrijednosti govori nam koliko je davno nešto bilo, pa možemo ignorirati svaku promjenu koja dođe prerano nakon prethodne.

now = time.ticks_ms()

if reading != last_state and time.ticks_diff(now, last_change_ms) > DEBOUNCE_MS:
last_change_ms = now
⚠️
Vrijednosti brojača uvijek uspoređuj pomoću time.ticks_diff(), nikada oduzimanjem. Brojač koji vraća time.ticks_ms() ne raste unedogled. Dosegne granicu i prelije se natrag na mali broj. Kada se to dogodi, now - last_change_ms daje veliki negativan odgovor i debouncer prestaje raditi dok se pločica ne restarta. time.ticks_diff(now, last_change_ms) zna za to prelijevanje i vraća pravu razliku. To je jedno mjesto gdje je MicroPython stroži od Arduino funkcije millis() na koju si možda navikao.
ℹ️
DEBOUNCE_MS je 25, što znači da se svaka promjena koja dođe manje od 25 ms nakon posljednje prihvaćene ignorira. To je ugodno duže od odskakivanja tipke i daleko kraće od toga da čovjek pritisne dvaput, pa filtrira šum a da nikada ne izgubi stvarni pritisak.

Prebacivanje LED-ice

Svaki put kada se detektira valjani pritisak, preokrenemo stanje LED-ice. Ovaj kod sjedi unutar debouncera, pa se izvršava samo za pritiske koji su preživjeli filtar.

if last_state == 1 and reading == 0:

# Preokrećemo led_state. Operator "not" pretvara True u False i False u True.
led_state = not led_state

# Palimo ili gasimo LED-icu ovisno o trenutnom led_state.
if led_state:
led.value(1)
else:
led.value(0)

print("Button pressed, toggling LED")

last_state = reading
ℹ️
Uvjet last_state == 1 and reading == 0 je ono što ovo pokreće jednom po pritisku, a ne jednom po otpuštanju. Istinit je samo u točnom trenutku kada se vrijednost mijenja iz otpuštene u pritisnutu, što se naziva padajući brid. Puštanje tipke mijenja vrijednost natrag s 0 na 1, što debouncer vidi, ali ovaj uvjet ignorira.

Kod

Ispod je cijeli kod primjera za prebacivanje LED-ice tipkom uz debounce.

from machine import Pin
import time

# Ovo je varijabla kojoj predajemo broj pina na koji smo spojili TIPKU.
# NULA pločica ima logiku imenovanja pinova ovako: IO19, gdje je 19 broj koji dajemo varijabli.
BUTTON_PIN = 19

# Ovo je varijabla kojoj predajemo broj pina na koji smo spojili LED-icu.
# Zapamti da LED-ici treba otpornik od 330 ohma u seriji s njom. Taj otpornik ograničava koliko struje teče,
# a bez njega LED-ica vuče više nego što su ona ili pin građeni izdržati, pa se oboje može oštetiti.
LED_PIN = 4

# Ovdje stvaramo naša dva Pin objekta. Tipka je postavljena na Pin.IN jer je čitamo, a Pin.PULL_UP uključuje
# otpornik unutar čipa koji veže pin na 3.3V dok je tipka otpuštena. LED-ica je postavljena na
# Pin.OUT jer u nju pišemo.
btn = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)
led = Pin(LED_PIN, Pin.OUT)

# Ovo su varijable koje se koriste za debounce tipke.
# "led_state" pamti je li LED-ica trenutno upaljena, "last_state" pamti što je tipka pokazala kad smo
# zadnji put pogledali, a "last_change_ms" pamti kada se ta vrijednost zadnji put promijenila.
led_state = False
last_state = 1
last_change_ms = 0
DEBOUNCE_MS = 25

# Pobrini se da LED-ica počne ugašena, tako da se stanje koje pamtimo poklapa s onim što stvarno vidiš.
led.value(0)

while True:

# value() je funkcija koja čita vrijednost s našeg pina, ili 1 (visoko) ili 0 (nisko).
reading = btn.value()

# time.ticks_ms() vraća broj milisekundi koje su prošle od uključivanja pločice. Taj brojač
# se na kraju prelije natrag na nulu, i zato nikada ne uspoređujemo dva takva broja direktno i
# uvijek koristimo time.ticks_diff() da izračunamo razliku između njih.
now = time.ticks_ms()

# Ovo je naša debounce logika: provjeravamo je li se vrijednost tipke promijenila i je li prošlo dovoljno
# vremena da ne dobijemo lažna čitanja zbog šuma u signalu.
if reading != last_state and time.ticks_diff(now, last_change_ms) > DEBOUNCE_MS:
last_change_ms = now

# Budući da za čitanje tipke koristimo pull-up metodu, vrijednosti su obrnute od onoga
# što bi mogao očekivati: pin čita visoko dok je tipka otpuštena, a nisko dok je pritisnuta.
# Zato LED-icu moramo prebaciti kada stanje tipke ide s visokog na nisko.
if last_state == 1 and reading == 0:

# Preokrećemo led_state. Operator "not" pretvara True u False i False u True.
led_state = not led_state

# Palimo ili gasimo LED-icu ovisno o trenutnom led_state.
if led_state:
led.value(1)
else:
led.value(0)

print("Button pressed, toggling LED")

last_state = reading

# Vrlo kratka pauza ostavlja procesoru trenutak da obavi svoj posao u pozadini.
time.sleep_ms(10)

Što bi trebao vidjeti

Pritisni Run. Isprva se ne događa ništa. LED-ica počinje ugašena. Pritisni tipku jednom i upalit će se, pritisni je ponovno i ugasit će se. Svaki prihvaćeni pritisak također ispisuje liniju u Shell, pa možeš vidjeti debouncer u radu:

Button pressed, toggling LED
Button pressed, toggling LED
Button pressed, toggling LED
LED-ica upaljena nakon pritiska tipke
Jedan pritisak, jedno prebacivanje
ℹ️
LED-ica se ne pali dok držiš tipku. Prebacuje se jednom, u trenutku kada tipka ide dolje. To je test brida last_state == 1 and reading == 0 koji radi svoj posao, a ne debouncer: istinit je samo u onom jednom prolazu gdje se vrijednost mijenja iz otpuštene u pritisnutu. Ono što debouncer dodaje u odnosu na 2.1 Brojač pritisaka tipke je to što nikada ne blokira. 2.1 čeka u petlji da pustiš tipku, pa pločica ne može raditi ništa drugo dok je tipka pritisnuta; ova verzija pročita tipku, odluči i nastavi, a to je ono što svakom programu koji ima drugog posla stvarno treba.

Cijeli primjer

Pogledaj cijeli kod primjera na linku ispod:

2.2_Button_Debounce.py

Primjer koji prebacuje LED-icu pritiskom tipke uz debounce.