Урок 04Легко30 минOʻZ · РУ · ENЕсть в симуляторе

Измеряем расстояние с помощью HC-SR04

Как HC-SR04 превращает эхо в сантиметры: запускающий импульс и импульс эха, почему в формуле деление на 58, тайм-аут pulseIn(), что сбивает датчик с толку и медианный фильтр.

Видеона YouTube

Главное

  • Импульс на ECHO длится столько, сколько звук летит до предмета и обратно.
  • Расстояние в см = длительность импульса в микросекундах / 58, если скорость звука около 343 м/с (20 °C).
  • pulseIn() с тайм-аутом 25 мс возвращает 0, если эхо не вернулось; скетч превращает это в 400.
  • Между замерами ждите около 60 мс, чтобы старое эхо успело затихнуть.
  • Медиана трёх замеров убирает одно неверное значение.

HC-SR04 измеряет расстояние звуком. Он посылает короткий ультразвуковой сигнал и измеряет, через сколько времени вернётся эхо. Чем дальше предмет, тем дольше приходится ждать.

Как эхо превращается в расстояние

HC-SR04 посылает звук 40 кГц, стена возвращает эхо, а время t в микросекундах даёт расстояние: d = t / 58 см.

При 20 °C звук летит в воздухе со скоростью около 343 м/с. Когда звук встречает стену, часть его отражается назад. Если знать, сколько времени звук был в пути, можно узнать, какое расстояние он прошёл. Летучие мыши по тому же принципу находят дорогу в темноте.

HC-SR04 использует звук частотой 40 кГц. Человек слышит только примерно до 20 кГц, поэтому вы ничего не услышите. Два круглых «глаза» датчика — это маленький динамик с буквой T (transmitter, передатчик) и микрофон с буквой R (receiver, приёмник).

Четыре пина

Пин Что делает В робот-машине
VCC Питание, 5 В +5V драйвера L298N
TRIG Вход: короткий импульс на нём запускает измерение D9
ECHO Выход: импульс длится столько же, сколько звук в пути D10
GND Земля Пин GND платы Uno

Датчик рассчитан на 5 В. Никогда не подключайте VCC к батарее.

ОткудаКудаЗачем
Батарея +L298N +12VПитание моторов: около 6 В от четырёх батареек AA
Батарея -L298N GNDМинус батареи
L298N +5VUno 5VСтабилизатор драйвера питает Uno: около 4,4 В
L298N GNDUno GNDОбщая земля: сигналам нужен общий 0 В
Uno D5L298N ENAСкорость левого мотора (ШИМ); перемычка ENA снята
Uno D4L298N IN1Направление левого мотора
Uno D7L298N IN2Направление левого мотора
Uno D8L298N IN3Направление правого мотора
Uno D12L298N IN4Направление правого мотора
Uno D6L298N ENBСкорость правого мотора (ШИМ); перемычка ENB снята
Левый мотор M+L298N OUT1
Левый мотор M-L298N OUT2
Правый мотор M+L298N OUT4Переставлено специально: этот мотор стоит зеркально
Правый мотор M-L298N OUT3Переставлено специально: этот мотор стоит зеркально
HC-SR04 VCCL298N +5V5 В для датчика
HC-SR04 GNDUno GNDЗемля
Uno D9HC-SR04 TRIGЗапускает измерение: импульс 10 мкс
Uno D10HC-SR04 ECHOИмпульс длиной во время полёта эха туда и обратно
Детали для урока

Одно измерение шаг за шагом

  1. Uno выставляет на TRIG LOW на 2 микросекунды (мкс), чтобы импульс начался с чистого LOW.
  2. Затем выставляет на TRIG HIGH на 10 мкс и снова LOW. Этот запускающий импульс означает «измеряй сейчас».
  3. Датчик посылает пачку из восьми импульсов звука частотой 40 кГц.
  4. Как только звук ушёл, датчик выставляет на ECHO HIGH.
  5. Когда эхо возвращается, датчик выставляет на ECHO LOW.
  6. Длительность импульса HIGH на ECHO — это время, за которое звук долетел до предмета и вернулся.

Скетч

DistanceMeter.ino
// Distance meter: measures with the HC-SR04 and prints centimetres to the Serial Monitor.
// The median of three readings throws away a single wrong echo.
// HC-SR04: VCC -> 5 V, GND -> GND, TRIG -> D9, ECHO -> D10

