Повороты на перекрёстке и маршрут
Робот сворачивает с перекрёстка на нужную линию и проходит маршрут из нескольких решений.
Результат урока
Робот поворачивает с перекрёстка налево или направо до новой линии, разворачивается на линии и проходит маршрут из нескольких решений, записанных в программе по порядку.
Ты научишься
- Объяснить, почему поворот по датчикам надёжнее поворота по времени.
- Подобрать выезд на центр перекрёстка, чтобы после поворота робот оказался на линии.
- Записать маршрут последовательностью решений «налево / направо / прямо».
- Проверить маршрут на поле и найти, на каком решении он ломается.
Нужно знать
- 4.1 Находим и считаем перекрёстки
- 2.2 Повороты и развороты
Понадобится
Зачем этот урок
Робот находит перекрёсток. Теперь он должен свернуть — к грузу, к месту доставки или дальше по дороге. В уроке 2.2 повороты были по времени и зависели от батареи. Сегодня робот будет поворачивать по датчикам: крутиться, пока не найдёт новую линию.
- Почему поворот по времени получается разным при разряженной батарее?
- Что делает
passCrossing()? - Как робот понимает, что под ним перекрёсток?
Как повернуть точно
8 минЧто должен делать робот. Роботу нужно пройти несколько перекрёстков и выбрать нужное направление на каждом. Используем датчики линии для обнаружения событий и заранее записанный план маршрута для выбора действия.
Что используем и как это работает. Датчики отвечают на вопрос «что сейчас под роботом», а маршрут — «что делать следующим». Программа доезжает до перекрёстка, выполняет поворот или проезд прямо, выходит с поперечной полосы и переходит к следующему пункту. Массив или последовательность вызовов хранит порядок, но не измеряет положение робота.
Как связать измерение с действием. Проверяем маршрут по участкам: отмечаем, какой перекрёсток найден и какое действие выполнено. Если робот не обнаружил полосу или повернул не туда, следующие команды будут выполняться уже в другом месте. Поэтому сначала проверим каждую функцию, затем соберём целый маршрут.
Робот стоит на перекрёстке и крутится налево. Как он может сам понять, что повернул достаточно и уже смотрит вдоль новой линии?
- Обсудите с напарником. Поставьте робота на перекрёсток и медленно поверните руками.
- Следите за левым датчиком: когда он окажется над новой линией?
Ты можешь объяснить идею: крутиться, пока датчик со стороны поворота не увидит новую линию.
Робот стоит на перекрёстке и крутится налево. Как он может сам понять, что повернул достаточно и уже смотрит вдоль новой линии?
- Поставь робота на центр перекрёстка и медленно поверни руками налево, потом направо.
- Для каждого поворота запиши: какой датчик первым оказался над новой линией и какой датчик проходит над старой линией в начале поворота.
| Поворот | Первым видит новую линию | Сначала проходит над старой |
|---|---|---|
| Налево | ||
| Направо |
Таблица заполнена. Ты можешь объяснить идею: крутиться, пока датчик со стороны поворота не увидит новую линию.
Проверь идею «крутиться до новой линии» измерением, прежде чем её программировать.
- Положи под робота лист с транспортиром или линиями через 10°. Поставь робота на центр перекрёстка.
- Медленно поворачивай руками налево. Запиши угол, на котором левый датчик впервые видит новую линию, и угол, на котором робот смотрит точно вдоль неё.
- Повтори направо.
- Совпадают ли углы? Что тогда сделает робот, который останавливается в момент, когда датчик увидел линию? Как это исправить?
В журнале — два угла для каждого поворота и вывод о том, где остановится робот.
Выезд на центр перекрёстка
12 минДатчики стоят впереди колёс. Когда датчики заметили перекрёсток, колёса ещё до него не доехали. Если повернуть сразу, робот окажется сбоку от новой линии. Поэтому сначала надо проехать вперёд так, чтобы колёса встали над центром перекрёстка.
- Поставь робота так, чтобы датчики были на поперечной линии.
- Измерь линейкой, сколько ещё ехать колёсам до центра перекрёстка. Запиши в журнал.
- Время этого проезда —
centerMs— подберём на следующем шаге.
Ты знаешь, почему перед поворотом нужен выезд на центр.
Датчики стоят впереди колёс. Когда датчики заметили перекрёсток, колёса ещё до него не доехали.
Робот повернул на месте сразу, как только датчики увидели перекрёсток. Где окажется робот относительно новой линии? Покажи на дорожке руками.
- Поставь робота датчиками на поперечную линию и поверни его руками на месте на 90°. Где оказались датчики — над новой линией или сбоку?
- Измерь линейкой, сколько ещё ехать колёсам до центра перекрёстка. Запиши в журнал.
- Время этого проезда —
centerMs— подберёшь на следующем шаге. Как ты поймёшь, что время слишком маленькое, а как — что слишком большое? Запиши.
Прогноз проверен руками, расстояние записано, признаки «мало» и «много» для centerMs записаны.
Датчики стоят впереди колёс, поэтому перед поворотом робот выезжает на центр перекрёстка. Рассчитай время этого выезда, а не подбирай наугад.
- Измерь расстояние от датчиков до оси колёс в миллиметрах.
- Возьми из журнала урока 2.1 свою скорость в миллиметрах в секунду при близкой
baseSpeed. Нет таблицы — измерь: проезд 1 секунду, путь линейкой, три раза. - Посчитай
centerMsи запиши расчёт. На следующем шаге сравни его с подобранным на дорожке. - Запиши, почему выезд по времени — слабое место и от чего его точность зависит.
В журнале — расстояние, скорость, расчёт centerMs и две причины, по которым выезд по времени может ошибаться.
Поворот на перекрёстке
22 мин- Создай скетч
L42_Turn_At_Crossing. Перенеси измерения,kpиbaseSpeed.
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() {}
- Разберите функцию
turnUntilLine. Зачем в нейdelay(leaveMs)перед поиском линии? - Запусти на дорожке с перекрёстком. Подбери
centerMs: после поворота робот должен уверенно поехать по новой линии.
Робот доезжает до перекрёстка, поворачивает налево и едет по новой линии.
Как это устроено: Зачем leaveMs
В начале поворота датчик ещё над старой линией и сразу «нашёл бы» её. Поэтому первые leaveMs миллисекунд робот просто крутится, не глядя на датчики, — чтобы датчик ушёл со старой линии. Потом ищем новую.
| Что видишь | Что сделать |
|---|---|
| После поворота робот сбоку от линии | Подбери centerMs |
| Робот крутится бесконечно | Датчик не видит линию: проверь порог 30 и измерения |
| Робот останавливается сразу | Увеличь leaveMs |
- Создай скетч
L42_Turn_At_Crossingпо шаблону. Перенеси измерения,kpиbaseSpeed. Функции линии и перекрёстков уже готовы. - Напиши сам выезд на центр
toCenter(), поворот до линииturnUntilLine()и повороты с перекрёстка налево и направо.setup()ниже проверяет их на дорожке.
// Шаблон среднего уровня. Напиши код в разделе «Твоя часть» по целям в комментариях.// 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?
- Запусти на дорожке с перекрёстком. Подбери
centerMsпо признакам, которые записал в прошлом шаге.
Подсказка: Крутиться до линии
Это похоже на passCrossing() из урока 4.1: повторять чтение датчиков, пока условие верно. Какое условие значит «линии под датчиком ещё нет»?
Подсказка: Какой датчик смотреть
Поворачиваем налево — новую линию первым увидит левый датчик.
Робот доезжает до перекрёстка, поворачивает налево и едет по новой линии. Прогноз записан.
| Что видишь | Что сделать |
|---|---|
| После поворота робот сбоку от линии | Подбери centerMs |
| Робот крутится бесконечно | Датчик не видит линию: проверь условие поиска и измерения |
| Робот останавливается сразу | Увеличь leaveMs |
Требование 1. Продолжи свою программу L41_Count и сохрани как L42_Turn_At_Crossing. Функция turnUntilLine(left) поворачивает робота на месте до новой линии:
- сначала робот крутится, не глядя на датчики, пока датчик не уйдёт со старой линии;
- потом — пока датчик со стороны поворота не увидит линию;
- если линия не нашлась за 3 секунды, робот останавливается: на поле без защиты он будет крутиться бесконечно.
Требование 2. turnLeftAtCrossing() и turnRightAtCrossing(): выезд на центр по твоему centerMs, затем поворот до линии.
Приёмка. По пять поворотов налево и направо: после каждого робот уезжает по новой линии. Отдельно проверь защиту: подними робота над столом во время поворота.
Подсказка: Идея (1 из 3)
Поиск линии — цикл «читать датчики, пока линии нет». Для защиты запомни время начала поиска.
Подсказка: План (2 из 3)
- Включить поворот, подождать
leaveMs. - Запомнить
millis(). - Повторять чтение, пока линии нет и прошло меньше 3 секунд.
- Остановить моторы.
Подсказка: Фрагмент кода (3 из 3)
while ((left ? leftLevel : rightLevel) > 30 && millis() - start < 3000) — прочитай условие словами.
Программа выполняет требования 1–2, десять поворотов и проверка защиты записаны. Подобранный centerMs сравнён с расчётом.
Разворот на линии
10 минВ конце ответвления, у груза или места доставки, роботу нужно развернуться и вернуться к перекрёстку. Разворот — тот же поворот по датчикам, только линия теперь сзади.
- В скетче найди вызов
turnAroundOnLine(). Проверь на дорожке: проехать по ответвлению, развернуться, вернуться к перекрёстку.
Робот разворачивается, находит ту же линию и возвращается к перекрёстку.
В конце ответвления роботу нужно развернуться и вернуться к перекрёстку. Разворот — тот же поворот по датчикам, только линия теперь сзади.
- Найди в скетче функцию
turnAroundOnLine(). Почему в ней нет выезда на центр, как в повороте на перекрёстке? Запиши ответ. - Проверь на дорожке три раза: проехать по ответвлению, развернуться, вернуться к перекрёстку.
Робот три раза разворачивается и возвращается к перекрёстку. Ответ про выезд на центр записан.
Требование 3. turnAroundOnLine() разворачивает робота на линии и останавливается, когда линия снова под датчиком. Защита 3 секунды из требования 1 работает и здесь.
Приёмка. Пять разворотов на конце ответвления: после каждого робот возвращается к перекрёстку. Для каждого измерь, насколько нос смотрит мимо линии сразу после разворота.
| Разворот | Вернулся? | Нос мимо линии, мм |
|---|---|---|
| 1 |
Если нос каждый раз смотрит в одну сторону, предложи и проверь одно исправление.
Пять разворотов в таблице, исправление систематической ошибки проверено.
Маршрут по порядку
22 минМаршрут — это решения на перекрёстках по порядку. Робот не видит, какой это перекрёсток: он просто выполняет следующее решение.
- Создай скетч
L42_Route. Начерти в журнале тренировочную дорожку и путь робота.
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() {}
По программе: налево, прямо, направо. Нарисуй, где робот остановится на дорожке.
- Запусти. Если маршрут сломался — на каком решении? Почему?
Робот проходит маршрут из трёх решений и останавливается там, где ты предсказал.
У соседей после turnLeftAtCrossing() сразу стоит turnRightAtCrossing(), без followLineToCrossing() между ними. Что сделает робот?
Маршрут — это решения на перекрёстках по порядку. Робот не видит, какой это перекрёсток: он просто выполняет следующее решение.
- Начерти в журнале тренировочную дорожку и путь: налево, прямо, направо.
- Создай скетч
L42_Routeпо шаблону и напиши маршрут в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 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() {}
Нарисуй, где робот остановится на дорожке.
- Запусти. Если маршрут сломался — на каком решении? Почему?
Подсказка: Одно решение
Перед каждым решением робот должен доехать до перекрёстка.
Робот проходит маршрут из трёх решений и останавливается там, где ты предсказал.
У соседей после turnLeftAtCrossing() сразу стоит turnRightAtCrossing(), без followLineToCrossing() между ними. Что сделает робот?
Требование 4. Маршрут планируется до программы и проверяется по частям.
- Выбери на тренировочном поле путь из четырёх решений или больше, в котором есть налево, направо и прямо. Если в классе разложено поле итоговой задачи, возьми путь от старта до одной зоны доставки. Запиши его таблицей:
| № | Перекрёсток | Решение | Куда смотрит робот после |
|---|---|---|---|
| 1 |
- Запиши маршрут в программу
L42_Routeиз своих функций. - Если маршрут сломался, не переписывай всё: запусти только первые решения, потом половину, и найди первое решение, на котором робот ошибается. Запиши, как искал.
На каком решении маршрут скорее всего сломается и почему? Запиши до первого запуска.
Подсказка: Как проверять по частям
Временно поставь stopMotors(); и return; после нужного решения в setup().
Таблица маршрута, программа по ней, прогноз и запись поиска ошибки. Робот проходит маршрут на тренировочной дорожке.
Задания по уровням
13 минПовернуть с перекрёстка налево и направо и уехать по новой линии; развернуться на линии и вернуться.
Составь на тренировочном поле маршрут из четырёх решений, в котором есть налево, направо и прямо. Запиши его в журнал, затем в программу, и пройди без ошибок.
Запиши решения массивом: char route[] = {'L', 'S', 'R', 'L'};. Напиши функцию decide(d), которая выполняет одно решение, и цикл, который проходит весь массив. Составь маршрут по схеме поля так, чтобы для нового маршрута менялась только строка с массивом.
Проверь себя
Выберите ответы для подготовки. Они не сохраняются и не оцениваются на сайте. Пройдите проверку знаний в программе курса или обсудите ответы с преподавателем.
● Основное
★ Средний уровень
★★ Продвинутый уровень
Отвечай на вопросы своего уровня. Нужно верных ответов: Начальный — 4 из 5; Средний — 4 из 5; Продвинутый — 4 из 5. Ответы проверит преподаватель или программа курса.
Итог
Робот сворачивает по датчикам и проходит маршрут из решений. Для итоговой задачи осталось научиться видеть груз и брать его захватом.
Как подтвердить результат
Для каждого опыта сохраняй условия, версию программы, наблюдение и вывод. Сравнивая попытки, меняй одну причину за раз; неудачные попытки тоже остаются в журнале. Отметка результата относится к уже выполненным действиям, повторять их ради отметки не нужно. Если результат не получен, запиши расхождение и следующую проверяемую гипотезу. Модель, измерения настоящего робота и объяснение — разные виды доказательств.
Словарь урока
| маршрут | Последовательность решений на перекрёстках: налево, направо или прямо. |
| центр перекрёстка | Точка, где пересекаются линии. Для поворота на месте над ней должны оказаться колёса. |
| массив | Список значений под одним именем: route[0], route[1] и так далее. |
| millis() | Сколько миллисекунд прошло с включения платы. Позволяет ехать заданное время, не останавливая программу. |