💻 Программируем умный P-регулятор

Практическая работа: создание идеального следования по линии

🎯 Цель: Создаем программу с пропорциональным регулятором
⭐ Результат: Робот плавно и точно следует по любой линии

👨‍🏫 Учитель: Ахметов Рустам
🏫 Школа: ГБОУ № 1362
📅 Дата: 2025-06-14
Время: 100 минут

🎯 Супер-задача дня!

🏆 Наша амбициозная цель

Создаем робота-мастера линии:

  • 📏 Точно следует по любой черной линии
  • 🌊 Плавно движется без рывков и колебаний
  • Быстро реагирует на повороты и изменения
  • 🎛️ Автоматически настраивается под разные ситуации

🎪 Критерии идеального результата

Ваш робот должен:

  • ✅ Проходить прямые участки без колебаний
  • ✅ Плавно входить в повороты
  • ✅ Не терять линию на острых углах
  • ✅ Поддерживать постоянную скорость
  • ✅ Работать стабильно при повторных запусках

💡 Секрет успеха: правильно настроенный пропорциональный регулятор!

🧠 Теория в действии: от формул к коду

📊 Математика P-регулятора

Вспоминаем формулу: \[u(t) = K_p \times e(t)\]

где:

  • u(t) - управляющее воздействие (коррекция скорости)
  • Kp - коэффициент пропорциональности (наш главный параметр!)
  • e(t) - ошибка (отклонение от центра линии)

🎛️ Применение для следования по линии

Для робота с двумя датчиками линии: \[\text{error} = \text{leftSensor} - \text{rightSensor}\]

\[\text{correction} = K_p \times \text{error}\] \[\begin{align} \text{leftMotor} &= \text{baseSpeed} + \text{correction} \\ \text{rightMotor} &= \text{baseSpeed} - \text{correction} \end{align}\]

Логика работы:

  • Если робот отклонился влево → левый мотор замедляется, правый ускоряется
  • Если робот отклонился вправо → правый мотор замедляется, левый ускоряется

💻 Базовая реализация P-регулятора

🔧 Структура программы

// Глобальные параметры регулятора
float Kp = 1.0;              // Коэффициент пропорциональности
int baseSpeed = 150;         // Базовая скорость (0-255)
int maxCorrection = 100;     // Максимальная коррекция

// Пины датчиков и моторов
const int LEFT_SENSOR = A0;
const int RIGHT_SENSOR = A1;
const int LEFT_MOTOR_PWM = 5;
const int LEFT_MOTOR_DIR = 4;
const int RIGHT_MOTOR_PWM = 6;
const int RIGHT_MOTOR_DIR = 7;

void setup() {
    Serial.begin(9600);
    
    // Инициализация пинов
    pinMode(LEFT_MOTOR_DIR, OUTPUT);
    pinMode(RIGHT_MOTOR_DIR, OUTPUT);
    
    // Направление моторов (вперед)
    digitalWrite(LEFT_MOTOR_DIR, HIGH);
    digitalWrite(RIGHT_MOTOR_DIR, HIGH);
    
    Serial.println("P-Controller Line Follower Ready!");
    delay(2000);  // Время на размещение робота
}

void loop() {
    followLineWithPController();
    delay(20);  // 50 Hz update rate
}

📏 Чтение и обработка датчиков

struct SensorData {
    int leftRaw;      // Сырые данные левого датчика
    int rightRaw;     // Сырые данные правого датчика
    float leftNorm;   // Нормализованные данные (0.0-1.0)
    float rightNorm;  // Нормализованные данные (0.0-1.0)
    float error;      // Ошибка регулирования
};

SensorData readSensors() {
    SensorData data;
    
    // Читаем сырые значения
    data.leftRaw = analogRead(LEFT_SENSOR);
    data.rightRaw = analogRead(RIGHT_SENSOR);
    
    // Нормализация (черная линия = 0, белый фон = 1)
    data.leftNorm = map(data.leftRaw, 0, 1023, 0.0, 1.0);
    data.rightNorm = map(data.rightRaw, 0, 1023, 0.0, 1.0);
    
    // Инвертируем для удобства (линия = высокое значение)
    data.leftNorm = 1.0 - data.leftNorm;
    data.rightNorm = 1.0 - data.rightNorm;
    
    // Вычисляем ошибку
    data.error = data.leftNorm - data.rightNorm;
    
    return data;
}

⚙️ Основной алгоритм регулирования

void followLineWithPController() {
    // Читаем датчики
    SensorData sensors = readSensors();
    
    // Пропорциональное регулирование
    float correction = Kp * sensors.error;
    
    // Ограничиваем коррекцию
    correction = constrain(correction, -maxCorrection, maxCorrection);
    
    // Вычисляем скорости моторов
    int leftSpeed = baseSpeed + correction;
    int rightSpeed = baseSpeed - correction;
    
    // Ограничиваем скорости
    leftSpeed = constrain(leftSpeed, 0, 255);
    rightSpeed = constrain(rightSpeed, 0, 255);
    
    // Применяем к моторам
    analogWrite(LEFT_MOTOR_PWM, leftSpeed);
    analogWrite(RIGHT_MOTOR_PWM, rightSpeed);
    
    // Отладочная информация
    if (Serial.available() > 0 && Serial.read() == 'd') {
        printDebugInfo(sensors, correction, leftSpeed, rightSpeed);
    }
}

void printDebugInfo(SensorData sensors, float correction, int leftSpeed, int rightSpeed) {
    Serial.print("L:");    Serial.print(sensors.leftNorm, 2);
    Serial.print(" R:");   Serial.print(sensors.rightNorm, 2);
    Serial.print(" E:");   Serial.print(sensors.error, 2);
    Serial.print(" C:");   Serial.print(correction, 1);
    Serial.print(" LS:");  Serial.print(leftSpeed);
    Serial.print(" RS:");  Serial.println(rightSpeed);
}

⭐ Для любознательных: Продвинутая версия

class AdvancedPController {
private:
    float Kp;
    int baseSpeed;
    int maxCorrection;
    
    // Калибровочные данные
    int sensorMin[2] = {0, 0};
    int sensorMax[2] = {1023, 1023};
    
    // Фильтрация шумов
    float sensorHistory[2][5] = {{0}};  // История для сглаживания
    int historyIndex = 0;
    
public:
    AdvancedPController(float kp, int speed, int maxCorr) 
        : Kp(kp), baseSpeed(speed), maxCorrection(maxCorr) {}
    
    void calibrateSensors() {
        Serial.println("Calibrating... Move robot over line for 5 seconds");
        
        unsigned long startTime = millis();
        while (millis() - startTime < 5000) {
            int left = analogRead(LEFT_SENSOR);
            int right = analogRead(RIGHT_SENSOR);
            
            // Обновляем минимумы и максимумы
            sensorMin[0] = min(sensorMin[0], left);
            sensorMin[1] = min(sensorMin[1], right);
            sensorMax[0] = max(sensorMax[0], left);
            sensorMax[1] = max(sensorMax[1], right);
            
            delay(10);
        }
        
        Serial.println("Calibration complete!");
        Serial.print("Left range: "); Serial.print(sensorMin[0]); 
        Serial.print("-"); Serial.println(sensorMax[0]);
        Serial.print("Right range: "); Serial.print(sensorMin[1]); 
        Serial.print("-"); Serial.println(sensorMax[1]);
    }
    
    float getFilteredSensorValue(int sensorIndex) {
        int rawValue = analogRead(sensorIndex == 0 ? LEFT_SENSOR : RIGHT_SENSOR);
        
        // Нормализация с калибровкой
        float normalized = map(rawValue, sensorMin[sensorIndex], sensorMax[sensorIndex], 0.0, 1.0);
        normalized = constrain(normalized, 0.0, 1.0);
        
        // Добавляем в историю
        sensorHistory[sensorIndex][historyIndex] = normalized;
        
        // Вычисляем скользящее среднее
        float sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += sensorHistory[sensorIndex][i];
        }
        
