Иногда возникает необходимость прервать выполнение программы, выполняемой микроконтроллером, и например сбросить его. Предположим, что микроконтроллер отправляет данные на сервер, а сервер по каким-то причинам не отвечает, или какая-то подобная нештатная ситуация происходит.
На помощь приходит сторожевой таймер Arduino, который позволяет задать тайм-аут для выполнения операции (от 16 мс до 8 секунд), а затем может быть вызван вектор прерывания, и микроконтроллер может быть перезагружен (системный сброс).
Использование сторожевого таймера определяется программированием FUSE-бита WDTON и установкой флагов WDE и WDIE регистра WDTCSR:
Сторожевой таймер тактируется от собственного встроенного осциллятора с частотой 128 кГц. Тайм-аут сторожевого таймера задаётся установкой предделителя, и может принимать одно из следующих значений:
Для использования режима сброса системы важно, чтобы было обеспечено следующее условие. Сразу после перезагрузки, прошивка должна проверить и сбросить бит WDRF в регистре MCUCSR.
Дело в том, что этот бит определяет состояние бита WDE, и сразу после сброса системы по срабатыванию сторожевого таймера, бит WDRF будет установлен. Если его не сбросить, то сторожевой таймер сработает ещё раз. И будет срабатывать в бесконечном цикле, пока не будет отключено питание.
Но если при программировании сторожевого таймера мы можем установить тайм-аут, который нам требуется, то сразу после сброса регистр WDTCSR будет обнулён, и предделитель таймера примет минимальное значение — 16 мс.
В случае с Arduino, сразу после сброса управление передаётся загрузчику Arduino, который некоторое время ждёт управляющей последовательности по последовательному порту, и это время больше, чем минимальный тайм-аут сторожевого таймера.
Таким образом, если в загрузчике Arduino первым делом не выключить сторожевой таймер сбросом флага WDRF, то после этого потребуется отключение питания, чтобы смог работать загрузчик Arduino, не говоря уже о пользовательском скетче в основной памяти микроконтроллера.
В загрузчике Arduino отключение сторожевого таймера присутствует, но только в виде дополнительной опции WATCHDOG_MODS, с которой загрузчик должен быть скомпилирован. Не факт, что загрузчик в используемом Вами микроконтроллере включает в себя эту проверку.
Иначе говоря прежде, чем использовать сторожевой таймер, потребуется проверить, как именно запрограммирован FUSE-бит WDTON, а также нужно будет скомпилировать и перепрошить загрузчик, с использованием ключа -DWATCHDOG_MODS. который прописывается в Makefile.
Чтобы скомпилировать загрузчик для Arduino UNO, откройте файл hardware/arduino/avr/bootloaders/atmega/Makefile.
Найдите строку 155, которая выглядит примерно так:
atmega328: CFLAGS += '-DMAX_TIME_COUNT=F_CPU>>4' '-DNUM_LED_FLASHES=1' -DBAUD_RATE=57600
Добавьте ключ -DWATCHDOG_MODS:
atmega328: CFLAGS += '-DMAX_TIME_COUNT=F_CPU>>4' '-DNUM_LED_FLASHES=1' -DBAUD_RATE=57600 '-DWATCHDOG_MODS'
Затем переименуйте оригинальный загрузчик ATmegaBOOT_168_atmega328.hex например в ATmegaBOOT_168_atmega328.old.hex.
Скомпилируйте загрузчик командой
make atmega328
Эта команда создаст новую версию файла ATmegaBOOT_168_atmega328.hex.
Чтобы прошить скомпилированную версию загрузчика, используйте функцию Записать загрузчик в меню Инструменты Arduino Software.
Затем используйте avrdude, чтобы убедиться, что бит WDTON установлен в значение 1. Для этого запустите avrdude в терминальном режиме:
avrdude -c dapa -p m328p -P /dev/parport0 -t
где
-c dapa — тип программатора (параллельный порт)
-p m328p — тип микроконтроллера (ATmega328P)
-P /dev/parport0 — порт, к которому подключен программатор (в данном случае параллельный порт принтера)
-t — запустить avrdude в терминальном режиме
В терминале avrdude используйте команды dump и write, чтобы запрограммировать бит WDTON. Например, команда
dump hfuse
для моей платы Arduino UNO возвращает значение 0xDE (0b11011110).
Четвёртый бит (начиная с нуля) — это бит WDTON микроконтроллера ATmega328P. По умолчанию он имеет значение 1, но если бы он был равен 0, то я мог бы запрограммировать его в нужное значение командой
write hfuse 0 0xde
Для вывода подсказки, используйте команду
help
Для завершения работы с программой avrdude, используйте команду
quit
Теперь можно приступать к рассмотрению примеров использования сторожевого таймера Arduino.
Следующий скетч демонстрирует использование сторожевого таймера Arduino в режиме прерывания.
В функции loop() устанавливается срабатывание сторожевого таймера в режиме прерывания. По тайм-ауту вызывается обработчик вектора прерывания WDT_vect, который сообщает об этом событии, меняя значения переменной bFlag и увеличивая на единицу счётчик прерываний.
#include <ve_avr.h>
volatile bool bFlag; // Флаг прерывания.
int counter; // Счётчик прерываний.
void setup()
{
Serial.begin(57600); // Запуск последовательного порта.
Serial.println("Started.");
Serial.print("Setting up WDT... ");
DEV_WATCHDOG.setPrescaler(Watchdog::OSC_256K); // Тайм-аут сторожевого таймера равен 2 сек.
DEV_WATCHDOG.enableInt(); // Разрешить прерывание сторожевого таймера.
interrupts(); // Разрешить прерывания.
Serial.println((int) DEV_WATCHDOG.prescaler(), HEX);
counter = 0;
}
void loop()
{
DEV_WATCHDOG.reset(); // Перезапуск сторожевого таймера.
bFlag = false;
while(!bFlag) { // Ожидаем прерывания.
delay(25);
}
Serial.print("WDT Interrupt "); // Сообщаем о прерывании.
Serial.println(counter, DEC);
}
ISR(WDT_vect) // Обработчик прерывания сторожевого таймера.
{
bFlag = true; // Установить флаг.
++counter; // Увеличить значение счётчика на 1.
}
Сторожевой таймер срабатывает один раз, и после этого его следует заново перезапускать. Это делается вызовом функции DEV_WATCHDOG.reset() в теле функции loop(). Затем функция ждёт прерывания от сторожевого таймера, проверяя значение переменной bFlag.
Скачать скетч можно по ссылке. Для работы скетча потребуется библиотека VEduino версии 0.21 или новее.
В следующем скетче демонстрируется использование сторожевого таймера для прерывания с последующей перезагрузкой микроконтроллера.
При старте скетча производится настройка и установка сторожевого таймера с тайм-аутом 8 секунд. После чего управление передаётся основной программе в функции loop().
В функции loop() проверяется, наступило ли событие прерывания, а также каждые полсекунды увеличивается счётчик. При наступлении события прерывания, сообщение об этом выводится в монитор порта.
#include <ve_avr.h>
#define LED13 DEV_GPIOB, 5 // 7 для Arduino MEGA
volatile bool bFlag; // Флаг прерывания.
int counter; // Счётчик.
void setup()
{
setModeOutput(LED13); // Выключить светодиод LED.
setLow(LED13);
Serial.begin(57600); // Запуск последовательного интерфейса.
Serial.println("Started.");
Serial.print("Setting up WDT... ");
DEV_WATCHDOG.setPrescaler(Watchdog::OSC_1024K); // Тайм-аут сторожевого таймера равен 8 сек.
DEV_WATCHDOG.enableInt(); // Разрешить прерывание сторожевого таймера.
DEV_WATCHDOG.enableReset(); // Разрешить системный сброс.
interrupts(); // Разрешить прерывания.
Serial.println((int) DEV_WATCHDOG.prescaler(), HEX);
DEV_WATCHDOG.reset(); // Рестарт сторожевого таймера.
counter = 32;
bFlag = false;
}
void loop()
{
Serial.print(counter, DEC); // Выводим значение счётчика.
if (bFlag) { // Проверяем, было ли прерывание.
Serial.print(" WDT Interrupt");
bFlag = false;
}
Serial.println();
delay(500); // Ждём 0.5 сек.
--counter; // Уменьшаем значение счётчика.
}
ISR(WDT_vect) // Обработчик прерывания сторожевого таймера.
{
setHigh(LED13); // Включить светодиод.
//DEV_WATCHDOG.setPrescaler(Watchdog::OSC_2K); // Раскомментируйте для быстрого сброса.
bFlag = true; // Установить флаг прерывания.
}
Можно увидеть, что по тайм-ауту вызывается обработчик прерывания WDT_vect, после чего проходит ещё 8 секунд, и только затем следует системный сброс.
Я добавил в обработчик прерывания строку, устанавливающую предделитель на минимальное значение — 16 мс. Если её раскомментировать, то сброс будет происходить быстрее, и основная программа не будет успевать сообщить о наступлении события прерывания.
Скачать код скетча можно по ссылке. Для работы скетча потребуется библиотека VEduino версии 0.21 или новее.
Автор: Андрей Шаройко <vanyamboe@gmail.com>