Робот-курьер Урок 3.1 Все курсыДля учителяПройти в программе Ученик Arduino Uno + шилд моторов L298P
Тема 3 · Урок 3.1

Датчики линии и первое следование

Робот «смотрит» на пол, отличает чёрное от белого и сам держится линии.

2 × 45 мин1–2 ученика на роботаПрактика

Результат урока

Робот проезжает по чёрной линии с плавным изгибом, поворачивая по сигналам двух датчиков. В бортовом журнале записаны показания датчиков на белом и чёрном.

Ты научишься

  • Прочитать показания датчика командой analogRead и вывести их в монитор порта.
  • Различать строки Serial Monitor и каналы телеметрии с временем и единицами; сравнивать сохранённые показания модели и настоящего робота.
  • Измерить показания на белом и чёрном и объяснить, почему у каждого датчика они свои.
  • Записать правило «показание ближе к чёрному — значит, под датчиком линия».
  • Составить два правила поворота и проверить их сначала на подставке, затем на дорожке.

Нужно знать

  • 1.1 Собираем робота и оживляем плату
  • 2.1 Моторы: едем и останавливаемся

Понадобится

Датчик линии (аналоговый, Trema) × 2USB-кабель для Arduino

Зачем этот урок

До сих пор робот делал только то, что ты заранее записал: ехать столько-то, повернуть так-то. Сегодня он начнёт сам принимать решения. Два датчика будут смотреть на пол, и робот будет поворачивать, когда линия уходит в сторону.

Разминка · вспомни
  1. Какая команда задаёт скорость мотора и какие числа она принимает?
  2. Что делает delay(500)?
  3. Какое колесо надо остановить, чтобы робот повернул налево?
Предскажи

Как робот может «увидеть» чёрную линию, если у него нет камеры? Обсудите с напарником и запишите догадку в бортовой журнал. В конце урока проверите.

Безопасность

Пока меняешь провода, USB-кабель отключён, питание моторов выключено. Опыты с моторами — сначала на подставке: колёса не касаются стола.

1
Занятие 1 · 45 мин
Датчики и их показания
1

Подготовь дорожку и робота

5 мин

Что должен делать робот. Чтобы ехать по дорожке, роботу нужно замечать, когда линия оказалась под левым или правым датчиком. Для этого используем два направленных вниз датчика отражения; они смотрят на пол, а не ищут предметы впереди.

Что используем и как это работает. В датчике есть излучатель и приёмник отражённого света. Поверхности отражают свет по-разному, а приёмник превращает отражённый свет в электрический сигнал. analogRead считывает его как число. Это не готовый ответ «чёрное»: сначала измеряем белое и чёрное для каждого датчика при рабочей высоте.

Как связать измерение с действием. Программа сравнивает чтение с измеренным порогом и выбирает действие колёс. Над линией слева и справа должны получиться разные команды коррекции. Проверим четыре сочетания карточек на подставке, затем движение по линии. Одинаковые показания обоих датчиков ещё не доказывают, что робот стоит правильно: оба могут оказаться и на белом, и на чёрном.

Датчику нужна понятная картинка: светлый фон и тёмная линия.

  1. Возьми светлое матовое поле с чёрной линией (или наклей чёрную матовую изоленту на белый лист). Для начала хватит прямой с одним плавным изгибом.
  2. Подготовь две карточки: белую и чёрную.
  3. Найди на роботе левый и правый датчики. Они смотрят вниз и стоят по обе стороны от линии, когда робот стоит на ней.
Должно получиться

Дорожка и карточки готовы. Ты можешь показать левый и правый датчик.

Датчику нужна понятная картинка: светлый фон и тёмная линия. Для заданий среднего уровня на дорожке понадобится и перекрёсток.

  1. Возьми светлое матовое поле с чёрной линией или наклей чёрную матовую изоленту на белый лист: прямая, плавный изгиб и один перекрёсток.
  2. Подготовь две карточки: белую и чёрную.
  3. Найди на роботе левый и правый датчики. Поставь робота на линию и проверь: линия проходит между датчиками и не касается ни одного из них?
Должно получиться

Дорожка с перекрёстком и карточки готовы. Линия помещается между датчиками.

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

  1. Сделай дорожку: прямая, плавный изгиб, крутая дуга и перекрёсток. Подготовь белую и чёрную карточки.
  2. Измерь линейкой ширину линии и расстояние между датчиками (между серединами окошек).
  3. Поставь робота на линию. Сколько миллиметров остаётся от края линии до каждого датчика? Запиши в журнал: что будет, если линия шире расстояния между датчиками?
Должно получиться

Дорожка готова, в журнале — ширина линии, расстояние между датчиками, запас с каждой стороны и ответ на вопрос.

2

Посмотри, что видит датчик

12 мин

Датчик светит на пол и меряет, сколько света вернулось. Плата превращает это в число.

