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

Находим и считаем перекрёстки

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

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

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

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

Ты научишься

  • Описать, что показывают датчики на перекрёстке.
  • Записать условие перекрёстка и объяснить выбор порога.
  • Объяснить, почему один перекрёсток может посчитаться много раз, и исправить это.
  • Остановить робота на перекрёстке с заданным номером.

Нужно знать

  • 3.2 П-регулятор: плавно и быстро

Понадобится

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

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

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

Разминка · вспомни
  1. Чему равна ошибка П-регулятора, когда линия ровно посередине?
  2. Что показывает датчик по шкале 0–100 на чёрном?
  3. Что делает constrain?
1
Занятие 1 · 45 мин
Перекрёсток глазами датчиков
1

Что видят датчики на перекрёстке

12 мин

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

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

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

Предскажи

Робот подъезжает к перекрёстку. Что покажут оба датчика по шкале 0–100, когда он окажется на поперечной линии?

  1. Загрузи скетч L32_Error из урока 3.2.
  2. Медленно провези робота руками по тренировочной дорожке через перекрёсток. Смотри на монитор порта.
  3. Запиши в журнал показания: на линии посередине, на перекрёстке, после перекрёстка.
Должно получиться

На перекрёстке оба датчика показывают почти 0: под ними чёрная поперечная линия.

Предскажи

Робот подъезжает к перекрёстку. Что покажут оба датчика по шкале 0–100, когда он окажется на поперечной линии? А на изгибе линии?

  1. Загрузи скетч со шкалой из урока 3.2 (L32_Error или свой шаблон).
  2. Медленно провези робота руками по дорожке через изгиб и через перекрёсток. Смотри на монитор порта.
  3. Заполни таблицу и ответь: по какому признаку перекрёсток можно отличить от изгиба?
ПоложениеleftLevelrightLevel
Линия посередине
На изгибе
На перекрёстке
После перекрёстка
Должно получиться

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

Сколько показаний робот успевает получить, пока едет по поперечной линии?

  1. Добавь в свою программу из урока 3.2 счётчик повторов loop() и раз в секунду печатай, сколько повторов прошло. Запиши число повторов в секунду на рабочей скорости.
  2. Возьми свою скорость в мм/с из урока 2.1 или измерь её заново.
  3. Посчитай: сколько повторов придётся на поперечную линию шириной 19 мм?
  4. Провези робота руками через перекрёсток и сравни показания со своей таблицей положений.
Подсказка: Как посчитать

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

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

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

2

Условие перекрёстка

10 мин

Запишем правило: перекрёсток — когда оба датчика видят чёрное. «Чёрное» — показание меньше порога 30.

bool onCrossing() {
  return leftLevel < 30 && rightLevel < 30;
}

&& читается как «и»: условие верно, только если верны обе части.

Подсказка: Почему 30, а не 0

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

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

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

Сформулируй правило сам по своим записям из прошлого шага: какие показания бывают только на перекрёстке и никогда — на прямой или изгибе?

  1. Запиши правило словами в журнал: что должно быть у левого датчика и что у правого.
  2. Выбери порог «чёрного» по своим измерениям. Объясни, почему не 0 и почему не 50.
  3. Запиши условие на языке Arduino. Понадобится знак «и»: &&.
Предскажи

Кто-то написал условие через || («или»). Где робот тогда остановится по ошибке?

Подсказка: Как записать «и»

a < 30 && b < 30 верно, только если верны обе части.

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

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

Требование 1. Функция onCrossing() отвечает true на поперечной линии и false во всех остальных положениях робота.

Проверь условие на бумаге, прежде чем писать код. Для каждого положения запиши показания левого и правого датчика по шкале 0–100 и ответ функции:

ПоложениеleftLevelrightLevelonCrossing()
Линия посередине
Линия под одним датчиком на крутой дуге 125 мм
Линия ушла за оба датчика
Поперечная линия
  1. Выбери порог по своим измерениям и обоснуй его: какой запас до самого тёмного показания вне перекрёстка?
  2. Найди положение, в котором твоё условие может ошибиться. Что тогда сделает робот?
Подсказка: Где искать ошибку

Провези робота руками по дуге 125 мм из урока 3.2: насколько тёмным становится «край» линии под датчиком?

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

