HC-SR04: ultradźwiękowy czujnik odległości

HC-SR04 to jeden z podstawowych czujników w arsenale robotyka-amatora. Jest niezbyt szybki, niezbyt dokładny – ale za to bardzo tani, łatwy do podłączenia i prosty w obsłudze.

HC-SR04 zasilamy 5V. Na pobudzenie odpowiada sygnałem o wartości 5V. Taki poziom napięcia pasuje Arduino, ale może uszkodzić Raspberry. Chociaż… niektóre HC-SR04 mogą pracować na 3.3V – zobaczcie wpis HC-SR04 – standard? Oczywiście są sposoby na rozwiązanie tego problemu – wszystko znajdziecie poniżej.

CH340 - znane i lubiane; czy wszystkie takie same?
CH340 – znane i lubiane; czy wszystkie takie same?

Wyprowadzenia

Czujniki HC-SR04 mają 4 wyprowadzenia:

ch340, który działa na 5v

Ich znaczenie i sposób podłączenia do Arduino/Raspberry zestawiłem poniżej:

Pin HC-SR04 Znaczenie Arduino… podłącz do: Raspberry… podłącz do:
 VCC  Zasilanie czujnika 5V Pin 2/4: 5V
 TRIG Wejście czujnika, pobudzenie: na ten pin wysyłamy krótki sygnał, który spowoduje wyemitowanie wiązki ultradźwięków z czujnika Dowolny pin cyfrowy (Dx) skonfigurowany jako wyjście Dowolny pin ogólnego przeznaczenia skonfigurowany jako wyjście (można podłączyć bezpośrednio do GPIO)
 ECHO Wyjście czujnika: odpowiedź, przetworzony sygnał odebrany przez odbiornik czujnika Dowolny pin cyfrowy skonfigurowany jako wejście Konwerter logiki lub dzielnik napięciowy
– i dopiero wtedy do pinu Raspberry skonfigurowanego jako wejście
GND Masa GND np. pin 6: GND

Zasada działania

Po podłączeniu do kontrolera:

  1. Wygeneruj 10[uS] (10 – mikrosekundowy) impuls wysoki na TRIG; 10[uS] to 0.00001[s].
  2. Czekaj, aż pin ECHO przejdzie w stan wysoki,
  3. Gdy tak się stanie, czekaj aż pin ECHO przejdzie z powrotem w stan niski,
  4. Zapamiętaj długość trwania impulsu wysokiego na ECHO.

Czas trwania impulsu wysokiego na ECHO odpowiada czasowi, w  jakim sygnał dotarł do przeszkody i odbity od niej, powrócił do czujnika (czyli przebył drogę do przeszkody dwa razy – tam i z powrotem).

sketch_raspi_echpOdległość do przeszkody s możemy łatwo obliczyć: wystarczy ilość mikrosekund t, przez które na pinie ECHO utrzymywał się stan wysoki, podzielić przez 58.
s={t \over {58}}

Uzyskacie wtedy odległość do przeszkody w centymetrach (zob. instrukcja HC-SR04).

Arduino…

Do napisania programu odczytującego odległość z Arduino, przyda się Wam kilka instrukcji:

Instrukcja Znaczenie Przykładowe użycie
pinMode(pin, OUTPUT); Ustawia pin Arduino jako wyjście Ustawia pin D3 jako wyjście – podłączcie go do pinu TRIG czujnika:
pinMode(3, OUTPUT);
pinMode(pin, INPUT); Ustawia pin Arduino jako wejście Ustawia pin D4 jako wejście – podłączcie do pinu ECHO czujnika:
pinMode(4, INPUT);
digitalWrite(pin, HIGH); Ustawia pin na wartość wysoką (5V) Ustawia pin D3 na wartość wysoką:
digitalWrite(3, HIGH);
digitalWrite(pin, LOW); Ustawia pin na wartość niską (0V) Ustawia pin D3 na wartość niską:
digitalWrite(3, HIGH);
delayMicroseconds(us); Wstrzymuje działanie programu na zadaną ilość uS (mikrosekund) Generuje sygnał startowy dla czujnika:
delayMicroseconds(10);
delay(ms); Wstrzymuje działanie programu na zadaną ilość ms (milisekund) Czekaj 100 ms:
delay(100);
pulseIn(pin, HIGH); Czeka, aż pin będzie miał stan wysoki (HIGH), wtedy zaczyna mierzyć czas  aż pin przejdzie z powrotem w stan niski (LOW); zwraca ilość uS (mikrosekund) przez jaką pin był w stanie wysokim Zmierzy ilość mikrosekund, przez jaką pin D4 był w stanie wysokim; wynik zapisuje w zmiennej signalDelay:
int signalDelay = pulseIn(4, HIGH);
Serial.begin(speed); Konfiguruje wysyłanie danych po porcie szeregowym Skonfiguruje wysyłanie danych z szybkością 9600 bodów:
Serial.begin(9600);
Serial.println(signalDelay); Wypisz zawartość zmiennej signalDelay na ekranie i przejdź do następnej linii Ustawi wartość zmiennej delay i wyświetli na ekranie:
int signalDelay=pulseIn(4, HIGH);
Serial.println(signalDelay);
Serial.print(signalDelay); Wypisz zawartość zmiennej signalDelay na ekranie (nie przejdzie do następnej linii) Ustawi wartość zmiennej signalDelay i wyświetli na ekranie:
int signalDelay=pulseIn(4, HIGH);
Serial.print(signalDelay);

Dla czujnika podłączonego pinem TRIG do D3 oraz ECHO do D4 Arduino,  program odczytujący odległość wygląda tak:

#define TRIG 3
#define ECHO 4

void setup(){
  //Set direction
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  //Initialize serial
  Serial.begin(9600);
  Serial.println("--------");
  
}
//Endless loop
void loop(){
  //Pulse to start measure with HC-SR04
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  
  int signalDelay = pulseIn(ECHO, HIGH);
  int distance = signalDelay/58;

  Serial.print("Distance:");
  Serial.print(distance);
  Serial.println(" cm");
  
  delay(100);
}

sr_04Załadujcie ten program na Arduino. Na konsoli ([CTRL]+[SHIFT]+[M]) zobaczycie:

--------
Distance:137 cm
Distance:114 cm
Distance:137 cm
Distance:113 cm
Distance:112 cm
Distance:136 cm

pulseIn z ograniczonym zasięgiem

W większości przykładów znajdziecie wywołanie funkcji „pulseIn()” z dwoma parametrami:
int signalDelay = pulseIn(ECHO, HIGH);
Funkcja ta czeka, aż pin ECHO przejdzie w stan wysoki (HIGH) – wtedy zaczyna mierzyć czas, aż pin ECHO przejdzie z powrotem w stan niski (LOW). Na koniec zwraca ilość uS (mikrosekund) przez jaką pin był w stanie wysokim (tu przypisuje ją do zmiennej signalDelay).
Jeżeli jednak w polu widzenia czujnika nie ma żadnej przeszkody, „podróż” impulsu tam i z powrotem zajmie trochę czasu. Możecie ograniczyć zasięg czujnika, do interesującego Was pola. Powiedzmy, że szukacie przeszkód w promieniu 100cm. Stąd, korzystając ze wzoru na obliczenie odległości przeszkody:
100cm = {t \over {58}}
t = 100cm \cdot 58 = 5800 uS
Wiemy więc, że czujnik spędzi conajmniej 5800uS (lub 5.8ms) mierząc odległość 100cm. Możemy wykorzystać tą wiedzę, wywołując funkcję „pulseIn” z trzecim parametrem:
int signalDelay = pulseIn(ECHO, HIGH, 5800);
Ostatnie „5800” to tzw. „timeout” – ilość mikrosekund, po których funkcja stwierdzi, że nie ma już sensu dalej mierzyć – i zwróci nam wartość „0”. Możecie zmodyfikować powyższy program następująco

//...
void loop(){
  //...  
  int signalDelay = pulseIn(ECHO, HIGH, 5800);
  if( signalDelay == 0 ){
    Serial.println("Out of range");
  }else{
    int distance = signalDelay/58;

    Serial.print("Distance:");
    Serial.print(distance);
    Serial.println(" cm");
  }
  delay(100);
}
Biblioteka NewPing