Чтобы увидеть число, плата будет отправлять его на компьютер, а мы откроем монитор порта.

  1. Создай скетч L31_Read_Sensors и набери программу. Левый датчик подключён к A4, правый — к A5.
Показания двух датчиковL31_Read_Sensors.ino.ino
const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; void setup() {  Serial.begin(9600);} void loop() {  int leftValue = analogRead(LEFT_SENSOR_PIN);  int rightValue = analogRead(RIGHT_SENSOR_PIN);   Serial.print("L=");  Serial.print(leftValue);  Serial.print("  R=");  Serial.println(rightValue);   delay(200);}
  1. Загрузи программу. Открой Инструменты → Монитор порта и выбери скорость 9600.
  2. Поднеси под левый датчик белую карточку, затем чёрную. Держи карточку на той же высоте, на которой датчик стоит над полом.
Должно получиться

В мониторе бегут строки вида L=… R=…. Когда меняешь карточку под датчиком, его число заметно меняется.

Как это устроено: Что значит каждая строка
  • analogRead(LEFT_SENSOR_PIN) — прочитать показание датчика: число от 0 до 1023.
  • int leftValue = … — сохранить число в переменную leftValue. int — целое число.
  • Serial.begin(9600) — начать связь с компьютером. 9600 — скорость, такая же должна быть выбрана в мониторе порта.
  • Serial.print печатает в той же строке, Serial.println — печатает и переходит на новую.
  • Текст в кавычках печатается как есть, а имя переменной без кавычек заменяется её числом.
Если не получилось
Что видишьЧто сделать
Вместо чисел непонятные символыВ мониторе порта выбери 9600.
Число не меняетсяПроверь провод сигнала S и номер входа в программе.
Меняется не тот датчикПровода левого и правого датчиков перепутаны. Поменяй их местами или номера в программе.

Датчик светит на пол и меряет, сколько света вернулось. Плата превращает это в число, а чтобы увидеть его, программа отправляет число на компьютер.

КомандаЧто делает
analogRead(контакт)Показание датчика: число от 0 до 1023
Serial.begin(9600);Включает связь с компьютером, один раз
Serial.print(x);Печатает в той же строке
Serial.println(x);Печатает и переходит на новую строку
  1. Создай скетч L31_Read_Sensors по шаблону и напиши обе части сам. Левый датчик подключён к A4, правый — к A5.
Шаблон: показания двух датчиковL31_Read_Sensors_Gaps.ino.ino
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // ===== Твоя часть: напиши сам ===== void setup() {  // Включи связь с компьютером на той же скорости, что в мониторе порта: 9600.} void loop() {  // Пять раз в секунду печатай одну строку: L=число левого датчика  R=число правого.}
  1. Загрузи программу и открой монитор порта со скоростью 9600.
  2. Поднеси под левый датчик белую карточку, затем чёрную. Держи карточку на той же высоте, на которой датчик стоит над полом.
Предскажи

Что напечатает монитор, если имя переменной в Serial.print взять в кавычки?

Подсказка: Текст и число в одной строке

Текст в кавычках печатается как есть, а имя переменной без кавычек заменяется её числом.

Должно получиться

В мониторе бегут строки вида L=… R=…. Когда меняешь карточку под датчиком, его число заметно меняется.

Если не получилось
Что видишьЧто сделать
Вместо чисел непонятные символыСкорость в мониторе порта и в программе должна совпадать: 9600.
Все числа в одной длинной строкеНет перехода на новую строку.
Меняется не тот датчикПровода левого и правого датчиков перепутаны.

Датчик светит на пол и меряет, сколько света вернулось. Левый датчик подключён к A4, правый — к A5.

Как это устроено: Справка по командам
КомандаЧто делает
analogRead(pin)Показание датчика: число от 0 до 1023
Serial.begin(9600)Включает связь с компьютером, один раз в setup()
Serial.print(x) / Serial.println(x)Печатает в монитор порта; println — с переходом строки
millis()Сколько миллисекунд прошло с включения платы

Требование. Скетч L31_Read_Sensors пять раз в секунду печатает строку t=… L=… R=…, где t — время в миллисекундах с включения.

Приёмка.

  1. По числам t в мониторе проверь, что строки идут примерно каждые 200 мс. На сколько миллисекунд отличается промежуток от 200 и почему?
  2. Поднеси под левый датчик белую, затем чёрную карточку на рабочей высоте. Запиши, на сколько меняется число.
Подсказка: Почему промежуток не ровно 200

Сколько времени занимают сами чтение и печать, кроме delay(200)?

Должно получиться

Программа выполняет требование. В журнале — реальный промежуток между строками с объяснением и изменение числа на белом и чёрном.

3

Проверь оба датчика

8 мин
Предскажи

Будут ли числа левого и правого датчиков одинаковыми над одним и тем же белым полем?

  1. Поставь робота на белое поле. Запомни числа L и R.
  2. Подведи чёрную карточку только под левый датчик, потом только под правый.
  3. Сдвинь робота так, чтобы линия была точно между датчиками. Какие числа сейчас?
