Урок 1.11. Использование ультразвукового датчика

Базовый курс "Программирование микроконтроллеров"
Модуль 1. Введение в Arduino. Работа с цифровым и аналоговым сигналом

Ультразвуковой дальномер HC-SR04

Дальномер — это устройство для измерения расстояния до некоторого предмета. Дальномер помогает роботам в разных ситуациях. Простой колесный робот может использовать этот прибор для обнаружения препятствий. Летающий дрон использует дальномер для барражирования над землей на заданной высоте. С помощью дальномера можно даже построить карту помещения, применив специальный алгоритм SLAM и прочее.

В наших уроках мы будем использовать ультразвуковой дальномер HC-SR04.

Технические характеристики

Рабочий диапазон измерений от 2 до 400 см;
Точность измерений – 1 см;
Рабочий угол наблюдения – 30º;
Рабочий угол измерений – 15º;
Рабочее напряжение – 5В;
Потребляемый ток – 2мА в режиме ожидания, 15мА в режиме измерений;
Ультразвуковой диапазон – 40 кГц;
Диапазон рабочих температур – от 0 до 60ºС;
Ширина импульса триггера – 10 мкс;
Габариты датчика – 45х20х15 мм.

Принцип действия

Разберем работу одного из самых популярных датчиков для определения расстояния — ультразвукового (УЗ) дальномера. Существует много разных модификаций подобных устройств, но все они работают по принципу измерения времени прохождения отраженного звука.

1. Датчик отправляет звуковой сигнал в заданном направлении;
2. Звуковой сигнал достигает объекта и отражается от него;
3. Затем датчик ловит отраженное эхо и вычисляет время полета звука от датчика до препятствия и обратно.

Обратите внимание: результате замеров датчика мы имеем только время прохода волны до объекта и обратно, и это пока что не расстояние от датчика до объекта.

Расчет расстояния до объекта

Для поиска расстояния до объекта решим простую задачку по физике, где нам нужно найти расстояние до объекта (S), а известно нам:

t_{общ} – время прохода волны до объекта и обратно,
т.е. время прохода волной расстояния равное 2S,

V_{звука} = 340 \frac{м}{с}

Расстояние до объекта можно вычислить из знакомой нам формулы из школьного курса математики:

S=V_{звука}t_{до объекта}.

Поскольку t_{до объекта}= \frac{t_{общ}}{2}, то S = \frac{V_{звука}t}{2}.

Забегая немного вперед отметим, что измеренное нами время будет измерено в микросекундах, и нам необходимо в своей формуле это учесть!

S = \frac{V_{звука}t}{2}=\frac{340\frac{м}{с}t}{2}=\frac{340 * 10^{-6}\frac{м}{мкс}t}{2}=170* 10^{-6}\frac{м}{мкс}t

Так же переведем метры в сантиметры:

S=170* 10^{-6}\frac{м}{мкс}t=170* 10^{-6}*10^{2}\frac{см}{мкс}t=170* 10^{-4}t=0.017t \approx \frac{t}{58}

Итак, попробуем приказать датчику отправить зондирующий ультразвуковой импульс, а затем зафиксируем его возвращение. Посмотрим как выглядит временная диаграмма работы HC-SR04.

На диаграмме видно, что для начала измерения нам необходимо:

1. Сгенерировать на выводе TRIG положительный импульс длиной 10 мкс;
2. Вслед за этим, датчик выпустит серию из 8 импульсов и поднимет уровень на выводе ECHO, перейдя при этом в режим ожидания отраженного сигнала;
3. Как только дальномер почувствует, что звук вернулся, он завершит положительный импульс на ECHO.

Получается, что нам нужно сделать всего две вещи:

1. Создать импульс на TRIG для начала измерения;
2. Замерить длину импульса на ECHO, чтобы потом вычислить дистанцию по нехитрой формуле.

 

Проектом этого урока будет парктроник –  устройство, которое измеряет расстояние до препятствий во время парковки автомобиля и издает предупреждающий звуковой сигнал, так что помимо самого ультразвукового дальномера мы добавим в наше устройство экран и динамик.

Схема сборки и программа для проверки УЗД

// пины подключения УЗД
int echoPin = 3;
int trigPin = 2;

