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

Повороты на перекрёстке и маршрут

Робот сворачивает с перекрёстка на нужную линию и проходит маршрут из нескольких решений.

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

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

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

Ты научишься

  • Объяснить, почему поворот по датчикам надёжнее поворота по времени.
  • Подобрать выезд на центр перекрёстка, чтобы после поворота робот оказался на линии.
  • Записать маршрут последовательностью решений «налево / направо / прямо».
  • Проверить маршрут на поле и найти, на каком решении он ломается.

Нужно знать

  • 4.1 Находим и считаем перекрёстки
  • 2.2 Повороты и развороты

Понадобится

Датчик линии (аналоговый, Trema) × 2

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

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

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

Как повернуть точно

8 мин

Что должен делать робот. Роботу нужно пройти несколько перекрёстков и выбрать нужное направление на каждом. Используем датчики линии для обнаружения событий и заранее записанный план маршрута для выбора действия.

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

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

Предскажи

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

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

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

Предскажи

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

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

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

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

  1. Положи под робота лист с транспортиром или линиями через 10°. Поставь робота на центр перекрёстка.
  2. Медленно поворачивай руками налево. Запиши угол, на котором левый датчик впервые видит новую линию, и угол, на котором робот смотрит точно вдоль неё.
  3. Повтори направо.
  4. Совпадают ли углы? Что тогда сделает робот, который останавливается в момент, когда датчик увидел линию? Как это исправить?
Должно получиться

В журнале — два угла для каждого поворота и вывод о том, где остановится робот.

2

Выезд на центр перекрёстка

12 мин

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

  1. Поставь робота так, чтобы датчики были на поперечной линии.
  2. Измерь линейкой, сколько ещё ехать колёсам до центра перекрёстка. Запиши в журнал.
  3. Время этого проезда — centerMs — подберём на следующем шаге.
Должно получиться

Ты знаешь, почему перед поворотом нужен выезд на центр.

Датчики стоят впереди колёс. Когда датчики заметили перекрёсток, колёса ещё до него не доехали.

Предскажи

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

  1. Поставь робота датчиками на поперечную линию и поверни его руками на месте на 90°. Где оказались датчики — над новой линией или сбоку?
  2. Измерь линейкой, сколько ещё ехать колёсам до центра перекрёстка. Запиши в журнал.
  3. Время этого проезда — centerMs — подберёшь на следующем шаге. Как ты поймёшь, что время слишком маленькое, а как — что слишком большое? Запиши.
Должно получиться

Прогноз проверен руками, расстояние записано, признаки «мало» и «много» для centerMs записаны.

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

  1. Измерь расстояние от датчиков до оси колёс в миллиметрах.
  2. Возьми из журнала урока 2.1 свою скорость в миллиметрах в секунду при близкой baseSpeed. Нет таблицы — измерь: проезд 1 секунду, путь линейкой, три раза.
  3. Посчитай centerMs и запиши расчёт. На следующем шаге сравни его с подобранным на дорожке.
  4. Запиши, почему выезд по времени — слабое место и от чего его точность зависит.
Должно получиться

В журнале — расстояние, скорость, расчёт centerMs и две причины, по которым выезд по времени может ошибаться.

3

Поворот на перекрёстке

22 мин
  1. Создай скетч L42_Turn_At_Crossing. Перенеси измерения, kp и baseSpeed.
