Урок 16. Повышение надежности программ для Ардуино. Сторожевой таймер.

 

Сторожевой таймер Ардуино

В этом уроке поговорим о способах повышения надежности программ для Ардуино, узнаем, как использовать сторожевой таймер в системе Ардуино.

Предыдущий урок     Список уроков     Следующий урок

Как и любое электронное устройство на микроконтроллерах, платы Ардуино могут зависать.  Причины могут быть разные:

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

 

Конечно, это ненормальное явление. Допускать этого нельзя, но что-то не предусмотрели, что-то случилось независящее от нас. К тому же мы программируем на языке высокого уровня, а это означает, что есть программная среда, которую мы не контролируем. Компилятор создает какие-то стеки, области собственных данных, и мы не знаем, насколько надежно они реализованы.

Поэтому необходимо контролировать ход выполнения программы и принимать меры, если она работает ненормально.

Сторожевой таймер (watchdog) в системе Ардуино.

Одним из способов повышения надежности является использование сторожевого таймера (watchdog) для контроля выполнения программы. Он представляет собой аппаратный таймер, который должен периодически сбрасываться программой. Если сброс  сторожевого таймера не произойдет в течение заданного времени, то он вызовет перезагрузку всей системы, т.е. выработает сигнал сброс микроконтроллера. Таким образом, если программа зависнет и перестанет сбрасывать сторожевой таймер, то микроконтроллер будет перезагружен, как будто нажали кнопку сброс.

В системах Ардуино есть сторожевой таймер, который является внутренним узлом микроконтроллера ATmega.

 

Библиотека для работы со сторожевым таймером Ардуино.

Для управления сторожевым таймером необходимо подключить к проекту библиотеку avr/wdt.h.

Эту библиотеку не надо искать в интернете, скачивать. Ее не надо устанавливать. Это стандартная библиотека, она находится в  каталоге Arduino. У меня в D:\Arduino\hardware\tools\avr\avr\include\avr\wdt.h

Просто добавьте в проект строку:

#include <avr/wdt.h>

Библиотека имеет три функции.

void wdt_enable(timeout)

Функция разрешает работу сторожевого таймера, задает время тайм-аута. Аргумент timeout (время тайм-аута) может принимать следующие значения.

WDTO_15MS      // 15 мс
WDTO_30MS      // 30 мс
WDTO_60MS      // 60 мс
WDTO_120MS    // 120 мс
WDTO_250MS    // 250 мс
WDTO_500MS    // 500 мс
WDTO_1S            // 1 сек
WDTO_2S            // 2 сек
WDTO_4S           // 4 сек
WDTO_8S          // 8 сек

Пример:

wdt_enable(WDTO_120MS);  // разрешение работы сторожевого таймера с тайм-аутом 120 мс

void wdt_reset(void)

Сброс сторожевого таймера. Для нормальной работы необходимо вызывать эту функцию не реже периода сторожевого таймера. При задержке превышающей тайм-аут произойдет аппаратный сброс контроллера.

wdt_reset();  // сброс сторожевого таймера

void wdt_disable(void)

Отключение сторожевого таймера.

wdt_disable();  // запрет работы сторожевого таймера

 

Применение сторожевого таймера в системе Ардуино.

Сторожевой таймер позволяет контролировать выполнение отдельных циклов программы. Функцию его сброса не надо ставить, где попало.  Я обычно контролирую цикл прерывания по таймеру, от которого отсчитываются остальные циклы программы. В предыдущих уроках я поставил бы сброс watchdog в цикле обработки прерывания по таймеру 2 мс.

Давайте проверим работу сторожевого таймера на реальной программе. В программе:

  • Организован цикл прерывания по таймеру 2мс.
  • В основном асинхронном цикле реализовано управление светодиодом платы (мигает с периодом 1 сек).
  • В основном цикле проверяются данные с последовательного порта, и при появлении любого данного запрещается работа прерывания по таймеру.  Этим имитируется сбой установок таймера.

