HC-SR04: ультразвуковой датчик расстояния для Arduino и ESP32

HC-SR04: ультразвуковой датчик расстояния для Arduino и ESP32

Полное руководство по HC-SR04: принцип работы ультразвукового датчика расстояния, распиновка, подключение к Arduino и ESP32, делитель уровня для Echo, примеры кода, фильтрация и поиск ошибок.

HC-SR04 — дешёвый и очень распространённый ультразвуковой дальномер. Его ставят в роботов, автоматические крышки мусорных баков, сигнализацию парковки, бесконтактные дозаторы, измерители уровня воды и проекты, где нужно понять: «что-то передо мной есть?» или «на каком расстоянии находится объект?»

Модуль не умеет видеть цвет и почти не интересуется освещением: ему всё равно, темно в комнате или ярко. Зато он зависит от формы и материала объекта, угла поверхности, температуры воздуха и акустического шума. Это важно: HC-SR04 — не лазерная рулетка. Он хорош для грубой оценки расстояния в понятной геометрии, но не для точного измерения любого предмета в любых условиях.

Что такое HC-SR04

На лицевой стороне модуля находятся две круглые «банки», похожие на глаза:

  • один излучатель отправляет ультразвук
  • второй принимает отражённый сигнал

Ультразвук — звук с частотой выше диапазона человеческого слуха. HC-SR04 работает на частоте около 40 кГц, поэтому писк модуля мы не слышим. После запуска он посылает восемь периодов колебаний, ждёт отражение и сообщает результат длительностью импульса на выводе Echo.

Паспортные характеристики классического HC-SR04 обычно выглядят так:

ПараметрЗначение
Питание5 В DC, типично 15 мА
Частота ультразвука40 кГц
Диапазон по паспорту2–400 см
Заявленная точностьДо ±3 мм в хороших условиях
Угол измеренияОколо 15°
Запуск измеренияИмпульс HIGH на Trig не менее 10 мкс
ВыходИмпульс 5 В TTL на Echo, длина пропорциональна расстоянию
Интервал между измерениямиНе меньше примерно 60 мс

Цифры из таблицы — ориентир, а не обещание для любой ситуации. В реальном проекте наиболее стабильная зона обычно заметно уже паспортных 2–400 см. Маленькие, мягкие, наклонные, пористые или неровные предметы отражают звук плохо; поверхность воды, ткань, ковёр и узкая ножка стула могут давать пропуски или скачки.

Как HC-SR04 измеряет расстояние

Рабочий цикл состоит из пяти шагов:

  1. Контроллер подаёт на Trig высокий уровень минимум на 10 мкс.
  2. HC-SR04 отправляет восемь периодов ультразвука частотой 40 кГц.
  3. Ультразвук летит до объекта.
  4. Отражённая волна возвращается к приёмнику модуля.
  5. На Echo появляется HIGH-импульс: его длительность равна времени пути звука туда и обратно.

Скорость звука в воздухе при 20 °C примерно равна 343 м/с, то есть 0,0343 см/мкс. Но звук проходит расстояние дважды, поэтому формула выглядит так:

D = (t · v) / 2

где:

  • D — расстояние до объекта
  • t — длительность HIGH-импульса Echo в микросекундах
  • v — скорость звука

Для обычных условий формулу удобно свернуть:

Расстояние (см) ≈ время Echo (мкс) / 58

Например, если Echo держится HIGH 1450 мкс:

1450 / 58 = 25 см

Деление на два уже «спрятано» в числе 58. Если разделить время на 29, получится полный путь звука туда и обратно, то есть расстояние станет вдвое больше правильного.

Где формула начинает врать

Скорость звука зависит прежде всего от температуры воздуха. Приближённая формула:

v = 331,3 + 0,606 · T

где v — скорость в м/с, а T — температура в °C. При 0 °C звук летит около 331 м/с, при 30 °C — около 349 м/с. На дистанции 1–2 метра эта ошибка редко важна для бытового робота, но при измерении уровня или длинного расстояния она уже заметна.