Поворот на перекрёсткеL42_Turn_At_Crossing.ino.ino
const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5;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;const int BACKWARD = (FORWARD == HIGH) ? LOW : HIGH; // Измерения и настройки из журнала (уроки 3.1 и 3.2).const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0;float kp = 0.5;int baseSpeed = 100; int leftLevel = 100;int rightLevel = 100; int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} void readSensors() {  leftLevel = level(analogRead(LEFT_SENSOR_PIN), LEFT_WHITE, LEFT_BLACK);  rightLevel = level(analogRead(RIGHT_SENSOR_PIN), RIGHT_WHITE, RIGHT_BLACK);} 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);} void stopMotors() {  analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);} void followLineStep() {  int error = leftLevel - rightLevel;  int correction = kp * error;  drive(FORWARD, constrain(baseSpeed + correction, 0, 255),        FORWARD, constrain(baseSpeed - correction, 0, 255));} void setupRobot() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);  stopMotors();  // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }  delay(2000);} // Перекрёсток: оба датчика видят чёрное.bool onCrossing() {  return leftLevel < 30 && rightLevel < 30;} // Ехать по линии, пока не найдём перекрёсток.void followLineToCrossing() {  while (true) {    readSensors();    if (onCrossing()) {      return;    }    followLineStep();  }} // Проехать перекрёсток прямо, пока датчики не съедут с чёрного.void passCrossing() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  do {    readSensors();  } while (onCrossing());  delay(100);} int turnSpeed = 100;int centerMs = 250; // подбираем: проехать от датчиков до центра перекрёсткаint leaveMs = 200;  // время, чтобы датчик ушёл со старой линии // Выехать на центр перекрёстка: датчики стоят впереди колёс.void toCenter() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  delay(centerMs);  stopMotors();} // Крутиться на месте, пока датчик со стороны поворота не найдёт новую линию.void turnUntilLine(bool left) {  if (left) {    drive(BACKWARD, turnSpeed, FORWARD, turnSpeed);  } else {    drive(FORWARD, turnSpeed, BACKWARD, turnSpeed);  }  delay(leaveMs);  do {    readSensors();  } while ((left ? leftLevel : rightLevel) > 30);  stopMotors();} void turnLeftAtCrossing() {  toCenter();  turnUntilLine(true);} void turnRightAtCrossing() {  toCenter();  turnUntilLine(false);} // Развернуться на линии: крутиться, пока линия снова не окажется под датчиком.void turnAroundOnLine() {  turnUntilLine(true);} // Ехать по линии заданное время.void followLineFor(unsigned long ms) {  unsigned long start = millis();  while (millis() - start < ms) {    readSensors();    followLineStep();  }  stopMotors();} void setup() {  setupRobot();  followLineToCrossing();  turnLeftAtCrossing();  followLineFor(1500);  turnAroundOnLine();  followLineToCrossing();  stopMotors();} void loop() {}
  1. Разберите функцию turnUntilLine. Зачем в ней delay(leaveMs) перед поиском линии?
  2. Запусти на дорожке с перекрёстком. Подбери centerMs: после поворота робот должен уверенно поехать по новой линии.
Должно получиться

Робот доезжает до перекрёстка, поворачивает налево и едет по новой линии.

Как это устроено: Зачем leaveMs

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

Если не получилось
Что видишьЧто сделать
После поворота робот сбоку от линииПодбери centerMs
Робот крутится бесконечноДатчик не видит линию: проверь порог 30 и измерения
Робот останавливается сразуУвеличь leaveMs
  1. Создай скетч L42_Turn_At_Crossing по шаблону. Перенеси измерения, kp и baseSpeed. Функции линии и перекрёстков уже готовы.
  2. Напиши сам выезд на центр toCenter(), поворот до линии turnUntilLine() и повороты с перекрёстка налево и направо. setup() ниже проверяет их на дорожке.
