
Содержание
Сторожевой таймер в Arduino
В данном уроке рассматривается устройство и работа со сторожевым таймером, для управления которым предлагается использовать библиотеку GyverWDT (by N1gra), предоставляющую полный контроль за WDT и доступ ко всем его настройкам и режимам работы. Библиотека очень лёгкая и подходит для большинства МК AVR, т.е. в частности для всех плат Arduino на их базе.
Сторожевой таймер в МК AVR представляет собой независимый асинхронный блок, тактирующийся от отдельного внутреннего RC генератора, частота которого составляет около 128 кГц (несколько зависит от температуры и напряжения питания). Следует учесть, что изначально данный генератор спроектирован как энергоэффективный источник тактирования, и не предполагает высокой точности, отклонение частоты при производстве составляет около 10%. При желании этот генератор может тактировать и ядро AVR, в случае, если внешний кварцевый резонатор не предусмотрен, а внутренний RC генератор на 8МГц не удовлетворяет требованиям энергопотребления.
Сторожевой таймер представляет из себя простой 10-битный счетный регистр, он инкрементируется при каждом фронте тактового сигнала, приходящего из делителя. Как только значение в этом счетном регистре достигнет 1023 – произойдет тайм-аут, а сторожевой таймер, в зависимости от установленного режима, инициирует сброс МК или вызывает прерывание. Чтобы предотвратить тайм-аут, можно сбросить счет сторожевого таймера в 0, воспользовавшись короткой функцией Watchdog.reset() библиотеки GyverWDT, или ассемблерной инструкцией “WDR”.
Зачем нужен сторожевой таймер? Он позволяет автоматически перезагрузить контроллер в случае зависания программы, что может быть просто необходимо в автономных проектах. Помимо этого, библиотека GyverWDT предоставляет возможность использовать сторожевой таймер и в других целях, например для генерации прерываний с высоким приоритетом без использования основных аппаратных таймеров, что может очень пригодиться в сложных проектах, например для регулярного опроса датчиков. Не стоит забывать, что сторожевой таймер остается активным во всех, даже самых глубоких режимах сна, и может разбудить контроллер прерыванием. Полезным может оказаться и комбинированный режим сторожевого таймера, в котором он может одновременно опрашивать датчики, выступать сторожевым таймером для внешнего устройства, и следить за работой собственного ядра, чтобы в случае зависания сбросить его.
Делители частоты сторожевого таймера
Константа делителя GyverWDT | Приблизительный тайм-аут | Приблизительная частота тайм-аутов |
WDT_PRESCALER_2 | ~ 16ms | ~ 62Hz |
WDT_PRESCALER_4 | ~ 32ms | ~ 32Hz |
WDT_PRESCALER_8 | ~ 64ms | ~ 16Hz |
WDT_PRESCALER_16 | ~ 0.125s | ~ 8Hz |
WDT_PRESCALER_32 | ~ 0.25s | ~ 4Hz |
WDT_PRESCALER_64 | ~ 0.5s | ~ 2Hz |
WDT_PRESCALER_128 | ~ 1s | ~ 1Hz |
WDT_PRESCALER_256 | ~ 2s | ~ 0.5Hz |
WDT_PRESCALER_512 | ~ 4s | ~ 0.25Hz |
WDT_PRESCALER_1024 | ~ 8s | ~ 0.125Hz |
Почему приблизительная? Потому что частота генератора сторожевого таймера уже при производстве имеет приличный разброс, и если на малых значениях делителя это практически не проявляется (16 мс +/- 1.6 мс), то на больших это может стать серьезной проблемой (8 с +/- 1 с).
Не стоит забывать, что RC генератор, в отличии от кварцевого резонатора, имеет небольшую зависимость частоты от температуры и напряжения питания.
Библиотека GyverWDT
Для работы с Watchdog можно использовать встроенную avr библиотеку, но у нас есть своя библиотека с более широким набором настроек: GyverWDT. Отдельная страница с документацией и ссылка на загрузку здесь.
Функции библиотеки GyverWDT:
Watchdog.reset();
короткая и быстрая команда сброса сторожевого таймера в 0, вызывается внутри программы , для подтверждения нормальной работы.Watchdog.disable();
короткая команда остановки работы сторожевого таймера. Вызывается для полного отключения watchdog.Watchdog.enable(mode, prescaler);
функция для конфигурации и запуска таймера (см. пример)
Режим (константа) | Действие при тайм-ауте |
INTERRUPT_MODE | Прерывание, вызывающее функцию, указанную в watchdog_enable () |
INTERRUPT_RESET_MODE | Прерывание, вызывающее функцию, указанную в watchdog_enable () , и автоматическая установка сторожевого таймера в режим reset. |
(комбинированный режим) | Соответственно первый тайм-аут инициирует прерывание, второй тайм-аут – инициирует сброс. |
Зачем нужен комбинированный режим? Он позволяет одновременно использовать как прерывания, так и сброс, что может оказаться очень мощным инструментом. Как это работает:
- Если причина зависания кроется в программной ошибке, например, в бесконечном ожидании поступления данных. Первый тайм-аут вызовет прерывание с высоким приоритетом, если мы смогли в него войти и понять, что это случилось – зависание явно носит программный, а не аппаратный характер. В таком случае мы можем попытать исправить программную ошибку прямо внутри прерывания, или предупредить пользователя, что написанный код имеет проблемные участки. Если удалось оперативно решить проблему до наступления следующего тайм-аута, мы можем не отходя от кассы перенастроить сторожевой таймер функцией
Watchdog.enable(…)
, чтобы следующий тайм- аут отправил нас в прерывание-сигнализатор, вместо hard-reset. Если же не удалось оперативно исправить причину зависания – следующий тайм-аут автоматически сбросит контроллер. - Если необходимо использовать одновременно прерывания (например, для опроса датчиков), и защиту от зависания. В пользовательской функции прерывания, помимо необходимых нам действий, опроса датчиков и т.д. просто дописывается функция перенастройки
Watchdog.enable (…)
, если перенастройка не произошла, значит контроллер не вошел в процедуру прерывания, попросту зависнув. Следующий тайм-аут сбросит контроллер. - Если необходимо аппаратно следить следить как за работоспособностью внешнего устройства, так и самого МК. Например, при совместной работе МК и Bluetooth/Wi-Fi /GSM/GPS модуля. Как это реализовать? Watchdog настраивается на необходимый период. Внешнее устройство обязано периодически посылать на МК подтверждение своей работоспособности, это может быть как импульс, периодически создаваемый с помощью GPIO внешнего устройства, инициирующий внешнее прерывание МК, так и посылка, отправляемая внешним устройством по одному из последовательных интерфейсов, и аналогично инициируя прерывания МК. Внутри процедуры прерывания, вызывается короткая функция
Watchdog.reset ();
, сбрасывающая сторожевой таймер. Если внешнее устройство зависло и перестало посылать данные/импульсы, сбросов watchdog не происходит и происходит прерывание, внутри которого мы можем без труда сбросить внешнее устройство, подав на него низкий уровень (необходимо наличии внешней подтяжки к VCC). В этом же прерывании производится перенастройка watchdog. Если МК зависнет – он не перенастроит watchdog вовремя, и следующий тайм-аут инициирует сброс МК.
Примеры из библиотеки
#include "GyverWDT.h" /* Пример использования watchdog в качестве генератора прерываний Зависимость таймаутов от делителей см. в GyverWDT.h */ void setup() { Serial.begin(9600); pinMode(13, OUTPUT); Watchdog.enable(INTERRUPT_MODE, WDT_PRESCALER_128); // Режим генерации прерываний , таймаут ~1с } void loop() { /* Загруженный цикл */ digitalWrite(13, HIGH); delay(5000); digitalWrite(13, LOW); delay(5000); } /* Прерывание watchdog */ ISR(WATCHDOG) { Serial.println("Hello from interrupt"); }
#include "GyverWDT.h" /* Пример классического применения watchdog - сброс устройства при зависании Если не вызвать Watchdog.reset() вовремя - произойдет сброс Зависимость таймаутов от делителей см. в GyverWDT.h */ void setup() { pinMode(13, OUTPUT); Watchdog.enable(RESET_MODE, WDT_PRESCALER_512); // Режим сторжевого сброса , таймаут ~4с } void loop() { /* Ваш полезный код */ digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); Watchdog.reset(); // Переодический сброс watchdog, означающий, что устройство не зависло }
#include "GyverWDT.h" #include <avr/sleep.h> /* Пример использования watchdog в "будильника" Зависимость таймаутов от делителей см. в GyverWDT.h */ void setup() { pinMode(13, OUTPUT); set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Выбираем нужный режим сна } void loop() { /* Мигаем светодиодом , а в паузах спим */ digitalWrite(13, HIGH); Watchdog.enable(INTERRUPT_MODE, WDT_PRESCALER_128); // Режим прерываний , таймаут ~1c sleep_enable(); // Разрешаем сон sleep_cpu(); // Уходим в сон digitalWrite(13, LOW); Watchdog.enable(INTERRUPT_MODE, WDT_PRESCALER_128); // Режим прерываний , таймаут ~1c sleep_enable(); // Разрешаем сон sleep_cpu(); // Уходим в сон } /* Прерывание watchdog , в нем мы просыпаемся */ ISR(WATCHDOG) { sleep_disable(); // Запрещаем сон Watchdog.disable(); // Выключаем watchdog }
#include "GyverWDT.h" /* Пример работы watchdog для контроля сразу за двумя устройствами В данном случае - arduino + esp8266 модуль Зависимость таймаутов от делителей см. в GyverWDT.h */ void setup() { Serial.begin(9600); // На этом последовательном порту у нас wifi-модуль pinMode(8, INPUT); // Этот пин сбрасывает wifi-модуль при переходе в режим OUTPUT Watchdog.enable(INTERRUPT_RESET_MODE, WDT_PRESCALER_1024); // Комбинированный сторжевой режим , таймаут ~8с } void loop() { /* Ваш полезный код */ } /* Если wifi-модуль нам что то отправил , а мы его поняли - никто из нас не завис */ void serialEvent() { Watchdog.reset(); // Сбросить watchdog } /* Если wifi-модуль не отправлял нам что то более 8с - он завис */ ISR(WATCHDOG) { pinMode(8, OUTPUT); // Тянем за reset модуля Watchdog.enable(INTERRUPT_RESET_MODE, WDT_PRESCALER_1024); // Если мы не смогли перенастроить watchdog - значит мы зависли Watchdog.reset(); // Если не зависли - сбросить watchdog и продолжить pinMode(8, INPUT); // Отпускаем reset модуля }
#include "GyverWDT.h" /* Пример использования watchdog в комбинированном режиме Может использоваться для оповещения в случае зависания Первый тайм-аут вызывает прерывание, второе - сброс Перенастройте watchdog чтобы избежать сброса Зависимость таймаутов от делителей см. в GyverWDT.h */ void setup() { Serial.begin(9600); Serial.println("Program started"); Watchdog.enable(INTERRUPT_RESET_MODE, WDT_PRESCALER_128); // Комбинированный режим , таймаут ~1c Serial.println("watchdog enabled"); while (1); // Причина зависания Serial.println("loop started"); // Этого мы не увидим из-за зависания } void loop() { /* этого кода программа не достигнет - зависание в setup */ Serial.println("hello"); delay(500); } /* Первый тайм-аут вызывает прерывание */ ISR(WATCHDOG) { // Если причина зависания программная - тут можно попытаться исправить ее */ Serial.println("warning!"); // Если исправить причину не вышло - следующий таймаут вызывает сброс // Watchdog.enable(INTERRUPT_RESET_MODE, WDT_PRESCALER_128); // Если перенастроить watchdog здесь - сброса не будет }
Watchdog и сон
- Настроить МК на один из режимов сна
- Разрешить сон
- Взвести watchdog на определенный тайм-аут в режиме прерываний
- Уйти в сон
- При пробуждении, оказавшись в процедуре прерывания watchdog, запретить сон
- Продолжить выполнение программы
Проблемы
Основная проблема заключается в том, что режим автоматического сброса не работает на некоторых платах Arduino. Вернее работает то он просто отлично, только приводит к бесконечной перезагрузке или boot loop. Почему так происходит? Дело в том, что контроллеры свежих партий, в случае перезагрузки по watchdog автоматически перенастраивают сторожевой таймер на минимальный период, т.е. 16 мс, не отключая его. Чтобы избежать бутлупа необходимо при старте программы выключить watchdog, но как известно, в большинстве плат мы имеем МК с зашитым загрузчиком, время выполнения которого выше, чем 16 мс. Это приводит к тому, что контроллер даже не успевает приступить к выполнению основной программы, как уже улетает в hard reset , происходит это циклически и бесконечно долго. Единственный способ остановить этот процесс – обесточить плату. Во многих платах выпуска 2018+ года зашит более оптимизированный и продуманный загрузчик optiboot, который автоматически выключает watchdog при старте, что предотвращает бутлуп и позволяет нам использовать сторожевой таймер на 100%.
Проверка работоспособности Watchdog
В примерах к библиотеке идёт скетч тестирования поддержки watchdog на вашей плате. Загрузите его в плату и откройте монитор порта. Спустя 10 секунд паузы, установленной для упрощения перепрошивки при boot loop, в монитор порта начнет выводиться время работы программы в секундах. Если при достижении таймаута программа сбросилась и отсчет начался заново – ваша плата (загрузчик) поддерживает watchdog на все 100%. Если после перезагрузки корректный вывод времени в монитор порта прекратился, а светодиод на плате начал быстро и безостановочно мигать – загрузчик на вашей плате не поддерживает перезагрузку watchdog. Загрузите в плату любой другой скетч, чтобы продолжить использование.
#include <GyverWDT.h> /* Пример тестирующий поддержку всех функций watchdog на вашем устройстве > После 10 секунд отсчета программа стартует заного -> поддерживаются все функции > После таймаута устройство зависает, светодиод на D13 начинает мигать -> поддерживается только INTERRUPT_MODE В случае bootloop у вас будет 10 секунд на перепрошивку устройства после подачи питания Для добавления поддержки всего функционала watchdog загрузите optiboot или откажитесь от загрузчика */ void setup() { Serial.begin(9600); Serial.println("Program started , wait 10 seconds"); delay(10000); // 10 секунд на перепрошивку в случае bootloop Watchdog.enable(RESET_MODE, WDT_PRESCALER_1024); // Режим сторожевого сброса , таймаут ~8с Serial.println("watchdog enabled"); while (1) { // Бесконечный цикл , эмуляция "зависания" if (!(millis() % 1000)) { // Каждую секунду Serial.println((uint16_t)((millis() / 1000) - 10)); // Вывести время после включения watchdog в секундах delay(10); } } } void loop() {}
Что делать, если загрузчик на моей плате не поддерживает watchdog?
Если вам действительно нужен полный функционал сторожевого таймера – необходимо перепрошить загрузчик, для этого необходим программатор, который поддерживает МК AVR, или другая плата Arduino + скетч Arduino ISP. Подробнее читайте в уроке про работу с программатором. Без перепрошивки загрузчика вам доступны обычные прерывания, не использующие таймеров и имеющие высокий приоритет, и соответственно возможность использования watchdog в режиме будильника МК.
Важные страницы
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макро, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)
- Articles coming soon