Должно получиться

Ты знаешь, какое число у каждого датчика на белом и как оно меняется на чёрном. Когда линия между датчиками, оба показывают «белое».

Предскажи

Будут ли числа левого и правого датчиков одинаковыми над одним и тем же белым полем?

  1. Заполни таблицу для четырёх положений робота:
ПоложениеLR
Оба над белым
Чёрное только под левым
Чёрное только под правым
Линия точно между датчиками
  1. Почему в последней строке оба числа «белые», хотя робот стоит на линии? Запиши.
Должно получиться

Таблица заполнена, прогноз проверен, ответ про положение «между датчиками» записан.

Сними профиль датчиков: как меняются числа, пока линия проходит под роботом поперёк.

  1. Положи робота поперёк линии так, чтобы линия была далеко справа от правого датчика.
  2. Сдвигай робота влево шагами по 5 мм, пока линия не уйдёт за левый датчик. На каждом шаге записывай L и R.
Сдвиг, ммLR
0
5
…
  1. Нарисуй в журнале два графика: L и R от сдвига. Найди по графику, где линия ровно между датчиками и на сколько миллиметров можно уйти в сторону, пока датчик её не заметит.
Должно получиться

Таблица и два графика. В журнале — ширина «слепой зоны» между датчиками в миллиметрах.

4

Измерь белое и чёрное

12 мин

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

  1. Поставь робота на белое поле. Посмотри на несколько строк и запиши типичное число левого датчика в бортовой журнал: «Левый, белое».
  2. Подведи под левый датчик чёрную линию. Запиши «Левый, чёрное».
  3. Повтори для правого датчика.
  4. Проверь себя: повтори каждое измерение ещё раз. Числа должны быть близки к записанным.
ДатчикБелоеЧёрное
Левый
Правый
Должно получиться

В журнале четыре числа. Белое и чёрное у каждого датчика заметно отличаются.

Подсказка: Белое и чёрное почти одинаковые

Датчик может стоять слишком высоко или линия блестит. Опусти датчики ближе к полу и проверь линию в другом месте поля, где нет бликов.

Как это устроено: Почему у одних датчиков чёрное — большое число, а у других — маленькое

Это зависит от устройства датчика. Одни выдают тем большее число, чем больше света вернулось, другие — наоборот. Поэтому мы не запоминаем «чёрное — это много», а измеряем свои числа.

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

  1. Для каждого датчика запиши по три показания на белом и три на чёрном, в разных местах дорожки.
  2. Выбери типичное число для каждой клетки и запиши, почему именно его.
ДатчикБелое: 3 числаБелоеЧёрное: 3 числаЧёрное
Левый
Правый
Подсказка: Числа в разных местах разные

Освещение и поле неодинаковые. Берут число, близкое к остальным, а не самое удачное.

Должно получиться

В журнале таблица с повторами и четыре выбранных числа. Белое и чёрное заметно отличаются.

Откалибруй датчики и докажи, что калибровке можно доверять.

  1. Для каждого датчика запиши по 10 показаний на белом и 10 на чёрном. Найди наименьшее и наибольшее в каждой серии — это разброс.
  2. Сравни разброс с разницей белого и чёрного. Во сколько раз разница больше разброса?
  3. Повтори белое у окна и в тёмном углу класса. Меняются ли числа сильнее разброса?
  4. Запиши вывод: где можно доверять калибровке и когда её нужно повторить.
ДатчикБелое: мин–максЧёрное: мин–максРазница типичных
Левый
Правый
Должно получиться

Таблица разбросов, сравнение с разницей и вывод, когда калибровку повторять.

2
Занятие 2 · 45 мин
Робот едет по линии
5

Научи программу замечать линию

10 мин

Как программе решить, что под датчиком линия? Сравним новое показание с нашими двумя измерениями: если число ближе к чёрному, чем к белому, — под датчиком линия.

Пример: белое — 100, чёрное — 800. Пришло число 650. До чёрного 150, до белого 550. Ближе к чёрному — значит, линия.

  1. Создай скетч L31_Line_Detect. Впиши свои четыре числа вместо нулей.
Программа замечает линиюL31_Line_Detect.ino.ino
const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // Впиши свои измерения из бортового журнала.const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0; void setup() {  Serial.begin(9600);} void loop() {  int leftValue = analogRead(LEFT_SENSOR_PIN);  int rightValue = analogRead(RIGHT_SENSOR_PIN);   bool leftOnLine = abs(leftValue - LEFT_BLACK) < abs(leftValue - LEFT_WHITE);  bool rightOnLine = abs(rightValue - RIGHT_BLACK) < abs(rightValue - RIGHT_WHITE);   Serial.print("L=");  Serial.print(leftValue);  Serial.print("  R=");  Serial.print(rightValue);   if (leftOnLine) {    Serial.print("  слева линия");  }  if (rightOnLine) {    Serial.print("  справа линия");  }   Serial.println();  delay(200);}
  1. Загрузи программу, открой монитор порта и подводи чёрную карточку под датчики по очереди.
