От учебного робота к соревновательной машине
Учебный проект: Соревнования:
────────────── ─────────────
✓ Работает как-то ✓ Работает НАДЁЖНО
✓ Дома на столе ✓ На ЛЮБОЙ поверхности
✓ Время неважно ✓ БЫСТРЕЕ всех
✓ Один запуск ✓ 10 запусков подряд
Соревнования — это стресс-тест! 💪
┌─────────────────────────────────────┐
│ START │
│ ══╗ │
│ ║ ╔════════╗ │
│ ║ ║ ║ │
│ ╚═══╝ ╚═══════╗ │
│ ║ FINISH │
│ ─────────────────────────╝ │
└─────────────────────────────────────┘
Критерии: время, сход с линии
┌─────────────┐
╱ ● ● ● ╲
│ ● 🤖 ● │
│ ● ● ● │
╲ ╱
└─────────────┘
Цель: вытолкнуть кегли за 60 секунд
│ START │═══════════════════════│ FINISH │
│ │ │ │
│ 🤖A │ → → → → → → → → → → │ │
│ │ │ │
│ 🤖B │ → → → → → → → → → → │ │
│ │ │ │
Кто быстрее проедет прямую?
┌─┬───┬─┬───┬─┐
│S│ │ │ │ │
├─┘ ┌─┘ └─┐ └─┤
│ │ │ │
├───┴─┬───┴───┤
│ │ │
├─┬───┤ ┌───┬─┤
│ │ │ │ │F│
└─┴───┴─┴───┴─┘
Найти выход за минимум времени
╱‾‾‾‾‾‾‾‾‾‾‾╲
╱ ╲
│ 🤖A 🤖B │
│ │
╲ (чёрный ╱
╲ круг) ╱
‾‾‾‾‾‾
Белая граница — не выезжать!
Цель: вытолкнуть соперника
□ Надёжные соединения (пайка > dupont)
□ Защита от вибраций (резинки, термоклей)
□ Лёгкий доступ к батарее
□ Запасной комплект (датчики, провода)
□ Кнопка аварийной остановки
□ Индикатор заряда батареи
□ Датчики защищены от света
□ Калибровка датчиков перед стартом
□ Несколько программ (профили трассы)
□ Настройка PID через Serial/кнопки
□ Логирование данных (для анализа)
□ Watchdog (перезагрузка при зависании)
□ Graceful degradation (работа при отказе)
□ Колёса не проскальзывают (резина!)
□ Центр тяжести низко
□ Датчики на правильной высоте
□ Ничего не болтается
□ Провода не цепляют колёса
□ Шасси жёсткое (не гнётся)
int whiteValue[8]; // Массив для белого
int blackValue[8]; // Массив для чёрного
void calibrate() {
Serial.println("Поставь на БЕЛОЕ, нажми кнопку");
waitButton();
for (int i = 0; i < 8; i++) {
whiteValue[i] = analogRead(sensorPins[i]);
}
Serial.println("Поставь на ЧЁРНОЕ, нажми кнопку");
waitButton();
for (int i = 0; i < 8; i++) {
blackValue[i] = analogRead(sensorPins[i]);
}
Serial.println("Калибровка завершена!");
}
int readSensor(int i) {
int raw = analogRead(sensorPins[i]);
return map(raw, whiteValue[i], blackValue[i], 0, 1000);
}
void loop() {
float error = readLineError();
float absError = abs(error);
// На прямой — быстро, на повороте — медленно
int speed;
if (absError < 10) {
speed = MAX_SPEED; // Прямая!
} else if (absError < 30) {
speed = MEDIUM_SPEED;
} else {
speed = MIN_SPEED; // Крутой поворот
}
float correction = computePID(0, error);
setMotors(speed + correction, speed - correction);
}
int lineWidth = countSensorsOnLine();
if (lineWidth >= 6) {
// Перекрёсток или финиш!
if (lapCount >= 3) {
stop(); // Финиш!
} else {
// Продолжаем по правилу (например, всегда прямо)
goStraight();
lapCount++;
}
}
unsigned long lastLineTime = 0;
void loop() {
if (lineDetected()) {
lastLineTime = millis();
normalDriving();
} else {
// Линия потеряна!
unsigned long lostTime = millis() - lastLineTime;
if (lostTime < 200) {
// Недавно потеряли — продолжаем в том же направлении
continueLastDirection();
} else if (lostTime < 1000) {
// Давно потеряли — ищем
searchPattern(); // Спираль или зигзаг
} else {
// Совсем потерялись — стоп
stop();
}
}
}
enum State { SEARCH, ATTACK, ESCAPE };
State state = SEARCH;
void loop() {
bool enemyDetected = (frontDistance < 50);
bool onEdge = (floorSensor == WHITE);
switch (state) {
case SEARCH:
// Крутимся, ищем соперника
spin();
if (enemyDetected) state = ATTACK;
if (onEdge) state = ESCAPE;
break;
case ATTACK:
// Таран!
fullSpeedForward();
if (!enemyDetected) state = SEARCH;
if (onEdge) state = ESCAPE;
break;
case ESCAPE:
// Отъезжаем от края
reverse();
delay(200);
turn180();
state = SEARCH;
break;
}
}
1. ЗАСАДА
Стоим у края, ждём атаки, уворачиваемся
→ Соперник вылетает за ринг!
2. КЛЕЩИ
Заезжаем сбоку, толкаем под углом
→ Соперник не может упереться
3. ЛИФТ
Низкий клин под соперника
→ Поднимаем и выталкиваем
4. ЛОЖНОЕ ОТСТУПЛЕНИЕ
Отъезжаем назад
→ Соперник разгоняется
→ Уворачиваемся, он вылетает!
□ Полностью заряди все батареи
□ Проверь запасные части
□ Распечатай регламент
□ Подготовь инструменты (отвёртки, изолента)
□ Сделай backup кода
□ Выспись!
□ Осмотри трассу/ринг
□ Проверь освещение (влияет на датчики!)
□ Откалибруй на РЕАЛЬНОЙ поверхности
□ Сделай тестовый заезд
□ Запомни особенности трассы
□ Познакомься с соперниками 🤝
□ Анализируй: что пошло не так?
□ Меняй ОДНУ переменную за раз
□ Записывай настройки
□ Следи за зарядом батареи
□ Не паникуй!
| Ошибка | Как избежать |
|---|---|
| Сели батареи | Запасной комплект! |
| Отвалился провод | Пайка + термоклей |
| Не та программа | Подписывай профили |
| Датчик ослеп от света | Экран или юбка |
| Перегрев моторов | Паузы между заездами |
Заезд 1: Kp=1.5, Ki=0, Kd=0.3
Время: 12.4с, сход на повороте 3
Заезд 2: Kp=1.2, Ki=0, Kd=0.5
Время: 13.1с, без сходов
Заезд 3: Kp=1.2, Ki=0.05, Kd=0.5
Время: 11.8с, без сходов ← ЛУЧШИЙ!
1. Снимай КАЖДЫЙ заезд
2. Смотри в замедленном режиме
3. Отмечай:
- Где теряет скорость?
- Где колеблется?
- Где сходит?
4. Сравнивай с победителями
ЧТО СРАБОТАЛО:
+ Калибровка перед каждым заездом
+ Запасная батарея
+ Низкий центр тяжести
ЧТО НЕ СРАБОТАЛО:
- Датчики слепли от прожекторов
- PID не успевал на резких поворотах
- Провода мешали
ПЛАН НА СЛЕДУЮЩИЙ РАЗ:
□ Добавить экран над датчиками
□ Увеличить частоту loop до 200 Hz
□ Переделать проводку
Level 3 — Инженер ждёт тебя: