Парковочным маркером у меня в гараже годами служил теннисный мячик на верёвке от потолка: вкатился — мячик коснулся лобового стекла — стой. Схема работала, пока я не прикатил велосипед с высоким рулём: мячик начал звонить о руль на полметра раньше, чем до стены добирался бампер, а опустить его ниже значило оставить машину без маркера. Прибор, который честно называет «до стены столько-то сантиметров» и не завязан на высоту бампера, стоил на распродаже восемьдесят рублей — дальномер HC-SR04. Первый вечер с ним подарил мне три классические ошибки разом: дальномер без таймаута замирал на целую секунду на каждом обороте loop, печатал dist=0 на честном препятствии в полтора метра и однажды измерил глубину двора сквозь закрытое окно мастерской. В этом уроке собираем правильную версию с первого раза: подключение, арифметика микросекунд в сантиметры, таймаут и разбор всех трёх граблей — чтобы вы их не дарили себе сами.
Как свист превращается в сантиметры
HC-SR04 — коробочка с двумя пьезо-дисками: один свистит, второй слушает. Свист ультразвуковой, около сорока килогерц — ухо его не слышит, а собака моей соседки, по-моему, слышала: начинала беспокоиться, когда датчик работал часто. Выводов четыре: питание 5 вольт, земля и два сигнальных — Trig, которым мы просим свистнуть, и Echo, которым датчик отвечает длительностью импульса. Протокол простой: импульс не меньше десяти микросекунд на Trig — модуль выпускает пачку из восьми ультразвуковых цугов и поднимает Echo; звук летит до препятствия, отражается, возвращается — Echo опускается. Длительность высокого уровня на Echo — время полного рейса звука туда и обратно.
Дальше арифметика, и она честнее любой таблицы. Скорость звука в воздухе — около 343 метров в секунду при двадцати градусах; один сантиметр пути звук проходит за 29,2 микросекунды. Рейс двойной — до препятствия и обратно, — поэтому на сантиметр расстояния приходится 58,3 микросекунды эха, и вся формула выглядит так: dist = t / 58. Число с условием: в моём неотапливаемом гараже в феврале держится минус десять, скорость звука падает до 325 метров в секунду, коэффициент вырастает до 61,5 — без поправки дальномер занижает показания примерно на шесть процентов, а на двух метрах это двенадцать сантиметров, то есть полбампера. Летом в том же гараже под тридцать — коэффициент 56, ошибка в другую сторону. Для «ближе — дальше» это неважно, для честного сантиметра — поправка обязательна.
pulseIn: таймер, который умеет ждать
Мерить длительность импульса руками — читая пин в цикле и считая итерации, — на Uno бессмысленно: одна итерация loop стоит десятки наносекунд, и рассинхрон обеспечен. Для этого у Arduino есть pulseIn: pulseIn(ECHO, HIGH, таймаут) ждёт фронт на пине, засекает, сколько пин продержался в высоком уровне, и возвращает число в микросекундах. Третий аргумент — таймаут в микросекундах — я в первый вечер не написал вовсе, и это была главная ошибка того дня. Без таймаута pulseIn ждёт фронт секунду: ворота гаража были открыты, звук улетал во двор и не возвращался, и каждый оборот loop замирал на полную секунду. Симптом выглядел как поломка платы: вблизи стены замеры шли живо, четыре строки в секунду, а на открытом пространстве — одна строка в секунду, будто Uno задумалась. Диагноз поставил Serial: печать millis() между замерами показала ровно 1000 мс паузы. С тех пор таймаут стоит всегда: 30000 микросекунд с запасом перекрывают паспортные 400 сантиметров — тот потолок даёт 23200 микросекунд эха.
Второй мой прокол того вечера прятался в delay(60): между свистами я оставлял шестьдесят миллисекунд, а эхо в гараже гуляет дольше, отражаясь от стен и полок. Соседние пачки звука накладывались друг на друга, и замеры начинали плавать на десятки сантиметров при неподвижном препятствии. Рабочая пауза, которую я выстрадал подбором: не меньше 25–30 миллисекунд между замерами. Третий прокол — ноль от pulseIn: не услышав эха, функция возвращает 0, и наивное t / 58 честно превращает его в dist=0 — «препятствие вплотную». Гаражный робот с такой логикой тормозил бы у каждой открытой калитки.
const int TRIG = 12;const int ECHO = 11; void setup() { pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT); Serial.begin(9600);} void loop() { digitalWrite(TRIG, LOW); delayMicroseconds(2); digitalWrite(TRIG, HIGH); delayMicroseconds(10); digitalWrite(TRIG, LOW); long t = pulseIn(ECHO, HIGH); // таймаута нет: ждём секунду Serial.print("t="); Serial.println(t); delay(60); // эхо не успевает затухнуть, замеры плавают}Рабочая версия разнимает эти проблемы по одной. Замер вынесен в функцию zamerMks() — приём из урока про свои функции: в скетче ниже она возвращает микросекунды либо ноль, а loop разбирает ответ ветками, обычными if/else из урока про условия. Ноль — это «эха нет», и я печатаю его как dist=-1, чтобы ноль-как-ошибка не путался с нулём-как-«упёрлись»; есть число — делим на 58 и отдаём в монитор пару dist= t=, как учил первый урок про Serial: пара имя=значение и в мониторе читается глазами, и в плоттере ложится рядом именованными рядами. Пауза между замерами — 250 миллисекунд: запас от тех самых 25–30 минимума и комфортный темп для журнала.
Про таблицу ниже — два слова. Она не про паспорт, а про то, что я перепроверяю на реальном модуле: цифры с условиями, при которых они верны. HC-SR04 стоит копейки и копейку же просит внимания: держите его в этих рамках, и он меряет честно годами — мой первый живёт в гараже четвёртый год.
| Параметр | Число | При каком условии верно |
|---|---|---|
| Диапазон замера | 2–400 см | твёрдая мишень перпендикулярно лучу, воздух без пара и пыли |
| Коэффициент | 58 мкс на сантиметр | 20 градусов Цельсия; в минус десять — 61,5, в жару тридцать — 56 |
| Trig-импульс | 10 мкс и не короче | короче модуль не засчитывает: свист просто не запускается |
| Таймаут pulseIn | 30000 мкс | потолок 400 см даёт 23200 мкс эха плюс запас на затухание |
| Пауза между замерами | не меньше 25–30 мс | эхо предыдущего свиста успевает затухнуть; в гараже я ставлю 250 |
| Ширина луча | конус около 15 градусов | маленькая плоская мишень ловится в лоб, косая стена — мимо |
Практика: дальномер в симуляторе
Практика — четыре опыта, и каждый о границе. Подвиньте препятствие вплотную, на два сантиметра: журнал покажет dist=2 t=116, и в симуляторе это выглядит как честный замер. Реальный модуль здесь слеп — приёмник глушится собственным свистом, — поэтому помнить о двух сантиметрах надо до монтажа датчика в корпус, а не после. Отодвиньте мишень на четыреста: t=23200, и арифметика сходится на глазах, 23200 / 58 = 400. Теперь исправьте таймаут 30000 на 15000: потолок упадёт до 258 сантиметров, и дальномер рапортует dist=-1 на честном препятствии — таймаут режет потолок раньше паспорта.
Третий опыт — про температуру: замените делитель 58 на 61,5, февральскую поправку, и при том же препятствии показания просядут на пять-шесть процентов — цена одного числа в формуле, а не нового модуля. Четвёртый — на живость: устройте качели слайдером от десяти до трёхсот сантиметров и обратно, и пары dist= в плоттере Serial нарисуют лесенку синхронно с рукой. Дальше дальномер просится с проводов в корпус, и разводка его четырёх проводов на настоящей плате — та же дисциплина, что в уроке про светодиод на макетке: питание от рельсы, земля к земле, сигнальные — по подписанным рядам, без веры в цвет изоляции.
TRIG и эхо вживую: слепая зона 2 см, конус ±15°, таймаут pulseIn и ткань, глушащая отражение.
Подгружаем интерактив…
- Trig — 10-микросекундный импульс на запуск свиста, Echo — длительность эха в микросекундах; расстояние = t / 58 при двадцати градусах, в холоде коэффициент растёт до 61–62.
- pulseIn пишем всегда с таймаутом: 30000 мкс перекрывает паспортные 400 сантиметров; без таймаута функция ждёт фронт секунду, и loop замирает на открытом пространстве.
- t = 0 — это «эха нет», а не «препятствие вплотную»: печатайте dist=-1 и разбирайте поверхность и угол мишени, а не тормозите колёсами.
- Мягкое глушит звук, косое отражает мимо приёмника, стекло наискось работает зеркалом: перед дальномером — воздух и твёрдая перпендикулярная стена.
- Пауза между замерами не меньше 25–30 мс: незатухшее эхо от предыдущего свиста портит следующий замер, а в журнале это выглядит как плавающие цифры.
- 1. Дальномер на открытом пространстве печатает одну строку в секунду вместо четырёх. Самая вероятная причина?
- 2. pulseIn вернул t = 1740 микросекунд. Что напечатает скетч со строкой dist = t / 58?
- 3. Куртка на крючке в полутора метрах — дальномер упорно печатает dist=-1. Что произошло?
- 4. Зачем между свистами оставлять паузу хотя бы 25–30 миллисекунд?
Частые вопросы по теме урока
- Дальномер на открытом пространстве печатает одну строку в секунду вместо четырёх. Самая вероятная причина?
- Без третьего аргумента pulseIn ждёт фронт секунду: звук ушёл и не вернулся — функция висит весь этот второй. Таймаут 30000 мкс закрывает вопрос и заодно режет мёртвый потолок диапазона. pulseIn без таймаута ждёт фронт эха полную секунду, когда эха нет
- pulseIn вернул t = 1740 микросекунд. Что напечатает скетч со строкой dist = t / 58?
- 58,3 микросекунды на сантиметр — это уже с учётом пути туда и обратно: 1740 / 58 = 30. Пополам делить не нужно — популярное заблуждение после чтения формулы скорости звука. 30 сантиметров — деление на 58 уже учитывает двойной рейс звука
- Куртка на крючке в полутора метрах — дальномер упорно печатает dist=-1. Что произошло?
- Войлок, пух и поролон глушат сорокакилогерцовый свист почти полностью: отражаться просто не от чего. Направьте датчик на жёсткую стену рядом — замер вернётся, и диагноз будет честным. Мягкая ткань поглотила ультразвук — эхо не родилось, pulseIn упёрся в таймаут
- Зачем между свистами оставлять паузу хотя бы 25–30 миллисекунд?
- Звук живёт в комнате дольше прямого рейса: стенки и полки дают вторичное эхо, и приёмник ловит чужой хвост вместо своего свиста. Симптом — плавающие цифры при неподвижной мишени. Эхо предыдущего свиста не успевает затухнуть и портит следующий замер