🎯 Цель: Создаем программу с пропорциональным регулятором
⭐ Результат: Робот плавно и точно следует по любой линии
👨🏫 Учитель: Ахметов Рустам
🏫 Школа: ГБОУ № 1362
📅 Дата: 2025-06-14
⏰ Время: 100 минут
Создаем робота-мастера линии:
Ваш робот должен:
💡 Секрет успеха: правильно настроенный пропорциональный регулятор!
Вспоминаем формулу: \[u(t) = K_p \times 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}\]Логика работы:
// Глобальные параметры регулятора
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);
}
};
Пошаговый алгоритм:
Начальная настройка:
Постепенное увеличение:
Поиск оптимума:
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: “Скорость и точность”
Этап 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("==========================");
}
};
Игра 1: “Балансир-регулятор”
Настройки P-регулятора:
Игра 2: “Робот-оператор”
Наблюдения:
Технические навыки:
Математическое понимание:
Почему простой P-регулятор так эффективен:
Ограничения P-регулятора:
“Программирование умного регулятора - это не просто набор команд. Это создание цифрового мозга, который думает, анализирует и принимает решения быстрее человека!”
1. Технический отчет Создайте отчет о практической работе:
2. Анализ кода Изучите написанную программу и ответьте:
3. Улучшенный регулятор Модифицируйте базовую программу:
4. Исследование алгоритмов Сравните P-регулятор с другими подходами:
5. ПИД-регулятор Расширьте P-регулятор до полного ПИД:
6. Машинное обучение Исследуйте возможность автоматической настройки:
Практические результаты:
Инженерные навыки:
Где используются те же принципы:
🎯 Сегодня вы создали основу для понимания современных систем автоматического управления!
Следующие шаги в робототехнике:
Ваша программа P-регулятора - это первый шаг к созданию по-настоящему умных роботов!
Теория управления:
Практическое применение:
Симуляторы:
Библиотеки:
Поздравляем с освоением программирования умного управления! 💻🤖✨