Шаблон: поворот на перекрёсткеL42_Turn_At_Crossing_Gaps.ino.ino
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях.// setup() ниже — проверка твоих функций на дорожке. const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5;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;const int BACKWARD = (FORWARD == HIGH) ? LOW : HIGH; // Измерения и настройки из журнала (уроки 3.1 и 3.2).const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0;float kp = 0.5;int baseSpeed = 100;int leftLevel = 100;int rightLevel = 100;int turnSpeed = 100;int centerMs = 250; // подбираем: проехать от датчиков до центра перекрёсткаint leaveMs = 200;  // время, чтобы датчик ушёл со старой линии // ===== Твоя часть: напиши сам ===== void toCenter() {  // Колёса встают над центром перекрёстка: проехать вперёд centerMs и остановиться.} void turnUntilLine(bool left) {  // Крутиться на месте (left = true — налево) до новой линии; в конце колёса стоят.  // Пока датчик ещё над старой линией, она не должна считаться новой — для этого есть leaveMs.} void turnLeftAtCrossing() {  // Повернуть с перекрёстка налево на новую линию.} void turnRightAtCrossing() {  // Повернуть с перекрёстка направо на новую линию.} // ===== Готовое из уроков 3.2–4.1 и проверка — не меняй ===== int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} void readSensors() {  leftLevel = level(analogRead(LEFT_SENSOR_PIN), LEFT_WHITE, LEFT_BLACK);  rightLevel = level(analogRead(RIGHT_SENSOR_PIN), RIGHT_WHITE, RIGHT_BLACK);} 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);} void stopMotors() {  analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);} void followLineStep() {  int error = leftLevel - rightLevel;  int correction = kp * error;  drive(FORWARD, constrain(baseSpeed + correction, 0, 255),        FORWARD, constrain(baseSpeed - correction, 0, 255));} void setupRobot() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);  stopMotors();  // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }  delay(2000);} // Перекрёсток: оба датчика видят чёрное.bool onCrossing() {  return leftLevel < 30 && rightLevel < 30;} // Ехать по линии, пока не найдём перекрёсток.void followLineToCrossing() {  while (true) {    readSensors();    if (onCrossing()) {      return;    }    followLineStep();  }} // Проехать перекрёсток прямо, пока датчики не съедут с чёрного.void passCrossing() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  do {    readSensors();  } while (onCrossing());  delay(100);} // Развернуться на линии: крутиться, пока линия снова не окажется под датчиком.void turnAroundOnLine() {  turnUntilLine(true);} // Ехать по линии заданное время.void followLineFor(unsigned long ms) {  unsigned long start = millis();  while (millis() - start < ms) {    readSensors();    followLineStep();  }  stopMotors();} void setup() {  setupRobot();  followLineToCrossing();  turnLeftAtCrossing();  followLineFor(1500);  turnAroundOnLine();  followLineToCrossing();  stopMotors();} void loop() {}
Предскажи

Что случится, если начать искать новую линию сразу, не выждав leaveMs?

  1. Запусти на дорожке с перекрёстком. Подбери centerMs по признакам, которые записал в прошлом шаге.
Подсказка: Крутиться до линии

Это похоже на passCrossing() из урока 4.1: повторять чтение датчиков, пока условие верно. Какое условие значит «линии под датчиком ещё нет»?

Подсказка: Какой датчик смотреть

Поворачиваем налево — новую линию первым увидит левый датчик.

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

Робот доезжает до перекрёстка, поворачивает налево и едет по новой линии. Прогноз записан.

Если не получилось
Что видишьЧто сделать
После поворота робот сбоку от линииПодбери centerMs
Робот крутится бесконечноДатчик не видит линию: проверь условие поиска и измерения
Робот останавливается сразуУвеличь leaveMs

Требование 1. Продолжи свою программу L41_Count и сохрани как L42_Turn_At_Crossing. Функция turnUntilLine(left) поворачивает робота на месте до новой линии:

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

Требование 2. turnLeftAtCrossing() и turnRightAtCrossing(): выезд на центр по твоему centerMs, затем поворот до линии.

Приёмка. По пять поворотов налево и направо: после каждого робот уезжает по новой линии. Отдельно проверь защиту: подними робота над столом во время поворота.

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

Поиск линии — цикл «читать датчики, пока линии нет». Для защиты запомни время начала поиска.

Подсказка: План (2 из 3)
  1. Включить поворот, подождать leaveMs.
  2. Запомнить millis().
  3. Повторять чтение, пока линии нет и прошло меньше 3 секунд.
  4. Остановить моторы.
Подсказка: Фрагмент кода (3 из 3)

while ((left ? leftLevel : rightLevel) > 30 && millis() - start < 3000) — прочитай условие словами.

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

Программа выполняет требования 1–2, десять поворотов и проверка защиты записаны. Подобранный centerMs сравнён с расчётом.

2
Занятие 2 · 45 мин
Маршрут из решений
4

Разворот на линии

10 мин

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

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

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

В конце ответвления роботу нужно развернуться и вернуться к перекрёстку. Разворот — тот же поворот по датчикам, только линия теперь сзади.

  1. Найди в скетче функцию turnAroundOnLine(). Почему в ней нет выезда на центр, как в повороте на перекрёстке? Запиши ответ.
  2. Проверь на дорожке три раза: проехать по ответвлению, развернуться, вернуться к перекрёстку.