        return sum / 5.0;
    }
    
    void update() {
        // Обновляем историю
        historyIndex = (historyIndex + 1) % 5;
        
        // Читаем отфильтрованные значения
        float leftValue = getFilteredSensorValue(0);
        float rightValue = getFilteredSensorValue(1);
        
        // Инвертируем (линия = высокое значение)
        leftValue = 1.0 - leftValue;
        rightValue = 1.0 - rightValue;
        
        // Вычисляем ошибку
        float error = leftValue - rightValue;
        
        // P-регулирование
        float correction = Kp * error;
        correction = constrain(correction, -maxCorrection, maxCorrection);
        
        // Применяем к моторам
        int leftSpeed = constrain(baseSpeed + correction, 0, 255);
        int rightSpeed = constrain(baseSpeed - correction, 0, 255);
        
        analogWrite(LEFT_MOTOR_PWM, leftSpeed);
        analogWrite(RIGHT_MOTOR_PWM, rightSpeed);
    }
};

🧪 Экспериментальная настройка коэффициентов

📋 Методика настройки

Пошаговый алгоритм:

  1. Начальная настройка:

    • Kp = 0.5 (осторожное начало)
    • baseSpeed = 100 (медленно и надежно)
    • maxCorrection = 50
  2. Постепенное увеличение:

    • Увеличиваем Kp на 0.2-0.5
    • Тестируем на прямой линии
    • Проверяем на поворотах
  3. Поиск оптимума:

    • Слишком малый Kp → медленная реакция, робот съезжает
    • Слишком большой Kp → колебания, неустойчивость
    • Оптимальный Kp → быстрая реакция без колебаний

📊 Таблица экспериментов

void runTuningExperiments() {
    float testValues[] = {0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0};
    int numTests = sizeof(testValues) / sizeof(testValues[0]);
    
    Serial.println("Starting automatic tuning...");
    Serial.println("Kp\tTime\tOscillations\tOffTrack");
    
    for (int i = 0; i < numTests; i++) {
        Kp = testValues[i];
        
        Serial.print("Testing Kp = ");
        Serial.println(Kp);
        
        TestResults results = runSingleTest(10000);  // 10 секунд
        
        Serial.print(Kp); Serial.print("\t");
        Serial.print(results.completionTime); Serial.print("\t");
        Serial.print(results.oscillationCount); Serial.print("\t");
        Serial.println(results.offTrackCount);
        
        delay(2000);  // Пауза между тестами
    }
}

struct TestResults {
    unsigned long completionTime;
    int oscillationCount;
    int offTrackCount;
    float averageError;
};

TestResults runSingleTest(unsigned long duration) {
    TestResults results = {0};
    unsigned long startTime = millis();
    
    float lastError = 0;
    int errorDirection = 0;  // Для подсчета колебаний
    
    while (millis() - startTime < duration) {
        SensorData sensors = readSensors();
        
        // Подсчет колебаний
        if ((sensors.error > 0 && lastError < 0) || 
            (sensors.error < 0 && lastError > 0)) {
            results.oscillationCount++;
        }
        
        // Подсчет съездов с линии
        if (abs(sensors.error) > 0.8) {
            results.offTrackCount++;
        }
        
        // Средняя ошибка
        results.averageError += abs(sensors.error);
        
        followLineWithPController();
        lastError = sensors.error;
        delay(20);
    }
    
    results.completionTime = millis() - startTime;
    results.averageError /= (duration / 20);  // Количество итераций
    
    return results;
}

📈 Анализ поведения системы

Признаки разных настроек:

Kp слишком мал (< 1.0):

Поведение: ___/‾‾‾\___/‾‾‾\___
Проблемы: 
- Медленная реакция на повороты
- Большие отклонения от линии
- Может потерять линию на острых углах

Kp оптимальный (1.5-2.5):

Поведение: ~~~~~~~~~~~~~~~~~~~
Достоинства:
- Быстрая реакция
- Минимальные отклонения  
- Плавное движение
- Стабильность на поворотах

Kp слишком велик (> 3.0):

Поведение: \/\/\/\/\/\/\/\/\/\/
Проблемы:
- Постоянные колебания
- Резкие движения
- Износ механики
- Потеря эффективности

⭐ Для любознательных: Автоматическая настройка