Таблица заполнена по измерениям, порог обоснован запасом, опасное положение и его последствие записаны.

3

Стоп на перекрёстке

20 мин
  1. Создай скетч L41_Crossing_Stop. Перенеси измерения, kp и baseSpeed из журнала.
Стоп на перекрёсткеL41_Crossing_Stop.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 setup() {  setupRobot();  followLineToCrossing();  stopMotors();} void loop() {}
  1. Найди функцию followLineToCrossing(). Объясни напарнику, как из неё выходит программа.
  2. Поставь робота на линию перед перекрёстком и запусти.
Должно получиться

Робот едет по линии и останавливается на первом перекрёстке.

Как это устроено: Как работает followLineToCrossing

while (true) — повторять всегда. Внутри: прочитать датчики; если перекрёсток — return, то есть выйти из функции; иначе сделать шаг П-регулятора. После выхода программа продолжается со строки stopMotors(); в setup().

Если не получилось
Что видишьЧто сделать
Робот проезжает перекрёстокСнизь baseSpeed или подними порог до 40
Робот останавливается на изгибеПорог слишком высокий, попробуй 20
Робот не едетВписаны ли измерения из журнала?
  1. Создай скетч L41_Crossing_Stop по шаблону. Перенеси измерения, kp и baseSpeed из журнала. Функции линии из урока 3.2 уже готовы.
  2. Напиши сам onCrossing() по своему правилу из прошлого шага, followLineToCrossing() и движение в setup(). Пока onCrossing() не написана, она всегда отвечает «перекрёсток», и робот не трогается.
Шаблон: стоп на перекрёсткеL41_Crossing_Stop_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; // ===== Твоя часть: напиши сам ===== bool onCrossing() {  // true — только когда робот на поперечной линии; на прямой и на изгибе — false.  return true;} void followLineToCrossing() {  // Ехать по линии с П-регулятором, пока не найдёшь перекрёсток; на перекрёстке функция заканчивается.} void setup() {  setupRobot();   // Доехать по линии до первого перекрёстка и остановиться.} // ===== Готовое из уроков 3.1–3.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);} void loop() {}
Предскажи

Что сделает робот, если после поиска перекрёстка забыть остановить моторы?

  1. Поставь робота на линию перед перекрёстком и запусти.
Подсказка: Ехать, пока…

Функция повторяет одно и то же — прочитать датчики, сделать шаг по линии — и заканчивается, когда найден перекрёсток. Какой цикл повторяет «всегда», и как выйти из функции посреди него?

Подсказка: Как выйти из функции

return внутри функции сразу заканчивает её. Программа продолжается со следующей строки в setup().

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

Робот едет по линии и останавливается на первом перекрёстке. Прогноз записан.

Если не получилось
Что видишьЧто сделать
Робот проезжает перекрёстокСнизь baseSpeed или подними порог
Робот останавливается на изгибеПорог слишком высокий
Робот не едетВписаны ли измерения? Написана ли onCrossing()?

Требование 2. Продолжи свою программу L32_P_Line из урока 3.2 и сохрани как L41_Crossing_Stop:

  • шаг П-регулятора вынесен в функцию followLineStep(), чтение датчиков — в readSensors(); П-регулятор при этом работает как раньше;
  • функция followLineToCrossing() ведёт робота по линии и заканчивается, как только onCrossing() вернул true;
  • в setup() робот доезжает до первого перекрёстка и останавливается;
  • защита без измерений и пауза на старте остаются.

Приёмка. Робот останавливается на перекрёстке пять раз из пяти и ни разу на дуге своего уровня.

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

Функция «едет, пока…» — это бесконечный цикл, из которого есть выход по условию.

Подсказка: План (2 из 3)
  1. Повторять всегда: прочитать датчики.
  2. Перекрёсток — выйти из функции.
  3. Иначе — шаг П-регулятора.
Подсказка: Фрагмент кода (3 из 3)

while (true) { readSensors(); if (onCrossing()) { return; } … }

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

Программа выполняет требования 1–2, пять остановок из пяти записаны в журнал.

2
Занятие 2 · 45 мин
Считаем перекрёстки
4

Найди ошибку счёта

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

Соседи хотели считать перекрёстки так:

void loop() {
  readSensors();
  if (onCrossing()) {
    count = count + 1;
  }
  followLineStep();
}