void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  int duration;
  float cm;
  // генерируем на выводе Trig положительный импульс длиной 10 мкс
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // считываем длительность импульса, т.е. время между испусканием УЗ волны и её возвратом
  duration = pulseIn(echoPin, HIGH);
  // вычисляем расстояние
  cm = (float)duration / 58;
  Serial.print(cm);
  Serial.println(" cm");
  delay(100);
}

В этой программе мы использовали новую функцию:

pulseIn(НОМЕР ПИНА, ОТСЛЕЖИВАЕМОЕ СОСТОЯНИЕ)

Функция считывает длину сигнала на заданном порту (HIGH или LOW). Например, если задано считывание HIGH:

– функция ожидает пока на заданном порту не появиться HIGH;
– когда HIGH получен, включается таймер;
– таймер будет остановлен когда на порту вход/выхода будет LOW.

Функция pulseIn() возвращает длину сигнала в микросекундах. Откроем монитор порта и убедимся что всё работает.

Программа вывода показаний УЗД на ЖК-дисплей

// Подключаем библиотеку для работы 
// с LCD дисплеем по шине I2C

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // пины подключения УЗД int echoPin = 3; int trigPin = 2; void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); lcd.init(); // Инициируем работу с LCD дисплеем lcd.backlight(); // Включаем подсветку LCD дисплея }
void loop() {
  int duration;
  float cm;
  // генерируем на выводе Trig положительный импульс длиной 10 мкс
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // считываем длительность импульса, т.е. время между испусканием УЗ волны и её возвратом
  duration = pulseIn(echoPin, HIGH);
  // вычисляем расстояние
  cm = (float)duration / 58;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Dist = " + String(cm) + " cm");
  delay(200);
}

Вы можете заметить, что показания датчика “плавают” даже если объект находится от датчика на фиксированном расстоянии. Чтобы это исправить, мы можем воспользоваться различными математическими фильтрами данных. Самый простой способ – это взять среднеарифметическое значение по нескольким показаниям:

#include <LiquidCrystal_I2C.h>  //  Подключаем библиотеку для работы с LCD дисплеем по шине I2C
LiquidCrystal_I2C lcd(0x27, 16, 2);
// пины подключения УЗД
int echoPin = 3;
int trigPin = 2;
int N = 50;     // количество измерений для вычисления среднеарифметического
float sum = 0;  // переменная для записи суммы показаний

void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  lcd.init();       //  Инициируем работу с LCD дисплеем
  lcd.backlight();  //  Включаем подсветку LCD дисплея
}

void loop() {
  int duration;
  float cm;
  sum = 0;
  for (int i = 0; i < N; i++) {
    // генерируем на выводе Trig положительный импульс длиной 10 мкс
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    // считываем длительность импульса, т.е. время между испусканием УЗ волны и её возвратом
    duration = pulseIn(echoPin, HIGH);
    // вычисляем расстояние
    cm = (float)duration / 58;
    sum = sum + cm;
  }
  cm = (float)sum / N;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Dist = " + String(cm) + " cm");
}

Визуальный и звуковой эффект

Добавим визуальный эффект полосу индикации расстояния на второй строчке нашего экрана, ограничим измерения расстояния от 5 до 30 сантиметров.

//  Подключаем библиотеку для работы 
// с LCD дисплеем по шине I2C

#include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); // пины подключения УЗД int echoPin = 3; int trigPin = 2;
// количество измерений
для вычисления среднеарифметического
int N = 100;
// переменная для записи суммы показаний
float sum = 0;
void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  lcd.init();       //  Инициируем работу с LCD дисплеем
  lcd.backlight();  //  Включаем подсветку LCD дисплея
}

void loop() {
  int duration;
  float cm;
  sum = 0;
  for (int i = 0; i < N; i++) {
    // генерируем на выводе Trig положительный импульс длиной 10 мкс
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    // считываем длительность импульса, т.е. время между испусканием УЗ волны и её возвратом
    duration = pulseIn(echoPin, HIGH);
    // вычисляем расстояние
    cm = (float)duration / 58;
    // ограничиваем измеренное расстояние между 5 и 30 сантиметрами
    cm = constrain(cm, 5, 30);
    sum = sum + cm;
  }
  cm = (float)sum / N;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Dist = " + String(cm) + " cm");
  // переменная для записи количества закрашенных секторов экрана
  int x = 0;
  x = map(cm, 5, 30, 16, 0);
  lcd.setCursor(0, 1);
  for (int i = 0; i < x; i++) {
    lcd.print("-");  // символ индикации
  }
}