// проверка работы сторожевого таймера
#include <MsTimer2.h>
#include <avr/wdt.h>

#define LED_PIN 13 // светодиод подключен к выводу 13
int ledCount;             // счетчик времени мигания светодиода

void setup() {
  pinMode(LED_PIN, OUTPUT);      // определяем вывод светодиода как выход
  Serial.begin(9600);     // инициализируем последовательный порт
  MsTimer2::set(2, timerInterupt); // задаем период прерывания от таймера 2 мс
  MsTimer2::start();              // разрешаем прерывание от таймеру
  // wdt_enable(WDTO_15MS); // разрешение работу сторожевого таймера с тайм-аутом 15 мс 
}

void loop() {
  // мигание светодиода
  if ( ledCount > 250 ) {
     ledCount= 0; 
     digitalWrite(LED_PIN, ! digitalRead(LED_PIN));  // инверсия состояния светодиода
  }   

  // проверка данных в буфере последовательного порта (имитация сбоя)
  if ( Serial.available() != 0 ) MsTimer2::stop(); // запрет прерывания от таймера   
}

// обработчик прерывания
void  timerInterupt() {
  ledCount++; // счетчик светодиода
  // wdt_reset();  // сброс сторожевого таймера 
}

Загрузим программу в плату. Светодиод мигает раз в секунду. Откроем монитор порта и пошлем какой-нибудь символ. Светодиод перестанет мигать. Мы имитировали сбой установок таймера и программа зависла. Если нажать кнопку сброс на плате, то программа снова начнет работать, светодиод замигает.

Теперь освободим от комментариев две строчки разрешения и сброса сторожевого таймера.

wdt_enable(WDTO_15MS); // разрешение работы сторожевого таймера с тайм-аутом 15 мс  
wdt_reset();  // сброс сторожевого таймера

Загрузим программу в плату, запустим. Теперь при имитации сбоя сторожевой таймер сбрасывает микроконтроллер, и программа продолжает работать.

 

Способы повышения надежности работы программы.

Сторожевой таймер не панацея от неправильной работы программы. Представьте, что в предыдущем примере установки таймера прерывания собьются так, что он будет работать с периодом не 2 мс, а 5 мс. И как тут поможет сторожевой таймер? Если собьются параметры последовательного порта? Программа не будет выполнять своих функций.

Только комплекс мер позволит написать надежную программу. Я разрабатываю программы для PIC-контроллеров фирмы Microchip. Программы работают в устройствах, зависание или неправильная работа контроллеров в которых приведет к фатальным последствиям. Это мощные специализированные источники питания, станции катодной защиты, системы контроля технологических процессов, GSM телеметрия и т.п. Надежность программ это очень обширная, сложная тема. Я коротко расскажу об основных принципах создания надежных программ.

Что может случиться с программой? После чего она перестает правильно работать?

  • Могут испортиться данные (переменные) в оперативной памяти.
  • Возможно ошибочное изменение состояния регистров микроконтроллера.
  • Программа может бесконечно ожидать какого-то события, которое не наступает, а разработчик полагал, что оно будет обязательно.

 

Контроль данных, переменных, регистров микроконтроллера.

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

В моих программах на PIC-контроллерах всегда есть программный блок с названием ”циклические установки”. В нем с определенным периодом переустанавливаются все переменные и регистры микроконтроллера. Конечно те, которые не меняются в цикле.

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

Большие блоки данных я защищаю контрольными суммами и периодически проверяю. О контроле целостности данных написано в уроке 14.

В случае ошибочных данных лучше перезагрузить всю программу, сформировав программный сброс. Неизвестно, что в ней еще испортилось.

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

wdt_enable(WDTO_15MS);
while (1) { }

Контроль хода выполнения программы.

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

Допустим, Вы принимаете данные с компьютера по последовательному порту. Ждете 10 байтов, а пришло 9. И программа бесконечно ждет 10го байта. Я видел много программ, которые зависали при нарушении приема данных по последовательному интерфейсу.