Влажность и давление тоже влияют, но обычно гораздо слабее температуры. Для аккуратного измерителя можно подключить BME280, SHT31, DS18B20 и пересчитывать скорость звука по текущей температуре. Для простой автоматики достаточно константы 0,0343 см/мкс.

Распиновка HC-SR04

Если держать модуль «глазами» к себе, а контактами вниз, у классической версии выводы обычно расположены слева направо так:

VCC | Trig | Echo | GND
КонтактНазначениеЧто подключать
VCCПитание модуля5 В
TrigВход запуска измеренияЛюбой цифровой выход контроллера
EchoВыход длительности эхаЦифровой вход; для ESP32 через делитель уровня
GNDОбщий проводGND контроллера

Не путайте Trig и Echo. Если поменять их местами, модуль не сломается, но измерений не будет.

Подключение к Arduino Uno и Nano

Arduino Uno и классический Arduino Nano работают с логическими уровнями 5 В, поэтому HC-SR04 подключается прямо, без дополнительных деталей.

HC-SR04Arduino Uno / Nano
VCC5V
GNDGND
TrigD9
EchoD10


Пины D9 и D10 выбраны только для примера. Можно использовать другие свободные цифровые пины, если изменить номера в скетче.

Первый скетч для Arduino

Этот пример запускает измерение раз в 100 мс, ждёт ответ максимум 30 мс и печатает расстояние в Serial Monitor.

constexpr byte TRIG_PIN = 9;
constexpr byte ECHO_PIN = 10;

float readDistanceCm() {
  // Гарантируем короткий LOW перед запуском.
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);

  // Импульс запуска: минимум 10 мкс.
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // 30 000 мкс — тайм-аут примерно для дистанции до 5 м туда-обратно.
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);

  if (duration == 0) {
    return -1.0;  // Эхо не пришло: объект далеко, плохое отражение или ошибка подключения.
  }

  return duration * 0.0343f / 2.0f;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void loop() {
  float distance = readDistanceCm();

  if (distance < 0) {
    Serial.println("Нет эха");
  } else {
    Serial.print("Расстояние: ");
    Serial.print(distance, 1);
    Serial.println(" см");
  }

  delay(100);
}

Откройте Tools → Serial Monitor и выберите скорость 115200 бод. Если всё подключено верно, увидите значения вроде:

Расстояние: 37.6 см
Расстояние: 37.4 см
Расстояние: 37.7 см

Подключение к ESP32: главное правило про Echo

ESP32 работает с логикой 3,3 В. Классический HC-SR04 питается от 5 В и выдаёт на выводе Echo высокий уровень, близкий к 5 В. Подать эти 5 В непосредственно на GPIO ESP32 нельзя: входы ESP32 не являются 5-вольтовыми tolerant-входами, и такой эксперимент может повредить микроконтроллер.

При этом сигнал Trig можно подать с GPIO ESP32 напрямую: уровень 3,3 В обычно распознаётся входом Trig как логическая единица. А вот Echo нужно понизить до безопасного уровня.

Делитель напряжения для Echo

Самый простой вариант — резистивный делитель:

HC-SR04 Echo ----[ 1 кОм ]----+---- GPIO19 ESP32
                              |
                            [ 2 кОм ]
                              |
                             GND

При 5 В на входе такого делителя на GPIO получится:

5 В × 2 кОм / (1 кОм + 2 кОм) ≈ 3,33 В

Это безопасный уровень для ESP32. Номиналы не обязаны быть именно 1 кОм и 2 кОм: подойдут, например, 4,7 кОм + 10 кОм или 10 кОм + 20 кОм. Главное — сохранить отношение нижнего резистора к верхнему примерно 2:1 и не делать цепь слишком высокоомной без необходимости.

Нельзя надеяться на «встроенную защиту» GPIO. Она не предназначена для постоянной подачи 5 В. Делитель стоит копейки и защищает ESP32 от очень неприятной ошибки.

Схема HC-SR04 + ESP32

Ниже — безопасное подключение для ESP32 DevKit.

