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) (Odkaz na YouTube)

  

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) (Odkaz na YouTube)

 


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:

Odkaz na YouTube

 

 

 

 


úvodní strana webu AstroMiK.org

poslední úprava stránky 16.6.2013