class AutoTuner {
private:
    float currentKp = 1.0;
    float step = 0.2;
    float bestKp = 1.0;
    float bestScore = 999999;
    
public:
    float optimizeKp() {
        Serial.println("Starting automatic optimization...");
        
        // Грубая настройка
        for (float kp = 0.5; kp <= 4.0; kp += 0.5) {
            float score = evaluatePerformance(kp);
            if (score < bestScore) {
                bestScore = score;
                bestKp = kp;
            }
        }
        
        // Точная настройка вокруг лучшего значения
        float rangeMin = max(0.1, bestKp - 0.5);
        float rangeMax = bestKp + 0.5;
        
        for (float kp = rangeMin; kp <= rangeMax; kp += 0.1) {
            float score = evaluatePerformance(kp);
            if (score < bestScore) {
                bestScore = score;
                bestKp = kp;
            }
        }
        
        Serial.print("Optimal Kp found: ");
        Serial.println(bestKp);
        
        return bestKp;
    }
    
private:
    float evaluatePerformance(float kp) {
        Kp = kp;
        TestResults results = runSingleTest(5000);  // 5 секунд
        
        // Комплексная оценка (чем меньше, тем лучше)
        float score = results.averageError * 100 +          // Точность
                     results.oscillationCount * 10 +        // Стабильность  
                     results.offTrackCount * 50;            // Надежность
        
        Serial.print("Kp="); Serial.print(kp);
        Serial.print(" Score="); Serial.println(score);
        
        return score;
    }
};

🎮 Интерактивная настройка в реальном времени

🕹️ Система живой настройки

class LiveTuner {
private:
    float kpStep = 0.1;
    
public:
    void setup() {
        Serial.println("Live Tuning Mode");
        Serial.println("Commands:");
        Serial.println("  + : Increase Kp by 0.1");
        Serial.println("  - : Decrease Kp by 0.1");
        Serial.println("  s : Show current settings");
        Serial.println("  d : Toggle debug mode");
        Serial.println("  r : Reset to default");
    }
    
    void processCommands() {
        if (Serial.available() > 0) {
            char command = Serial.read();
            
            switch (command) {
                case '+':
                    Kp += kpStep;
                    Serial.print("Kp increased to: "); Serial.println(Kp);
                    break;
                    
                case '-':
                    Kp = max(0.1, Kp - kpStep);
                    Serial.print("Kp decreased to: "); Serial.println(Kp);
                    break;
                    
                case 's':
                    showSettings();
                    break;
                    
                case 'd':
                    debugMode = !debugMode;
                    Serial.print("Debug mode: "); 
                    Serial.println(debugMode ? "ON" : "OFF");
                    break;
                    
                case 'r':
                    Kp = 1.0;
                    baseSpeed = 150;
                    Serial.println("Settings reset to default");
                    break;
                    
                case 'f':  // Fast mode
                    baseSpeed = min(255, baseSpeed + 20);
                    Serial.print("Speed increased to: "); Serial.println(baseSpeed);
                    break;
                    
                case 'w':  // sloW mode
                    baseSpeed = max(50, baseSpeed - 20);
                    Serial.print("Speed decreased to: "); Serial.println(baseSpeed);
                    break;
            }
        }
    }
    
private:
    bool debugMode = false;
    
    void showSettings() {
        Serial.println("=== Current Settings ===");
        Serial.print("Kp: "); Serial.println(Kp);
        Serial.print("Base Speed: "); Serial.println(baseSpeed);
        Serial.print("Max Correction: "); Serial.println(maxCorrection);
        Serial.print("Debug Mode: "); Serial.println(debugMode ? "ON" : "OFF");
        Serial.println("=======================");
    }
};

LiveTuner tuner;

void setup() {
    // ... основная инициализация ...
    tuner.setup();
}

void loop() {
    tuner.processCommands();
    followLineWithPController();
    delay(20);
}

🏁 Тестовые трассы для испытаний

🛣️ Уровни сложности

Уровень 1: Новичок 🟢

Трасса: ═══════════════════════════
        Прямая линия 2 метра
        
Критерии:
- Без отклонений > 1 см
- Время прохождения < 10 секунд
- Плавность движения

Уровень 2: Любитель 🟡

Трасса: ═══╗     ╔═══╗     ╔═══
           ╚═════╝   ╚═════╝
        Плавные повороты 90°
        
Критерии:
- Проходит все повороты
- Скорость поворота адаптивная
- Не теряет линию

Уровень 3: Эксперт 🔴