HC-SR04ESP32 DevKitПримечание
VCC5V / VINКлассическому HC-SR04 обычно нужно 5 В
GNDGNDЗемля должна быть общей
TrigGPIO18Можно напрямую
EchoGPIO19 через делительНельзя подключать 5 В прямо к GPIO
HC-SR04                            ESP32 DevKit
-------                            -----------
VCC     -------------------------- 5V / VIN
GND     -------------------------- GND
Trig    -------------------------- GPIO18
Echo    ----[1 кОм]----+---------- GPIO19
                       |
                     [2 кОм]
                       |
GND     --------------+---------- GND

Для первого проекта выбирайте GPIO, которые не мешают загрузке ESP32. GPIO18 и GPIO19 подходят для большинства DevKit-плат.

Код для ESP32 в Arduino IDE

Создайте новый скетч, выберите свою ESP32-плату в меню Tools → Board, подключите модуль по схеме выше и загрузите код:

constexpr int TRIG_PIN = 18;
constexpr int ECHO_PIN = 19;

float readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);

  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // Тайм-аут не даёт зависнуть, если Echo не вернулся.
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);

  if (duration == 0) {
    return -1.0;
  }

  return duration * 0.0343f / 2.0f;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void loop() {
  float distance = readDistanceCm();

  if (distance < 0) {
    Serial.println("Нет эха: объект вне диапазона или сигнал не отразился");
  } else {
    Serial.printf("Расстояние: %.1f см\n", distance);
  }

  // HC-SR04 нужен интервал между циклами; 60 мс — безопасный минимум.
  delay(100);
}

Код намеренно использует pulseIn() с тайм-аутом. Без третьего параметра функция может ждать слишком долго, если объект не отражает звук, датчик отключён или провод Echo выпал из макетки. Для простых проектов pulseIn() удобно и достаточно понятно; в роботе с несколькими датчиками, Wi-Fi, дисплеем и другой параллельной логикой лучше перейти на неблокирующее измерение по прерыванию или на отдельную задачу FreeRTOS.

Мини-проект: парковочный индикатор с LED и зуммером

Идея проста: чем ближе препятствие, тем чаще пищит зуммер. Подходит для макета парковочного радара, робота или проверки работы датчика.

Подключение к Arduino

  • HC-SR04: как в первой схеме (Trig D9, Echo D10)
  • Пьезо-зуммер: + к D6, к GND
  • Светодиод: анод через резистор 220–1000 Ом к D5, катод к GND
constexpr byte TRIG_PIN = 9;
constexpr byte ECHO_PIN = 10;
constexpr byte LED_PIN = 5;
constexpr byte BUZZER_PIN = 6;

float readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);
  if (duration == 0) return -1.0;

  return duration * 0.0343f / 2.0f;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
}

void loop() {
  float distance = readDistanceCm();

  if (distance < 0 || distance > 150) {
    digitalWrite(LED_PIN, LOW);
    noTone(BUZZER_PIN);
    delay(100);
    return;
  }

  if (distance < 15) {
    digitalWrite(LED_PIN, HIGH);
    tone(BUZZER_PIN, 2200);
    delay(80);
    return;
  }

  digitalWrite(LED_PIN, HIGH);

  // Чем ближе объект, тем меньше пауза между короткими сигналами.
  int pauseMs = map((int)distance, 15, 150, 100, 900);
  pauseMs = constrain(pauseMs, 100, 900);

  tone(BUZZER_PIN, 2200, 50);
  delay(pauseMs);
}

Не подключайте электромагнитный зуммер, мотор или реле напрямую к пину Arduino/ESP32: для нагрузки с заметным током нужен транзисторный ключ. Почему это важно, разобрано в .

Как сделать показания стабильнее

Один замер HC-SR04 может случайно дать неверный результат. Причины обычные: отражение пришло от края предмета, рядом проехал другой робот с таким же датчиком, человек слегка повернулся, или провод в макетке имеет плохой контакт. Для автоматики лучше фильтровать измерения.

