Skip to main content

3.2 Udaljenost i zatamnjivanje LED-ice

Cilj ovog primjera je upravljati svjetlinom LED-ice na temelju udaljenosti izmjerene ultrazvučnim senzorom. Što je objekt bliže senzoru, to LED-ica jače svijetli.

To je veći korak nego što zvuči. Do sada je pin bio ili uključen ili isključen, a LED-ica ili upaljena ili ugašena. Ovdje pin mora proizvesti sve između, a udaljenost se mora pretvoriti u razinu svjetline. Taj posao obavljaju dva alata: PWM i map funkcija koju napišeš sam.

U ovoj dokumentaciji naučit ćeš:

  • Što je PWM i kako čini da LED-ica izgleda slabije od potpuno upaljene.
  • Kako duty_u16() postavlja svjetlinu i koji je njegov raspon.
  • Kako napisati vlastitu map funkciju, jer MicroPython ne dolazi s njom.
  • Kako ograničavanje (clamping) drži vrijednost unutar raspona koji možeš koristiti.
  • Što se dogodi kada obrneš izlazni raspon.

Potreban hardver:

  • 1x Soldered NULA MINI pločica
  • 1x Breadboard
  • 1x Ultrazvučni senzor udaljenosti (HC-SR04)
  • 1x LED-ica (bilo koje boje)
  • 1x otpornik od 330 ohma
  • 1x Qwiic kabel
  • 3x Jumper žice
  • 1x USB-C kabel
ℹ️
Ovaj sklop je 3.1 s dodanom LED-icom. Ako ti je taj još spojen, ostavi Qwiic kabel gdje je i počni od koraka 2.

Spajanje komponenti

1. Umetni NULA MINI pločicu na breadboard

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.

NULA MINI pločica postavljena na breadboard, zauzima redove 25 do 30
Korak 1: pločica postavljena na breadboard, s IO2 u redu 25 i GND u redu 30

2. Izvedi IO2 u slobodan red

IO2 je pin koji će pobuđivati LED-icu, a pločica prekriva cijeli red 25 osim krajnje rupice. Zato prva žica samo nosi taj pin nekamo gdje možeš raditi: jumper žica iz j25 gore u j15.

Jumper žica ide duž stupca j iz reda 25 u red 15
Korak 2: IO2 izveden iz j25 u j15

3. Dodaj otpornik od 330 Ω

Otpornik je sljedeći član lanca. Postavi ga tako da premosti red 15, red u koji je jumper žica upravo došla, i red 13, oba na f–j strani.

Otpornik od 330 ohma premošćuje redove 15 i 13 na strani f do j
Korak 3: otpornik od 330 Ω premošćuje redove 15 i 13
ℹ️
Otpornik nema polaritet, pa nije važno kojim je krajem okrenut. Nožice su mu dugačke i lako se savijaju, i zato se izvijaju preko površine umjesto da idu ravno. To je u redu, dokle god je svaki kraj čvrsto utisnut u svoju rupicu.

4. Dodaj LED-icu

LED-ica nastavlja lanac: njezina duga nožica (anoda) u red 13, isti red u kojem otpornik završava, i njezina kratka nožica (katoda) u red 11.

LED-ica dodana, s jednom nožicom u redu 13 uz otpornik i drugom u redu 11
Korak 4: 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 IO2; kratka nožica mora biti okrenuta prema masi. Okrenuta obrnuto, jednostavno se neće upaliti. Ništa se neće pokvariti, ali se ni ništa neće dogoditi. Ako nožice ne možeš razlikovati po dužini, potraži ravni zarez na rubu plastičnog tijela: nožica na toj strani je kratka.

5. Spoji LED-icu i pločicu na masu

Dvije jumper žice zaokružuju krug, i obje idu na plavu vodilicu, koja djeluje kao dijeljena linija mase:

  • Iz reda 11, gdje sjedi kratka nožica LED-ice, do plave vodilice.
  • Iz j30 (GND) do te iste vodilice.
