Проект 8: Датчики линии (3 аналоговых канала)
Введение
Представьте, что ваш робот Phobo может самостоятельно двигаться по чёрной линии на белом фоне, как поезд по рельсам! В этом уроке мы научим Arduino «видеть» линию с помощью трёх аналого-цифровых инфракрасных датчиков.
У робота Phobo три датчика линии расположены под передней частью: левый, центральный и правый. Они работают как команда: если робот отклоняется влево, левый датчик это обнаружит; если вправо — сработает правый датчик. Центральный датчик помогает понять, что робот движется точно по линии.
Зачем это нужно?
Робот сможет ездить по нарисованной дорожке (line following)
Можно создать лабиринт или гоночную трассу
Робот не потеряется и не съедет с маршрута
Это основа автономного движения!
Примечание
Важно для новичков: В этом уроке мы впервые будем работать с аналоговыми входами (A0, A1, A2)! Раньше мы использовали цифровые пины (D3, D4, D11), которые дают только 0 или 1. Аналоговые пины могут считывать значения от 0 до 1023 — это позволяет измерять не только «есть линия» или «нет линии», но и «насколько тёмная поверхность».
Что понадобится
Из набора Phobo:
Робот Phobo (собран в Проекте 1 с тремя датчиками линии)
9 проводов Female-Female (по 3 на датчик)
Чёрная изолента (ширина 2-3 см, длина 1-2 метра)
Светлая поверхность: белая бумага формата A3/A2, ватман, светлый картон или гладкий светлый стол
Что такое датчик линии?
Технические характеристики
Тип: Аналого-цифровой инфракрасный датчик отражения
Рабочее напряжение: 3.3V - 5V (мы используем 5V от Arduino)
Рабочий ток: ~20 mA на датчик (всего ~60 mA для трёх)
Рабочая температура: 0°C ~ +50°C
Дальность обнаружения: 2-10 мм (оптимально 3-5 мм от поверхности)
Выходные сигналы:
A0 — аналоговый выход (0-1023), используем в этом проекте
D0 — цифровой выход (0 или 1), не используем
Пины: VCC (питание), GND (земля), A0 (аналоговый), D0 (цифровой)
Подстроечный резистор: Есть (для настройки порога цифрового выхода D0)
Индикация: Встроенный светодиод (горит при обнаружении линии на D0)
Размер: 28×13 мм
Как работает датчик?
Датчик линии состоит из двух компонентов:
ИК-светодиод (Infrared LED) — излучает инфракрасный свет (невидимый для глаз)
Фототранзистор — принимает отражённый свет и преобразует в электрический сигнал
Процесс обнаружения:
ИК-светодиод постоянно излучает инфракрасный свет на поверхность
Свет отражается от поверхности по-разному в зависимости от её цвета: - Белая поверхность (или светлая) отражает ~80-90% света → фототранзистор получает много света → высокое значение (800-1023) - Чёрная линия (или тёмная) поглощает ~90-95% света → фототранзистор получает мало света → низкое значение (0-200)
Фототранзистор преобразует количество света в напряжение (0-5V)
Arduino считывает это напряжение через аналоговый вход и преобразует в число от 0 до 1023
Примечание
Два режима работы датчика:
Аналоговый выход (A0): Даёт точные значения 0-1023 в зависимости от количества отражённого света. Мы используем этот режим, так как он позволяет определить не только «есть линия» или «нет», но и «насколько близко к линии». Калибровка происходит программно — мы просто запоминаем значения белого и чёрного и находим среднее.
Цифровой выход (D0): Даёт только 0 (чёрное) или 1 (белое). Порог между чёрным и белым настраивается подстроечным резистором на датчике. Мы не используем D0 в этом проекте.
Примечание
Интересный факт: Мы не видим инфракрасное излучение, но его можно увидеть через камеру смартфона! Наведите камеру телефона на ИК-светодиод датчика (в темноте) — вы увидите фиолетовое свечение на экране! 📱✨
Код калибровки датчиков
Сначала напишем программу для считывания аналоговых значений с датчиков — это нужно для калибровки.
/*
* Проект 8: Датчики линии (аналоговые)
* Робот Phobo — Alash Electronics
*
* Считываем аналоговые значения с трёх датчиков линии
* и выводим их в монитор порта для калибровки
*/
// Определяем пины аналоговых входов
const uint8_t LEFT_SENSOR = A0; // Левый датчик
const uint8_t CENTER_SENSOR = A1; // Центральный датчик
const uint8_t RIGHT_SENSOR = A2; // Правый датчик
void setup() {
// Запускаем монитор порта со скоростью 9600
Serial.begin(9600);
Serial.println("=== Робот Phobo: Датчики линии ===");
Serial.println("Калибровка датчиков...");
Serial.println("Поместите датчики над белой, затем над чёрной поверхностью");
Serial.println();
delay(2000); // Пауза 2 секунды для подготовки
}
void loop() {
// Считываем аналоговые значения (0-1023)
int leftValue = analogRead(LEFT_SENSOR);
int centerValue = analogRead(CENTER_SENSOR);
int rightValue = analogRead(RIGHT_SENSOR);
// Выводим значения в удобном формате
Serial.print("Левый: ");
Serial.print(leftValue);
Serial.print("\t"); // Табуляция для выравнивания
Serial.print("Центр: ");
Serial.print(centerValue);
Serial.print("\t");
Serial.print("Правый: ");
Serial.println(rightValue);
// Задержка 200 мс для удобства чтения
delay(200);
}
Загрузка и калибровка
Шаг 1: Загрузите код в Arduino (процесс загрузки см. Урок 1)
Шаг 2: Откройте Serial Monitor (9600 baud)
Шаг 3: Откалибруйте датчики — узнайте, какие значения выдают датчики над белой и чёрной поверхностью
Процесс калибровки:
Шаг 1: Положите робота на светлую поверхность (белая бумага, ватман, светлый стол)
- Шаг 2: Запишите значения в Serial Monitor. Примерные значения:
Левый: 900-1000 (белая поверхность)
Центр: 900-1000
Правый: 900-1000
Шаг 3: Наклейте полоску чёрной изоленты на поверхность (ширина 2-3 см, длина 20-30 см)
Шаг 4: Подвиньте робота так, чтобы датчики оказались над чёрной изолентой
- Шаг 5: Запишите новые значения. Примерные значения:
Левый: 50-150 (чёрная линия)
Центр: 50-150
Правый: 50-150
Шаг 6: Запишите значения для каждого датчика (белое, чёрное, порог) — понадобятся в Проекте 9
Предупреждение
Важно! У разных моделей датчиков значения могут быть инвертированы:
Вариант 1 (обычный): белое = 900-1000, чёрное = 50-150 → threshold = 500
Вариант 2 (инвертированный): белое = 20-40, чёрное = 30-50 → threshold = 35
Как определить: Если значение на чёрной линии БОЛЬШЕ, чем на белой поверхности — у вас инвертированный датчик. В этом случае логика меняется: if (value > THRESHOLD) = чёрная линия.
Примечание
Почему каждый датчик разный?
Даже датчики из одного набора могут давать разные значения из-за: - Разброса характеристик фототранзисторов (±10%) - Разной яркости ИК-светодиодов - Неравномерного расстояния до поверхности (±1-2 мм)
Рекомендация: Калибруйте каждый датчик отдельно и используйте индивидуальные пороги. Это даст гораздо лучшую точность!
Примечание
Про индикатор D0: На датчике есть светодиод, который загорается когда цифровой выход D0 обнаруживает линию. Подстроечным резистором можно настроить порог срабатывания этого индикатора — это помогает визуально проверить работу датчика. Но при маленьких значениях (20-50) настроить резистор очень сложно, поэтому мы используем аналоговый выход A0 с программной калибровкой.
Что вы должны увидеть в Serial Monitor:
=== Робот Phobo: Датчики линии ===
Калибровка датчиков...
Поместите датчики над белой, затем над чёрной поверхностью
Левый: 950 Центр: 948 Правый: 955
Левый: 952 Центр: 951 Правый: 957
Левый: 120 Центр: 95 Правый: 110 ← поднесли к чёрной линии
Левый: 115 Центр: 88 Правый: 105
Левый: 960 Центр: 965 Правый: 970 ← вернули на белую поверхность
Попробуйте: Двигайте робота над линией — цифры должны резко меняться!
Эксперименты
Теперь, когда датчики откалиброваны, попробуем интересные эксперименты!
Эксперимент 1: Визуальный индикатор положения линии
Задача: Создать графическую визуализацию в мониторе порта, показывающую, под каким датчиком находится чёрная линия.
Логика работы:
Для каждого датчика используем индивидуальный порог (threshold), рассчитанный как среднее между значениями белого и чёрного. Программа автоматически определяет, инвертированные ли датчики (если чёрное > белого), и меняет логику сравнения.
Полный код:
/*
* Эксперимент 1: Визуальный индикатор линии
* Показывает положение линии графически
* С индивидуальной калибровкой для каждого датчика
*/
const uint8_t LEFT_SENSOR = A0;
const uint8_t CENTER_SENSOR = A1;
const uint8_t RIGHT_SENSOR = A2;
// КАЛИБРОВКА: Замените эти значения на свои!
// Вариант 1 (обычный): белое=большое, чёрное=маленькое
const int LEFT_WHITE = 950, LEFT_BLACK = 100;
const int CENTER_WHITE = 940, CENTER_BLACK = 95;
const int RIGHT_WHITE = 960, RIGHT_BLACK = 110;
// Вариант 2 (инвертированный): белое=маленькое, чёрное=большое
// const int LEFT_WHITE = 28, LEFT_BLACK = 36;
// const int CENTER_WHITE = 30, CENTER_BLACK = 38;
// const int RIGHT_WHITE = 25, RIGHT_BLACK = 35;
// Вычисляем пороги (среднее между белым и чёрным)
const int LEFT_THRESHOLD = (LEFT_WHITE + LEFT_BLACK) / 2;
const int CENTER_THRESHOLD = (CENTER_WHITE + CENTER_BLACK) / 2;
const int RIGHT_THRESHOLD = (RIGHT_WHITE + RIGHT_BLACK) / 2;
// Определяем, инвертированные ли датчики (чёрное > белого)
const bool INVERTED = (LEFT_BLACK > LEFT_WHITE);
void setup() {
Serial.begin(9600);
Serial.println("=== Phobo Line Position Indicator ===");
Serial.println("Двигайте датчики над линией...");
Serial.print("Режим: ");
Serial.println(INVERTED ? "ИНВЕРТИРОВАННЫЙ" : "ОБЫЧНЫЙ");
Serial.println();
}
// Функция для определения, на линии ли датчик
bool isOnLine(int value, int threshold) {
if (INVERTED) {
return value > threshold; // Для инвертированных: больше = чёрное
} else {
return value < threshold; // Для обычных: меньше = чёрное
}
}
void loop() {
// Считываем значения
int leftValue = analogRead(LEFT_SENSOR);
int centerValue = analogRead(CENTER_SENSOR);
int rightValue = analogRead(RIGHT_SENSOR);
// Определяем, на линии ли каждый датчик
bool leftOnLine = isOnLine(leftValue, LEFT_THRESHOLD);
bool centerOnLine = isOnLine(centerValue, CENTER_THRESHOLD);
bool rightOnLine = isOnLine(rightValue, RIGHT_THRESHOLD);
// Выводим визуализацию
Serial.print("Левый ");
Serial.print(leftOnLine ? "██████" : "░░░░░░");
Serial.print(" ");
Serial.println(leftValue);
Serial.print("Центр ");
Serial.print(centerOnLine ? "██████" : "░░░░░░");
Serial.print(" ");
Serial.println(centerValue);
Serial.print("Правый ");
Serial.print(rightOnLine ? "██████" : "░░░░░░");
Serial.print(" ");
Serial.println(rightValue);
// Определяем направление движения робота
Serial.print("\n📍 Позиция: ");
if (leftOnLine && !centerOnLine && !rightOnLine) {
Serial.println("Линия СЛЕВА — поворот налево! ◀");
}
else if (!leftOnLine && centerOnLine && !rightOnLine) {
Serial.println("Линия В ЦЕНТРЕ — двигаться прямо! ↑");
}
else if (!leftOnLine && !centerOnLine && rightOnLine) {
Serial.println("Линия СПРАВА — поворот направо! ▶");
}
else if (leftOnLine && centerOnLine && !rightOnLine) {
Serial.println("Линия между ЛЕВЫМ и ЦЕНТРОМ ↖");
}
else if (!leftOnLine && centerOnLine && rightOnLine) {
Serial.println("Линия между ЦЕНТРОМ и ПРАВЫМ ↗");
}
else if (leftOnLine && centerOnLine && rightOnLine) {
Serial.println("Все датчики на линии (перекрёсток?) ⊗");
}
else {
Serial.println("Линия потеряна! ✖");
}
Serial.println("=====================================");
delay(500);
}
Что должно получиться:
Двигайте робота над линией (или лист бумаги под роботом) — в Serial Monitor вы увидите графическое представление и рекомендации для робота!
Важно: Перед запуском замените значения калибровки в коде на свои (из основной программы калибровки). Если ваш датчик инвертированный (чёрное=36, белое=28), раскомментируйте строки «Вариант 2» и закомментируйте «Вариант 1».
Пример вывода:
=== Phobo Line Position Indicator ===
Двигайте датчики над линией...
Левый ░░░░░░ 950
Центр ██████ 120
Правый ░░░░░░ 960
📍 Позиция: Линия В ЦЕНТРЕ — двигаться прямо! ↑
=====================================
Левый ░░░░░░ 955
Центр ░░░░░░ 940
Правый ██████ 110
📍 Позиция: Линия СПРАВА — поворот направо! ▶
=====================================
Эксперимент 2: Система подсчёта пересечений линии
Задача: Посчитать, сколько раз робот пересёк чёрную линию (полезно для подсчёта перекрёстков или меток на трассе).
Логика работы:
Когда центральный датчик переходит с белого на чёрное (пересекает линию), увеличиваем счётчик.
Полный код:
/*
* Эксперимент 2: Счётчик пересечений линии
* Считает количество пересечений чёрной линии
*/
const uint8_t CENTER_SENSOR = A1;
// КАЛИБРОВКА: Замените на свои значения!
const int CENTER_WHITE = 940;
const int CENTER_BLACK = 95;
const int THRESHOLD = (CENTER_WHITE + CENTER_BLACK) / 2;
const bool INVERTED = (CENTER_BLACK > CENTER_WHITE);
bool wasOnWhite = true; // Начинаем на белой поверхности
int crossCount = 0; // Счётчик пересечений
void setup() {
Serial.begin(9600);
Serial.println("=== Phobo Line Cross Counter ===");
Serial.println("Проводите датчик через линию...");
Serial.print("Порог: ");
Serial.print(THRESHOLD);
Serial.print(" (");
Serial.print(INVERTED ? "инвертированный" : "обычный");
Serial.println(")");
Serial.println();
}
void loop() {
int centerValue = analogRead(CENTER_SENSOR);
// Определяем текущий цвет (учитываем инверсию)
bool onWhite;
if (INVERTED) {
onWhite = (centerValue < THRESHOLD); // Инвертированный
} else {
onWhite = (centerValue >= THRESHOLD); // Обычный
}
// Если перешли с белого на чёрное — пересечение!
if (wasOnWhite && !onWhite) {
crossCount++;
Serial.print("🚗 Пересечение #");
Serial.println(crossCount);
Serial.println("---");
}
// Обновляем предыдущее состояние
wasOnWhite = onWhite;
// Выводим текущее значение
Serial.print("Значение: ");
Serial.print(centerValue);
Serial.print(" | Поверхность: ");
Serial.print(onWhite ? "Белая ░" : "Чёрная █");
Serial.print(" | Всего пересечений: ");
Serial.println(crossCount);
delay(100);
}
Что должно получиться:
Двигайте робота через чёрную линию несколько раз (или двигайте лист бумаги с линией под роботом) — счётчик будет увеличиваться при каждом пересечении!
Пример вывода:
=== Phobo Line Cross Counter ===
Проводите датчик через линию...
Значение: 950 | Поверхность: Белая ░ | Всего пересечений: 0
Значение: 945 | Поверхность: Белая ░ | Всего пересечений: 0
Значение: 120 | Поверхность: Чёрная █ | Всего пересечений: 0
🚗 Пересечение #1
---
Значение: 115 | Поверхность: Чёрная █ | Всего пересечений: 1
Значение: 960 | Поверхность: Белая ░ | Всего пересечений: 1
Значение: 110 | Поверхность: Чёрная █ | Всего пересечений: 1
🚗 Пересечение #2
---
Эксперимент 3: Виртуальный симулятор движения
Задача: Создать текстовую анимацию движения робота в зависимости от положения линии.
Полный код:
/*
* Эксперимент 3: Виртуальный симулятор движения
* Показывает, как робот будет двигаться
*/
const uint8_t LEFT_SENSOR = A0;
const uint8_t CENTER_SENSOR = A1;
const uint8_t RIGHT_SENSOR = A2;
// КАЛИБРОВКА: Замените на свои значения!
const int LEFT_WHITE = 950, LEFT_BLACK = 100;
const int CENTER_WHITE = 940, CENTER_BLACK = 95;
const int RIGHT_WHITE = 960, RIGHT_BLACK = 110;
const int LEFT_THRESHOLD = (LEFT_WHITE + LEFT_BLACK) / 2;
const int CENTER_THRESHOLD = (CENTER_WHITE + CENTER_BLACK) / 2;
const int RIGHT_THRESHOLD = (RIGHT_WHITE + RIGHT_BLACK) / 2;
const bool INVERTED = (LEFT_BLACK > LEFT_WHITE);
void setup() {
Serial.begin(9600);
Serial.println("=== Phobo Virtual Line Following ===");
Serial.println("Симулятор движения робота по линии");
Serial.println();
}
bool isOnLine(int value, int threshold) {
return INVERTED ? (value > threshold) : (value < threshold);
}
void loop() {
int leftValue = analogRead(LEFT_SENSOR);
int centerValue = analogRead(CENTER_SENSOR);
int rightValue = analogRead(RIGHT_SENSOR);
bool leftOnLine = isOnLine(leftValue, LEFT_THRESHOLD);
bool centerOnLine = isOnLine(centerValue, CENTER_THRESHOLD);
bool rightOnLine = isOnLine(rightValue, RIGHT_THRESHOLD);
// Рисуем робота ASCII-графикой
Serial.println(" [РОБОТ PHOBO]");
Serial.println(" / 🤖 \\");
Serial.print(" ");
Serial.print(leftOnLine ? "█" : "░");
Serial.print(" ");
Serial.print(centerOnLine ? "█" : "░");
Serial.print(" ");
Serial.println(rightOnLine ? "█" : "░");
Serial.println(" L C R");
Serial.println();
// Определяем команду управления
Serial.print("🎮 Команда: ");
if (centerOnLine && !leftOnLine && !rightOnLine) {
Serial.println("⬆️ ВПЕРЁД (моторы: лево 100%, право 100%)");
}
else if (leftOnLine && !rightOnLine) {
Serial.println("↖️ ПОВОРОТ НАЛЕВО (моторы: лево 30%, право 100%)");
}
else if (rightOnLine && !leftOnLine) {
Serial.println("↗️ ПОВОРОТ НАПРАВО (моторы: лево 100%, право 30%)");
}
else if (!leftOnLine && !centerOnLine && !rightOnLine) {
Serial.println("🛑 СТОП! Линия потеряна (моторы: стоп)");
}
else {
Serial.println("❓ Неопределённое положение");
}
Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
delay(500);
}
Что должно получиться:
ASCII-рисунок робота и команды управления моторами в реальном времени!
Пример вывода:
=== Phobo Virtual Line Following ===
Симулятор движения робота по линии
[РОБОТ PHOBO]
/ 🤖 \
░ █ ░
L C R
🎮 Команда: ⬆️ ВПЕРЁД (моторы: лево 100%, право 100%)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[РОБОТ PHOBO]
/ 🤖 \
░ ░ █
L C R
🎮 Команда: ↗️ ПОВОРОТ НАПРАВО (моторы: лево 100%, право 30%)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Решение проблем
Проблема 1: Датчики выдают всегда одинаковые значения
Причины:
Неправильное подключение проводов
Датчики слишком далеко от поверхности (>10 мм)
Датчики повёрнуты не той стороной
Решение:
Проверьте все 9 проводов — они должны сидеть плотно
Убедитесь, что датчики располагаются ИК-светодиодами ВНИЗ (к поверхности)
Поднесите датчики ближе к поверхности (3-5 мм оптимально)
Проверьте, что ИК-светодиоды горят (невидимые глазом, но видны на камере телефона)
Проблема 2: Значения слишком близки (нет разницы между белым и чёрным)
Причины:
Датчики слишком близко к поверхности (<2 мм) — свет отражается под неправильным углом
Датчики слишком далеко от поверхности (>10 мм) — свет рассеивается
Слишком яркое освещение или прямой солнечный свет
Недостаточный контраст поверхности (серый вместо белого/чёрного)
Решение:
Найдите оптимальное расстояние 3-5 мм от поверхности (попробуйте разные высоты!)
Тестируйте в помещении с обычным освещением (не на прямом солнце)
Используйте чёрный маркер (или чёрную изоленту) на белой бумаге для максимального контраста
Если разница маленькая (например, белое=28, чёрное=36, разница=8), это нормально для некоторых моделей датчиков — просто используйте индивидуальные пороги для каждого датчика
Проблема 3: Один или два датчика работают неправильно
Причины:
Неправильное подключение именно этого датчика
Неисправный датчик
Загрязнение ИК-светодиода или фототранзистора
Решение:
Поменяйте местами работающий и неработающий датчики (вместе с проводами)
Если проблема переместилась — дело в датчике; если осталась на месте — дело в проводах или пинах
Протрите линзы ИК-светодиода и фототранзистора мягкой тканью
Попробуйте подключить «плохой» датчик к другому аналоговому входу (например, A3)
Проблема 4: Значения «прыгают» (нестабильны)
Причины:
Вибрация или движение датчиков
Неравномерная поверхность
Помехи от других устройств
Решение:
Зафиксируйте датчики на неподвижной поверхности
Используйте гладкую ровную поверхность для теста
Добавьте программную фильтрацию (среднее из нескольких измерений):
int readSensorAverage(uint8_t pin) {
long sum = 0;
for (int i = 0; i < 10; i++) {
sum += analogRead(pin);
delay(5);
}
return sum / 10; // Среднее из 10 измерений
}
Для любознательных
Почему расстояние 3-5 мм критично?
Датчик настроен на фокусное расстояние ~3-5 мм. Если слишком далеко (>10 мм) — свет рассеивается, мало возвращается обратно. Если слишком близко (<2 мм) — свет отражается под неправильным углом, не попадает в фототранзистор.
ИК-излучение и камера телефона:
Инфракрасное излучение (длина волны 700-1000 нм) невидимо для человеческого глаза, но камера смартфона может его «видеть»! Попробуйте навести камеру на ИК-светодиод датчика в темноте — вы увидите фиолетовое свечение.
Что дальше?
Поздравляем! Вы научились работать с тремя аналоговыми датчиками линии. Теперь робот Phobo может «видеть» чёрную линию и определять своё положение!
Важно
После завершения проекта: Отключите провода от Sensor Shield. Сохраните записанные значения калибровки (белое/чёрное для каждого датчика) — они понадобятся в Проекте 9 для программирования движения по линии.
Что мы изучили:
✅ Принцип работы инфракрасных датчиков линии
✅ Разница между аналоговыми и цифровыми входами Arduino
✅ Подключение трёх датчиков к Sensor Shield V5
✅ Калибровка датчиков (определение порога white/black)
✅ Считывание аналоговых значений с помощью analogRead()
✅ Логика определения положения линии (левая/центр/правая)
✅ Практичные эксперименты: визуализация, счётчик пересечений, симулятор движения
Что дальше:
Проект 9: Следование по линии — робот самостоятельно едет по чёрной линии
Проект 10: ИК-пульт — дистанционное управление роботом
Успехов в робототехнике! 🤖