Line Follower — классика соревновательной робототехники
Робот должен ехать по чёрной линии на белом фоне.
┌─────────────────────────────────────┐
│ │
│ 🤖 ═══════════════════► │
│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
│ (линия) │
│ │
└─────────────────────────────────────┘
Звучит просто? Но дьявол в деталях! 😈
💡 ИК-светодиод
│
▼ (излучает)
════════════ ← Поверхность
△
│ (отражается)
👁️ Фотодиод
01Один датчик плохо, много — хорошо!
Датчики (вид сверху)
┌─┬─┬─┬─┬─┬─┬─┬─┐
│0│0│0│1│1│0│0│0│ ← 8 датчиков
└─┴─┴─┴─┴─┴─┴─┴─┘
▀▀▀
(линия)
Чем больше датчиков — тем точнее знаем положение линии.
| Модуль | Датчиков | Особенности |
|---|---|---|
| 1-канальный | 1 | Для начинающих |
| TCRT5000 x3 | 3 | Минимум для работы |
| QTR-8A | 8 | Аналоговый, точный |
| QTR-8RC | 8 | Цифровой с таймингом |
Самый простой — всего 2 датчика:
Левый Правый Действие
───── ───── ────────
0 0 Вперёд (линия между)
1 0 Влево (съехали вправо)
0 1 Вправо (съехали влево)
1 1 Стоп или назад (потеряли)
const int LEFT_SENSOR = A0;
const int RIGHT_SENSOR = A1;
const int THRESHOLD = 500; // Порог белое/чёрное
void loop() {
int left = analogRead(LEFT_SENSOR);
int right = analogRead(RIGHT_SENSOR);
bool leftOnLine = (left > THRESHOLD);
bool rightOnLine = (right > THRESHOLD);
if (!leftOnLine && !rightOnLine) {
forward(); // Линия между датчиками
}
else if (leftOnLine && !rightOnLine) {
turnLeft(); // Съехали вправо
}
else if (!leftOnLine && rightOnLine) {
turnRight(); // Съехали влево
}
else {
stop(); // Потеряли линию
}
}
Робот дёргается:
Позиция Действие
──────── ────────
..█.. Вперёд
.█... ВЛЕВО!
..█.. Вперёд
...█. ВПРАВО!
..█.. Вперёд
Результат: 🐍 змейка вместо плавного движения
Решение? Пропорциональное управление!
Идея: Чем дальше отклонился — тем сильнее поворачиваем.
// С массивом из 8 датчиков
int position = readLinePosition(); // -100 (слева) ... +100 (справа)
int error = position - 0; // Целевая позиция = 0 (центр)
int correction = Kp * error;
int leftSpeed = BASE_SPEED + correction;
int rightSpeed = BASE_SPEED - correction;
setMotors(leftSpeed, rightSpeed);
Позиция линии Ошибка Коррекция
────────────── ────── ─────────
[████░░░░] -50 Влево 25%
[░░██░░░░] -25 Влево 12%
[░░░██░░░] 0 Прямо
[░░░░██░░] +25 Вправо 12%
[░░░░░███] +50 Вправо 25%
Результат: Плавное движение без дёрганий! 🎯
P + I + D = идеальное следование
// Полный PID-регулятор
float Kp = 0.5, Ki = 0.01, Kd = 0.2;
float lastError = 0;
float integral = 0;
void loop() {
int position = readLinePosition();
float error = position;
integral += error;
float derivative = error - lastError;
float correction = Kp * error + Ki * integral + Kd * derivative;
lastError = error;
setMotors(BASE_SPEED + correction, BASE_SPEED - correction);
}
Подробнее о PID — в уроке 2.3!
| Компонент | Количество |
|---|---|
| Arduino Nano | 1 |
| Датчик линии (3+ канала) | 1 |
| DC моторы с редуктором | 2 |
| Драйвер TB6612 или L298N | 1 |
| Аккумулятор Li-Po 7.4V | 1 |
| Шасси | 1 |
Arduino Nano
┌────────────┐
Датчик линии ──────►│ A0-A4 │
│ │
│ D5,D6 ─────┼──► TB6612 ──► Мотор L
│ D9,D10 ────┼──► TB6612 ──► Мотор R
│ │
Аккумулятор ───────►│ VIN │
└────────────┘
const int SENSORS[] = {A0, A1, A2}; // Левый, Центр, Правый
const int THRESHOLD = 500;
const int BASE_SPEED = 150;
const float Kp = 0.8;
void loop() {
// Читаем датчики
int left = analogRead(SENSORS[0]) > THRESHOLD ? 1 : 0;
int center = analogRead(SENSORS[1]) > THRESHOLD ? 1 : 0;
int right = analogRead(SENSORS[2]) > THRESHOLD ? 1 : 0;
// Вычисляем позицию: -1 (слева), 0 (центр), +1 (справа)
int position = -1 * left + 0 * center + 1 * right;
int count = left + center + right;
if (count > 0) {
float error = (float)position / count;
int correction = Kp * error * 100;
setMotors(BASE_SPEED + correction, BASE_SPEED - correction);
} else {
// Линия потеряна — ищем!
searchLine();
}
}
| Проблема | Причина | Решение |
|---|---|---|
| Съезжает на поворотах | Слишком быстро | Уменьши BASE_SPEED |
| Дёргается | Kp слишком большой | Уменьши Kp |
| Медленно реагирует | Kp слишком маленький | Увеличь Kp |
| Теряет на перекрёстках | Нет логики | Добавь обработку |
Калибровка перед стартом
void calibrate() {
// Робот проезжает по белому и чёрному
// Запоминает MIN и MAX для каждого датчика
}
Адаптивная скорость
// На прямой — быстрее, на повороте — медленнее
int speed = map(abs(error), 0, 100, MAX_SPEED, MIN_SPEED);
Предсказание поворотов
// Смотрим на боковые датчики заранее
if (farLeftSensor) prepareForLeftTurn();
Открой симулятор и настрой Kp для плавного движения!
| Алгоритм | Сложность | Качество |
|---|---|---|
| Бинарный (2 датчика) | ⭐ | Дёрганый |
| Пропорциональный (P) | ⭐⭐ | Плавный |
| PID | ⭐⭐⭐ | Идеальный |
error = position - target
correction = Kp × error
leftSpeed = base + correction
rightSpeed = base - correction
Добавляем несколько датчиков, умную логику и память!