3.27. Сторожевой таймер

Сторожевой таймер – это оборудование, которое сбрасывает микроконтроллер, если работающий скрипт когда-либо перестаёт периодически его «подкармливать». Скрипт «кормит» сторожевой таймер из места, о котором он знает, что там выполняется исправный код; если ошибка, зависание или непредвиденное исключение когда-либо не дадут камере покормить сторожевой таймер в течение заданного тайм-аута, микросхема сбрасывает себя, и скрипт начинается заново.

На развёрнутом устройстве, рядом с которым нет человека, чтобы перезапустить его по питанию, это разница между кратковременной ошибкой, которая восстанавливается за секунды, и кирпичом, требующим выезда сервиса.

График счётчика сторожевого таймера во времени. Счётчик начинается со значения тайм-аута, линейно падает к нулю и перезагружается до значения тайм-аута каждый раз, когда скрипт вызывает feed(). После трёх успешных вызовов feed() в четвёртом интервале feed() отсутствует, и счётчик достигает нуля, вызывая сброс MCU.

Счётчик сторожевого таймера отсчитывает вниз от своего тайм-аута. Каждый feed() перезагружает его; если он достигает нуля, микросхема сбрасывается.

3.27.1. Класс machine.WDT

machine.WDT включает сторожевой таймер и предоставляет единственный метод, feed(). После запуска сторожевой таймер нельзя остановить – единственные выходы: кормить его по графику или дать ему сбросить микросхему:

from machine import WDT

wdt = WDT(timeout=2000)    # reset if not fed within 2 seconds

while True:
    do_work()
    wdt.feed()

Значение timeout задаётся в миллисекундах. Правильное значение зависит от того, сколько занимает самая длинная допустимая итерация основного цикла, с комфортным запасом – 100-мс цикл с тайм-аутом в 2 с имеет достаточный запас для медленной итерации без ложных сбросов.

3.27.2. Где вызывать feed()

Где живёт feed() – это критическое проектное решение; сторожевой таймер ловит ошибки только в тех частях кода, которые не выполняются между подкормками.

  • Вызывайте из основного цикла, в начале или в конце. Самый распространённый шаблон. Сторожевой таймер ловит всё, что подвешивает основной цикл – взаимоблокировку, бесконечный while, периферийное устройство, которое никогда не возвращает управление, – и сбрасывает микросхему обратно в цикл.

  • Не вызывайте из обработчика прерывания. Смысл сторожевого таймера – ловить зависания в обычном пути выполнения кода. ISR, срабатывающий независимо от того, застрял ли основной цикл, продолжал бы кормить сторожевой таймер, который должен был бы сработать.

  • Не вызывайте изнутри длинной блокирующей операции. Сетевой запрос или чтение датчика, занимающее десять секунд, – это именно тот вид зависания, который должен ловить сторожевой таймер. Помещение feed() внутрь него сводит защиту на нет.

Рекомендация, работающая для большинства программ: кормите один раз за итерацию основного цикла, установив тайм-аут в несколько раз больше ожидаемой длительности цикла. Если одной итерации законно требуется больше времени, чем тайм-аут – скажем, намеренная фаза калибровки, – организуйте эту фазу как последовательность меньших фрагментов с feed() между ними или измените тайм-аут с помощью timeout_ms() (где поддерживается) перед входом в неё.

3.27.3. Доступность

Сторожевой таймер доступен на большинстве камер OpenMV, но не на всех – оборудование присутствует на каждом компоненте, но Python API подключён ещё не везде. Сверьтесь с Платы OpenMV или попробуйте сконструировать WDT и перехватите AttributeError, если он не поддерживается.

Даже на камерах, где WDT не предоставлен, развёрнутое в поле устройство может использовать программный эквивалент – отдельную задачу или шаг основного цикла, который отслеживает прогресс и вызывает machine.reset(), если что-то выглядит заклинившим. Он менее надёжен, чем аппаратный сторожевой таймер (заклинивший обработчик прерывания может уронить и программный монитор), но он покрывает те же случаи на уровне приложения.