Должно получиться

Робот три раза разворачивается и возвращается к перекрёстку. Ответ про выезд на центр записан.

Требование 3. turnAroundOnLine() разворачивает робота на линии и останавливается, когда линия снова под датчиком. Защита 3 секунды из требования 1 работает и здесь.

Приёмка. Пять разворотов на конце ответвления: после каждого робот возвращается к перекрёстку. Для каждого измерь, насколько нос смотрит мимо линии сразу после разворота.

РазворотВернулся?Нос мимо линии, мм
1

Если нос каждый раз смотрит в одну сторону, предложи и проверь одно исправление.

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

Пять разворотов в таблице, исправление систематической ошибки проверено.

5

Маршрут по порядку

22 мин

Маршрут — это решения на перекрёстках по порядку. Робот не видит, какой это перекрёсток: он просто выполняет следующее решение.

  1. Создай скетч L42_Route. Начерти в журнале тренировочную дорожку и путь робота.
МаршрутL42_Route.ino.ino
const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5;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;const int BACKWARD = (FORWARD == HIGH) ? LOW : HIGH; // Измерения и настройки из журнала (уроки 3.1 и 3.2).const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0;float kp = 0.5;int baseSpeed = 100; int leftLevel = 100;int rightLevel = 100; int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} void readSensors() {  leftLevel = level(analogRead(LEFT_SENSOR_PIN), LEFT_WHITE, LEFT_BLACK);  rightLevel = level(analogRead(RIGHT_SENSOR_PIN), RIGHT_WHITE, RIGHT_BLACK);} 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);} void stopMotors() {  analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);} void followLineStep() {  int error = leftLevel - rightLevel;  int correction = kp * error;  drive(FORWARD, constrain(baseSpeed + correction, 0, 255),        FORWARD, constrain(baseSpeed - correction, 0, 255));} void setupRobot() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);  stopMotors();  // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }  delay(2000);} // Перекрёсток: оба датчика видят чёрное.bool onCrossing() {  return leftLevel < 30 && rightLevel < 30;} // Ехать по линии, пока не найдём перекрёсток.void followLineToCrossing() {  while (true) {    readSensors();    if (onCrossing()) {      return;    }    followLineStep();  }} // Проехать перекрёсток прямо, пока датчики не съедут с чёрного.void passCrossing() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  do {    readSensors();  } while (onCrossing());  delay(100);} int turnSpeed = 100;int centerMs = 250; // подбираем: проехать от датчиков до центра перекрёсткаint leaveMs = 200;  // время, чтобы датчик ушёл со старой линии // Выехать на центр перекрёстка: датчики стоят впереди колёс.void toCenter() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  delay(centerMs);  stopMotors();} // Крутиться на месте, пока датчик со стороны поворота не найдёт новую линию.void turnUntilLine(bool left) {  if (left) {    drive(BACKWARD, turnSpeed, FORWARD, turnSpeed);  } else {    drive(FORWARD, turnSpeed, BACKWARD, turnSpeed);  }  delay(leaveMs);  do {    readSensors();  } while ((left ? leftLevel : rightLevel) > 30);  stopMotors();} void turnLeftAtCrossing() {  toCenter();  turnUntilLine(true);} void turnRightAtCrossing() {  toCenter();  turnUntilLine(false);} // Развернуться на линии: крутиться, пока линия снова не окажется под датчиком.void turnAroundOnLine() {  turnUntilLine(true);} // Ехать по линии заданное время.void followLineFor(unsigned long ms) {  unsigned long start = millis();  while (millis() - start < ms) {    readSensors();    followLineStep();  }  stopMotors();} // Маршрут: решения на перекрёстках по порядку.void setup() {  setupRobot();   followLineToCrossing();  turnLeftAtCrossing();   // решение 1: налево   followLineToCrossing();  passCrossing();         // решение 2: прямо   followLineToCrossing();  turnRightAtCrossing();  // решение 3: направо   followLineToCrossing();  stopMotors();           // конец маршрута} void loop() {}
Предскажи

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

  1. Запусти. Если маршрут сломался — на каком решении? Почему?
