Урок написан для AVR Arduino
В данном уроке рассматривается устройство и работа со сторожевым таймером на МК AVR, для управления которым предлагается использовать библиотеку GyverWDT (by Nich1con), предоставляющую полный контроль за WDT и доступ ко всем его настройкам и режимам работы. Библиотека очень лёгкая и подходит для большинства МК AVR, т.е. в частности для всех плат Arduino на их базе.
Зачем нужен сторожевой таймер? Он позволяет автоматически перезагрузить контроллер в случае зависания программы, что может быть просто необходимо в автономных проектах. Помимо этого, библиотека GyverWDT предоставляет возможность использовать сторожевой таймер и в других целях, например для генерации прерываний с высоким приоритетом без использования основных аппаратных таймеров, что может очень пригодиться в сложных проектах, например для регулярного опроса датчиков. Не стоит забывать, что сторожевой таймер остается активным во всех, даже самых глубоких режимах сна, и может разбудить контроллер прерыванием. Полезным может оказаться и комбинированный режим сторожевого таймера, в котором он может одновременно опрашивать датчики, выступать сторожевым таймером для внешнего устройства, и следить за работой собственного ядра, чтобы в случае зависания сбросить его.
Сторожевой таймер в МК AVR представляет собой независимый асинхронный блок, тактирующийся от отдельного внутреннего RC генератора, частота которого составляет около 128 кГц (несколько зависит от температуры и напряжения питания). Следует учесть, что изначально данный генератор спроектирован как энергоэффективный источник тактирования и не предполагает высокой точности, отклонение частоты при производстве составляет около 10%. При желании этот генератор может тактировать и ядро AVR, в случае, если внешний кварцевый резонатор не предусмотрен, а внутренний RC генератор на 8МГц не удовлетворяет требованиям энергопотребления.
Сторожевой таймер представляет из себя простой 10-битный счетный регистр, он инкрементируется при каждом фронте тактового сигнала, приходящего из делителя. Как только значение в этом счетном регистре достигнет 1023 - произойдет тайм-аут, а сторожевой таймер, в зависимости от установленного режима, инициирует сброс МК или вызывает прерывание. Чтобы предотвратить тайм-аут, можно сбросить счет сторожевого таймера в 0
, воспользовавшись короткой функцией Watchdog.reset()
библиотеки GyverWDT или ассемблерной инструкцией WDR
.
Делители частоты сторожевого таймера #
Константа делителя 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()
- короткая команда остановки работы сторожевого таймера. Вызывается для полного отключения watchdogWatchdog.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 вовремя, и следующий тайм-аут инициирует сброс МК.
Примеры из библиотеки #
WDT как источник прерываний
#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, означающий, что устройство не зависло
}
Спим и просыпаемся по WDT
#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. Загрузите в плату любой другой скетч, чтобы продолжить использование.
Проверка WDT
#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 в режиме будильника МК.