const byte TRIG = 9, ECHO = 10;

// Distance in centimetres; 400 when no echo came back (nothing within about 4 m).
int readDistanceCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);            // at least 10 microseconds starts a measurement
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, 25000UL);  // echo pulse length, 0 after 25 ms
  if (us == 0) return 400;
  return us / 58;                      // there and back at 343 m/s: 58 us per cm
}

int median3(int a, int b, int c) {
  if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
  if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
  return c;
}

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  Serial.begin(9600);
}

void loop() {
  int a = readDistanceCm();
  delay(60);                           // let the last echo die away
  int b = readDistanceCm();
  delay(60);
  int c = readDistanceCm();
  Serial.print("Distance: ");
  Serial.print(median3(a, b, c));
  Serial.println(" cm");
  delay(60);
}

readDistanceCm() выполняет шаги 1–6 и переводит импульс в сантиметры. median3() выбирает из трёх замеров средний по величине. loop() измеряет три раза с промежутком 60 мс и выводит средний из трёх результатов. setup() делает TRIG выходом, а ECHO — входом и запускает Serial. delay(60) в конце loop() даёт такую же паузу между последним замером одного прохода и первым замером следующего. Дальше разобрано каждое число.

Почему делим на 58?

При скорости 343 м/с звук за одну микросекунду пролетает 0,0343 см. Значит, на один сантиметр уходит около 29 мкс. Но звук должен пролететь туда и обратно: 2 см пути на каждый сантиметр расстояния. Поэтому каждый сантиметр расстояния добавляет к импульсу ECHO около 58 мкс:

расстояние в см = импульс ECHO в мкс / 58

Импульс ECHO Расстояние
580 мкс 10 см
1160 мкс 20 см
5800 мкс 100 см
23 200 мкс (около 23 мс) 400 см, предел датчика

В скетче us / 58 делит целые числа, поэтому и результат получается целым. Дробная часть отбрасывается: 1199 / 58 даёт 20, а не 20,7. Для робота шаг в 1 см — достаточная точность.

pulseIn() и тайм-аут 25 мс

pulseIn(ECHO, HIGH, 25000UL) ждёт, когда на ECHO появится HIGH, измеряет, сколько он держится, и возвращает это время в микросекундах. UL помечает 25000 как unsigned long — именно такого типа тайм-аут.

Третье число — это тайм-аут: 25 000 мкс, или 25 мс. Если импульс к этому времени не закончился, pulseIn() перестаёт ждать и возвращает 0. Почему 25 мс? Эхо от предмета в 4 м, на пределе датчика, возвращается примерно за 23 мс. Значит, 25 мс хватает на любое эхо в пределах дальности датчика, да ещё с небольшим запасом. Без тайм-аута pulseIn() ждёт до одной секунды, и всё это время скетч больше ничего не делает.

Даже с тайм-аутом pulseIn() заставляет скетч ждать, пока летит звук: около 6 мс для стены в 1 м и все 25 мс, если эхо не пришло. Для робота такие короткие паузы допустимы. А вот долгие ожидания — это проблема, как показывает урок Программы, которые никогда не ждут.

Если в пределах примерно 4 м ничего нет, никакое эхо не закончит импульс вовремя, и pulseIn() вернёт 0. Скетч превращает это в 400:

if (us == 0) return 400;
return us / 58;

400 означает «в пределах примерно 4 м ничего нет». Это число специально выбрано большим: тогда проверка вроде distanceCm < 25 понимает «эха нет» как «путь свободен».

Зачем ждать 60 мс между замерами?

После замера звук ещё какое-то время мечется по комнате, отражаясь от стен. Если следующий замер начнётся слишком рано, может прийти старое эхо от дальней стены — и оно будет выглядеть как новое, от близкого предмета. Поэтому скетч ждёт между замерами 60 мс. За 60 мс звук пролетает около 20 м. К этому времени старое эхо уже слишком слабое, чтобы обмануть датчик.

Медиана трёх замеров

Иногда один замер просто неверный. Эхо теряется, и вы получаете 400. Или приходит старое эхо, и значение получается слишком маленьким. Одно неверное число может заставить робота повернуть без всякой причины.

Поэтому скетч измеряет три раза с промежутком 60 мс и оставляет среднее по величине значение — медиану. median3() сначала проверяет, лежит ли b между a и c (в любом порядке). Если да, b и есть медиана. Если нет, так же проверяется a. Если ни одно из них не посередине, значит, посередине c.