Oczywiście nie musicie implementować wszystiego sami. Znalazłem ciekawą bibliotekę do obsługi czujnika HC-SR04 Tim’a Eckel’a. Dzięki niej, nie musicie się martwić o większość z powyższych szczegółów (chociaż oczywiście będzie lepiej, jeżeli zrozumieniecie zasadę działania czujnika).  Pobierzcie bibliotekę, dodajcie do Arduino IDE.

Jeżeli nie wiecie, jak zainstalować tą bibliotekę – ten filmik Wam pomoże:

Otwórzcie nowy projekt i wpiszcie następujący kod (wzorowany na przykładzie z Complete Guide for Ultrasonic Sensor):

#include <NewPing.h>

#define TRIG 3
#define ECHO 4
//Max distance 100 cm
#define MAX_DISTANCE 100

//Create instance of NewPing class 
NewPing sonar(TRIG, ECHO, MAX_DISTANCE); 
 
void setup() {
   Serial.begin(9600);
}
 
void loop() {
  //Read the distance
  unsigned int distance =  sonar.ping_cm();
  
  Serial.print("Distance:");
  Serial.print(distance);
  Serial.println(" cm");

  delay(100);
}

Biblioteka umożliwia obsługę wielu czujników, obsługę przerwań itp. Co jeszcze ciekawsze, możliwe jest również podłączenie czujnika HC-SR04 na 3 kabelkach – nie 4.

NewPing: 3 kabelki zamiast 4

Biblioteka NewPing obsługuje również tryb, w którym czujnik HC-SR04 podłączony jest do Arduino na 3 przewodach, a nie 4. W takiej konfiguracji łączycie razem ECHO i TRIG czujnika do jednego pinu Arduino. Biblioteka przełącza kierunek tego portu – raz jest wyjściem (gdy wysyła pobudzenie na TRIG), a później wejściem (gdy odbiera dane z ECHO).

Takie przełączanie może nie działać na wszystkich płytkach Arduino. Na moich klonach UNO biblioteka działała bez problemu – podobnie jak na Mega2560.

Widać to dokładniej na oscyloskopie podczepionym pod pin wspólny dla ECHO i TRIG :

Czujnik HC-SR04 podłączony na 3 kable (TRIG+ECHO zwarte) - z bibliotegą NewPing
Czujnik HC-SR04 podłączony na 3 kable (TRIG+ECHO zwarte) – z bibliotegą NewPing

Zauważcie, że pobudzenie (10uS) ma wartość tylko 2.48.

SDS00242Sprawdziłem to dla UNO i Mega2560. Ta sprawa wymaga wyjaśnienia!

Musicie teraz zmodyfikować swój program tak, żeby konstruktor klasy „NewPing” dostał ten sam pin jako ECHO i TRIG, np:

#include <NewPing.h>

#define IO_PIN 3
//Max distance 100 cm
#define MAX_DISTANCE 100

//Create instance of NewPing class 
NewPing sonar(IO_PIN, IO_PIN, MAX_DISTANCE); 
 
//...

Raspberry…

Podłączenie tego czujnika do Raspberry wymaga trochę więcej gimnastyki. Problemem jest tu pin ECHO czujnika, który na pobudzenie TRIG odpowiada wartością 5v (zgodną z zasilaniem Vcc). Samo TRIG czujnika możecie bezpiecznie bezpośrednio połączyć z GPIO Raspberry: czujnik bez problemu „zrozumie” 3.3v przychodzące z GPIO.

Raspberry Pi posługuje się logiką 3.3v. Wysoki stan portu (lub logiczna „1”) to 3.3V (tak dokładniej, jest to pewien zakres napięć, od 2.4 do 3.3V na wyjściu). Arduino posługuje się logiką 5V, gdzie logiczna „jedynka” to conajmniej 4..5V na wyjściu.

W drugą stronę: bezpośrednie podłączenie pinu ECHO czujnika do GPIO Raspberry, może ją uszkodzić. GPIO Raspberry toleruje tylko 3,3V – i niewiele więcej.

Konieczne jest więc obniżenie napięcia wychodzącego z ECHO czujnika do poziomu 3.3V. Sposobów jest kilka – od konwerterów logiki, przedstawiany przeze mnie bufor 4050 aż po dzielnik napięcia.

