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 Ωh21h19Ogranič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 Ωh15h13Ogranič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 Ωh9h7Ogranič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.