Raspberry Pi
39) Ultrazvukový dálkoměr HC-SR04
Další věc, která se dá připojit k RasPi je
ultrazvukový dálkoměr HC-SR04. Je to velice běžná součástka, takže
se dá sehnat skoro na každém čínském e-shopu za cenu kolem 3$. Třeba
tady: http://dx.com/p/...
, nebo tady http://www.ebay.com/....
Teorie:
Princip zařízení je velice jednoduchý:
Jedním impulzem na vývodu "Trig"
se vnitřní elektronika dálkoměru postará o vyslání 8 krátkých impulzů do vysílacího
"reproduktorku". Frekvence těchto 8 pulzů je 40kHz
(neslyšitelný ultrazvuk)
Signál naměřený přímo na vysílači (červený signál je spouštěcí impulz
(Trig), žlutý signál je reproduktorek)
Vyslaný ultrazvukový signál se odrazí od překážky,
jejíž vzdálenost se zjišťuje, a odraz se zachytí na mikrofonu.
Vnitřní elektronika dálkoměru se postará o to, že se na výstupu
"Echo" objeví na chvíli logická "1". Délka trvání
této jedničky udává čas, který uběhne mezi vysláním
ultrazvukové dávky impulzů a přijetím jejich odrazu.
(červený signál je spouštěcí impulz (Trig), žlutý signál je
"echo" výstup při různých vzdálenostech překážky)
Software:
Vzhledem k jednoduchosti obsluhy je tento dálkoměr často
využíván pro školní
projekty a proto existuje i mnoho vyhodnocovacích programů v různých
programovacích jazycích.
Například:
http://pi.nhsa.co.uk/index.php?page=projects-hcsr04
http://www.instructables.com/id/Simple-Arduino-and-HC-SR04-Example
a další...
Nejjednodušší způsob připojení vypadá takto:
Dělič napětí složený z odporů 1k a 1k8 slouží k
tomu, aby se snížilo napětí na vstupu GPIO8.
Senzor totiž na výstupu
"Echo" pracuje s pětivoltovou TTL logikou. Pro Raspberry Pi by
ale bylo 5V zničujících.
Proto se musí napěťová úroveň snížit.
Při použití odporů 1k a 1k8 bude maximální napětí na vstupu RasPi
(při 5V na výstupu čidla) :
UH = 5 * (1800 / (1000 + 1800))) =
3,2V
Ovládací program pak už jenom v pravidelných
intervalech posílá impulzy na GPIO7 a měří čas trvání logické
"1" na GPIO8:
První verze programu může vypadat třeba takto:
#!/usr/bin/python
# -*- encoding: utf-8 -*-
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
pin_in = 8
GPIO.setup(pin_in, GPIO.IN) # GPIO8 (= hardwerovy pin24) bude pouzit pro cteni delky signalu Echo
pin_out = 7 # GPIO7 (= hardwerovy pin26) - generator spoustecich impulzu
frekvence = 20 # frekvence signalu
strida = 1 # signal bude 1% casu v logicke "1" a 99% casu v logicke "0"
GPIO.setup(pin_out, GPIO.OUT) # GPIO prepne na vystup
signal1 = GPIO.PWM(pin_out , frekvence) # nastaveni pinu do PWM vystupniho rezimu
signal1.start(strida)
while True:
suma = 0
for i in range(10): # bude se prumerovat 10 vzorku
while GPIO.input(pin_in) == False: # dokud je vystup v "0", tak se ceka ...
time.sleep(0.00000001)
start= time.time() # kdyz se prepne vystup do "1", zaznamena se startovni cas
while GPIO.input(pin_in) == True: # a opet se ceka, dokud ten vystup nepadne zpatky do "0"
time.sleep(0.00000001)
cas= time.time() - start # v tom okamziku se zjisti rozdil mezi aktualnim a startovnim casem
suma = suma + cas # cas vsech 10 vzorku se scita
prumer = suma / 10 # vypocteni prumerneho casu
print "Cas = " + str(prumer) + " ... tomu odpovida vzdalenost : " + str(int((prumer * 340 / 2) * 100)) + " cm"
time.sleep(0.3)
|
Na GPIO7 se pomocí PWM pravidelně posílají krátké
spouštěcí pulzy.
GPIO8 se čte dvěma klasickými rychlými smyčkami "while...".
Toto měření však hodně zatěžuje procesor a také je
dost nepřesné a proto je dobré k tomu zavést ještě průměrování.
V přikladu se vzdálenost měří 10x a z naměřených časů se pak
vypočte průměrný čas na 1 vzorek.
Celková vzdálenost, kterou zvuk urazil od vyslání do přijetí
se vypočte tak, že se naměřený čas vynásobí rychlostí zvuku
(340m/s).
Když se tato vzdálenost vydělí dvěma, dostaneme vzdálenost překážky
v metrech.
Rychlost zvuku ve vzduchu záleží na mnoha dalších
parametrech (teplota, tlak, složení vzduchu). Pro ukázkové účely
ale není třeba s těmito proměnnými počítat. Samotné čidlo není
tak přesné, aby tyto parametry nějak zásadně ovlivňovaly měření.
Video ukázka z činnosti programu při použití smyčky
"while...": dalkomer-while.avi
(5MB) ()
Abych snížil zatížení procesoru, zkusil jsem i jiné
řešení s využitím GPIO pollingu. Jenže to bylo ještě horší, než
použití smyček "while...".
V podstatě nepoužitelné. GPIO polling má mnohem nižší rychlost čtení
stavu GPIO pinu, než smyčka "while". Hodí se tedy na čtení
tlačítek, ale rozhodně ne na nějaké rychlé čtení stavu pinu.
Navíc po nějakém čase (po několika desítkách sekund) vždycky
program zhavaroval právě na tom čekání na hranu.
Jen pro ukázku tady je část programu, která
nahrazovala "while" smyčky
.
.
.
for i in range(10): # prumerovani 10 vzorku
GPIO.wait_for_edge(pin_in, GPIO.RISING) # cekani na nabeznou hranu na GPIO vstupu
start= time.time() # zaznam casu startu
GPIO.wait_for_edge(pin_in, GPIO.FALLING) # cekani na sestupnou hranu na GPIO vstupu
cas= time.time() - start # vypocet delky impulzu
suma = suma + cas
prumer = suma / 10 # vypocteni prumerneho casu z 10 vzorku
print "Cas = " + str(prumer) + " ... tomu odpovida vzdalenost : " + str(int((prumer * 340 / 2) * 100)) + " cm"
.
.
.
|
Video ukázka z činnosti programu při použití GPIO
pollingu (wait_for_edge) včetně havárie programu: dalkomer-edge.avi
(6MB) ()
Předchozí řešení měly nevýhodu v tom, že byly hodně
závislé na stabilitě časovačů v RasPi. Když RasPi začalo vykonávat
nějaký proces s vyšší prioritou, program se pozastavil a naměřené
časy neodpovídaly skutečnosti.
Vytvořil jsem tedy dálkoměr na stejném principu, který
jsem použil i v článku o přesné časomíře.
Zapojení je v tomto případě trochu jednodušší, ale
princip je stejný:
(Šedý Pull-Up odpor na vstupu čítače se použije v případě, že má
hradlo NAND otevřený kolektor.)
Raspíčko v tomto případě slouží pro generování
spouštěcího signálu a v závěru měření pro I2C
komunikaci s čítačem.
Frekvence spouštěcích signálů není
podstatná, takže vůbec nezáleží na tom, jestli se někdy RasPi trochu opozdí.
Po vygenerování pulzu se program na chvíli zastaví (na půl
sekundy - což je dostatečná doba na to, aby se signál odrazil od překážky
která by mohla být teoreticky až 85m daleko). Reálně je maximální
vzdálenost překážky, kterou je čidlo schopné zaznamenat, asi 1,5m (podle katalogového listu
jsou to 4m, ale buď je
to nesmysl, nebo mám vadné čidlo.)
Během této doby se na chvíli objeví na vývodu
"Echo" logická "1".
Tato "1" sepne digitální spínač tvořený hradlem NAND.
Když je NAND "sepnutý", propouští impulzy generované RTC
obvodem (PCF8563) do vstupu čítače
(PCF8583).
Když se "Echo" výstup z čidla přepne zpátky
na "0", spínač se přeruší a čítač přestane dostávat
impulzy z RTC generátoru.
Po 0,5s od vyslání spouštěcího signálu si RasPi přes
I2C zjistí počet nasčítaných impulzů v čítači.
Když je známá přesná frekvence RTC generátoru (32768Hz), je možné
z počtu nasčítaných impulzů v čítači určit přesný čas trvání
logické "1" na "Echo" výstupu (jednoduše se počet
impulzů vydělí číslem 32768 a výsledkem je čas v sekundách).
Tento čas se pak převede na vzdálenost stejně, jako v
původním programu, kde se pro zjišťování času používaly smyčky
"while...".
Nakonec se čítač přes I2C smaže a celý cyklus se vysláním
spouštěcího signálu může opakovat.
V tomto případě byla stabilita měření tak dobrá, že
nebylo třeba zavádět žádné průměrování.
Při frekvenci generátoru 32768Hz je rozlišení tohoto
zapojení +/- jeden impulz generátoru. To je (1/32768) = 30,5
mikrosekundy.
Zvuk za takovou dobu urazí asi centimetr (340 / 32768) = 1,04cm.
Další zlepšení přesnosti je možné zvýšením
frekvence generátoru.
Čítač PCF8583 dokáže zpracovávat impulzy o
frekvenci až 1MHz.
Takovouto frekvenci ale není schopný generovat RTC obvod (PCF8563),
takže
by to vyžadovalo sestrojení jiného přesného generátoru.
Pokud by existoval přesný generátor s frekvencí 1MHz,
byla by rozlišovací schopnost zapojení :
(340 / 1000000) = 0,34mm
Jen pro představu - kapacita čítače by se v tomto případě naplnila
přesně za 1 sekundu. Za takovou dobu zvuk urazí 340m.
Program:
#!/usr/local/bin/python
# -*- encoding: utf-8 -*-
import RPi.GPIO as GPIO
import time
import smbus
bus = smbus.SMBus(1) # novejsi verze Raspberry Pi (512MB)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
start_pin = 7 # GPIO7 = pin26 (timto pinem se bude spoustet vysilani impulzu)
GPIO.setup(start_pin, GPIO.OUT)
adresa_ec = 0x50 # pocitadlo impulzu (PCF8583)je nastaveno na adresu 0x50 (vyvod A v "0")
adresa_gen = 0x51 # generator hodinovych pulzu je na adrese 0x51 (PCF8563)
# nastaveni pinu RTC-OUT na obvodu 8563 na frekvenci 32768Hz
bus.write_byte_data(adresa_gen,0x0D,0b10000000)
frekvence_gen = 32768.0
# status registr citace PCF8583 nastavit na rezim "EVENT COUNTER"
bus.write_byte_data(adresa_ec,0x00,0b00100000)
while True: # nekonecna merici smycka
#vynulovani pocitadala v event counteru
bus.write_byte_data(adresa_ec,0x01,0x00) # LSB
bus.write_byte_data(adresa_ec,0x02,0x00)
bus.write_byte_data(adresa_ec,0x03,0x00) # MSB
# poslat kratky impulz do TRIGu - tim se vysle ultrazvukovy signal
GPIO.output(start_pin, True)
time.sleep(0.001) # sirka signalu 1ms
GPIO.output(start_pin, False)
# pockat, nez se signal prijme (pul sekundy je dostatecna doba)
time.sleep(0.5)
# zjisteni poctu impulzu v pocitadle a prevod do desitkove soustavy
counter = bus.read_i2c_block_data(adresa_ec,0x00)
rad1 = (counter[1] & 0x0F) # rad jednotek
rad10 = (counter[1] & 0xF0) >> 4 # rad desitek
rad100 = (counter[2] & 0x0F) # rad stovek
rad1000 = (counter[2] & 0xF0) >> 4 # rad tisicu
rad10000 = (counter[3] & 0x0F) # rad desetitisicu
rad100000 = (counter[3] & 0xF0) >> 4 # rad statisicu
count = (rad100000 * 100000) + (rad10000 * 10000) + (rad1000 * 1000) + (rad100 * 100) + (rad10 * 10) + rad1
cas = count / frekvence_gen # sirka signalu v sekundach
vzdalenost = int((cas * 34000) /2) # vzdalenost prekazky v cm
print "Pocet impulzu = " + str(count) , "... tomu odpovida vzdalenost = " + str(vzdalenost) + "cm"
|
Video ukázka měření vzdálenosti s využitím RTC
generátoru a čítače:
|