Трасса: ═══╗ ╔╗ ╔═══════╗ ╔═╗ ╔═══
           ╚═╝╚═╝       ╚═╝ ╚═╝
        Острые углы, зигзаги, S-повороты
        
Критерии:
- Все углы 90° и острее
- Минимальное время
- Максимальная точность

Уровень 4: Мастер 🏆

Трасса:    ╔═══╗
          ╔╝   ╚╗
         ╔╝     ╚╗
        ╔╝       ╚╗
       ╔╝         ╚╗
       ╚═══════════╝
        Спираль с уменьшающимся радиусом
        
Критерии:
- Переменная кривизна
- Адаптивная скорость
- Безукоризненная точность

🏆 Соревновательные испытания

🎯 Турнир точности

Этап 1: “Идеальная линия”

  • Робот должен пройти прямую линию без единого отклонения
  • Измеряется максимальное отклонение от центра
  • Победитель: минимальное отклонение

Этап 2: “Скорость и точность”

  • Комбинированная трасса с поворотами
  • Балл = (Точность × 1000) / Время
  • Нужен баланс между скоростью и качеством

Этап 3: “Адаптация”

  • Трасса меняется во время движения
  • Робот должен быстро адаптироваться
  • Оценивается время реакции на изменения

🏅 Система оценки

class CompetitionJudge {
public:
    struct Score {
        float accuracy;      // 0-100 баллов
        float speed;        // 0-100 баллов  
        float smoothness;   // 0-100 баллов
        float adaptability; // 0-100 баллов
        float total;        // Общий балл
    };
    
    Score evaluateRun(TestResults results, unsigned long time) {
        Score score;
        
        // Точность (чем меньше ошибка, тем больше баллов)
        score.accuracy = max(0.0, 100.0 - results.averageError * 100);
        
        // Скорость (бонус за быстрое прохождение)
        float timeBonus = max(0.0, 100.0 - time / 100.0);
        score.speed = timeBonus;
        
        // Плавность (штраф за колебания)
        float oscillationPenalty = min(100.0, results.oscillationCount * 5);
        score.smoothness = max(0.0, 100.0 - oscillationPenalty);
        
        // Адаптивность (штраф за съезды)
        float trackPenalty = min(100.0, results.offTrackCount * 10);
        score.adaptability = max(0.0, 100.0 - trackPenalty);
        
        // Общий балл (взвешенная сумма)
        score.total = score.accuracy * 0.4 + 
                     score.speed * 0.2 + 
                     score.smoothness * 0.3 + 
                     score.adaptability * 0.1;
        
        return score;
    }
    
    void printScore(Score score) {
        Serial.println("=== COMPETITION RESULTS ===");
        Serial.print("Accuracy:     "); Serial.println(score.accuracy);
        Serial.print("Speed:        "); Serial.println(score.speed);
        Serial.print("Smoothness:   "); Serial.println(score.smoothness);
        Serial.print("Adaptability: "); Serial.println(score.adaptability);
        Serial.print("TOTAL SCORE:  "); Serial.println(score.total);
        Serial.println("==========================");
    }
};

🏃 Физкультминутка: Человеческий P-регулятор

🎮 Упражнение “Живой регулятор”

Игра 1: “Балансир-регулятор”

  • Один ученик стоит на одной ноге (система)
  • Другой дает команды коррекции (регулятор)
  • Цель: минимальные колебания

Настройки P-регулятора:

  • Kp мал: “Слегка наклонись влево”
  • Kp велик: “РЕЗКО влево!”
  • Оптимальный: “Умеренно скорректируй положение”

Игра 2: “Робот-оператор”

  • “Робот” с завязанными глазами
  • “Регулятор” видит линию на полу
  • Команды только “левее/правее” с разной интенсивностью

Наблюдения:

  • При слишком слабых командах робот теряет линию
  • При слишком сильных - начинает “болтаться”
  • Оптимальные команды - четкие, но пропорциональные ошибке

🤔 Рефлексия: от теории к практике

🎯 Что мы освоили

Технические навыки:

  • ✅ Программирование пропорционального регулятора
  • ✅ Экспериментальная настройка параметров
  • ✅ Анализ поведения системы по графикам
  • ✅ Отладка и оптимизация алгоритмов

