Мой первый радар был сборкой всего, что я тогда любил: серво, дальномер, зелёная развёртка на экране ноутбука — выглядело космически и меряло ерунду. Половина лучей возвращала нули, соседние углы показывали одну и ту же дистанцию, а стена из книги выглядела на карте как горб в случайном месте. Я переписал обработку трижды, прежде чем записал экран на видео в slow motion и увидел: рупор ещё доезжает до луча, а скетч уже дёргает триг. Я мерил на ходу — вся остальная программа была честной.
Физика здесь простая и беспощадная. Серво ведёт рупор с дальномером по сектору от 30 до 150 градусов шагом в десять — тринадцать лучей. Конус HC-SR04 узкий, градусов пятнадцать, поэтому соседние лучи перекрываются лишь краями — карта имеет право на детали. Измерять эхо надо стоя: разворот, пауза на затухание вибраций, серия замеров, медиана из трёх — и только потом печать и следующий луч.
Соберём радар заново, по-взрослому: автомат из двух этапов на millis, на каждом луче медиана из трёх замеров, строка для Serial Plotter и карта из решёток, которую читают прямо в мониторе. Бонус — направление на ближайший объект, готовая логика для парктроника.
Луч: остановился, дождался, измерил
Сколько ждать после write? SG90 проходит 60 градусов за около 0,1 секунды — арифметика из урока про скорость серво, — значит, шаг в десять градусов механика проходит за десятки миллисекунд. Но рог с приделанным к нему дальномером — консоль, и после остановки она ещё сотню-другую миллисекунд мелко колеблется; эхо с колеблющегося рупора гуляет на сантиметры. Мой стандарт — 300 мс на промежуточный луч: запас трёхкратный, темп сканирования всё ещё бодрый. На краях сектора серво разгоняется против направления и вибрирует дольше — там даю полсекунды.
Серия из трёх замеров с паузой 40 мс закрывает вторую болячку — одиночные выбросы. Пылинка на излучателе, край дверного косяка на границе конуса, отражение от стойки крепления — любой из этих гостей может испортить одно чтение из десяти. Среднее от такого выброса уезжает на несколько сантиметров, медиана его просто игнорирует: две честные замера перекрывают одну чушь. Тот же приём я разбирал в уроке про бак со стрелкой, только там медиана во времени, здесь — по серии на луче.
Паузу между замерами серии я держу 40 мс не по вкусу: хвост дальнего эха ползёт обратно до самого таймаута в 25 мс, и триг, отправленный раньше, ловит чужую посылку — в серии всплывает дистанция соседнего луча. Ниже тридцати миллисекунд между замерами не опускаюсь, выше — разницы не вижу.
Пустое направление — отдельный случай. pulseIn с таймаутом возвращает ноль, когда эха нет вообще: луч ушёл в окно или в коридор длиннее четырёх метров. Ноль — это не нуль сантиметров, а «дальше горизонта»; правило из шлагбаумного урока работает и здесь: ноль превращаем в прочерк на карте, а не в объект вплотную к радару.
У скорости звука есть паспортная зависимость, о которой вспоминают зимой: 343 метра в секунду верны при двадцати градусах, дальше — примерно 0,6 метра в секунду на градус. Делитель 58 в скетче — та самая комнатная скорость; в холодном подвале при плюс пяти честнее 60, и на метре разница — два-три сантиметра в минус. Для картографии комнаты это косметика, но радару, меряющему сугробы на улице, поправку я вписываю константой.
#include <Servo.h> Servo rypor; // рупор с дальномером, D11 const int PIN_TRIG = 2;const int PIN_ECHO = 3; const int UGOL_MIN = 30; // левый край сектораconst int UGOL_MAX = 150; // правый крайconst int SHAG = 10; // 13 лучей на секторconst unsigned long T_DOEZD = 300; // пауза на лучеconst unsigned long T_KRAJ = 500; // пауза на краю сектораconst unsigned long T_MEZH = 40; // между замерами серии int ugol = UGOL_MIN;int napr = 1; // 1 — вправо, -1 — влевоint etap = 0; // 0 — доезжаем, 1 — серияint nSer = 0;int d1 = 0, d2 = 0, d3 = 0;int minD = 999;int minUgol = 0;int svip = 1; unsigned long tEtap = 0;unsigned long tM = 0; int zamer() { digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2); digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG, LOW); unsigned long us = pulseIn(PIN_ECHO, HIGH, 25000); if (us == 0) return -1; // эха нет — это НЕ нуль см return us / 58;} int median3(int a, int b, int c) { int t; if (a > b) { t = a; a = b; b = t; } if (b > c) { t = b; b = c; c = t; } if (a > b) { t = a; a = b; b = t; } return b;} void setup() { rypor.attach(11); rypor.write(UGOL_MIN); pinMode(PIN_TRIG, OUTPUT); pinMode(PIN_ECHO, INPUT); Serial.begin(9600); Serial.println("Radar: 30-150, shag 10");} void loop() { unsigned long now = millis(); if (etap == 0) { // рупор доезжает до луча unsigned long pauza = T_DOEZD; if (ugol == UGOL_MIN || ugol == UGOL_MAX) pauza = T_KRAJ; if (now - tEtap >= pauza) { etap = 1; nSer = 0; tM = now; } } else { // серия из трёх замеров if (nSer < 3 && now - tM >= T_MEZH) { tM = now; int d = zamer(); if (nSer == 0) d1 = d; else if (nSer == 1) d2 = d; else d3 = d; nSer = nSer + 1; } if (nSer >= 3) { int dist = median3(d1, d2, d3); // канал для Serial Plotter: угол и дистанция Serial.print(ugol); Serial.print(" "); Serial.println(dist); // строка карты: одна клетка — 10 сантиметров Serial.print(ugol); Serial.print(" | "); if (dist > 0) { for (int i = 0; i < dist / 10; i++) Serial.print("#"); Serial.println(" sm"); } else { Serial.println("---"); } if (dist > 0 && dist < minD) { minD = dist; minUgol = ugol; } ugol = ugol + SHAG * napr; if (ugol >= UGOL_MAX || ugol <= UGOL_MIN) { napr = -napr; Serial.print("svip "); Serial.print(svip); Serial.print(": min "); Serial.print(minD); Serial.print(" sm na "); Serial.print(minUgol); Serial.println(" grad"); svip = svip + 1; minD = 999; } rypor.write(ugol); tEtap = now; etap = 0; } }}Автомат из двух этапов держит всю дисциплину: пока рупор доезжает, скетч молчит и считает время; когда доехал — снимает серию и только потом двигает луч. Никакой замер не случается в движении, и карта перестаёт плыть. Обратите внимание на минимальную дистанцию: она копится по сектору и печатается в конце каждого прохода — это готовая строчка для парктроника, где голосит зуммер по близости цели. Константы развёртки крутятся под задачу без перестройки автомата: хотите частую карту — режьте шаг до пяти градусов, лучей станет двадцать пять, и сектор растянется до одиннадцати секунд; хотите живой темп — шаг двадцать и один замер на луч, семь лучей за три секунды. Я начинал с частого шага, чтобы понять форму препятствий, а после разметки стенда переходил на грубый — так карта сначала рисуется, а потом просто поддерживается.
Темп получается такой: луч — 300 мс ожидания, плюс три замера по 40 мс и доли миллисекунды на печать — всего около 430 мс. Сектор из тринадцати лучей — почти шесть секунд, вместе с обратным проходом — около одиннадцати. Для машины, ползущей пять километров в час, это почти восемь метров за сектор: радар — не автопилот, а картограф. Хотите быстрее — на дальних лучах снимайте один замер вместо трёх и оставляйте серию только в секторе, где живёт цель.
Шаг в десять градусов выбран не с потолка. Конус HC-SR04 — около пятнадцати градусов, значит, соседние лучи с шагом десять перекрываются краями, и объект размером с банку не провалится между ними; при шаге в двадцать маленькая ножка стола уже может остаться незамеченной. Заодно держите в голове калибровочную метку: угол девяносто — перпендикуляр от вала, и если стена напротив стоит ровно, максимум дистанции обязан приходиться на середину сектора. У моего стенда она уехала на шесть градусов влево — кронштейн был приклеен криво, и никакой код это не спасёт, только канцелярский нож и повторная посадка рупора.
Про две печати подряд. Строка «угол, дистанция» построена под Serial Plotter: два числа в строке — два канала, пила угла и пики дистанции, объекты видны как провалы. Строка с решётками — для монитора: длина гирлянды пропорциональна дистанции, и дверной проём читается глазами — короткие ряды, длинный между ними. Плоттер примет решётки за третий канал с мусором, так что если смотрите картинку — закомментируйте печать карты, и наоборот.
Если захочется картинку посерьёзнее монитора, тот же лог превращается в лепестковую диаграмму: сохраните пары «угол; дистанция» в файл — двадцать шесть значений за полный проход туда и обратно, поместится хоть в блокнот, — и постройте полярный график в любой таблице. Я так однажды поймал фантомный столб на одном угле: на живой диаграмме он торчал идеально ровно от прохода к проходу, и стало ясно, что это стойка крепления, а не призрак эха. Данные с честной структурой — угол как ключ, дистанция как значение — ценнее любых зелёных развёрток из роликов.
| Этап луча | Время | Зачем |
|---|---|---|
| разворот на 10° | 30–50 мс | механика доезжает: 60° за 0,1 с |
| пауза затухания | 250–450 мс | вибрация консоли гаснет, эхо перестаёт гулять |
| серия из трёх | 120 мс | медиана выбрасывает одиночную чушь |
| печать строки | меньше 5 мс | журнал и канал для плоттера |
В симуляторе препятствие одно и стоит на одной дистанции для всех углов — так движок рисует дальномер, и это честно обозначено. На живой плате начинается настоящая картография: стена встаёт длинной гирляндой на дальних лучах, ножка штатива — короткий ряд сбоку, и по карте сразу видно, что конус рупора шире, чем вам казалось. Если вертикали не хватает, ставьте вторую серво и водите луч по двум осям — механика та же, что у пан-тилт подвеса для камеры.
Крепёж и границы сектора
Крепёж — половина успеха, и про него молчат все ролики. Проводам от платы к рупору нужна слабина-петля: без неё через неделю поворотов жила отрывается у паянного контакта, и радар начинает врать на одних и тех же углах. Дальномер я сажаю на рог не вплотную, а с прокладкой в пару миллиметров и отступаю по пять градусов от крайних положений механики: у упора серво дрожит сильнее всего. А если луч должен не сканировать, а держаться за объект — это уже не радар, а трекер на фоторезисторах или его ультразвуковой собрат.
И про сектор, который я без колебаний отрезаю. Полные ноль– сто восемьдесят на живом стенде не живут: у крайних градусов серво упирается в внутренние ограничители, дрожит и греется, а конус при таких углах всё равно смотрит вдоль монтажной плиты и собирает отражения от неё. Мои тридцать и сто пятьдесят — не суеверие, а отступ по двадцать градусов от упоров; если задача требует шире — сначала расширяем механику, перенося ось поворота ближе к фронту рупора, а уже потом код.
- Дисциплина луча: остановился — дождался 300 мс — снял серию — померил медианой из трёх
- Медиана против среднего: одиночный выброс не портит две честные замера
- Ноль от pulseIn — «дальше горизонта», на карте прочерк, а не объект вплотную
- Луч около 430 мс, сектор 13 лучей — почти 6 секунд: радар — картограф, а не автопилот
- Две печати: «угол дистанция» — для Serial Plotter, решётки — для чтения глазами в мониторе
- 1. Радар мерит сразу после write без паузы. Как это выглядит на карте?
- 2. Почему на луче медиана из трёх, а не среднее?
- 3. pulseIn с таймаутом вернул 0 на пустом направлении. Что пишем в карту?
- 4. Сколько времени занимает один луч в бюджете из урока?
- 5. Зачем проводам дальномера слабина-петля на поворотном рупоре?
Частые вопросы по теме урока
- Радар мерит сразу после write без паузы. Как это выглядит на карте?
- Эхо снимается с движущегося рупора, и результат относится и к старому, и к новому углу. Мой первый радар мерил именно так — стена на карте превращалась в горб. Дистанция размазывается между двумя соседними углами, стена плывёт
- Почему на луче медиана из трёх, а не среднее?
- Пылинка или край препятствия могут испортить одно чтение из серии. Среднее уезжает на несколько сантиметров, медиане две честные замера перекрывают одну чушь. Одиночный выброс не тянет медиану в сторону
- pulseIn с таймаутом вернул 0 на пустом направлении. Что пишем в карту?
- Ноль от pulseIn — знак, что эха не дождались, а не измеренная дистанция. Если скормить его карте как нуль сантиметров, радар отчитается о стене в носу у рупора. Прочерк — «дальше горизонта», объект не обнаружен
- Сколько времени занимает один луч в бюджете из урока?
- 300 мс на затухание вибраций, 3 × 40 мс серия и несколько миллисекунд на печать — примерно 430 мс. Сектор из 13 лучей идёт почти шесть секунд. Около 430 миллисекунд
- Зачем проводам дальномера слабина-петля на поворотном рупоре?
- Рупор крутится тысячи раз в день, и туго натянутый провод работает на излом у пайки. Ломается он характерно: радар начинает врать на одних и тех же углах. Запас на поворот — иначе жила отрывается у контакта через неделю