Skip to main content

Watchdog и Избыточность. Как не зависнуть и не сломаться

Представьте, что робот-пожарный должен найти людей в горящем здании. Что случится, если его программа зависнет? Или если один датчик закоптится? Watchdog и избыточность — это системы, которые спасают в таких ситуациях.


1. Watchdog Timer — электронный “сторожевой пёс”

Что это такое?

Это отдельный таймер внутри микроконтроллера, который не зависит от основной программы. Если программа зависнет и перестанет “кормить” этого пса, он “кусает” микроконтроллер — перезагружает его.

Как работает:

Нормальная работа:
[Программа] → каждые 0.5 сек → [WDT: сброс] → [Программа]
                          (всё хорошо)

Зависание:
[Программа ЗАВИСЛА] → ... → [WDT: таймер 0] → ПЕРЕЗАГРУЗКА!

Код для Arduino:

#include <avr/wdt.h>  // Библиотека для Watchdog

void setup() {
  Serial.begin(9600);
  Serial.println("Робот запускается...");
  
  // Включаем Watchdog на 2 секунды
  // WDTO_2S значит: перезагрузить через 2 секунды тишины
  wdt_enable(WDTO_2S);
}

void loop() {
  // Делаем свою работу
  readSensors();
  makeDecision();
  controlMotors();
  
  // ВАЖНО: "Кормим собаку" - сбрасываем таймер
  // Если эта строка не выполнится за 2 секунды -> перезагрузка
  wdt_reset();
}

// Пример зависания - ЗАКОММЕНТИРУЙТЕ для теста!
void badFunction() {
  while(true) {
    // Бесконечный цикл!
    // Watchdog перезагрузит Arduino через 2 секунды
  }
}

Для ESP32 (более сложно):

#include "esp_task_wdt.h"

void setup() {
  // Подключаем Watchdog к главной задаче (Task)
  esp_task_wdt_init(2, true); // Таймаут 2 секунды
  esp_task_wdt_add(NULL); // Добавляем текущую задачу
  
  // Теперь если задача зависнет >2 сек -> перезагрузка
}

void loop() {
  // Делаем что-то...
  
  // Сбрасываем Watchdog для этой задачи
  esp_task_wdt_reset();
  
  // Если добавили другие задачи, их тоже нужно сбрасывать
}

2. Избыточность — если один сломался, есть запасной

Типы избыточности:

А) Дублирование датчиков

Пример: Робот с двумя ультразвуковыми датчиками спереди.

float getDistance() {
  float dist1 = ultrasonic1.read();
  float dist2 = ultrasonic2.read();
  
  // Если датчики показывают примерно одинаково
  if (abs(dist1 - dist2) < 10) {
    return (dist1 + dist2) / 2; // Среднее значение
  }
  // Если один сломался (показывает 0 или 1000)
  else if (dist1 < 1 || dist1 > 500) {
    return dist2; // Используем только рабочий
  }
  else if (dist2 < 1 || dist2 > 500) {
    return dist1;
  }
  // Если оба показывают разное, но в пределах
  else {
    return min(dist1, dist2); // Берем ближайший (безопаснее)
  }
}

Б) Разные типы датчиков

Пример: Измерение расстояния тремя способами:

Для робота-исследователя:
1. Ультразвуковой датчик: точен на 20-200 см
2. Инфракрасный датчик: точен на 10-80 см  
3. Лазерный дальномер (LiDAR): точен везде, но дорогой

Если один запылился или сломался — используем другие!

В) Дублирование моторов

Гусеничный робот с 2 моторами на борт:
[Левый борт] Мотор1 → Мотор2 (резервный)
[Правый борт] Мотор3 → Мотор4 (резервный)

Если Мотор1 сломался → включаем Мотор2
Робот продолжает движение, только медленнее

Практические примеры для школы

Проект 1: Робот для соревнований “Следование по линии”

Проблема: ИК-датчик может загрязниться или сломаться
Решение: 5 датчиков вместо 3

Логика:
1. Если все 5 работают → используем все
2. Если 1 сломался → используем 4
3. Если 2 сломался → используем 3 (минимум)
4. Если 3 сломалось → останавливаемся, сигнал ошибки

Код:
int sensors[5] = {0, 0, 0, 0, 0};
int workingCount = 0;
float average = 0;

for(int i = 0; i < 5; i++) {
  int val = readSensor(i);
  if(val >= 100 && val <= 900) { // Рабочий диапазон
    sensors[i] = val;
    workingCount++;
    average += val;
  }
}

if(workingCount >= 3) {
  average /= workingCount;
  followLine(average); // Работаем
} else {
  emergencyStop(); // Слишком много сломалось
}

Проект 2: Робот-метеостанция

Избыточность данных:
1. Температура: 2 датчика (DHT22 + DS18B20)
2. Давление: 2 датчика (BMP280 + BME280)
3. Влажность: 2 датчика (DHT22 + отдельный)

Если датчики показывают разное:
• Берём среднее
• Отмечаем в логе "расхождение"
• Если расхождение большое → используем более надежный

Проект 3: Робот с манипулятором

Защита от сбоев:
1. Watchdog на 500 мс (быстрая реакция)
2. 2 концевых выключателя на каждом суставе
3. Датчик тока на каждом моторе
4. Резервный источник питания для контроллера

При любой ошибке:
1. Watchdog перезагружает если завис
2. Концевик останавливает мотор физически
3. Датчик тока отключает при перегрузке
4. Резервное питание держит 5 сек для безопасной остановки

Как настроить Watchdog правильно

Настройка времени:

Короткое время (100-500 мс):
• Для быстрых роботов (дроны, гоночные)
• Быстрое обнаружение зависаний
• Но нужно часто сбрасывать

Длинное время (1-8 секунд):
• Для медленных роботов (манипуляторы, метеостанции)
• Можно делать долгие операции (измерения, отправка данных)
• Медленнее реагирует на зависания

Что делать после перезагрузки:

#include <avr/wdt.h>

void setup() {
  // Проверяем причину перезагрузки
  if (MCUSR & (1 << WDRF)) { // Перезагрузились из-за Watchdog
    Serial.println("ПЕРЕЗАГРУЗКА Watchdog! Программа зависла.");
    logError("Watchdog reset");
    
    // Можно восстановить состояние
    recoverFromCrash();
  }
  
  MCUSR = 0; // Сбрасываем флаг
  wdt_enable(WDTO_2S); // Включаем снова
}

Избыточность в действии: реальный пример

Ситуация: Автономный робот-уборщик

Компоненты:
• Основной контроллер: Raspberry Pi (планирование)
• Резервный контроллер: Arduino (аварийное управление)
• Датчики расстояния: 8 штук (4 основных + 4 резервных)
• Моторы: 4 (каждый может работать в одиночку)

При отказе:
1. Если Raspberry Pi завис → Arduino берет управление
2. Если 1-2 датчика сломались → используем остальные
3. Если 1 мотор сломался → едем на 3-х, медленнее
4. Если 2 мотора сломались → останавливаемся, зовем помощь

Код управления моторами:
void controlMotors() {
  // Проверяем работоспособность каждого мотора
  bool motorsOK[4] = {true, true, true, true};
  
  for(int i = 0; i < 4; i++) {
    if (getMotorCurrent(i) > MAX_CURRENT) motorsOK[i] = false;
    if (getMotorTemperature(i) > 80) motorsOK[i] = false;
  }
  
  // Сколько моторов работает?
  int working = 0;
  for(int i = 0; i < 4; i++) if(motorsOK[i]) working++;
  
  if (working == 4) {
    // Все хорошо - нормальная работа
    setAllMotors(normalSpeed);
  }
  else if (working >= 2) {
    // Работаем в ограниченном режиме
    setWorkingMotors(reducedSpeed);
    sendAlert("Некоторые моторы не работают");
  }
  else {
    // Слишком много сломалось
    emergencyStop();
  }
}

Частые ошибки с Watchdog и избыточностью

Ошибка 1: “Слишком длинные delay()”

Проблема: delay(5000) на 5 секунд
Watchdog сработает через 2 секунды → перезагрузка

Решение:
unsigned long start = millis();
while(millis() - start < 5000) {
  wdt_reset(); // Сбрасываем в цикле
  // Можно делать что-то полезное
}

Ошибка 2: “Дублирование одинаковых слабых мест”

Проблема: 2 одинаковых дешёвых датчика
Оба выходят из строя в одинаковых условиях

Решение: Разные типы датчиков
Ультразвуковой + инфракрасный + лазерный

Ошибка 3: “Нет проверки после дублирования”

Проблема: Включили резервный мотор, но не проверили
Через 5 минут и он сломался

Решение: Проверять резервные системы тоже
testBackupSystems();

Ошибка 4: “Watchdog срабатывает на нормальную работу”

Проблема: Программа делает сложные вычисления 3 секунды
Watchdog настроен на 2 секунды → постоянные перезагрузки

Решение: 
1. Увеличить время Watchdog
2. Разбить сложную задачу на части
3. Сбрасывать Watchdog внутри длинной операции

Эксперимент: Создай неубиваемый мигающий светодиод

Что нужно: Arduino, светодиод, резистор 220 Ом

Задача: Светодиод должен мигать ВСЕГДА, даже если мы “ломаем” программу.

Код с защитой:

#include <avr/wdt.h>

void dangerousFunction() {
  // ОПАСНО: может зависнуть
  while(random(100) != 42) {
    // Иногда зависает здесь надолго
  }
}

void setup() {
  wdt_enable(WDTO_1S); // Строгий Watchdog: 1 секунда
  pinMode(LED_BUILTIN, OUTPUT);
  randomSeed(analogRead(0));
}

void loop() {
  // Нормальная работа: мигаем
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  
  // Иногда вызываем опасную функцию
  if (random(10) == 0) {
    dangerousFunction();
  }
  
  // Сбрасываем Watchdog
  // Если dangerousFunction() зависнет >1 сек -> перезагрузка
  // Но светодиод СНОВА начнет мигать после перезагрузки!
  wdt_reset();
}

Что увидите: Светодиод почти всегда мигает. Иногда на секунду остановится (перезагрузка), и снова начнет мигать.


Проверь свой робот

Тест на надежность:

  1. Тест Watchdog: Заставьте программу зависнуть (while(1)). Перезагрузился ли робот сам через несколько секунд?
  2. Тест датчиков: Закройте один датчик рукой. Продолжает ли робот работать?
  3. Тест моторов: Отключите один мотор. Может ли робот продолжать движение?
  4. Тест питания: На короткое время отключите питание. Восстанавливается ли робот сам?

Если на все 4 теста ответ “да” — у вас надежный робот!


Что дальше?

Освоили Watchdog и избыточность? Теперь можно:

Совет: Добавляйте Watchdog в КАЖДЫЙ проект с самого начала. Проще сразу написать wdt_reset() в loop(), чем потом искать, почему робот зависает на соревнованиях. И помните: один резервный датчик может спасти весь проект.