Должно получиться

Когда под левым датчиком чёрное, в строке появляется «слева линия», под правым — «справа линия».

Найди ошибку · Почему сообщение не появляется

Команда соседей написала так:

bool leftOnLine = abs(leftValue - LEFT_BLACK) > abs(leftValue - LEFT_WHITE);

Под левым датчиком чёрная карточка, а «слева линия» не печатается. Найди ошибку и объясни, что программа проверяет сейчас.

Как это устроено: Что такое abs и bool
  • abs(число) убирает знак: abs(-150) равно 150. Так мы получаем расстояние между двумя числами, не думая, какое из них больше.
  • bool — переменная, в которой хранится не число, а ответ «да» или «нет» (true или false).
  • if (leftOnLine) { … } — если ответ «да», выполнить команды в скобках.

Программе нужно правило: когда считать, что под датчиком линия? Сравни новое показание с двумя измерениями: если оно ближе к чёрному, чем к белому, — под датчиком линия.

Предскажи

Белое — 100, чёрное — 800, пришло число 650. Линия под датчиком или нет? Посчитай до запуска: сколько от 650 до чёрного и сколько до белого?

  1. Создай скетч L31_Line_Detect по шаблону и впиши свои четыре числа вместо нулей.
  2. Напиши loop() сам: решение «линия или нет» для каждого датчика и печать.
Шаблон: программа замечает линиюL31_Line_Detect_Gaps.ino.ino
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // Впиши свои измерения из бортового журнала.const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0; // ===== Твоя часть: напиши сам ===== void loop() {  int leftValue = analogRead(LEFT_SENSOR_PIN);  int rightValue = analogRead(RIGHT_SENSOR_PIN);   // Для каждого датчика реши, линия ли под ним (правило «ближе к чёрному»): leftOnLine и rightOnLine.  // Печатай строку L=… R=…, а если под датчиком линия — допиши «слева линия» или «справа линия».  // Пять строк в секунду.} // ===== Готовое из прошлого шага — не меняй ===== void setup() {  Serial.begin(9600);}
  1. Загрузи программу, открой монитор порта и подводи чёрную карточку под датчики по очереди.
  2. Ответь в журнале: что начнёт печатать программа, если в ней перепутать белое и чёрное местами? Почему?
Подсказка: Как измерить расстояние между числами

От 650 до 800 — это 800 - 650. Но программа заранее не знает, какое число больше. Команда abs(…) убирает знак: abs(650 - 800) равно 150.

Подсказка: Да или нет

Ответ «да или нет» хранит тип bool. Команды в if (…) { … } выполняются, только если ответ «да».

Должно получиться

«слева линия» и «справа линия» появляются, только когда под датчиком чёрное. Прогноз про 650 сошёлся, ответ про перепутанные числа записан.

Найди ошибку · «Слева линия» на белом поле

Команда соседей написала так:

bool leftOnLine = abs(leftValue - LEFT_BLACK) < abs(leftValue - RIGHT_WHITE);

Иногда «слева линия» печатается, когда под левым датчиком белое поле. Найди ошибку и объясни, почему она проявляется не всегда.

Требование 1. Функция onLine(value, white, black) отвечает true, когда под датчиком линия:

  • показание ближе к чёрному измерению, чем к белому;
  • работает и когда у датчика чёрное — большое число, и когда маленькое;
  • внутри нет готовых чисел-порогов: только value, white и black.
Предскажи

Почему нельзя просто написать value > 500? Назови два случая, когда такое правило ошибётся.

Приёмка. Напиши onLine в тестовом скетче, загрузи его и открой монитор порта (9600). Тест проверяет восемь случаев для двух видов датчиков, в том числе показания за пределами твоих измерений.

Тест функции onLineL31_OnLine_Test.ino.ino
// Проверка функции onLine. Напиши функцию здесь, загрузи и открой монитор порта.// Когда все строки PASS, перенеси onLine в свою программу. // Требование 1: true, если показание ближе к чёрному, чем к белому.bool onLine(int value, int white, int black) {  return false;} int failed = 0; void expect(int value, int white, int black, bool expected) {  bool actual = onLine(value, white, black);  Serial.print(actual == expected ? "PASS " : "FAIL ");  Serial.print("onLine(");  Serial.print(value);  Serial.print(", ");  Serial.print(white);  Serial.print(", ");  Serial.print(black);  Serial.print(") = ");  Serial.print(actual ? "true" : "false");  Serial.print(", ожидаем ");  Serial.println(expected ? "true" : "false");  if (actual != expected) {    failed++;  }} void setup() {  Serial.begin(9600);   // Датчик, у которого чёрное — большое число, а белое — маленькое.  expect(780, 120, 780, true);  expect(120, 120, 780, false);  expect(600, 120, 780, true);  expect(300, 120, 780, false);  // За пределами измерений.  expect(900, 120, 780, true);  expect(50, 120, 780, false);  // Датчик, у которого наоборот: белое — большое число, чёрное — маленькое.  expect(300, 900, 120, true);  expect(700, 900, 120, false);   if (failed == 0) {    Serial.println("Все проверки пройдены");  } else {    Serial.print("Ошибок: ");    Serial.println(failed);  }} void loop() {}