Dvije crne jumper žice, jedna iz reda 11 i jedna iz j30, obje idu na negativnu vodilicu
Korak 5: LED-ica i GND pin pločice oboje vezani na plavu − vodilicu
ℹ️
Obje žice moraju ići u isti stupac vodilice, onaj koji ide uz plavu liniju. Vodilica uz ružičastu liniju je zaseban pojas i nije spojena s njom.

Cijeli put je sada IO2 → 330 Ω → LED-ica → − vodilica → GND. To je isti lanac kao u 1.2 Blinkanje LED-ice, izgrađen na drugom pinu. Razlika je u potpunosti u kodu: umjesto da pin uključujemo i isključujemo, držat ćemo ga negdje između.

ℹ️
Redovi 15, 13 i 11 samo su redovi koje te fotografije slučajno koriste. Bilo koji slobodni redovi rade, dokle god se drži redoslijed lanca: jumper žica s IO2, pa otpornik, pa LED-ica, pa masa.

6. Utisni senzor i USB-C kabel

Senzor ne treba nikakvo spajanje. To je Qwiic modul, pa jedan kabel s qwiic konektora pločice, onog bijelog između tipki USER i RST, do bilo kojeg konektora na senzoru nosi napajanje i podatke zajedno. Ako si upravo došao iz 3.1, već je utisnut.

Gotov sklop: krug s LED-icom na breadboardu, Qwiic kabel ide na ultrazvučni senzor, USB-C spojen
Korak 6: gotov sklop, s krugom LED-ice na breadboardu i senzorom na jednom Qwiic kabelu
ℹ️
Sve što se tiče senzorske strane ovog primjera točno je kako je bilo u 3.1: isti jedan kabel, ista adresa 0x30, isti ADDR klizači ostavljeni na miru, i isti zahtjev da dva srebrna pretvarača gledaju u čist prostor. Budući da razgovara preko I2C na IO6 i IO7, koji nisu izvedeni na headere, nikada se ne bori s LED-icom za pin.

Razumijevanje PWM-a i upravljanja svjetlinom

Digitalni pin ima samo dva stanja, pa ne može proizvesti pola napona. Ono što može je prebacivati između njih vrlo brzo i u svakom provesti različitu količinu vremena. To je PWM, Pulse Width Modulation.

Ako je pin uključen pola svakog ciklusa, LED-ica prima pola energije koju bi dobila da je pin uključen cijelo vrijeme, a tvoje oko, daleko presporo da vidi prebacivanje, to čita kao pola svjetline. Dio svakog ciklusa proveden u uključenom stanju naziva se radni ciklus (duty cycle).

Radni ciklusVizualni učinak
0%LED-ica potpuno UGAŠENA
50%LED-ica na pola svjetline
100%LED-ica potpuno UPALJENA
PWM valni oblici koji prikazuju mali radni ciklus uz veliki radni ciklus
Dva radna ciklusa: u drugom pin provodi dulje uključen, pa je prosjek veći i LED-ica izgleda svjetlije

U MicroPythonu LED-icu pobuđuje PWM objekt, a ne obični Pin, i on prima dvije postavke:

led = PWM(Pin(LED_PIN))
led.freq(5000)

freq() postavlja koliko brzo se pin uključuje i isključuje, ovdje 5000 puta u sekundi, daleko brže nego što tvoje oči mogu pratiti, pa vidiš postojanu svjetlinu umjesto treperenja. duty_u16() zatim postavlja samu svjetlinu, kao broj od 0 (potpuno ugašeno) do 65535 (potpuno upaljeno).

ℹ️
65535 je mjesto gdje se Arduino i MicroPython verzija ovog primjera razlikuju. Arduino skica prvo mora pozvati analogWriteResolution(LED_PIN, 12) i onda radi u rasponu 0–4095. MicroPython uvijek koristi cijeli 16-bitni raspon pomoću duty_u16(), pa nema rezolucije za postavljanje i nema što zaboraviti. Brojevi svjetline u konzoli su prema tome veći.
⚠️
Ne pomiješaj duty_u16() s istim pozivom u 2.4 Piskanje buzzera. Na buzzeru 65535 znači da pin nikada ne prebacuje i buzzer je tih. Na LED-ici 65535 znači da je pin uključen cijelo vrijeme i LED-ica je na punoj svjetlini. Ista funkcija, a razlika je u potpunosti u tome što je spojeno na pin.

