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()umjestotime.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
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.

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.

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

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.
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.

6. Spoji zelenu LED-icu na masu
Plava jumper žica vodi j17 dolje do plave − vodilice, čime se prvi lanac dovršava.

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

8. Dodaj otpornik za narančastu LED-icu
Drugi otpornik od 330 Ω premošćuje h15 do h13.

9. Utisni narančastu LED-icu
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.

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.

12. Dodaj otpornik za crvenu LED-icu
Treći otpornik od 330 Ω premošćuje h9 do h7.

13. Utisni crvenu LED-icu
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.

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

Cijeli krug na jedan pogled
| Dio | Spoj | Što radi |
|---|---|---|
| Jumper žica | j30 (GND) → plava − vodilica | Čini vodilicu dijeljenom masom |
| Jumper žica | j27 (IO4) → j21 | Zeleni signal s pločice |
| 330 Ω | h21 → h19 | Ograničava struju, zeleni kanal |
| Zelena LED-ica | i19 (duga nožica) → i17 | Zeleno svjetlo |
| Jumper žica | j17 → plava − vodilica | Zelena LED-ica natrag na masu |
| Jumper žica | j26 (IO3) → j15 | Narančasti signal s pločice |
| 330 Ω | h15 → h13 | Ograničava struju, narančasti kanal |
| Narančasta LED-ica | i13 (duga nožica) → i11 | Narančasto svjetlo |
| Jumper žica | j11 → plava − vodilica | Narančasta LED-ica natrag na masu |
| Jumper žica | j25 (IO2) → j9 | Crveni signal s pločice |
| 330 Ω | h9 → h7 | Ograničava struju, crveni kanal |
| Crvena LED-ica | i7 (duga nožica) → i5 | Crveno svjetlo |
| Jumper žica | j5 → plava − vodilica | Crvena 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:
| Varijabla | Značenje |
|---|---|
state | U kojoj smo fazi upravo sada |
last_change | Trenutak kada smo ušli u tu fazu |
state_duration | Koliko 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.
| Faza | Upaljene LED-ice | Trajanje |
|---|---|---|
GREEN | zelena | 5 s |
GREEN_BLINK | zelena, blinka svakih 0.4 s | 3 s |
ORANGE | narančasta | 2 s |
RED | crvena | 5 s |
RED_ORANGE | crvena i narančasta | 2 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.
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.
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.Dvije linije koje blink drže iskrenim
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.
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.