Вариант 1. Среднее из нескольких измерений

Подходит, если помех мало, а объект неподвижен или движется медленно.

float readAverageDistanceCm(byte count = 5) {
  float sum = 0;
  byte valid = 0;

  for (byte i = 0; i < count; i++) {
    float value = readDistanceCm();

    if (value > 0) {
      sum += value;
      valid++;
    }

    delay(60);
  }

  return valid ? sum / valid : -1.0;
}

Недостаток среднего: один очень плохой выброс заметно тянет результат вверх или вниз.

Вариант 2. Медиана из пяти измерений

Для HC-SR04 обычно лучше медиана: она отбрасывает одиночные скачки. Пример без библиотек:

float readMedianDistanceCm() {
  const byte N = 5;
  float values[N];
  byte valid = 0;

  for (byte i = 0; i < N; i++) {
    float value = readDistanceCm();

    if (value > 0) {
      values[valid++] = value;
    }

    delay(60);
  }

  if (valid == 0) return -1.0;

  for (byte i = 0; i < valid - 1; i++) {
    for (byte j = i + 1; j < valid; j++) {
      if (values[j] < values[i]) {
        float temp = values[i];
        values[i] = values[j];
        values[j] = temp;
      }
    }
  }

  return values[valid / 2];
}

Медиана из пяти значений немного медленнее, зато хорошо переживает один-два случайных неверных импульса.

Несколько HC-SR04 в одном проекте

Два ультразвуковых датчика могут слышать не только своё эхо, но и сигнал соседа. Это называется перекрёстной помехой. Если запустить оба модуля одновременно, один из них может измерить расстояние до объекта, который «увидел» другой, или вообще получить случайную цифру.

Правило простое: запускайте датчики по очереди и выдерживайте минимум 60 мс после каждого измерения. Для двух датчиков это может выглядеть так:

Датчик 1: Trig → ждём Echo → пауза 60 мс
Датчик 2: Trig → ждём Echo → пауза 60 мс
Повторяем

Также помогает механически разнести датчики, слегка направить их в разные стороны и поставить между ними перегородку из мягкого материала, если конструкция позволяет.

Почему HC-SR04 иногда не подходит

Ультразвук — хороший инструмент, но не универсальный. Рассмотрите другой датчик, если:

  • нужно измерять расстояние меньше 2 см
  • объект очень маленький, мягкий, тонкий или сильно наклонён
  • нужно измерять через стекло, герметичный корпус или воду
  • датчик будет работать на улице под дождём и пылью — обычный HC-SR04 не герметичен
  • нужен узкий луч, высокая повторяемость или надёжность на чёрных/сложных предметах
  • несколько датчиков должны опрашиваться очень быстро
  • нужна низкая потребляемая мощность от батареи

В зависимости от задачи подойдут:

ЗадачаЧто рассмотреть вместо HC-SR04
Компактное оптическое измерениеToF-датчик VL53L0X / VL53L1X
Работа снаружи и измерение уровняГерметичный ультразвуковой JSN-SR04T
Очень близкие расстоянияИнфракрасный ToF или аналоговый Sharp
Длинная дистанция и более серьёзная задачаЛидар или промышленный ультразвуковой датчик

У каждого варианта есть собственные ограничения: ToF может по-разному реагировать на очень тёмные и отражающие поверхности, а герметичные ультразвуковые датчики часто имеют большую слепую зону. Выбор делают по реальной среде, а не только по максимальной дальности из карточки товара.

Частые ошибки и диагностика

Всегда 0 см или Нет эха

Проверьте:

  • Питание: классический HC-SR04 ждёт 5 В, а не 3,3 В.
  • Общую землю: GND модуля и GND контроллера обязательно должны быть соединены.
  • Порядок проводов: VCC, Trig, Echo, GND.
  • Правильность номеров пинов в коде.
  • Не перепутаны ли Trig и Echo.
  • Не застрял ли провод в соседнем отверстии макетки.
  • Есть ли перед датчиком твёрдый объект на расстоянии примерно 20–100 см.