Pretvaranje udaljenosti u svjetlinu

Tri stvari moraju se dogoditi između čitanja senzora i pisanja na pin.

Prvo, odbacivanje ne-mjerenja. Udaljenost 0 znači da senzor nije čuo upotrebljiv odjek, pa nema s čime raditi. Skripta to kaže i odmah skoči na sljedeći prolaz:

if distance == 0:
print("No echo received, nothing in range.")
continue
ℹ️
continue napušta ostatak ovog prolaza kroz petlju i vraća se na vrh. Bez njega bi se nula tretirala kao objekt priljubljen uz senzor, i LED-ica bi bljesnula u punu svjetlinu svaki put kada prostorija utihne.

Drugo, ograničavanje. Skriptu zanima samo raspon od 2 cm do 50 cm, i svaku vrijednost izvan tog raspona sili na najbliži rub:

if distance < MIN_DISTANCE:
distance = MIN_DISTANCE
if distance > MAX_DISTANCE:
distance = MAX_DISTANCE

Bez ovoga bi objekt tri metra daleko proizveo svjetlinu daleko ispod nule, besmislenu vrijednost za pisanje na pin. Ograničavanje jamči da sljedeći korak dobije broj unutar raspona za koji je napravljen.

Treće, preskaliranje. Arduino za to ima ugrađenu funkciju map(). MicroPython nema, pa skripta definira vlastitu:

