Три буквы, которые изменили робототехнику
Робот по линии дёргается:
Цель: ехать по центру
Реальность: влево-вправо-влево-вправо 🐍
─────────────────────────── ← Линия
🤖~~~~~~~~~~~~~~~~~~~~~ ← Траектория робота
Как сделать плавно?
1. Вода холодная → Крутишь на ГОРЯЧО
2. Ждёшь...
3. КИПЯТОК! → Крутишь на ХОЛОДНО
4. Ждёшь...
5. ЛЕДЯНАЯ! → Крутишь на ГОРЯЧО
6. ... повторяется вечно
Проблема: Слишком резкие коррекции!
1. Вода холодная → Крутишь ЧУТЬ-ЧУТЬ на горячо
2. Ждёшь, следишь за изменением
3. Стало теплее, но мало → Ещё чуть-чуть
4. Приближается к норме → Замедляешь коррекцию
5. Идеально! → Фиксируешь
Это и есть PID!
| Буква | Название | Что делает |
|---|---|---|
| P | Proportional | Реагирует на текущую ошибку |
| I | Integral | Накапливает прошлые ошибки |
| D | Derivative | Предсказывает будущую ошибку |
Чем больше ошибка — тем сильнее коррекция
float error = target - current;
float P = Kp * error;
Ошибка: ████████░░░░░░░░ (большая)
Коррекция: ████████░░░░░░░░ (сильная)
Ошибка: ██░░░░░░░░░░░░░░ (маленькая)
Коррекция: ██░░░░░░░░░░░░░░ (слабая)
Колебания вокруг цели (перерегулирование):
Цель ─────────────────────────────
╱╲ ╱╲ ╱╲
╱ ╲ ╱ ╲ ╱ ╲
╱ ╲ ╱ ╲ ╱ ╲
─────╱ ╲╱ ╲╱ ╲────
Робот «перелетает» цель
Тормозит при быстром изменении (демпфер)
float derivative = (error - lastError) / dt;
float D = Kd * derivative;
lastError = error;
Аналогия: Тормоза в машине. Чем быстрее приближаешься к стене — тем сильнее тормозишь.
Только P
Цель ─────────────────────────────
╱╲ ╱╲
╱ ╲ ╱ ╲
──────╱ ╲──╱ ╲──────────────
P + D
Цель ─────────────────────────────
╱╲
╱ ╲___________________________
──────╱
D гасит колебания!
Устраняет постоянную ошибку (смещение)
integral += error * dt;
float I = Ki * integral;
Проблема: Робот систематически едет чуть левее центра.
P и D не помогут — ошибка маленькая!
I накапливает маленькие ошибки → большая коррекция.
Без I:
Цель ─────────────────────────────
___________________
───────────────╱
↑ Постоянная ошибка (offset)
С I:
Цель ─────────────────────────────
╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
─────────────╱
I устранил смещение!
// Коэффициенты (настраиваются!)
float Kp = 1.0;
float Ki = 0.05;
float Kd = 0.5;
// Состояние
float lastError = 0;
float integral = 0;
unsigned long lastTime = 0;
float computePID(float target, float current) {
// Время между вызовами
unsigned long now = millis();
float dt = (now - lastTime) / 1000.0; // в секундах
lastTime = now;
// Ошибка
float error = target - current;
// P
float P = Kp * error;
// I (с ограничением!)
integral += error * dt;
integral = constrain(integral, -100, 100); // Anti-windup
float I = Ki * integral;
// D
float derivative = (error - lastError) / dt;
float D = Kd * derivative;
lastError = error;
return P + I + D;
}
const int BASE_SPEED = 150;
const float TARGET = 0; // Центр линии
void loop() {
float position = readLinePosition(); // -100 ... +100
float correction = computePID(TARGET, position);
int leftSpeed = BASE_SPEED + correction;
int rightSpeed = BASE_SPEED - correction;
// Ограничиваем скорость
leftSpeed = constrain(leftSpeed, 0, 255);
rightSpeed = constrain(rightSpeed, 0, 255);
setMotors(leftSpeed, rightSpeed);
delay(10); // ~100 Hz
}
void loop() {
float angle = readIMU(); // Угол наклона в градусах
float correction = computePID(0, angle); // Цель = 0° (вертикально)
// Положительная коррекция = вперёд, отрицательная = назад
setMotors(correction, correction);
}
Kp = 0.6 × Ku
Ki = 2 × Kp / Tu
Kd = Kp × Tu / 8
Шаг 1: Только P
├── Начни с Kp = 1
├── Увеличивай, пока не начнутся колебания
└── Уменьши на 20%
Шаг 2: Добавь D
├── Начни с Kd = 0.1 × Kp
├── Увеличивай, пока колебания не исчезнут
└── Не переборщи — робот станет «вялым»
Шаг 3: Добавь I (если нужно)
├── Начни с Ki = 0.01
├── Увеличивай, пока не исчезнет постоянная ошибка
└── Осторожно — легко получить колебания!
| Применение | Kp | Ki | Kd |
|---|---|---|---|
| Line follower | 0.5-2.0 | 0-0.1 | 0.1-0.5 |
| Балансирующий | 10-50 | 0.1-1 | 1-5 |
| Термостат | 1-10 | 0.01-0.1 | 0-1 |
| Серво позиция | 1-5 | 0 | 0.1-0.5 |
Kp слишком мал: Kp хороший: Kp слишком велик:
Цель ────────── Цель ────────── Цель ──────────
╱‾‾‾‾ ╱╲ ╱╲
__________ ╱ ╱ ╲╱ ╲
─────╱ ─────╱ ─────╱
(медленно) (быстро, плавно) (колебания)
Без ограничения интеграл «накручивается» при долгой ошибке:
// ПЛОХО:
integral += error * dt; // Может стать ОГРОМНЫМ!
// ХОРОШО:
integral += error * dt;
integral = constrain(integral, -MAX_INTEGRAL, MAX_INTEGRAL);
При резком изменении цели (не позиции) D даёт «пинок»:
// ПЛОХО:
float derivative = (error - lastError) / dt;
// ЛУЧШЕ: Дифференцируем только измерение
float derivative = -(current - lastCurrent) / dt;
lastCurrent = current;
void loop() {
float error = abs(target - current);
if (error > 50) {
// Далеко от цели — агрессивный PID
Kp = 2.0; Ki = 0; Kd = 0.5;
} else if (error > 10) {
// Близко — стандартный
Kp = 1.0; Ki = 0.05; Kd = 0.3;
} else {
// Очень близко — мягкий
Kp = 0.5; Ki = 0.1; Kd = 0.2;
}
float correction = computePID(target, current);
// ...
}
Измени Kp, Ki, Kd через Serial и смотри результат!
| Компонент | Реагирует на | Эффект |
|---|---|---|
| P | Текущую ошибку | Быстрая реакция |
| I | Накопленную ошибку | Устраняет смещение |
| D | Скорость изменения | Гасит колебания |
P → D → I (не наоборот!)
📚 Алгоритмы управления
📚 Контуры обратной связи
📚 Фильтрация сигналов
Готовимся к реальным робототехническим соревнованиям!