Когда все строки PASS, добавь onLine в свою программу из шага «Посмотри, что видит датчик» и сохрани её как L31_Line_Detect. Пусть она печатает «слева линия» и «справа линия». Впиши свои измерения и проверь карточкой.

Подсказка: Идея (1 из 3)

Нужны два расстояния: от показания до чёрного и от показания до белого. Какое меньше?

Подсказка: План (2 из 3)
  1. Расстояние до чёрного — разница без знака.
  2. Расстояние до белого — так же.
  3. Вернуть результат сравнения двух расстояний.
Подсказка: Фрагмент кода (3 из 3)

abs(value - black) — расстояние до чёрного. Зачем здесь abs? Что будет без него у датчика, где чёрное — маленькое число?

Должно получиться

Тест показывает «Все проверки пройдены». Твоя программа печатает «слева линия» и «справа линия», только когда под датчиком чёрное.

6

Придумай, как поворачивать

7 мин

Робот едет вперёд. Линия должна оставаться между датчиками. Что делать, если линия оказалась под левым датчиком? Значит, робот уехал вправо, и ему надо повернуть налево.

Самый простой способ повернуть налево — остановить левое колесо, а правое пусть едет.

  1. Поставь выключенного робота на линию. Сдвинь его так, чтобы линия оказалась под левым датчиком. Покажи, какое колесо нужно остановить.
  2. То же — для правого датчика.
  3. Заполни таблицу в журнале:
Что видят датчикиЛевое колесоПравое колесоКуда едет робот
Оба белоевперёдвперёдпрямо
Левый — линия???
Правый — линия???
Оба — линия???
Должно получиться

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

Робот едет вперёд, и линия должна оставаться между датчиками. Если линия оказалась под одним датчиком, робот уехал в сторону. Колесо пока умеет только ехать или стоять. Как вернуть робота к линии?

  1. Поставь выключенного робота на линию. Сдвинь его так, чтобы линия оказалась под левым датчиком. Куда робот уехал? Куда ему повернуть? Какое колесо для этого остановить?
  2. То же — для правого датчика.
  3. Заполни таблицу в журнале:
Что видят датчикиЛевое колесоПравое колесоКуда едет робот
Оба белоевперёдвперёдпрямо
Левый — линия???
Правый — линия???
Оба — линия???
  1. Объясни в журнале одной фразой, почему остановка одного колеса поворачивает робота.
Подсказка: Какое колесо останавливать

Вспомни урок 2.2: левое колесо стоит, правое едет. Вокруг какого колеса повернётся робот и в какую сторону?

Должно получиться

Таблица заполнена, объяснение записано. Ты показал на выключенном роботе оба поворота.

Робот едет вперёд, и линия должна оставаться между датчиками. Составь правила сам: что делают колёса в каждом из четырёх случаев и почему.

Что видят датчикиЛевое колесоПравое колесоКуда едет роботПочему
Оба белое
Левый — линия
Правый — линия
Оба — линия
  1. Проверь каждую строку на выключенном роботе: сдвинь его рукой и покажи, куда он должен поехать.
  2. «Оба белое» бывает в двух разных положениях робота. В каких? Может ли программа их различить? Запиши, что тогда случится с роботом.
  3. На прямой дорожке «оба — линия» почти не бывает. Где на итоговом поле это встретится и что тогда должен делать робот?
Подсказка: Два случая «оба белое»

Подумай, где может быть линия, когда ни один датчик её не видит.

Должно получиться

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

7

Запиши правила в программу

10 мин

Соберём всё вместе: датчики, наше правило «ближе к чёрному» и моторы из урока 2.1.

  1. Создай скетч L31_First_Line. Перенеси свои четыре измерения.
