Измеряем расстояние с помощью HC-SR04
Как HC-SR04 превращает эхо в сантиметры: запускающий импульс и импульс эха, почему в формуле деление на 58, тайм-аут pulseIn(), что сбивает датчик с толку и медианный фильтр.
Главное
- Импульс на ECHO длится столько, сколько звук летит до предмета и обратно.
- Расстояние в см = длительность импульса в микросекундах / 58, если скорость звука около 343 м/с (20 °C).
- pulseIn() с тайм-аутом 25 мс возвращает 0, если эхо не вернулось; скетч превращает это в 400.
- Между замерами ждите около 60 мс, чтобы старое эхо успело затихнуть.
- Медиана трёх замеров убирает одно неверное значение.
HC-SR04 измеряет расстояние звуком. Он посылает короткий ультразвуковой сигнал и измеряет, через сколько времени вернётся эхо. Чем дальше предмет, тем дольше приходится ждать.
Как эхо превращается в расстояние
При 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 +5V | Uno 5V | Стабилизатор драйвера питает Uno: около 4,4 В |
| L298N GND | Uno GND | Общая земля: сигналам нужен общий 0 В |
| Uno D5 | L298N ENA | Скорость левого мотора (ШИМ); перемычка ENA снята |
| Uno D4 | L298N IN1 | Направление левого мотора |
| Uno D7 | L298N IN2 | Направление левого мотора |
| Uno D8 | L298N IN3 | Направление правого мотора |
| Uno D12 | L298N IN4 | Направление правого мотора |
| Uno D6 | L298N ENB | Скорость правого мотора (ШИМ); перемычка ENB снята |
| Левый мотор M+ | L298N OUT1 | |
| Левый мотор M- | L298N OUT2 | |
| Правый мотор M+ | L298N OUT4 | Переставлено специально: этот мотор стоит зеркально |
| Правый мотор M- | L298N OUT3 | Переставлено специально: этот мотор стоит зеркально |
| HC-SR04 VCC | L298N +5V | 5 В для датчика |
| HC-SR04 GND | Uno GND | Земля |
| Uno D9 | HC-SR04 TRIG | Запускает измерение: импульс 10 мкс |
| Uno D10 | HC-SR04 ECHO | Импульс длиной во время полёта эха туда и обратно |
Arduino Uno R3Есть в симуляторе× 1Цену смотрите в магазине
Модуль драйвера моторов L298NЕсть в симуляторе× 1Цену смотрите в магазине
Мотор-редуктор TT 1:48 с колесом 65 ммЕсть в симуляторе× 2Цену смотрите в магазине
Батарейный отсек 4 × AAЕсть в симуляторе× 1Цену смотрите в магазинеШаровая опора 20 ммЕсть в симуляторе× 1Цену смотрите в магазине
Ультразвуковой датчик HC-SR04Есть в симуляторе× 1Цену смотрите в магазине
Набор проводов-перемычек× 1Цену смотрите в магазине
Одно измерение шаг за шагом
- Uno выставляет на TRIG LOW на 2 микросекунды (мкс), чтобы импульс начался с чистого LOW.
- Затем выставляет на TRIG HIGH на 10 мкс и снова LOW. Этот запускающий импульс означает «измеряй сейчас».
- Датчик посылает пачку из восьми импульсов звука частотой 40 кГц.
- Как только звук ушёл, датчик выставляет на ECHO HIGH.
- Когда эхо возвращается, датчик выставляет на ECHO LOW.
- Длительность импульса HIGH на ECHO — это время, за которое звук долетел до предмета и вернулся.
Скетч
// 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 на число чуть больше расстояния на экране и проверьте, что светодиод загорается.
Подсказка
Сначала сохраните медиану в переменной, чтобы её можно было и вывести, и сравнить.