Skip to content
Занятия 6–7. Датчики: остановки по касанию, расстоянию и линии (задачи 4.5, 4.14–4.20, 4.23)

Занятия 6–7. Датчики: остановки по касанию, расстоянию и линии (задачи 4.5, 4.14–4.20, 4.23)

Занятия 6–7. Остановки по датчикам

Цель занятия 6: обучающиеся останавливают робота по датчику касания (4.14–4.15) и на заданном расстоянии от препятствия (4.5, 4.18), обнаруживают объект сбоку (4.19–4.20). Цель занятия 7: обучающиеся останавливаются на линии в обе стороны (4.16–4.17) и решают задачу «вторая линия на переменном расстоянии» (4.23).

Разминка-вспоминание (занятие 6, 5 мин)

  1. Формула тиков танкового поворота на θ? Почему k пивота ≠ k танкового?
  2. Что показал ваш «квадрат» и что вы исправили?
  3. Почему distMM() в HAL возвращает медиану из 5?
  4. m вашего робота после калибровки?

Теория: три правила работы с любым датчиком

  1. Калибруй, не верь даташиту. Порог «линия/фон», зона уверенного обнаружения дальномера, ход кнопки — измеряются на вашем поле при вашем свете. Калибровка — часть программы (режим при старте или отдельный скетч), а не разовое действие.
  2. Фильтруй. Одиночное показание — шум. Дальномер — медиана из 5; аналоговый датчик линии — среднее из 3–5; кнопка — антидребезг 20–30 мс или подтверждение двумя чтениями.
  3. Подъезжай медленно. Точность остановки по датчику = скорость × период опроса + тормозной путь. На PWM_MIN оба слагаемых минимальны. Схема всех задач «стоп по датчику»: быстро в зону ожидания → медленно до срабатывания → stopBrake().

Чем отличаются ваши дальномеры (важно при замене датчика)

СвойствоУЗ (HC-SR04-класс)Лазерный ToF (VL53L0X-класс)
Пятно измеренияконус ~15° — «видит» широколуч <2° — «видит» точку
Мёртвая зона~2 см~3–4 см
Слабые местамягкое, ткань, косые поверхности (эхо уходит), соседний УЗ-роботчёрные матовые поверхности, яркое солнце
5 В echo → ESP32делитель напряжения обязателенI²C, 3,3 В — напрямую
Типовое применениестоп перед стеной/банкой (4.18)объект сбоку (4.19–4.20), узкая метка (4.5)

Благодаря HAL алгоритмы ниже не знают, какой из них стоит: обнаружение объекта сбоку удобнее лазерным (узкий луч не цепляет пол и линию), но работает и с УЗ при аккуратной установке.

Разбор (worked examples)

4.14–4.15: стоп по касанию

void task_touch(int dir) {                 // dir: +1 вперёд (4.14), -1 назад (4.15)
  drive(dir*120, dir*120);
  while (!touch()) delay(2);
  stopBrake();
  // антидребезг живёт в HAL touch(): два чтения с интервалом 20 мс
}

Механика важнее кода: бампер должен нажимать кнопку при касании любой точкой передней кромки (планка-качалка на кнопке), для 4.15 — симметричный бампер сзади или разворот робота как «переднего».

4.5 + 4.18: стоп на L см от препятствия

void stopAtMM(int target) {
  while (distMM() > target + 150) drive(160,160);   // фаза 1: быстро до зоны +15 см
  while (distMM() > target)       drive(70,70);     // фаза 2: ползком до цели
  stopBrake();
  // 4.5 «определить расстояние до метки»: после стопа вывести distMM()
  // на серийник/дисплей/морганием — по формату, который требует судья
}

4.19–4.20: объект сбоку

Дальномер смотрит вбок (перпендикулярно курсу). Едем по прямой на энкодерной синхронизации (driveMM-логика без целевых тиков); объект = дистанция упала ниже фона:

// фон калибруем стоя на старте: background = distMM() вбок (там пусто, >1 м или стена)
bool sideObject(int background) { return distMM() < background - 200; } // порог: −20 см