Первое движение по линииL31_First_Line.ino.ino
const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // Впиши свои измерения из бортового журнала.const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0; const int LEFT_DIR_PIN = 7;const int LEFT_SPEED_PIN = 6;const int RIGHT_DIR_PIN = 4;const int RIGHT_SPEED_PIN = 5;const int FORWARD = HIGH; int speed = 100; // начальная скорость, подбираем на подставке void setup() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);   analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);  digitalWrite(LEFT_DIR_PIN, FORWARD);  digitalWrite(RIGHT_DIR_PIN, FORWARD);   // Без измерений робот не поедет: программа остановится здесь.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }   delay(2000); // время убрать руки от робота} void loop() {  int leftValue = analogRead(LEFT_SENSOR_PIN);  int rightValue = analogRead(RIGHT_SENSOR_PIN);   bool leftOnLine = abs(leftValue - LEFT_BLACK) < abs(leftValue - LEFT_WHITE);  bool rightOnLine = abs(rightValue - RIGHT_BLACK) < abs(rightValue - RIGHT_WHITE);   int leftSpeed = speed;  int rightSpeed = speed;   if (leftOnLine) {    leftSpeed = 0;  }  if (rightOnLine) {    rightSpeed = 0;  }   analogWrite(LEFT_SPEED_PIN, leftSpeed);  analogWrite(RIGHT_SPEED_PIN, rightSpeed);}
  1. Найди в loop() два правила из твоей таблицы. Объясни напарнику, что делает каждое.
  2. Нажми Проверить.
Как это устроено: Почему скорость задаётся заново в каждом повторе

В начале loop() обе скорости снова становятся равны speed. Если бы мы этого не делали, колесо, остановленное один раз, так бы и стояло, даже когда датчик снова увидит белое.

Как это устроено: Зачем строка while (true)

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

Соберём всё вместе: датчики, правило «ближе к чёрному» и моторы из урока 2.1. Колёса будут слушаться правил из твоей таблицы.

  1. Создай скетч L31_First_Line по шаблону. Впиши свои четыре измерения. Настройка моторов и защита уже готовы.
  2. Напиши loop() сам: перенеси свои условия из L31_Line_Detect, задай колёсам скорости по своей таблице правил и отправь их на моторы.
Шаблон: первое движение по линииL31_First_Line_Gaps.ino.ino
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // Впиши свои измерения из бортового журнала.const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0;const int LEFT_DIR_PIN = 7;const int LEFT_SPEED_PIN = 6;const int RIGHT_DIR_PIN = 4;const int RIGHT_SPEED_PIN = 5;const int FORWARD = HIGH;int speed = 100; // начальная скорость, подбираем на подставке // ===== Твоя часть: напиши сам ===== void loop() {  int leftValue = analogRead(LEFT_SENSOR_PIN);  int rightValue = analogRead(RIGHT_SENSOR_PIN);   // Реши, линия ли под каждым датчиком, — перенеси свои условия из L31_Line_Detect.  // Колёса выполняют твою таблицу правил: линия под датчиком — его колесо стоит.  // Колесо, которое правило остановило, снова едет, когда его датчик видит белое.} // ===== Готовое: настройка и защита — не меняй ===== void setup() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);   analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);  digitalWrite(LEFT_DIR_PIN, FORWARD);  digitalWrite(RIGHT_DIR_PIN, FORWARD);   // Без измерений робот не поедет: программа остановится здесь.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }   delay(2000); // время убрать руки от робота}
Предскажи

Что случится, если задать скорость колёс один раз в setup(), а не в начале каждого повтора loop()?

  1. Нажми Проверить.
Подсказка: Колесо остановилось и больше не едет

Когда правило снова разрешает колесу ехать? Откуда в каждом повторе берётся его скорость?

Подсказка: Правило колеса

if (проверка) { … } выполняет команды в скобках, только если проверка верна. Сверься со своей таблицей: какую переменную проверить и какую скорость поменять?

Должно получиться

loop() написан, Проверить проходит без ошибок, ответ на прогноз записан в журнал.

Как это устроено: Зачем строка while (true)

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

Возьми каркас: в нём контакты, моторы и функция drive из урока 2.1. Перенеси в него свои измерения и функцию onLine и сохрани как L31_First_Line. Эта программа понадобится тебе в уроке 3.2.

Каркас движения по линииL31_First_Line_Skeleton.ino.ino
// Каркас движения по линии. Требования к программе — в тексте урока.const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5; // Впиши свои измерения из бортового журнала.const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0; const int LEFT_DIR_PIN = 7;const int LEFT_SPEED_PIN = 6;const int RIGHT_DIR_PIN = 4;const int RIGHT_SPEED_PIN = 5;const int FORWARD = HIGH; void drive(int leftDir, int leftSpeed, int rightDir, int rightSpeed) {  digitalWrite(LEFT_DIR_PIN, leftDir);  digitalWrite(RIGHT_DIR_PIN, rightDir);  analogWrite(LEFT_SPEED_PIN, leftSpeed);  analogWrite(RIGHT_SPEED_PIN, rightSpeed);} // Требование 1: перенеси функцию onLine из тестового скетча.bool onLine(int value, int white, int black) {  return false;} void setup() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);  drive(FORWARD, 0, FORWARD, 0);   // Требование 3: защита от запуска без измерений и время убрать руки.} void loop() {  // Требование 2: прочитать датчики, решить по своей таблице правил, задать скорости.  drive(FORWARD, 0, FORWARD, 0);}