На ESP32 всё работало, а потом перестало

Первое, что нужно проверить, — подключён ли Echo через делитель. Прямые 5 В на GPIO могут повредить вход ESP32. Также проверьте питание модуля: с 3,3 В классический HC-SR04 часто работает нестабильно или не работает вовсе.

Значения прыгают

  • Добавьте медианный фильтр или усреднение.
  • Увеличьте интервал между запросами до 60–100 мс.
  • Уберите датчик от вентилятора, струи воздуха и вибрирующей поверхности.
  • Проверьте, что объект достаточно большой и расположен примерно перпендикулярно датчику.
  • Укоротите провода и проверьте контакты.
  • Если рядом несколько HC-SR04, запускайте их строго по очереди.

Датчик видит стену, но не видит ткань или руку под углом

Это нормальная физика. HC-SR04 ждёт, что отражённый звук вернётся именно к его приёмнику. Мягкая ткань поглощает часть энергии, а наклонная гладкая поверхность отражает звук в сторону — почти как зеркало отражает свет. Поверните датчик или объект, увеличьте площадь цели либо выберите другой принцип измерения.

Показания всегда примерно вдвое больше

В формуле забыли деление на два: Echo измеряет путь до объекта и обратно. Используйте:

cm = duration * 0.0343 / 2;

или сокращённый вариант:

cm = duration / 58.0;

Скетч периодически зависает

Добавьте тайм-аут в pulseIn():

unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);

Если Echo не приходит, pulseIn() без тайм-аута блокирует выполнение программы на заметное время. Это особенно неприятно в проектах с кнопками, дисплеем, Wi-Fi или управлением реле.

Практические идеи

  • Робот, объезжающий препятствия. HC-SR04 смотрит вперёд; если расстояние меньше порога, робот останавливается и поворачивает.
  • Уровень воды в баке. Датчик крепится над водой и измеряет расстояние до поверхности. Высота воды = высота бака − измеренное расстояние. Для влажной среды лучше использовать герметичный датчик.
  • Бесконтактная кнопка. Рука подошла ближе 10 см — включаем свет, подаём команду или открываем крышку.
  • Парковочный помощник. LED/зуммер меняяют режим, когда объект приближается.
  • Контроль заполнения контейнера. Измеряем свободное расстояние от крышки до содержимого.

Что запомнить

  • HC-SR04 измеряет время возврата ультразвукового эха и переводит его в расстояние.
  • На Trig подаётся импульс HIGH минимум 10 мкс, Echo выдаёт импульс, длительность которого измеряет контроллер.
  • В сантиметрах: Расстояние = время Echo / 58, потому что звук проходит путь туда и обратно.
  • Паспортный диапазон — 2–400 см, но реальная дальность зависит от предмета, угла, температуры и окружения.
  • Arduino Uno/Nano можно соединять с HC-SR04 напрямую, поскольку оба используют 5-вольтовую логику.
  • Для ESP32 классический HC-SR04 питают от 5 В, Trig подают с GPIO напрямую, а Echo обязательно заводят через делитель до 3,3 В.
  • Между измерениями выдерживают хотя бы 60 мс, а для устойчивого проекта добавляют тайм-аут и фильтрацию.

Проверь себя

  1. Почему расстояние считают как duration / 58, а не duration / 29?
  2. Можно ли соединить Echo HC-SR04 с GPIO ESP32 одним проводом?
  3. Почему датчик иногда не видит мягкую ткань или гладкую поверхность под углом?
Показать ответы
  1. Импульс Echo измеряет время полного пути звука: от датчика до предмета и обратно. Поэтому деление на 58 уже учитывает обратный путь.
  2. Нет. Классический HC-SR04 выдаёт около 5 В на Echo, а GPIO ESP32 рассчитаны на 3,3 В. Нужен делитель напряжения или преобразователь уровня.
  3. Ткань поглощает ультразвук, а наклонная гладкая поверхность отражает его в сторону. Поэтому сигнал возвращается слишком слабым или вообще не приходит к приёмнику.