Najprostszy jest dzielnik napięcia. Wystarczą 2 rezystory o wartościach jak 1:2, np 4.7kΩ i 9.8kΩ. Nie mogą być za małe, żeby nie popłynął przez nie zbyt duży prąd. Takie rezystory rządzą się zasadą:
U_{wy}=U_{we} \cdot {{R_2} \over {R_2+R_1}}

gdzie:

  • R_1 jest rezystorem 4.7kΩ między wyjściem z czujnika ECHO (czyli napięciem 5V) a wybranym pinem GPIO Raspberry – ja użyłem GPIO21 (fizycznie ostatni z pinów GPIO),
  • R_2 jest rezystorem 9.8kΩ między masą (pin 6 Raspberry, GND czujnika) a wybranym pinem Raspberry.

Stąd:

U_{wy}=5 \cdot {{9.8} \over {9.8+4.7}}=3.37[V]

Czujnik możecie podłączyć w następujący sposób:

sketch_raspiPozostaje napisać program – może w Pythonie? Otwórzcie plik:
$ nano readHC.py
I wpiszcie następujący kod (wybaczcie styl i brak GPIO.cleanup() – to tylko ilustracja!):

#import libs
import RPi.GPIO as GPIO
import time
#BCM numbering scheme
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
#Set pins
TRIG=21
ECHO=20
#Set direction
GPIO.setup(TRIG, GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)

#Endless loop
while True:
  #Pulse to start measure with HC-SR04
  GPIO.output(TRIG, True)
  time.sleep(0.00001)
  GPIO.output(TRIG, False)
  #Wait for HIGH on ECHO
  while GPIO.input(ECHO) == 0:
    pulse_start = time.time()
  #wait for LOW again
  while GPIO.input(ECHO) == 1:
    pulse_end = time.time()
  signalDelay = pulse_end - pulse_start
  #divider for uS to  s
  constDivider = 1000000/58
  distance = int(signalDelay * constDivider)
  print "Distance:", distance,"cm"
  time.sleep(0.1)

Uwaga Python! Pamiętajcie o wcięciach w kodzie.

Uruchomienie nie powinno być żadnym problemem:
$ sudo python readHC.py

IMG_20160120_184854

Podsumowanie

Robot PiBotta wyposażony był w czujnik HC-SR04
Robot PiBotta wyposażony był w czujnik HC-SR04

Mierzenie odległości wcale nie jest trudne. Dzięki taniemu czujnikowi HC-SR04 możecie to robić w zakresie ok. 2-150cm. Jego podłączenie i oprogramowanie jest proste – czy na Arduino, czy też na Raspberry. Przetestujcie, jak sygnał odbija się od różnych powierzchi i jak wygląda skuteczne pole jego działania.

Źródła

4 myśli w temacie “HC-SR04: ultradźwiękowy czujnik odległości”

    1. Witaj,
      Z tego co pamiętam, standardowy Scratch (S4A) nie obsługuje takiego czujnika. Ale widziałem gdzieś w sieci rozszerzenia, które na to pozwalają! Wydaje mi się jednak, że Scratch jest dobry, żeby poznać podstawy programowania – a potem już najlepiej przesiąść się na język Arduino.
      Pozdrawiam,
      Arek

    1. Witaj, RPi ma wystarczająco dużo GPIO, żeby obsłużyć 4 takie czujniki. Pamiętaj jednak, że czujniki HCSR04 wymagają pobudzenia 5v (najczęściej) i odpowiadają na nie 5v – natomiast GPIO Raspberry jest 3.3v. Będziesz więc potrzebował konwertera (ów) logiki. Musisz sprawdzić, ile kanałów ma wybrany przez Ciebie model konwertera, bo możesz np. potrzebować 2.
      Raspberry ma pin zasilający 5v – prądu powinno też wystarczyć na 4 czujniki.
      Jedyne co może nastręczyć pewne trudności to oprogramowanie. Nie ma problemu, jeżeli zamierzasz mierzyć jeden-po-drugim; trochę trudniejsze będzie czytanie ze wszystkich równocześnie.
      Pozdrawiam,
      A

Dodaj komentarz