Skip to main content

7.8 LED semafor

Tri LED-ice, tri otpornika i program koji prolazi kroz fiksni niz koraka a da se nikada ne zaustavi da čeka. Ovaj projekt gradi mali europski semafor (zeleno, blinkajuće zeleno, narančasto, crveno, pa crveno i narančasto zajedno) i uvodi konačni automat (finite state machine), uzorak na kojem je izgrađen gotovo svaki pravi ugradbeni program.

✅

U ovoj dokumentaciji naučit ćeš:

  • Što je konačni automat, i kako su tri varijable dovoljne da ga sagradiš
  • Kako pokrenuti vremenski niz pomoću time.ticks_ms() umjesto time.sleep()
  • Kako natjerati jednu LED-icu da blinka unutar stanja dok mjerač vremena tog stanja i dalje radi
  • Kako pratiti napredak programa u konzoli

Potreban hardver​

  • 1x Soldered NULA MINI pločica
  • 1x Breadboard
  • 1x Zelena LED-ica
  • 1x Narančasta ili žuta LED-ica
  • 1x Crvena LED-ica
  • 3x otpornika od 330 Ω
  • 7x Jumper žice
  • 1x USB-C kabel
ℹ️
Svakoj LED-ici treba vlastiti otpornik od 330 Ω u seriji s njom, s prstenovima narančasti-narančasti-smeđi. Tri LED-ice, tri otpornika, bez dijeljenja. Bez otpornika LED-ica vuče više struje nego što su ona ili pin građeni izdržati, i oboje se može oštetiti.

Spajanje komponenti​

Sve u ovom sklopu sjedi na f–j strani breadboarda. Ništa ne prelazi središnji kanal, a isti lanac od četiri dijela ponavlja se tri puta, svaki put šest redova dalje.

1. Umetni pločicu​

Utisni NULA MINI u jedan kraj breadboarda tako da zauzme redove 25 do 30. Tijelo pločice prekriva stupce f do i, pa je na ovoj strani još dostupan samo stupac j.

NULA MINI pločica utisnuta u jedan kraj breadboarda
Pločica u redovima 25–30

2. Sagradi vodilicu mase​

Kratka plava jumper žica vodi j30 (GND pin pločice) dolje do plave − vodilice. Sve u ovom krugu put natrag do mase naći će preko te vodilice.

ℹ️
Stavi oba kraja svake žice vodilice u stupac rupica koji ide neposredno uz plavu liniju. Pojas vodilice ima dva stupca rupica i to nije isti spoj.
Plava jumper žica spaja GND pin pločice na plavu vodilicu mase
Plava: j30 (GND) do plave − vodilice

3. Povuci zelenu signalnu žicu​

Zelena jumper žica ide iz j27 (pin IO4) do j21, odmaknuto od pločice.

⚠️
Red 27 je IO4 na f–j strani, ali IO19 na a–e strani. To je isti broj reda na suprotnim stranama kanala i to su različiti pinovi. Ostani na f–j strani za sve tri signalne žice.
Zelena jumper žica od pina IO4 do reda 21
Zelena: j27 (IO4) do j21

4. Dodaj otpornik za zelenu LED-icu​

Otpornik od 330 Ω premošćuje h21 do h19, iz reda u koji signalna žica dolazi do reda u kojem će LED-ica sjediti.

ℹ️
h21 i j21 su isti red, pa su otpornik i jumper žica spojeni iako sjede u različitim stupcima rupica. Ovdje je važan samo broj reda; koristi one rupice do kojih nožice ugodno dosežu.
Otpornik od 330 ohma premošćuje red 21 do reda 19
330 Ω: h21 do h19

5. Utisni zelenu LED-icu​

Zelena LED-ica ide unutra s dugom nožicom u i19, redu u koji otpornik dolazi, i s kratkom nožicom u i17.

⚠️
Duga nožica prema otporniku, svaki put. LED-ica provodi samo u jednom smjeru, pa se obrnuta jednostavno nikada ne upali, što izgleda točno kao pokvaren pin ili loša žica.
Zelena LED-ica utisnuta u redove 19 i 17
Zelena LED-ica: duga nožica u i19, kratka nožica u i17

6. Spoji zelenu LED-icu na masu​