Замеры По порядку Медиана Среднее арифметическое
52, 51, 53 51, 52, 53 52 52
52, 400, 51 51, 52, 400 52 около 168

Одно неверное значение не пройдёт, если два других согласуются между собой. А среднее арифметическое оно портит. Расплата — скорость: на каждый выведенный результат нужны три замера, поэтому новая строка появляется только четыре-пять раз в секунду, а не примерно шестнадцать.

Чего датчик не видит

  • Слишком близко или слишком далеко. Датчик измеряет примерно от 2 см до 4 м. Ближе 2 см показания неверные.
  • Сбоку от робота. Датчик слушает в узком конусе шириной около 15 градусов. Он видит то, что впереди, а не то, что сбоку от робота. Если в конусе два предмета, вы получите расстояние до ближнего.
  • Мягкие поверхности. Шторы, диваны, поролон и одежда поглощают звук. Эхо слабое или его нет совсем.
  • Наклонные поверхности. Стена, которую звук встречает под острым углом, отражает его в сторону, как зеркало отражает свет. Вы получите 400 или слишком большое расстояние, если звук вернётся, отразившись от другой стены. Датчик в симуляторе тоже не замечает стену, которую встречает под острым углом.
  • Маленькие или тонкие предметы. Ручка или ножка стула отражают слишком мало звука, и датчик может их пропустить.

Температура

Скорость звука меняется примерно на 0,6 м/с на каждый градус Цельсия. Число 58 рассчитано примерно на 20 °C. Вот что покажет скетч для стены ровно в 1 м:

Температура воздуха Скорость звука Скетч покажет около
0 °C около 331 м/с 104 см
20 °C около 343 м/с 100 см
35 °C около 352 м/с 98 см

Для робота, который объезжает стены, несколько сантиметров не важны. А для измерительного прибора, которым пользуются и зимой, и летом, — важны.

Читаем монитор порта на арене

Serial.begin(9600) в setup() открывает последовательную связь на скорости 9600 бод. Затем каждый проход loop() выводит одну строку, например Distance: 57 cm.

Запустите робота на арене и откройте монитор порта (Serial Monitor). Что вы увидите:

  • Новая строка появляется четыре-пять раз в секунду.
  • Число — это расстояние до того, что находится перед датчиком.
  • Если в пределах примерно 4 м ничего нет, в строке будет Distance: 400 cm.
  • Робот стоит на месте, потому что этот скетч вообще не включает моторы.

Расстояние показывает и панель показаний арены в реальном времени, так что по ней можно проверить числа скетча. Если в каждой строке 400, хотя стена близко, проверьте подключение. Датчик без питания или с отошедшим проводом TRIG или ECHO никогда не отвечает. На настоящем роботе откройте монитор порта в Arduino IDE и выберите 9600 бод — то же число, что и в Serial.begin().

Что дальше

Робот, объезжающий препятствия, вызывает readDistanceCm() прямо на ходу. Как он одновременно измеряет и едет, показывает урок Программы, которые никогда не ждут. А робот, который держит дистанцию, с помощью той же функции следует за вашей рукой.

Проверьте себя

1Импульс на ECHO длится 1740 микросекунд. Как далеко находится предмет?

1740 / 58 = 30, значит, предмет находится примерно в 30 см.

2Зачем скетч задаёт для pulseIn() тайм-аут 25 мс?

Эхо от предмета в 4 м, на пределе датчика, возвращается примерно за 23 мс. Без тайм-аута pulseIn() могла бы ждать до одной секунды, и скетч не мог бы делать ничего другого.

3Три замера: 48, 400 и 47. Что вернёт median3()?

Она вернёт 48 — среднее по величине значение, поэтому потерянное эхо (400) не учитывается. Среднее арифметическое было бы 165.

Задание

Сделайте так, чтобы светодиод L платы Uno (D13) загорался, когда измеренное расстояние меньше 50 см, и гас в остальных случаях. Запустите скетч на арене и сравните светодиод с монитором порта (Serial Monitor). Если так близко к роботу ничего нет, замените 50 на число чуть больше расстояния на экране и проверьте, что светодиод загорается.

Подсказка

Сначала сохраните медиану в переменной, чтобы её можно было и вывести, и сравнить.