Watchdog Timer Arduino. Прерывание и сброс

Сторожевой таймер Arduino

Иногда возникает необходимость прервать выполнение программы, выполняемой микроконтроллером, и например сбросить его. Предположим, что микроконтроллер отправляет данные на сервер, а сервер по каким-то причинам не отвечает, или какая-то подобная нештатная ситуация происходит.

На помощь приходит сторожевой таймер Arduino, который позволяет задать тайм-аут для выполнения операции (от 16 мс до 8 секунд), а затем может быть вызван вектор прерывания, и микроконтроллер может быть перезагружен (системный сброс).

Использование сторожевого таймера определяется программированием FUSE-бита WDTON и установкой флагов WDE и WDIE регистра WDTCSR:

Параметры сторожевого таймера

Сторожевой таймер тактируется от собственного встроенного осциллятора с частотой 128 кГц. Тайм-аут сторожевого таймера задаётся установкой предделителя, и может принимать одно из следующих значений:

Тайм-аут сторожевого таймера

Сброс по Watchdog и загрузчик Arduino

Для использования режима сброса системы важно, чтобы было обеспечено следующее условие. Сразу после перезагрузки, прошивка должна проверить и сбросить бит WDRF в регистре MCUCSR.

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

Но если при программировании сторожевого таймера мы можем установить тайм-аут, который нам требуется, то сразу после сброса регистр WDTCSR будет обнулён, и предделитель таймера примет минимальное значение — 16 мс.

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

Таким образом, если в загрузчике Arduino первым делом не выключить сторожевой таймер сбросом флага WDRF, то после этого потребуется отключение питания, чтобы смог работать загрузчик Arduino, не говоря уже о пользовательском скетче в основной памяти микроконтроллера.

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

Иначе говоря прежде, чем использовать сторожевой таймер, потребуется проверить, как именно запрограммирован FUSE-бит WDTON, а также нужно будет скомпилировать и перепрошить загрузчик, с использованием ключа -DWATCHDOG_MODS. который прописывается в Makefile.

Компиляция загрузчика Arduino

Чтобы скомпилировать загрузчик для 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.

Программирование FUSE-бита WDTON

Затем используйте 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.

Режим прерывания WDT

Следующий скетч демонстрирует использование сторожевого таймера 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>