Plava jumper žica vodi j17 dolje do plave − vodilice, čime se prvi lanac dovršava.

Plava jumper žica spaja red 17 na vodilicu mase
Plava: j17 do plave − vodilice

7. Povuci narančastu signalnu žicu​

Sada isti lanac ponovno, šest redova dalje. Narančasta jumper žica ide iz j26 (pin IO3) do j15.

Narančasta jumper žica od pina IO3 do reda 15
Narančasta: j26 (IO3) do j15

8. Dodaj otpornik za narančastu LED-icu​

Drugi otpornik od 330 Ω premošćuje h15 do h13.

Drugi otpornik od 330 ohma premošćuje red 15 do reda 13
330 Ω: h15 do h13

9. Utisni narančastu LED-icu​

Duga nožica u i13, kratka nožica u i11.

Narančasta LED-ica utisnuta u redove 13 i 11
Narančasta LED-ica: duga nožica u i13, kratka nožica u i11

10. Spoji narančastu LED-icu na masu​

Plava jumper žica vodi j11 dolje do plave − vodilice.

Plava jumper žica spaja red 11 na vodilicu mase
Plava: j11 do plave − vodilice

11. Povuci crvenu signalnu žicu​

Crvena jumper žica ide iz j25 (pin IO2) do j9. Dugačka je žica i ide slikovitim putem s pločice i natrag; ispod nije ništa spojeno, pa je petlja bezopasna.

Crvena jumper žica od pina IO2 koja se petlja do reda 9
Crvena: j25 (IO2) do j9

12. Dodaj otpornik za crvenu LED-icu​

Treći otpornik od 330 Ω premošćuje h9 do h7.

Treći otpornik od 330 ohma premošćuje red 9 do reda 7
330 Ω: h9 do h7

13. Utisni crvenu LED-icu​

Duga nožica u i7, kratka nožica u i5.

Crvena LED-ica utisnuta u redove 7 i 5
Crvena LED-ica: duga nožica u i7, kratka nožica u i5

14. Spoji crvenu LED-icu na masu​

Posljednja plava jumper žica vodi j5 dolje do plave − vodilice. Krug je sada potpun.

Posljednja plava jumper žica spaja red 5 na vodilicu mase
Plava: j5 do plave − vodilice

15. Spoji USB kabel​

Utisni USB-C kabel u pločicu i u računalo.

Gotov krug semafora sa spojenim USB-C kabelom
Gotov sklop, spreman za skriptu

Cijeli krug na jedan pogled​

DioSpojŠto radi
Jumper žicaj30 (GND) → plava − vodilicaČini vodilicu dijeljenom masom
Jumper žicaj27 (IO4) → j21Zeleni signal s pločice
330 Ωh21 → h19Ograničava struju, zeleni kanal
Zelena LED-icai19 (duga nožica) → i17Zeleno svjetlo
Jumper žicaj17 → plava − vodilicaZelena LED-ica natrag na masu
Jumper žicaj26 (IO3) → j15Narančasti signal s pločice
330 Ωh15 → h13Ograničava struju, narančasti kanal
Narančasta LED-icai13 (duga nožica) → i11Narančasto svjetlo
Jumper žicaj11 → plava − vodilicaNarančasta LED-ica natrag na masu
Jumper žicaj25 (IO2) → j9Crveni signal s pločice
330 Ωh9 → h7Ograničava struju, crveni kanal
Crvena LED-icai7 (duga nožica) → i5Crveno svjetlo
Jumper žicaj5 → plava − vodilicaCrvena LED-ica natrag na masu

Čitaj pločicu od dalekog kraja prema natrag i LED-ice izlaze crvena, narančasta, zelena, pravi semafor koji leži na boku.


Kako konačni automat radi​

Konačni automat je program koji je uvijek u točno jednoj od malog, fiksnog skupa situacija, i između njih se pomiče na neki okidač. Ovdje su situacije pet faza svjetla, a okidač je sat.

Cijelo pamćenje automata su tri varijable:

VarijablaZnačenje
stateU kojoj smo fazi upravo sada
last_changeTrenutak kada smo ušli u tu fazu
state_durationKoliko dugo namjeravamo ostati u njoj