Математическое понимание:

  • ✅ Связь формул с реальным поведением робота
  • ✅ Влияние коэффициентов на устойчивость
  • ✅ Компромиссы между скоростью и точностью
  • ✅ Методы оценки качества регулирования

🔍 Ключевые инсайты

Почему простой P-регулятор так эффективен:

  • Прост в реализации и понимании
  • Быстро реагирует на ошибки
  • Легко настраивается экспериментально
  • Подходит для большинства задач следования

Ограничения P-регулятора:

  • Статическая ошибка при постоянных возмущениях
  • Колебания при слишком большом Kp
  • Медленная реакция при слишком малом Kp
  • Не учитывает историю и тренды

🌟 Главное понимание

“Программирование умного регулятора - это не просто набор команд. Это создание цифрового мозга, который думает, анализирует и принимает решения быстрее человека!”

🏠 Домашнее задание

📋 Базовый уровень (для всех)

1. Технический отчет Создайте отчет о практической работе:

  • Таблица экспериментов с разными Kp
  • График зависимости поведения от коэффициента
  • Обоснование выбора оптимального значения
  • Описание наблюдаемых эффектов

2. Анализ кода Изучите написанную программу и ответьте:

  • Какая строка кода самая важная для работы регулятора?
  • Что произойдет, если убрать ограничение correction?
  • Как изменить код для работы с тремя датчиками?

🎯 Повышенный уровень (по желанию)

3. Улучшенный регулятор Модифицируйте базовую программу:

  • Добавьте адаптивную базовую скорость
  • Реализуйте автоматическую калибровку датчиков
  • Добавьте защиту от потери линии

4. Исследование алгоритмов Сравните P-регулятор с другими подходами:

  • Простое if-else управление
  • Регулятор с зоной нечувствительности
  • Нелинейный регулятор (квадратичная зависимость)

⭐ Для школьных аспирантов

5. ПИД-регулятор Расширьте P-регулятор до полного ПИД:

  • Добавьте интегральную составляющую
  • Реализуйте дифференциальную составляющую
  • Сравните качество работы с простым P-регулятором

6. Машинное обучение Исследуйте возможность автоматической настройки:

  • Алгоритм генетической оптимизации коэффициентов
  • Адаптивная система, обучающаяся на ошибках
  • Нейронная сеть для предсказания оптимального Kp

🎉 Заключение практикума

🏆 Наши достижения

Практические результаты:

  • 🤖 Создали робота с умным следованием по линии
  • 💻 Написали и отладили программу P-регулятора
  • 🔧 Освоили экспериментальную настройку параметров
  • 📊 Научились анализировать поведение системы

Инженерные навыки:

  • ⚙️ Связь математической теории с практикой
  • 🧪 Методика систематических экспериментов
  • 🎯 Оптимизация технических систем
  • 📈 Анализ данных и выявление закономерностей

🌟 Связь с большой инженерией

Где используются те же принципы:

  • 🚗 Системы стабилизации автомобилей
  • ✈️ Автопилоты самолетов и дронов
  • 🏭 Промышленные роботы-манипуляторы
  • 🚀 Системы ориентации космических аппаратов
  • 🏠 Умные системы климат-контроля

🎯 Сегодня вы создали основу для понимания современных систем автоматического управления!

🚀 Путь вперед

Следующие шаги в робототехнике:

  • Более сложные алгоритмы управления (ПИД, нечеткая логика)
  • Машинное обучение в робототехнике
  • Компьютерное зрение для навигации
  • Роевая робототехника и коллективное поведение

Ваша программа P-регулятора - это первый шаг к созданию по-настоящему умных роботов!

📚 Ресурсы для развития

🔗 Углубленное изучение

Теория управления:

Практическое применение:

🛠️ Инструменты разработчика

Симуляторы:

  • MATLAB/Simulink - моделирование систем управления
  • Gazebo - 3D симуляция роботов
  • Python Control Library - программирование регуляторов

Библиотеки:

  • Arduino PID Library - готовые решения
  • ROS Control - профессиональная робототехника
  • OpenCV - компьютерное зрение

Поздравляем с освоением программирования умного управления! 💻🤖✨