Требование 2. Колёса выполняют твою таблицу правил:

  • в каждом повторе программа заново читает оба датчика и решает заново: колесо, остановленное один раз, снова едет, когда его датчик видит белое;
  • решение принимает твоя функция onLine;
  • скорость — одна именованная переменная speed в начале скетча.

Требование 3. Робот безопасен:

  • без измерений белого и чёрного колёса стоят;
  • после включения есть 2 секунды, чтобы убрать руки.

Приёмка — на подставке в следующем шаге: робот выполняет каждую строку твоей таблицы правил, а без измерений колёса не крутятся.

Предскажи

Что сделает робот, если скорости обоих колёс задать один раз в setup()? Запиши ответ до проверки.

Подсказка: Идея (1 из 3)

Каждый повтор: прочитать датчики → решить → отправить скорости на моторы. Решение начинается с «оба едут», а правила его меняют.

Подсказка: План (2 из 3)
  1. Прочитать оба датчика.
  2. Спросить onLine для каждого.
  3. Обе скорости — speed.
  4. Правила из таблицы меняют скорости.
  5. Отправить скорости в drive.
Подсказка: Фрагмент кода (3 из 3)

if (onLine(leftValue, LEFT_WHITE, LEFT_BLACK)) { leftSpeed = 0; } — а для правого датчика? А строка «оба — линия» из твоей таблицы?

Должно получиться

Программа выполняет требования 2 и 3, Проверить проходит без ошибок.

8

Проверь на подставке

6 мин
Безопасность

Загружай программу при выключенном питании моторов. Колёса не касаются стола.

  1. Поставь робота на подставку. Загрузи программу.
  2. Включи питание моторов. Оба колеса должны крутиться вперёд.
  3. Подведи чёрную карточку под левый датчик: левое колесо остановится. Убери карточку: колесо снова поедет. Проверь правый датчик.
  4. Выключи питание моторов.
Должно получиться

Каждое колесо останавливается, когда под его датчиком чёрное, и снова едет на белом.

Если не получилось
Что видишьЧто сделать
Колёса не крутятсяПроверь, что вписаны измерения и включено питание моторов.
Колесо крутится назадПроверь FORWARD и подключение мотора.
Останавливается не то колесоПерепутаны датчики или моторы. Проверь по монитору порта и по меткам Л/П.
  1. Поставь робота на подставку, загрузи программу, включи питание моторов.
  2. Проверь все четыре строки своей таблицы правил: подставляй карточки так, чтобы датчики видели нужное. Записывай, что делают колёса.
Что видят датчикиЖдалУвидел
Оба белое
Левый — линия
Правый — линия
Оба — линия
  1. Проверь свой прогноз про скорость, заданную в setup(). Выключи питание моторов.
Должно получиться

Таблица заполнена, все строки совпали с правилами. Прогноз проверен.

Если не получилось
Что видишьЧто сделать
Колёса не крутятсяНаписан ли loop()? Вписаны ли измерения? Включено ли питание моторов?
Останавливается не то колесоПерепутаны датчики или моторы. Проверь по монитору порта и по меткам Л/П.

Приёмка требований 2–3 на подставке.

  1. Загрузи программу с нулями вместо измерений. Колёса не должны крутиться. Впиши измерения и загрузи снова.
  2. Проверь каждую строку своей таблицы правил карточками.
  3. Засеки, через сколько секунд после включения трогаются колёса.
ПроверкаЖдалУвидел
Без измерений
Оба белое
Левый — линия
Правый — линия
Оба — линия
Пауза на старте
Должно получиться

Все проверки совпали с требованиями. Если нет — исправлено и проверено снова.

9

Первый заезд по линии

8 мин
  1. Выключи питание моторов, отключи USB-кабель.
  2. Поставь робота на прямой участок: линия между датчиками.
  3. Включи питание и наблюдай. Будь готов выключить питание, если робот уходит с дорожки.
  4. Попробуй проехать изгиб. Запиши в журнал, что получилось.
Должно получиться

Робот едет вдоль линии и проходит плавный изгиб. Он двигается рывками — это нормально для такого способа.

  1. Выключи питание моторов, отключи USB-кабель.
  2. Поставь робота на прямой участок: линия между датчиками. Включи питание. Будь готов выключить его, если робот уходит с дорожки.
  3. Пройди дорожку три раза при speed = 100, потом три раза при 80 и при 130. Записывай, сколько раз робот прошёл изгиб.
speedПрошёл из 3Что видно
80
100
130
  1. Почему робот едет рывками? Запиши одной фразой.
Должно получиться

Таблица заездов при трёх скоростях и объяснение рывков.

  1. Выключи питание моторов, отключи USB-кабель. Будь готов выключить питание, если робот уходит с дорожки.
  2. Найди наибольшую speed, при которой робот проходит плавный изгиб три раза из трёх. Начни с 80 и поднимай шагами по 20.
  3. На наибольшей скорости, которую робот не выдержал, посмотри, где он теряет линию. Объясни в журнале причину через твою таблицу правил.
  4. Запиши, что нужно изменить в правилах, чтобы робот держал линию быстрее. Проверишь это в уроке 3.2.