Должно получиться

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

Найди ошибку · Робот поворачивает не на том перекрёстке

У соседей после turnLeftAtCrossing() сразу стоит turnRightAtCrossing(), без followLineToCrossing() между ними. Что сделает робот?

Маршрут — это решения на перекрёстках по порядку. Робот не видит, какой это перекрёсток: он просто выполняет следующее решение.

  1. Начерти в журнале тренировочную дорожку и путь: налево, прямо, направо.
  2. Создай скетч L42_Route по шаблону и напиши маршрут в setup() сам из готовых функций урока.
Шаблон: маршрутL42_Route_Gaps.ino.ino
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях. const int LEFT_SENSOR_PIN = A4;const int RIGHT_SENSOR_PIN = A5;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;const int BACKWARD = (FORWARD == HIGH) ? LOW : HIGH; // Измерения и настройки из журнала (уроки 3.1 и 3.2).const int LEFT_WHITE = 0;const int LEFT_BLACK = 0;const int RIGHT_WHITE = 0;const int RIGHT_BLACK = 0;float kp = 0.5;int baseSpeed = 100;int leftLevel = 100;int rightLevel = 100;int turnSpeed = 100;int centerMs = 250; // подбираем: проехать от датчиков до центра перекрёсткаint leaveMs = 200;  // время, чтобы датчик ушёл со старой линии // ===== Твоя часть: напиши сам ===== void setup() {  setupRobot();   // Маршрут из журнала: на каждом перекрёстке — своё решение по порядку.  // В конце остановиться на следующем перекрёстке.} // ===== Готовое из уроков 3.2–4.2 — не меняй ===== int level(int value, int white, int black) {  long result = (long)(value - black) * 100 / (white - black);  return constrain(result, 0, 100);} void readSensors() {  leftLevel = level(analogRead(LEFT_SENSOR_PIN), LEFT_WHITE, LEFT_BLACK);  rightLevel = level(analogRead(RIGHT_SENSOR_PIN), RIGHT_WHITE, RIGHT_BLACK);} 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);} void stopMotors() {  analogWrite(LEFT_SPEED_PIN, 0);  analogWrite(RIGHT_SPEED_PIN, 0);} void followLineStep() {  int error = leftLevel - rightLevel;  int correction = kp * error;  drive(FORWARD, constrain(baseSpeed + correction, 0, 255),        FORWARD, constrain(baseSpeed - correction, 0, 255));} void setupRobot() {  pinMode(LEFT_DIR_PIN, OUTPUT);  pinMode(LEFT_SPEED_PIN, OUTPUT);  pinMode(RIGHT_DIR_PIN, OUTPUT);  pinMode(RIGHT_SPEED_PIN, OUTPUT);  stopMotors();  // Без измерений робот не поедет.  if (LEFT_WHITE == LEFT_BLACK || RIGHT_WHITE == RIGHT_BLACK) {    while (true) {    }  }  delay(2000);} // Перекрёсток: оба датчика видят чёрное.bool onCrossing() {  return leftLevel < 30 && rightLevel < 30;} // Ехать по линии, пока не найдём перекрёсток.void followLineToCrossing() {  while (true) {    readSensors();    if (onCrossing()) {      return;    }    followLineStep();  }} // Проехать перекрёсток прямо, пока датчики не съедут с чёрного.void passCrossing() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  do {    readSensors();  } while (onCrossing());  delay(100);} // Выехать на центр перекрёстка: датчики стоят впереди колёс.void toCenter() {  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);  delay(centerMs);  stopMotors();} // Крутиться на месте, пока датчик со стороны поворота не найдёт новую линию.void turnUntilLine(bool left) {  if (left) {    drive(BACKWARD, turnSpeed, FORWARD, turnSpeed);  } else {    drive(FORWARD, turnSpeed, BACKWARD, turnSpeed);  }  delay(leaveMs);  do {    readSensors();  } while ((left ? leftLevel : rightLevel) > 30);  stopMotors();} void turnLeftAtCrossing() {  toCenter();  turnUntilLine(true);} void turnRightAtCrossing() {  toCenter();  turnUntilLine(false);} // Развернуться на линии: крутиться, пока линия снова не окажется под датчиком.void turnAroundOnLine() {  turnUntilLine(true);} // Ехать по линии заданное время.void followLineFor(unsigned long ms) {  unsigned long start = millis();  while (millis() - start < ms) {    readSensors();    followLineStep();  }  stopMotors();} void loop() {}
Предскажи

