Отладка и тестирование. Как найти баг до того, как робот врежется в стену
Вы написали код, загрузили в робота, нажали кнопку — и робот поехал не туда. Или вообще не поехал. Или загорелся. Отладка — это искусство находить, почему всё пошло не так.
Три уровня отладки
1. Программная отладка (код)
Вопрос: Почему программа делает не то, что я хочу?
2. Аппаратная отладка (железо)
Вопрос: Почему датчик показывает ерунду? Почему мотор не крутится?
3. Системная отладка (всё вместе)
Вопрос: Почему робот ведёт себя странно, хотя код и железо по отдельности работают?
1. Serial Monitor — ваш лучший друг
Базовое использование:
void setup() {
Serial.begin(115200); // Быстрее = лучше
Serial.println("=== Робот запущен ===");
}
void loop() {
int sensorValue = analogRead(A0);
Serial.print("Датчик: ");
Serial.println(sensorValue);
delay(100);
}
Продвинутые техники:
Условный вывод (чтобы не засорять консоль):
#define DEBUG true // Поставьте false для "боевого" режима
void debugPrint(String msg) {
#if DEBUG
Serial.println(msg);
#endif
}
void loop() {
debugPrint("Начало цикла");
// ... код ...
debugPrint("Конец цикла");
}
Форматированный вывод:
void printSensorData(float temp, float humidity, int distance) {
Serial.println("┌─────────────────────────┐");
Serial.print("│ Температура: ");
Serial.print(temp, 1); // 1 знак после запятой
Serial.println(" °C │");
Serial.print("│ Влажность: ");
Serial.print(humidity, 0);
Serial.println(" % │");
Serial.print("│ Расстояние: ");
Serial.print(distance);
Serial.println(" см │");
Serial.println("└─────────────────────────┘");
}
Временны́е метки:
unsigned long startTime;
void setup() {
Serial.begin(115200);
startTime = millis();
}
void logWithTime(String msg) {
unsigned long elapsed = millis() - startTime;
Serial.print("[");
Serial.print(elapsed / 1000.0, 3); // Секунды с 3 знаками
Serial.print("s] ");
Serial.println(msg);
}
// Вывод: [1.234s] Мотор запущен
// [1.567s] Датчик прочитан
2. Светодиодная отладка (когда Serial недоступен)
Иногда робот работает автономно, и Serial Monitor не подключить. Тогда используем светодиоды!
Код состояния:
void showStatus(int code) {
// code = количество миганий
for (int i = 0; i < code; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
delay(200);
}
delay(1000); // Пауза между сериями
}
// В коде:
// showStatus(1) = всё OK
// showStatus(2) = датчик не отвечает
// showStatus(3) = батарея разряжена
// showStatus(5) = критическая ошибка
RGB-светодиод для статуса:
void setStatusLED(char status) {
switch(status) {
case 'G': // Зелёный = OK
analogWrite(RED_PIN, 0);
analogWrite(GREEN_PIN, 255);
analogWrite(BLUE_PIN, 0);
break;
case 'Y': // Жёлтый = Предупреждение
analogWrite(RED_PIN, 255);
analogWrite(GREEN_PIN, 150);
analogWrite(BLUE_PIN, 0);
break;
case 'R': // Красный = Ошибка
analogWrite(RED_PIN, 255);
analogWrite(GREEN_PIN, 0);
analogWrite(BLUE_PIN, 0);
break;
}
}
3. Аппаратная отладка
Мультиметр — must have!
Что проверять:
| Проблема | Что измерять | Ожидаемое значение |
|---|---|---|
| Мотор не крутится | Напряжение на моторе | 3-12V (зависит от мотора) |
| Датчик не работает | Питание датчика | 3.3V или 5V |
| Arduino перезагружается | Напряжение на Vin | 7-12V стабильно |
| I2C не работает | SDA/SCL при передаче | Импульсы 0-3.3V |
Логический анализатор (для протоколов)
Когда нужен:
- I2C не видит устройства
- UART передаёт “мусор”
- SPI работает нестабильно
Что смотреть:
I2C: Начало (Start) → Адрес → ACK/NACK → Данные → Стоп
UART: Стартовый бит → 8 бит данных → Стоповый бит
SPI: CS низкий → Данные по фронтам CLK → CS высокий
Дешёвые варианты:
- Logic Analyzer 8ch USB (~500₽)
- Saleae Logic (дороже, но с крутым софтом)
- DSLogic (средний вариант)
4. Методы поиска ошибок
Метод “Разделяй и властвуй”:
Робот не едет по линии. Что проверить?
Шаг 1: Работают ли датчики линии?
→ Вывести значения в Serial
→ Если 0 или 1023 всегда → проблема в датчиках
Шаг 2: Работает ли логика?
→ Вручную подать "хорошие" значения
→ Проверить, правильно ли вычисляется направление
Шаг 3: Работают ли моторы?
→ Напрямую включить моторы
→ Если не крутятся → проблема в драйвере/питании
Шаг 4: Всё ли связано правильно?
→ Проверить, те ли пины используются
→ Правильная ли полярность моторов
Метод “Бинарный поиск” (для длинного кода):
void loop() {
Serial.println("1"); // ← Код доходит сюда?
readSensors();
Serial.println("2"); // ← А сюда?
calculatePath();
Serial.println("3"); // ← А сюда?
controlMotors();
Serial.println("4"); // ← И сюда?
}
// Если выводит "1", "2", но не "3" — проблема в calculatePath()
Метод “Резиновая уточка”:
Объясните коду (или уточке, или другу), что он должен делать. Часто в процессе объяснения вы сами найдёте ошибку!
5. Тестирование компонентов
Тест-скетч для моторов:
// Загрузите этот код, чтобы проверить моторы отдельно
void setup() {
pinMode(MOTOR_A_EN, OUTPUT);
pinMode(MOTOR_A_IN1, OUTPUT);
pinMode(MOTOR_A_IN2, OUTPUT);
}
void loop() {
Serial.println("Мотор A вперёд");
digitalWrite(MOTOR_A_IN1, HIGH);
digitalWrite(MOTOR_A_IN2, LOW);
analogWrite(MOTOR_A_EN, 200);
delay(2000);
Serial.println("Мотор A назад");
digitalWrite(MOTOR_A_IN1, LOW);
digitalWrite(MOTOR_A_IN2, HIGH);
delay(2000);
Serial.println("Стоп");
analogWrite(MOTOR_A_EN, 0);
delay(2000);
}
Тест-скетч для I2C (сканер устройств):
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(115200);
Serial.println("I2C Scanner");
}
void loop() {
byte count = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Найдено устройство: 0x");
Serial.println(addr, HEX);
count++;
}
}
Serial.print("Всего устройств: ");
Serial.println(count);
delay(5000);
}
Тест-скетч для сервоприводов:
#include <Servo.h>
Servo myServo;
void setup() {
myServo.attach(9);
Serial.begin(115200);
Serial.println("Введите угол (0-180):");
}
void loop() {
if (Serial.available()) {
int angle = Serial.parseInt();
if (angle >= 0 && angle <= 180) {
myServo.write(angle);
Serial.print("Угол: ");
Serial.println(angle);
}
}
}
6. Типичные ошибки и их симптомы
| Симптом | Вероятная причина | Как проверить |
|---|---|---|
| Робот дёргается | Плохой контакт | Пошевелить провода |
| Датчик даёт 0 | Нет питания / неверный пин | Мультиметр на питание |
| Serial выводит “мусор” | Неправильная скорость | Проверить baud rate |
| I2C не находит устройства | Нет подтягивающих резисторов | Добавить 4.7кОм на SDA/SCL |
| Мотор не стартует | Недостаточный ток | Проверить источник питания |
| ESP32 перезагружается | Просадка питания | Добавить конденсатор 1000мкФ |
7. Инструменты для ESP32 / Raspberry Pi
ESP32: Встроенный отладчик
// В platformio.ini:
// debug_tool = esp-prog
// debug_init_break = tbreak setup
// Теперь можно ставить breakpoints в VS Code!
Raspberry Pi: Удалённая отладка
# На Pi:
sudo apt install gdbserver
gdbserver :1234 ./my_robot
# На компьютере (VS Code + Remote SSH):
# Подключаетесь к Pi и отлаживаете как локально
Веб-интерфейс для телеметрии (ESP32):
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
void handleRoot() {
String html = "<h1>Телеметрия робота</h1>";
html += "<p>Температура: " + String(readTemp()) + " C</p>";
html += "<p>Батарея: " + String(readBattery()) + " V</p>";
html += "<meta http-equiv='refresh' content='1'>"; // Обновление каждую секунду
server.send(200, "text/html", html);
}
void setup() {
WiFi.begin("ssid", "password");
server.on("/", handleRoot);
server.begin();
}
8. Чек-лист перед соревнованиями
За неделю:
- Код работает стабильно 10+ запусков подряд
- Батарея держит всё время заезда + запас 50%
- Все провода припаяны (не на макетке!)
За день:
- Зарядить все батареи
- Проверить все датчики
- Проверить все моторы
- Загрузить ФИНАЛЬНУЮ версию кода
Перед стартом:
- Включить робота, подождать 5 секунд
- Проверить индикатор питания
- Убедиться, что датчики “видят” трассу
Проверь себя
✅ Смогу ли я:
- Найти, какая строка кода вызывает ошибку?
- Определить, работает ли датчик без компьютера?
- Понять, почему I2C-устройство не отвечает?
- Объяснить другу, что делает мой код?
Если на все вопросы можете ответить — вы готовы отлаживать роботов!
Что дальше?
- Логирование — как записывать данные для анализа потом
- Паттерны проектирования — как писать код, который легко отлаживать
- Надёжность систем — как сделать робота устойчивым к ошибкам