Должно получиться

Наибольшая надёжная скорость, место и причина схода, идея улучшения правил.

Основное · Прямая и плавный изгиб

Робот проходит прямую и плавный изгиб по двум правилам и не теряет линию.

Средний уровень · Едем через перекрёсток

Сейчас, если оба датчика видят чёрное, робот останавливается. На среднем поле через перекрёстки нужно проезжать прямо. Измени программу так, чтобы при чёрном под обоими датчиками оба колеса ехали вперёд. Проверь на дорожке с одним перекрёстком.

Продвинутый уровень · Не стоп, а замедление

Вместо остановки колеса замедляй его: например, до трети от speed. Сравни на одном изгибе два варианта — «стоп» и «замедление». Какой плавнее? При какой скорости каждый вариант ещё держит линию? Запиши результаты в журнал: это первый шаг к П-регулятору, который понадобится на крутых дугах продвинутого поля.

Проверь себя

Выберите ответы для подготовки. Они не сохраняются и не оцениваются на сайте. Пройдите проверку знаний в программе курса или обсудите ответы с преподавателем.

● Основное

1.Почему мы измеряем белое и чёрное для каждого датчика отдельно?
2.Белое — 100, чёрное — 800. Датчик показывает 300. Что под датчиком?
3.Робот едет вперёд, линия оказалась под правым датчиком. Что сделает наша программа?
4.Что напечатает этот код, если leftValue равно 512?
Serial.print("L=");Serial.println(leftValue);
5.Зачем в начале loop() снова писать leftSpeed = speed и rightSpeed = speed?

★ Средний уровень

1.На среднем поле оба датчика видят чёрное. Что должен делать робот по заданию ★?
2.Какое условие означает перекрёсток?
3.У датчика белое — 900, чёрное — 120. Он показывает 300. Что решит условие «ближе к чёрному, чем к белому»?
4.В программе перепутали LEFT_WHITE и LEFT_BLACK местами. Что будет печатать программа?
5.Почему после остановки левого колеса робот поворачивает налево?

★★ Продвинутый уровень

1.speed = 150. По заданию ★★ колесо не останавливается, а замедляется до трети скорости. Какое значение получит колесо?
2.Чем замедление колеса лучше остановки?
3.Тест onLine не проходит для датчика «наоборот» (white = 900, black = 120). Почему?
bool onLine(int value, int white, int black) {  return value > (white + black) / 2;}
4.Оба датчика видят белое. В каких двух положениях может быть робот?
5.Что будет делать робот с этой программой?
void setup() {  // … настройка моторов  leftSpeed = speed;  rightSpeed = speed;} void loop() {  readSensors();  if (onLine(leftValue, LEFT_WHITE, LEFT_BLACK)) {    leftSpeed = 0;  }  drive(FORWARD, leftSpeed, FORWARD, rightSpeed);}

Отвечай на вопросы своего уровня. Нужно верных ответов: Начальный — 4 из 5; Средний — 4 из 5; Продвинутый — 4 из 5. Ответы проверит преподаватель или программа курса.

Итог

Робот научился сам выбирать, какое колесо остановить, по показаниям двух датчиков. Мы не угадывали числа, а измерили их на своём поле. Рывки при движении — цена простого правила «едет или стоит». В уроке 3.2 робот научится поворачивать плавно: тем сильнее, чем дальше линия ушла в сторону.

Вернись к своей догадке из начала урока. Подтвердилась ли она?

Как подтвердить результат

Для каждого опыта сохраняй условия, версию программы, наблюдение и вывод. Сравнивая попытки, меняй одну причину за раз; неудачные попытки тоже остаются в журнале. Отметка результата относится к уже выполненным действиям, повторять их ради отметки не нужно. Если результат не получен, запиши расхождение и следующую проверяемую гипотезу. Модель, измерения настоящего робота и объяснение — разные виды доказательств.

Словарь урока

датчик линииДатчик, который светит на пол и измеряет, сколько света отразилось. Белое отражает много, чёрное — мало.
показаниеЧисло, которое программа получает от датчика. У нашей платы это число от 0 до 1023.
монитор портаПанель Serial Monitor в MyRobotica, где видны сообщения, которые плата отправляет на компьютер.
калибровкаНастройка программы по измерениям своего робота и своего поля.
переменнаяИменованное место в памяти, где программа хранит число.
условиеПроверка if: команды в фигурных скобках выполняются, только если проверка верна.
телеметрияПередача именованных измерений с временем и единицами, чтобы строить графики и сохранять наблюдения.
АЦПАналого-цифровое преобразование: напряжение на входе платы превращается в число от 0 до 1023.