Svaka faza zatim radi te iste dvije stvari, i samo njih: postavi LED-ice onako kako ta faza želi, i provjeri je li joj vrijeme isteklo. Ako jest, imenuje sljedeću fazu, pamti trenutni trenutak kao novu početnu točku i postavlja sljedeće trajanje. Taj uzorak, ponovljen pet puta, je cijeli program.

FazaUpaljene LED-iceTrajanje
GREENzelena5 s
GREEN_BLINKzelena, blinka svakih 0.4 s3 s
ORANGEnarančasta2 s
REDcrvena5 s
RED_ORANGEcrvena i narančasta2 s

Jedan cijeli krug traje 17 sekundi, a onda počinje ponovno. Faza crveno-narančasto je upozorenje koje se koristi na mnogim europskim cestama da zeleno upravo dolazi.

Imenovanje pet stanja​

Arduino skica za to bi koristila enum. Python u MicroPythonu nema enum, pa skripta jednostavno pet imena daje pet brojeva:

GREEN = 0
GREEN_BLINK = 1
ORANGE = 2
RED = 3
RED_ORANGE = 4

To je sve što je enum u stvari i bio. Bez tih imena pet faza bili bi goli brojevi 0 do 4, a grešku bi bilo vrlo lako napraviti i vrlo teško primijetiti. S njima pišeš GREEN i RED, a kod se čita gotovo kao opis prave stvari.

ℹ️
Lanac if / elif koji slijedi je MicroPython zamjena za switch izjavu, koju Python također nema. Ponaša se isto: izvršava se samo blok koji pripada trenutnom stanju, a ostali se preskaču.

Zašto ticks_ms() a ne sleep()​

time.sleep() zaustavlja program u mjestu. To je u redu kada program ima jednu stvar raditi, ali pogledaj fazu blinkajućeg zelenog: zelena LED-ica mora bljeskati uključeno i isključeno svakih 0.4 sekunde dok trosekundni mjerač same faze i dalje radi. Dva sata, u isto vrijeme, u istom bloku koda. Sleep to ne može, jer koji god od njih čekaš, drugi se zaustavlja.

time.ticks_ms() vraća broj milisekundi od uključivanja pločice. Umjesto da čeka, program samo pogleda sat i pita je li prošlo dovoljno vremena?:

now = time.ticks_ms()

if time.ticks_diff(now, last_change) >= state_duration:
# prijeđi u sljedeće stanje

Petlja se nastavlja vrtjeti a odgovor je gotovo svaki put False. Kada konačno postane True, faza se mijenja. Ništa nikada nije blokirano, pa bilo koji broj mjerača može raditi jedan uz drugi.

⚠️
Svaka usporedba ovdje ide kroz time.ticks_diff(), a ne kroz oduzimanje dvaju brojeva. Brojač otkucaja nakon nekog vremena prelije se natrag na malu vrijednost, a obično oduzimanje tada daje veliki negativan odgovor, i semafor se tada zamrzne u kojoj god fazi se slučajno nalazio. ticks_diff() zna za to prelijevanje. Ovo je ovdje važnije nego bilo gdje drugdje u kitu, jer je ovo program zamišljen da radi danima.

Kada program napusti GREEN za GREEN_BLINK, radi i ovo:

green_on = True
last_blink = now

Bez njih bi green_on i last_blink još držali ono što je prethodni krug ostavio za sobom. last_blink je onaj koji stvara problem: bio bi star cijeli krug, pa bi ticks_diff() već bio daleko iza BLINK_INTERVAL i prvo preokretanje dogodilo bi se u prvom prolazu faze umjesto 0.4 s u nju. green_on zatim odlučuje u kojem smjeru to rano preokretanje ide, pa se zelena LED-ica može ugasiti u trenutku kada blinkanje počne umjesto da ostane upaljena svojih prvih 0.4 s. Dvije linije da faza počne iz poznatog stanja, mala stvar koju je lako izostaviti a naknadno naporno pronaći.

Primijeti i da blok GREEN_BLINK u zelenu LED-icu nikada ne piše na vrhu kako to rade druge faze. Samo gasi druge dvije. To je namjerno: dok ta faza radi, kod za blinkanje posjeduje zelenu LED-icu, a njezino dovođenje u visoko stanje u svakom prolazu ukinulo bi blink.

Samo preokretanje je jedna linija:

green_on = not green_on
green_led.value(1 if green_on else 0)
ℹ️
not preokreće True u False i natrag. 1 if green_on else 0 nakon toga taj True ili False pretvara u 1 ili 0 koje pin očekuje, sve u jednoj liniji, isti uvjetni izraz koji se u 6.2 koristi za odabir između dvije riječi.

Kod​

from machine import Pin
import time

# Ovo su varijable kojima predajemo brojeve pinova na koje smo spojili tri LED-ice.
# NULA pločica ima logiku imenovanja pinova ovako: IO4, gdje je 4 broj koji dajemo varijabli.
#
# Zapamti da svakoj od ovih LED-ica treba vlastiti 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 je građena izdržati i može se oštetiti.
LED_GREEN = 4
LED_ORANGE = 3
LED_RED = 2

# Ovo daje imena fiksnom skupu vrijednosti. Bez njih bismo morali pamtiti da stanje 0 znači zeleno a
# stanje 3 znači crveno, a grešku u tome bilo bi vrlo lako napraviti i vrlo teško primijetiti.
# Svako od ovih imena je jedno stanje u kojem naš semafor može biti, a može biti samo u jednom u jednom trenutku.
GREEN = 0
GREEN_BLINK = 1
ORANGE = 2
RED = 3
RED_ORANGE = 4

# Ovdje stvaramo naša tri Pin objekta. Pin.OUT govori pločici da ovi pinovi treba pisati vrijednost umjesto da je
# čitaju, jer sva tri pobuđuju LED-icu.
green_led = Pin(LED_GREEN, Pin.OUT)
orange_led = Pin(LED_ORANGE, Pin.OUT)
red_led = Pin(LED_RED, Pin.OUT)

# Ova varijabla drži stanje u kojem smo upravo sada. Zajedno s dvjema ispod nje, ona je cijelo pamćenje našeg
# konačnog automata: "last_change" pamti trenutak kada smo ušli u trenutno stanje, a "state_duration" drži koliko dugo
# namjeravamo ostati u njemu. To je sve što konačnom automatu treba: gdje sam, od kada, i koliko dugo.
state = GREEN
last_change = time.ticks_ms()
state_duration = 5000

# Ove tri varijable koristi samo stanje blinkajućeg zelenog. "green_on" pamti je li zelena LED-ica
# trenutno upaljena, "BLINK_INTERVAL" je koliko dugo takva ostaje prije preokretanja, a "last_blink" pamti kada se
# posljednji put preokrenula. Slobodno eksperimentiraj s intervalom da blinkanje bude brže ili sporije.
green_on = True
BLINK_INTERVAL = 400
last_blink = time.ticks_ms()

# Ispiši početnu poruku da znamo da se program uspješno pokrenuo.
print("Traffic Light Example started!")
print("State: GREEN")

while True:

# Sat čitamo jednom na vrhu petlje i tu jednu vrijednost koristimo svugdje ispod, što sve
# usporedbe u ovom prolazu drži međusobno dosljednima.
now = time.ticks_ms()

# Ispod svako stanje dobiva svoj blok, a izvršava se samo blok koji pripada trenutnom stanju. Svaki blok radi
# te iste dvije stvari: postavi LED-ice onako kako to stanje želi, a zatim provjeri je li mu vrijeme isteklo.
if state == GREEN:

# Zelena upaljena, sve ostalo ugašeno.
green_led.value(1)
orange_led.value(0)
red_led.value(0)

# Nakon pet sekundi prijeđi u stanje blinkajućeg zelenog i daj mu tri sekunde.
if time.ticks_diff(now, last_change) >= state_duration:
state = GREEN_BLINK
last_change = now
state_duration = 3000

# Ovdje postavljamo blinkanje prije nego mu predamo. Bez ovih dviju linija blink bi nastavio
# odakle je stao prethodni put u ciklusu, pa bi zelena LED-ica u ovo stanje mogla ući
# ugašena a prvi bljesak došao bi u pogrešnom trenutku.
green_on = True
last_blink = now

print("State: GREEN_BLINK")

elif state == GREEN_BLINK:

# Ovdje zelenu LED-icu ostavljamo na miru, jer blinkanje ispod odlučuje je li upaljena ili ugašena.
orange_led.value(0)
red_led.value(0)