def value_map(value, in_min, in_max, out_min, out_max):
return int((value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
ℹ️
def je način na koji se u Pythonu definira funkcija. Nakon imena slijede vrijednosti koje očekuje u zagradama, a return predaje odgovor onome koji ju je pozvao. Napisati je jednom na vrhu datoteke znači da je petlja ispod može samo koristiti, a računanje postoji samo na jednom mjestu.
ℹ️
int() oko cijelog izraza je važan. Dijeljenje u Pythonu proizvodi decimalni broj čak i kada se obje strane dijele bez ostatka, a duty_u16() decimalni broj ne prima. Bez int() skripta pada s TypeError: can't convert float to int.

Zatim se poziva s izlaznim rasponom namjerno naopako:

brightness = value_map(distance, MIN_DISTANCE, MAX_DISTANCE, MAX_BRIGHTNESS, 0)

Čitaj to kao: udaljenost ide od 2 cm do 50 cm; svjetlina ide od 65535 prema 0. Mala udaljenost sleti na visoki kraj raspona svjetline, a velika na niski, a to je točno učinak koji želimo: bliže znači svjetlije.

Prolazak kroz nekoliko vrijednosti napamet pokazuje što će skripta ispisati:

UdaljenostSvjetlinaZašto
2 cm ili manje65535ograničeno na minimum, pa potpuno upaljeno
33 cm23210oko trećine puta gore po rasponu
39 cm15018slabo ali jasno upaljeno
45 cm6826tek jedva vidljivo
50 cm ili više0ograničeno na maksimum, pa potpuno ugašeno
ℹ️
Svjetlina ne pada ravnomjerno za oko. Prepolovljenje broja ne prepolovljuje svjetlinu koju vidiš, jer naša percepcija svjetline nije linearna. Niski kraj raspona pokriva mnogo više vidljive promjene od visokog, i zato se čini da LED-ica naglo plane kada tvoja ruka dođe unutar oko 45 cm, a nakon toga se mijenja blaže.

Kod

# Soldered driver za ultrazvučni senzor udaljenosti, koji se nalazi u lib folderu repozitorija primjera.
from UltrasonicSensor import UltrasonicSensor

# I2C je ono što Qwiic konektor nosi, a senzor je Qwiic modul.
# PWM je skraćeno od Pulse Width Modulation. Uključuje i isključuje pin vrlo brzo, i što dulje ostaje uključen
# unutar svakog ciklusa, to LED-ica spojena na njega našim očima izgleda svjetlija.
from machine import I2C, Pin, PWM
import time

# Ovo je varijabla kojoj predajemo broj pina na koji smo spojili LED-icu. Budući da ovu LED-icu želimo zatamnjivati
# a ne samo uključivati i isključivati, pobuđujemo je PWM-om a ne običnim Pin.
#
# 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 = 2

# Ove dvije varijable definiraju raspon udaljenosti koji nas zanima. Sve bliže od minimuma računa se kao "što je
# bliže moguće", a sve dalje od maksimuma računa se kao "daleko". Slobodno eksperimentiraj s ovima.
MIN_DISTANCE = 2
MAX_DISTANCE = 50

# duty_u16() postavlja svjetlinu kao broj od 0 (potpuno ugašeno) do 65535 (potpuno upaljeno), pa je 65535 najveća
# svjetlina koju možemo zatražiti.
MAX_BRIGHTNESS = 65535

# Ovoliko čekamo nakon što zatražimo mjerenje, u milisekundama.
MEASURE_WAIT_MS = 50

# Ovdje postavljamo I2C vezu. Na NULA MINI, I2C koristi IO6 za liniju podataka (SDA) i IO7 za liniju
# takta (SCL), a to su točno pinovi na koje je Qwiic konektor spojen.
i2c = I2C(0, scl=Pin(7), sda=Pin(6))

# Ovdje stvaramo naš objekt senzora i predajemo mu I2C vezu. Za senzor ne predajemo nikakve brojeve pinova:
# svaki Qwiic modul dijeli ta ista dva I2C pina, a ovaj senzor odgovara na adresi 0x30.
sensor = UltrasonicSensor(i2c)

# Ovdje stvaramo naš PWM objekt za LED-icu. freq() postavlja koliko brzo se pin uključuje i isključuje. 5000 puta u
# sekundi je daleko brže nego što naše oči mogu pratiti, pa vidimo postojanu svjetlinu umjesto treperenja.
led = PWM(Pin(LED_PIN))
led.freq(5000)


def value_map(value, in_min, in_max, out_min, out_max):
# Ovo je funkcija koju smo napisali sami, jer ne postoji gotova funkcija za preskaliranje broja.
# Uzima broj iz jednog raspona i preskalira ga u drugi raspon. Budući da ćemo izlazni raspon predati
# obrnuto, najmanja udaljenost proizvodi najveću svjetlinu.
return int((value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)


# begin() priprema senzor za upotrebu, prije nego počnemo čitati iz njega.
sensor.begin()

# Ispiši početnu poruku da znamo da se program uspješno pokrenuo.
print("Distance Fade LED Example started!")

while True:

# takeMeasure() traži od senzora da pošalje puls i izmjeri vrijeme odjeka, a čekanje nakon toga daje mu
# trenutak koji mu treba da taj posao dovrši i spremi odgovor.
sensor.takeMeasure()
time.sleep_ms(MEASURE_WAIT_MS)

# getDistance() zatim dohvaća spremljeni odgovor, već pretvoren u centimetre.
distance = sensor.getDistance()

# Senzor odgovara s 0 kada nije čuo nikakav odjek. To nije pravo mjerenje, pa preskačemo ostatak
# ovog prolaza umjesto da to tretiramo kao objekt priljubljen uz senzor.
# continue skače pravo natrag na vrh petlje, spreman za sljedeće mjerenje.
if distance == 0:
print("No echo received, nothing in range.")
continue

# Ovdje izmjerenu udaljenost držimo unutar raspona koji smo definirali iznad. To se naziva ograničavanje, a
# radimo to jer sljedeći korak očekuje vrijednost unutar poznatog raspona.
if distance < MIN_DISTANCE:
distance = MIN_DISTANCE
if distance > MAX_DISTANCE:
distance = MAX_DISTANCE

# Ovdje udaljenost pretvaramo u svjetlinu pomoću naše funkcije iznad. Primijeti da je izlazni raspon
# obrnut, od MAX_BRIGHTNESS prema 0: najmanja udaljenost daje najveću svjetlinu.
brightness = value_map(distance, MIN_DISTANCE, MAX_DISTANCE, MAX_BRIGHTNESS, 0)

# duty_u16() upisuje svjetlinu na pin, i LED-ica se mijenja odmah.
led.duty_u16(brightness)

# Ispiši obje vrijednosti, da vidimo kako se svjetlina mijenja zajedno s udaljenošću.
print("Distance: {} cm -> Brightness: {}".format(distance, brightness))

Što bi trebao vidjeti

Pritisni Run i drži ruku nekoliko centimetara pred senzorom. LED-ica dođe na punu svjetlinu:

LED-ica svijetli punom svjetlinom dok je ruka blizu pred ultrazvučnim senzorom
Ruka držana blizu senzora, i LED-ica na vrhu svog raspona

Sada polako odmakni ruku i sjaj slabi, a onda se ugasi sasvim. Svaka linija u Shellu nosi obje polovice priče: što je senzor našao i što je LED-ici rečeno da s tim učini:

Distance Fade LED Example started!
Distance: 2 cm -> Brightness: 65535
Distance: 33 cm -> Brightness: 23210
Distance: 39 cm -> Brightness: 15018
Distance: 45 cm -> Brightness: 6826
Distance: 50 cm -> Brightness: 0
Distance: 50 cm -> Brightness: 0

U tom ispisu vrijedi primijetiti tri stvari.

Svaka vrijednost od 50 cm ili više daje Brightness: 0. Svaka od njih ograniči se na MAX_DISTANCE, a 50 cm se preslika u nulu. Tako LED-ica ostaje ugašena za 52 cm, 65 cm, 90 cm i sve dalje od toga. Budi se samo kada ti je ruka stvarno blizu. Ako ne vidiš ništa osim nula, nisi daleko od kruga koji radi; samo si predaleko od senzora.

Ispisana udaljenost nikada ne izlazi iz raspona od 2 cm do 50 cm. To je ograničavanje u radu: skripta prepisuje distance ograničenom vrijednošću prije ispisa, pa se ruka metar daleko i dalje prikazuje kao 50. Sirova vrijednost ne prikazuje se nigdje.

Vrijednosti oko 1000 cm također daju Brightness: 0. Kako je opisano u 3.1, to je način na koji senzor prijavljuje da nije našao ništa, a ne prave udaljenosti, ali ih ograničavanje ipak pretvori u 50 cm, pa prazna prostorija na kraju izgleda točno kao "daleko" i LED-ica ostaje ugašena. To je ponašanje koje ovdje želiš, i dobiva se besplatno.

ℹ️
Na kraju petlje nema pauze. Jedino čekanje je 50 ms za senzor, pa vrijednosti dolaze oko dvadeset puta u sekundi i Shell brzo promiče. To je ono što zatamnjivanju daje osjećaj odzivnosti umjesto stupnjevitosti. Sve je u redu.
⚠️
Ako brojevi izgledaju ispravno a LED-ica se nikada ne upali, greška je u spajanju, a ne u kodu. Provjeri tri stvari po redu: duga nožica LED-ice okrenuta je prema otporniku a kratka prema masi; otpornik i LED-ica stvarno se sastaju u istom redu, pa je lanac neprekinut; i obje žice mase sjede u stupcu vodilice uz plavu liniju.

Probaj sam: promijeni MAX_DISTANCE s 50 na 150 i pokreni ponovno. LED-ica sada reagira s metar i pol daljine, ali svaki centimetar kretanja mijenja svjetlinu manje, pa je zatamnjivanje blaže i teže ga je vidjeti. Spusti ga na 20 i događa se obrnuto: ništa dok ti ruka nije vrlo blizu, a onda brz, očit prijelaz od tame do pune svjetline. Ništa se na krugu nije promijenilo. Samo si redefinirao što "blizu" znači.


Cijeli primjer

Pogledaj cijeli kod primjera na linku ispod:

3.2_Distance_Fade_LED.py

Primjer koji čita Qwiic ultrazvučni senzor udaljenosti i koristi udaljenost za postavljanje svjetline LED-ice pomoću PWM-a.