Робот проехал один перекрёсток, а count стал 40. Почему?

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

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

Найди ошибку · Пауза не помогла

Соседи заметили двойной счёт и добавили паузу после счёта:

void loop() {
  readSensors();
  if (onCrossing()) {
    count = count + 1;
    delay(100);
  }
  followLineStep();
}

Робот проехал один перекрёсток, а count стал 3. Почему пауза не помогла? Чем она ещё опасна для езды по линии?

  1. Запиши ответ в журнал.
  2. Предложи исправление словами: что должна сделать программа после того, как нашла перекрёсток, прежде чем искать следующий?
Должно получиться

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

Найди ошибку · Счёт по моменту появления, а всё равно 40

Соседи решили считать только момент, когда перекрёсток появился:

bool was = false;

void loop() {
  readSensors();
  bool now = onCrossing();
  if (now && !was) {
    count = count + 1;
  }
  followLineStep();
}

Робот проехал один перекрёсток, а count всё равно стал 40. Где ошибка?

  1. Найди ошибку и запиши исправленный код в журнал.
  2. Сравни 40 со своим расчётом числа повторов на линии из прошлого шага. Похоже?
  3. Чем счёт по моменту появления хуже или лучше проезда перекрёстка, который будет в следующем шаге?
Подсказка: Что хранит was

Когда в этой программе меняется значение was?

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

Ошибка найдена, исправление записано кодом, число 40 объяснено расчётом, сравнение двух способов записано.

5

Проехать перекрёсток

10 мин

Чтобы посчитать перекрёсток один раз, его нужно проехать: ехать прямо, пока датчики не съедут с чёрного.

void passCrossing() {
  drive(FORWARD, baseSpeed, FORWARD, baseSpeed);
  do {
    readSensors();
  } while (onCrossing());
  delay(100);
}

do { … } while (условие) — сделать и повторять, пока условие верно. Короткая пауза в конце уводит датчики подальше от края поперечной линии.

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

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

Чтобы посчитать перекрёсток один раз, его нужно проехать: ехать прямо, пока датчики не съедут с чёрного. Напиши функцию passCrossing() в журнале:

  1. Включи оба мотора вперёд со скоростью baseSpeed.
  2. Читай датчики, пока робот на перекрёстке.
  3. После этого подожди 100 мс.

do { … } while (условие); — сделать и повторять, пока условие верно.

Подсказка: Почему do, а не while

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

Подсказка: Зачем пауза в конце

Сразу после чёрного датчики видят край поперечной линии. Что будет, если в этот момент снова искать перекрёсток?

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

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

Требование 3. Функция passCrossing() проезжает перекрёсток прямо и заканчивается, когда датчики уже не на поперечной линии и не на её краю.

  1. Напиши функцию в своей программе.
  2. Исследуй паузу после схода с чёрного: 0, 50, 100, 200 мс. Для каждой запусти робота через два перекрёстка подряд (в setup() — followLineToCrossing(), passCrossing(), followLineToCrossing(), стоп) и отметь, на каком перекрёстке он остановился.
  3. Запиши, какая пауза нужна и от чего она зависит. Что будет, если поднять baseSpeed?
Пауза, мсГде остановилсяЧто видно
0
50
100
200
Подсказка: Идея (1 из 2)

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

Подсказка: Фрагмент кода (2 из 2)

do { readSensors(); } while (onCrossing()); — почему здесь do, а не while?

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

passCrossing() работает, таблица паузы заполнена, вывод о связи паузы и скорости записан.

6

Стоп на нужном перекрёстке

17 мин
  1. Создай скетч L41_Count. В TARGET укажи, на каком перекрёстке остановиться.
Счёт перекрёстковL41_Count.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);} const int TARGET = 1; // на каком перекрёстке остановиться void setup() {  setupRobot();  for (int i = 1; i < TARGET; i++) {    followLineToCrossing();    passCrossing();  }  followLineToCrossing();  stopMotors();} void loop() {}
Предскажи

TARGET = 2. Сколько раз выполнится цикл for? Что робот сделает на первом перекрёстке?

  1. Проверь на дорожке с тремя перекрёстками: TARGET = 1, 2, 3.
Должно получиться