# Ovo je samo blinkanje. Svaki put kada interval prođe, green_on preokrenemo u obrnutu vrijednost pomoću
# operatora "not" i novu vrijednost upišemo na pin. Budući da to radi na satu a ne na
# pauzi, automat iznad radi cijelo vrijeme dok LED-ica blinka.
if time.ticks_diff(now, last_blink) >= BLINK_INTERVAL:
green_on = not green_on
green_led.value(1 if green_on else 0)
last_blink = now

# Nakon tri sekunde blinkanja prijeđi na narančasto i daj mu dvije sekunde.
if time.ticks_diff(now, last_change) >= state_duration:
state = ORANGE
last_change = now
state_duration = 2000
print("State: ORANGE")

elif state == ORANGE:

# Narančasta upaljena, sve ostalo ugašeno.
green_led.value(0)
orange_led.value(1)
red_led.value(0)

# Nakon dvije sekunde prijeđi na crveno i daj mu pet sekundi.
if time.ticks_diff(now, last_change) >= state_duration:
state = RED
last_change = now
state_duration = 5000
print("State: RED")

elif state == RED:

# Crvena upaljena, sve ostalo ugašeno.
green_led.value(0)
orange_led.value(0)
red_led.value(1)

# Nakon pet sekundi prijeđi na crveno zajedno s narančastim i daj mu dvije sekunde.
if time.ticks_diff(now, last_change) >= state_duration:
state = RED_ORANGE
last_change = now
state_duration = 2000
print("State: RED_ORANGE")

elif state == RED_ORANGE:

# Crvena i narančasta upaljene u isto vrijeme, što je na mnogim europskim semaforima upozorenje da zeleno
# dolazi.
green_led.value(0)
orange_led.value(1)
red_led.value(1)

# Nakon dvije sekunde natrag smo na početku, i cijeli ciklus počinje ponovno.
if time.ticks_diff(now, last_change) >= state_duration:
state = GREEN
last_change = now
state_duration = 5000
print("State: GREEN")

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

Što bi trebao vidjeti​

Tri LED-ice neprekidno kruže, a cijeli krug traje 17 sekundi.

Četiri od pet faza su postojane, a ona u sredini nije: tijekom blinkajućeg zelenog LED-ica bljesne četiri puta, upaljena 0.4 s i ugašena 0.4 s. Kroz polovicu tih triju sekundi svaka LED-ica na pločici je tamna, a to vrijedi znati prije nego odlučiš da je nešto prestalo raditi. Jedina faza s dvije upaljene LED-ice odjednom je ona crveno-narančasta na kraju.

U konzoli​

Za razliku od većine projekata u ovom kitu, ova skripta govori. Svaka promjena faze najavljuje se kako se događa, pa automat možeš pratiti i kada ne gledaš LED-ice:

Traffic Light Example started!
State: GREEN
State: GREEN_BLINK
State: ORANGE
State: RED
State: RED_ORANGE
State: GREEN

a zatim te iste pet linija iznova.

Razmaci između linija su sama trajanja: pet sekundi nakon što se pojavi State: RED, slijedi ga State: RED_ORANGE. To konzolu čini korisnim testom za sebe: ako te linije nastavljaju promicati pravim tempom a LED-ice ne rade ništa, program je u redu a greška je u spajanju.

Ako se jedna boja nikada ne upali, vlastiti lanac te LED-ice nije u redu, a druge dvije dokazuju da su pločica i kod u redu. Provjeri prvo njezin polaritet, pa da njezin otpornik stvarno premošćuje dva različita reda. Ako se ništa uopće ne pali, provjeri žicu mase u j30: bez nje nijedan od tri lanca nema put natrag.

ℹ️
Pritisak na Stop ostavlja LED-ice točno kakve su bile u tom trenutku, jer ih ništa ne gasi na izlasku. Koja god faza bila prekinuta ostaje zamrznuta na breadboardu dok ne pokreneš nešto drugo ili resetiraš pločicu.

Cijeli primjer​

Pogledaj cijeli kod primjera na linku ispod:

7.8_LED_Traffic_Light.py

Simulacija semafora pomoću NULA MINI i tri LED-ice. Pokazuje konačne automate, neblokirajuće mjerenje vremena pomoću ticks_ms() i pobuđivanje nekoliko izlaza iz jednog vremenskog niza.