Парктроник должен издавать звуковой сигнал, добавим звуковую индикацию.

//  Подключаем библиотеку для работы 
// с LCD дисплеем по шине I2C

#include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); // пины подключения УЗД int echoPin = 3; int trigPin = 2;
// количество измерений для вычисления среднеарифметического int N = 50; float sum = 0; // переменная для записи суммы показаний int buz = 5; // пин подключения динамика int t = 0; // время между звуковыми сигналами
void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(buz, OUTPUT);
  pinMode(echoPin, INPUT);
  lcd.init();       //  Инициируем работу с LCD дисплеем
  lcd.backlight();  //  Включаем подсветку LCD дисплея
}

void loop() {
  int duration;
  float cm;
  sum = 0;
  for (int i = 0; i < N; i++) {
    // генерируем на выводе Trig положительный импульс длиной 10 мкс
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    // считываем длительность импульса, т.е. время между испусканием УЗ волны и её возвратом
    duration = pulseIn(echoPin, HIGH);
    // вычисляем расстояние
    cm = (float)duration / 58;
    // ограничиваем измеренное расстояние между 5 и 30 сантиметрами
    cm = constrain(cm, 5, 30);
    sum = sum + cm;
  }
  cm = (float)sum / N;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Dist = " + String(cm) + " cm");
  // переменная для записи количества закрашенных секторов экрана
  int x = 0;
  x = map(cm, 5, 30, 16, 0);
  lcd.setCursor(0, 1);
  for (int i = 0; i < x; i++) {
    lcd.print("-");  // символ индикации
  }
  t = map(cm, 5, 30, 50, 500);
  tone(buz, 2000);
  delay(t);
  noTone(buz);
  delay(t);
}

Терменвокс

Следующий проект это очень необычный музыкальный инструмент – терменвокс!

Терменвокс — одно из уникальных изобретений в истории музыки, принадлежащее советскому инженеру Льву Термену. В 1920 году Термен представил инструмент, к которому не нужно прикасаться для извлечения звуков.

Для превращение парктроника в терменвокс мы должны убрать звуковую задержку и масштабировать частоту звука в зависимости от расстояния.

Особенности ультразвукового дальномера

Обратите внимание: ультразвуковой дальномер - это довольно не точный датчик, и вот почему: излучатель устроен таким образом, что звук распространяется не во все стороны (как это бывает у обычных динамиков), а в узком направлении. На рисунке представлена диаграмма направленности типичного УЗ дальномера.

 

Теперь представим себе следующую ситуацию –
мы хотим найти объект на расстоянии 2 метра от датчика.

Допустим:
– угол распространения волны q равен 50 градусам;
B – это расстояние до искомого объекта;
2A – это фронт волны дошедший до объекта.

Исходя из наших данный найдём ширину фронта волны дошедшей до объекта, обратите внимание, что на нашей схеме мы имеем прямоугольный треугольник, а значит:

tg(y)= \frac{A}{B} \Rightarrow A=B*tg(y)=2*tg(25)=2*0.47 \approx1метр

Получается, что фронт волны в 2А равен примерно 2 метра – это довольно много. Получается, что всё, что будет находится в пределах фронта, сможет отразить волну, т.е. ситуация когда датчик “видит” объект, но при этом физически на него не направлен будет нормальной.


Также следует отметить два серьезных недостатка УЗ дальномера:

1. Поверхности имеющие пористую структуру хорошо поглощают ультразвук, и датчик не может измерить расстояние до них. Например, если мы задумаем измерить расстояние от мультикоптера до поверхности поля с высокой травой, то скорее всего получим очень нечеткие данные. Такие же проблемы нас ждут при измерении дистанции до стены покрытой поролоном.

2. Второй недостаток связан со скоростью звуковой волны. Эта скорость недостаточно высока, чтобы сделать процесс измерения более частым. Допустим, перед роботом есть препятствие на удалении 4 метра. Чтобы звук слетал туда и обратно, потребуется целых 24 мс. Следует 7 раз отмерить, прежде чем ставить УЗ дальномер на летающих роботов.