В подобных случаях необходимо контролировать время выполнения операции. Если байт не пришел в течение 1 сек, то он не придет никогда. Надо считать время ожидания события и при отсутствии его принимать меры. У каждого ожидаемого события должен быть свой тайм-аут – время ожидания. А использовать для этого сторожевой таймер или программные счетчики – решать программисту.

 

В следующем уроке разработаем на базе платы Ардуино полностью рабочее устройство – охранную сигнализацию. Знаний для этого теперь достаточно.

 

Предыдущий урок     Список уроков     Следующий урок

3

Автор публикации

не в сети 1 день

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

25 комментариев на «Урок 16. Повышение надежности программ для Ардуино. Сторожевой таймер.»

  1. Добрый день.
    А не могли бы вы отдельно объяснить по watchdog в части bootloop (crazy led) и перепрошивки платы optiboot — основы, почему так происходит, как проверить поддерживает ли плата watchdog, как перепрошить и т.д.?

    1
  2. Добрый … вечер 🙂
    В прошлом веке, когда были в моде телефоны с АОН, применяли НЕ ПРОГРАММНЫЕ блоки перезагрузки. На вход Reset подавали сигнал через интегрирующую цепь из резистора и конденсатора + логический элемент. С одного из выходов подавали импульс отмены Resrt. Если программа зависала, то через некоторое время (примерно 0,7*R*C) происходил сброс. Как вам такой способ?

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

      0
  3. Можно ли ввести глобальную переменную, чтобы её не сбрасывал сторожевой таймер.

    0
    • Здравствуйте!
      Обычно с использованием языков высокого уровня нельзя. Да и на Ассемблере это плохая идея с точки зрения логики. Произошел сброс по сторожевому таймеру. Значит в программе что-то не так, что-то залетело. Надо установить заново все переменные, все ресурсы.

      0
    • Смею предположить, что можно сохранять значение глобальной переменной в EEPROM и тут же считывать его. Тогда по логике после сброса сторожевого таймера ее значение будет таким же как до сброса сторожевым таймером

      0
  4. Установил сброс сторожевого таймера в асинхронном цикле 250 мс, чтобы он следил за синхронным циклом с периодом 100 мс. Когда последовательность операций в синхронном цикле преевысит случайно (по ошибке периферии) 100 мс и новый цикл прерывания начнется до завершения предыдущего, сторожевой таймер должен перезагрузить контроллер. Однако вместо этого контроллер зависает.

    Или установка сброса сторожевого таймера в асинхронном цикле не корректна? И прерывание запускает синхронный цикл во время «процедуры» сброса… и сброс не происходит?

    0
    • Здравствуйте!
      А как вы определяете, что «новый цикл прерывания начнется до завершения предыдущего»? Пока прерывание не будет закончено, новое не вызовется.

      0
      • Добрый день! Снова я.
        Начало нового цикла из старого цикла никак не определяем, лишь предполагаем. Спасибо за ответ.

        Прояснился один момент: проверив свою платку Nano v.3 скетчем из этого урока, обнаружил отказ контроллера во время срабатывания сторожевого таймера. Проверял все по Вашей инструкции: загрузил скетч с закоментированными строками — все работает — зависание имитируется; удалил «//» в двух строках, перезалил скетч — все работает — диод моргает раз в секунду…, отправляю в порт несколько символов — диод начинает моргать в несколько раз чаще и… больше контроллер ни на что не реагирует, даже на кнопку RESET. При выключении и повторном включении питания контроллера все возвращается к нормальному режиму — диод моргает раз в секунду. Отправляем символ в порт — диод снова мельтешит.

        Как же быть то теперь? Подскажите, пожалуйста.

        0
        • Здравствуйте!
          Не знаю, как микроконтроллер может не реагировать на кнопку RESET? А в других программах реагирует на эту кнопку?

          0
          • В других платах, кроме Uno, сторожевой таймер реализован некорректно. Поправить можно, перепрошив загрузчик optiboot, о котором спрашивали выше.
            При нажатии кнопки RESET сторожевой таймер остается включенным, и пока загрузчик ждет установления связи с компьютером для возможной загрузки новой прошивки, сторожевой таймер срабатывает и перезагружает. И все по новой.
            А отключение питания полностью сбрасывает периферию и сторожевой таймер в частности. поэтому начинает работать.

            0
          • Да, реагирует. Приветствую Вас!
            Я, кажется, нашел ответ на свой вопрос — bootloader «иногда» не поддерживает wdt. Необходимо его заменить. https://m.geektimes.ru/post/255800/
            Буду пробовать.

            p.s. Огромное Вам СПАСИБО за Ваши уроки! Очень интересные и понятные! ПОЛЕЗНЫЕ!

            0
  5. Если где-то в основной программе есть delay(100);,а прерывания сторожевого таймера каждые 2мс, то куда вернется выполнение программы, ведь строка кода одна?

    0
    • Здравствуйте!
      Сторожевой таймер вызывает не прерывания, а сброс микроконтроллера. Вся система будет перезапущена.

      0
      • Сторожевой таймер вызывает как прерывание так и сброс в зависимости от WDE и WDIE. Мат часть надо изучать.

        0
  6. Установка сторожевого таймера на некоторых платах Ардуино приводит к их «Окирпичиванию» с невозможностью программирования через загрузчик.
    для работы со сторожевым таймером нужно чтобы в загрузчике установка сторожевого таймера была сделана корректно, а то есть шанс ухода ардуино в вечный ребут
    перед тем как использовать в скетчах стороживой таймер желательно перешить загрузчик на Optiboot
    он меньше, быстрее и watchdog обрабатывает корректно

    0
    • Добрый день. Возможно мой вопрос будет дурацким, но все же. Я правильно понимаю, что проблема «дефолтного» загрузчика и watchdog касается только случая прошивания Ардуины через COM порт? А если я залью свой скетч программатором ISP или USBasp, который(судя по логу) перед прошивкой вообще вроде очищает полностью микроконтроллер, а значит убивает любой загрузчик и тогда, вообще нет проблемы с watchdog? Получается, что загрузчик необязателен для нормальной работы микроконтроллера. Зачем нужен загрузчик, кроме как для заливки скетчей через UART? Развейте или подтвердите мои мысли. Заранее спасибо.

      0
      • Да, вы правы. Практически я не могу ваши мысли подтвердить, но в теории все верно. Осталось дело за малым: прошить вот этот скетч с помощью железного программатора и посмотреть, будет ли работать WDT:
        // взято вот отсюда: https://habr.com/post/189744/
        #include

        void setup() {
        wdt_disable(); // бесполезная строка до которой не доходит выполнение при bootloop
        Serial.begin(9600);
        Serial.println(«Setup..»);

        Serial.println(«Wait 5 sec..»);
        delay(5000); // Задержка, чтобы было время перепрошить устройство в случае bootloop
        wdt_enable (WDTO_8S); // Для тестов не рекомендуется устанавливать значение менее 8 сек.
        Serial.println(«Watchdog enabled.»);
        }

        int timer = 0;

        void loop(){
        // Каждую секунду мигаем светодиодом и значение счетчика пишем в Serial
        if(!(millis()%1000)){
        timer++;
        Serial.println(timer);
        digitalWrite(13, digitalRead(13)==1?0:1); delay(1);
        }
        // wdt_reset();
        }

        0
        • Кусок includ’а куда-то пропал. Вот так правильно:
          #include «avr/wdt.h» // вместо кавычек — знаки «меньше» и «больше»

          0
  7. А нельзя ли настроить watchdog на прерывание, а в этом прерывании ассемблерной командой jmp 0000 перейти на нулевой адрес, с которого работает reset?

    0
    • Здравствуйте!
      Watchdog вызывает только сброс микроконтроллера.
      The MCU is reset when the Watchdog Timer period expires and the
      Watchdog System Reset mode is enabled.

      0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".