Нарисуй, где робот остановится на дорожке.

  1. Запусти. Если маршрут сломался — на каком решении? Почему?
Подсказка: Одно решение

Перед каждым решением робот должен доехать до перекрёстка.

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

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

Найди ошибку · Робот поворачивает не на том перекрёстке

У соседей после turnLeftAtCrossing() сразу стоит turnRightAtCrossing(), без followLineToCrossing() между ними. Что сделает робот?

Требование 4. Маршрут планируется до программы и проверяется по частям.

  1. Выбери на тренировочном поле путь из четырёх решений или больше, в котором есть налево, направо и прямо. Если в классе разложено поле итоговой задачи, возьми путь от старта до одной зоны доставки. Запиши его таблицей:
№ПерекрёстокРешениеКуда смотрит робот после
1
  1. Запиши маршрут в программу L42_Route из своих функций.
  2. Если маршрут сломался, не переписывай всё: запусти только первые решения, потом половину, и найди первое решение, на котором робот ошибается. Запиши, как искал.
Предскажи

На каком решении маршрут скорее всего сломается и почему? Запиши до первого запуска.

Подсказка: Как проверять по частям

Временно поставь stopMotors(); и return; после нужного решения в setup().

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

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

6

Задания по уровням

13 мин
Основное · Поворот и разворот

Повернуть с перекрёстка налево и направо и уехать по новой линии; развернуться на линии и вернуться.

Средний уровень · Четыре решения

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

Продвинутый уровень · Маршрут массивом

Запиши решения массивом: char route[] = {'L', 'S', 'R', 'L'};. Напиши функцию decide(d), которая выполняет одно решение, и цикл, который проходит весь массив. Составь маршрут по схеме поля так, чтобы для нового маршрута менялась только строка с массивом.

Проверь себя

Проверь себя

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

● Основное

1.Почему поворот «до линии» надёжнее поворота по времени?
2.Зачем перед поворотом проехать немного вперёд (centerMs)?
3.Что случится, если в turnUntilLine убрать delay(leaveMs)?
4.Маршрут: налево, прямо, направо. Что выполнит робот на втором перекрёстке?
5.char route[] = {'L', 'S', 'R'}; Чему равно route[2]?

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

1.Маршрут: налево, прямо, направо, налево. Сколько решений на перекрёстках выполнит робот?
2.Зачем маршрут сначала записывают в журнал, а потом в программу?
3.После поворота робот оказался сбоку от новой линии — со стороны, откуда приехал. Что поменять?
4.Для какого поворота подходит этот поиск линии?
do {  readSensors();} while (leftLevel > 30);
5.Почему каждое решение маршрута — это две строки: followLineToCrossing() и действие?

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

1.Какое решение выполнится третьим?
char route[] = {'L', 'S', 'R', 'L'};
2.Что нужно изменить в программе с массивом, чтобы пройти новый маршрут?
3.От датчиков до оси колёс 60 мм, скорость робота 200 мм/с. Каким должен быть centerMs по расчёту?
4.Робота подняли над столом во время поворота. Что произойдёт?
unsigned long start = millis();do {  readSensors();} while ((left ? leftLevel : rightLevel) > 30 && millis() - start < 3000);stopMotors();
5.Маршрут из восьми решений ломается. Как быстрее найти сломанное решение?

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

Итог

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

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

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

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

маршрутПоследовательность решений на перекрёстках: налево, направо или прямо.
центр перекрёсткаТочка, где пересекаются линии. Для поворота на месте над ней должны оказаться колёса.
массивСписок значений под одним именем: route[0], route[1] и так далее.
millis()Сколько миллисекунд прошло с включения платы. Позволяет ехать заданное время, не останавливая программу.