Skip to main content

Отладка и тестирование. Как найти баг до того, как робот врежется в стену

Вы написали код, загрузили в робота, нажали кнопку — и робот поехал не туда. Или вообще не поехал. Или загорелся. Отладка — это искусство находить, почему всё пошло не так.


Три уровня отладки

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 перезагружаетсяНапряжение на Vin7-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 секунд
  • Проверить индикатор питания
  • Убедиться, что датчики “видят” трассу

Проверь себя

Смогу ли я:

  1. Найти, какая строка кода вызывает ошибку?
  2. Определить, работает ли датчик без компьютера?
  3. Понять, почему I2C-устройство не отвечает?
  4. Объяснить другу, что делает мой код?

Если на все вопросы можете ответить — вы готовы отлаживать роботов!


Что дальше?