Робот останавливается ровно на перекрёстке с номером TARGET.

  1. Создай скетч L41_Count по шаблону. В TARGET укажи, на каком перекрёстке остановиться.
  2. Напиши сам passCrossing() — по своей записи в журнале — и движение в setup(). Пока твоя часть не написана, робот не трогается.
Шаблон: счёт перекрёстковL41_Count_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;const int TARGET = 1; // на каком перекрёстке остановиться // ===== Твоя часть: напиши сам ===== void passCrossing() {  // Проехать перекрёсток прямо. Функция заканчивается, когда датчики уже не на поперечной линии и не на её краю.} void setup() {  setupRobot();   // Перекрёстки до TARGET — найти и проехать прямо; на перекрёстке номер TARGET остановиться.} // ===== Готовое из уроков 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 loop() {}
Предскажи

TARGET = 3. Сколько раз робот должен проехать перекрёсток прямо?

  1. Проверь на дорожке с тремя перекрёстками: TARGET = 1, 2, 3.
Подсказка: Повторить нужное число раз

Цикл for из урока 1.1 повторяет команды заданное число раз. Сколько перекрёстков нужно проехать перед тем, на котором остановиться?

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

Робот останавливается ровно на перекрёстке с номером TARGET.

Требование 4. Функция goToCrossing(n) ведёт робота по линии, проезжает прямо перекрёстки до n-го и останавливается на n-м. Сохрани программу как L41_Count.

Приёмка. Дорожка с тремя перекрёстками, n = 1, 2, 3, по три запуска на каждое.

nЗапуск 1Запуск 2Запуск 3
1
2
3
Предскажи

Что должна сделать функция при n = 0? Реши сам, запиши и проверь, что программа делает именно это.

Подсказка: План (1 из 2)

n - 1 раз: найти перекрёсток и проехать его. Потом найти последний и остановиться.

Подсказка: Фрагмент кода (2 из 2)

for (int i = 1; i < n; i++) { … } — сколько раз выполнится цикл при n = 1?

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

Девять запусков без ошибки в таблице, поведение при n = 0 выбрано и проверено.

7

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

10 мин
Основное · Первый перекрёсток

Доехать по линии до первого перекрёстка и остановиться на нём.

Средний уровень · Третий перекрёсток

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

Продвинутый уровень · Без остановок

Сделай так, чтобы робот проезжал все перекрёстки без остановки и не терял линию после них. Подсказка: в loop() — если перекрёсток, passCrossing(), иначе шаг П-регулятора.

Проверь себя

Проверь себя

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

● Основное

1.По какому признаку программа узнаёт перекрёсток?
2.Что вернёт выражение leftLevel < 30 && rightLevel < 30, если leftLevel = 10, rightLevel = 80?
3.Почему счётчик перекрёстков растёт много раз на одном перекрёстке?
4.Что делает return внутри followLineToCrossing()?
5.TARGET = 3. Сколько перекрёстков робот проедет прямо до остановки?

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

1.Счётчик crossings начинается с 0 и увеличивается на каждом перекрёстке. При каком значении остановиться на третьем перекрёстке?
2.Условие перекрёстка записали через «или»: leftLevel < 30 || rightLevel < 30. Где робот остановится?
3.Почему порог «чёрного» — 30, а не 0?
4.В шаблоне onCrossing() пока возвращает true. Что делает робот после запуска?
5.Зачем в passCrossing() цикл do … while, а не while?

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

1.Что должна делать функция passCrossing()?
2.Что помогает не потерять линию сразу после перекрёстка? Выбери все верные ответы.
3.Без паузы после схода с поперечной линии робот иногда останавливается сразу за перекрёстком. Почему?
4.Вызвали goToCrossing(2). На каком перекрёстке остановится робот?
void goToCrossing(int n) {  for (int i = 0; i < n; i++) {    followLineToCrossing();    passCrossing();  }  followLineToCrossing();  stopMotors();}
5.На крутой дуге робот иногда останавливается, будто на перекрёстке. Что проверить первым?

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

Итог

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

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

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

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

перекрёстокМесто, где линию пересекает другая линия. Оба датчика одновременно видят чёрное.
порогЧисло для сравнения: показание меньше 30 по шкале 0–100 считаем чёрным.
цикл whileПовторяет команды, пока условие верно.
returnНемедленно завершает функцию и возвращает программу туда, откуда функцию вызвали.