Подтверждение двумя последовательными чтениями обязательно — иначе ловим выброс. Для «назад» (4.20) — тот же код, знак мощности.

4.16–4.17: стоп на линии + калибровка датчика линии

int thrLine;                          // порог: середина между белым и чёрным
void calibLine() {                    // режим калибровки при старте:
  int w = lineRaw(2);                 // 1) робот на белом -> запомнили w
  waitButton();                       // 2) переставили на чёрное -> ч
  int b = lineRaw(2);
  thrLine = (w + b) / 2;              // 3) порог = середина; в журнал: w, b, thr
}
bool onBlack() { return lineRaw(2) > thrLine; }  // у большинства модулей чёрное = больше;
                                                  // проверить СВОЙ и зафиксировать в HAL
void task_lineStop(int dir) {
  drive(dir*100, dir*100);
  while (!onBlack()) delay(1);
  stopBrake();
}

«Стоп на белой линии» из регламента — тот же код с инверсией условия: универсально хранить порог и флаг targetIsBlack.

4.23: вторая линия на переменном расстоянии — конечный автомат

Задача-маркер регламента: расстояние между линиями меняется от попытки к попытке, поэтому никаких «проехать X см» — только события датчика. Ловим не «на линии», а фронты (переходы фон→линия и линия→фон):

    stateDiagram-v2
    [*] --> ЕДЕМ_ДО_Л1
    ЕДЕМ_ДО_Л1 --> НА_Л1 : фон→чёрное
    НА_Л1 --> МЕЖДУ : чёрное→фон
    МЕЖДУ --> СТОП : фон→чёрное (это Л2!)
    СТОП --> [*] : stopBrake()
  
void task_4_23() {
  enum {TO_L1, ON_L1, BETWEEN} st = TO_L1;
  bool prev = onBlack();
  drive(110, 110);
  while (true) {
    bool cur = onBlack();
    if (st==TO_L1  &&  cur && !prev) st = ON_L1;    // въехали на линию 1
    if (st==ON_L1  && !cur &&  prev) st = BETWEEN;  // съехали с линии 1
    if (st==BETWEEN &&  cur && !prev) break;         // линия 2!
    prev = cur; delay(1);
  }
  stopBrake();
}
Ловушка 4.23 — «дребезг фронта»: на границе линии датчик мигает фон/линия, и автомат может насчитать лишние переходы (принять край Л1 за Л2). Лечение — гистерезис по пути, не по времени: после смены состояния игнорировать новые фронты, пока робот не проедет ≥ 1 см по энкодерам (толщина изоленты ~1,9 см, зазор ≥10 см по регламенту — запас есть). Это переиспользование энкодеров из занятия 4 — заметьте, как блоки собираются друг из друга.

Практика

Занятие 6: лотерея условий A

Карточки: {4.14 | 4.15 | 4.18 с L∈{10,15,25} см | 4.19 | 4.20}. Пара тянет две, сдаёт по протоколу (3 попытки). Между попытками судья двигает препятствие/объект.

Занятие 7: калибровка линии + 4.16/4.17

Калибровка на поле, значения w/b/thr — в журнал (интервальный возврат: сравним с калибровкой перед туром — освещение меняет всё).

Занятие 7: 4.23 с тремя расстояниями

Судья ставит вторую линию на 12, 25 и 40 см. Зачёт — все три без правки кода.

Типичные ошибки:

  • порог линии «на глаз 2000» вместо калибровки — работает дома, падает на турнире под другими лампами;
  • дальномер стоит низко и «видит» пол на дистанции >30 см — приподнять/наклонить, это механика;
  • в 4.19 объект обнаружен, а робот проехал мимо: между обнаружением и стопом прошло время → останавливаться надо сразу, а положение объекта при необходимости брать как «тики в момент срабатывания» (пригодится в 3.10);
  • УЗ-датчики двух соседних роботов на тренировке глушат друг друга — разводить по времени/полям.

Мини-проверка выхода

Занятие 6 — обе карточки лотереи сданы. Занятие 7 — 4.23 на трёх расстояниях + вопрос: «почему в 4.23 нельзя использовать driveMM?» (расстояние неизвестно и меняется — регламент требует событийного решения).