Используйте AVR Watchdog как обычный ISR

17

Я пытаюсь обернуть голову вокруг сторожевого таймера на серии ATTinyX5. То, что я прочитал, показалось, что вы можете использовать его для того, чтобы программа делала что-то конкретное за N секунд, но никогда не показывало, как. Другие создавали впечатление, будто он будет сбрасывать чип только в том случае, если что-то в коде будет сброшено, пока не будет подсчитано (что, по-видимому, является «нормальным» использованием).

Есть ли способ использовать WDT, как TIMER1_COMPA_vect или аналогичный. Я заметил, что у него есть 1-секундный режим тайм-аута, и я бы очень хотел иметь возможность использовать это для того, чтобы что-то происходило в моем коде каждую 1 секунду (и, желательно, спать между ними).

Мысли?

* Обновление: * Поскольку его спросили, я имею в виду раздел 8.4 таблицы ATTinyX5 . Не то чтобы я полностью это понял, в этом моя проблема ...

Адам Хейл
источник
1
+1 за нестандартное мышление. Дополнительные пальцы, если вы добавите ссылку на таблицу данных для AVR.
Джиппи

Ответы:

23

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

WDT на самом деле проще в настройке, чем обычный таймер, по той же причине, по которой он менее полезен: меньше вариантов. Он работает на тактовой частоте 128 кГц, что означает, что его тактовая частота не зависит от основной тактовой частоты MCU. Он также может продолжать работать в самых глубоких режимах сна, чтобы обеспечить источник пробуждения.

Я рассмотрю несколько примеров таблиц, а также некоторый код, который я использовал (на языке C).

Включенные файлы и определения

Для начала, вы, вероятно, захотите включить следующие два заголовочных файла для работы:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Кроме того, я использую макрос <_BV (BIT)>, который определен в одном из стандартных заголовков AVR следующим образом (который может быть более привычным для вас):

#define _BV(BIT)   (1<<BIT)

Начало кода

Когда MCU запускается впервые, вы обычно инициализируете ввод-вывод, настраиваете таймеры и т. Д. Где-то здесь хорошее время, чтобы убедиться, что WDT не вызвал сброс, потому что он мог сделать это снова, сохраняя вашу программу в нестабильная петля.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

Настройка WDT

Затем, после того, как вы настроите остальную часть чипа, переделайте WDT. Настройка WDT требует «временной последовательности», но это действительно легко сделать ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Конечно, ваши прерывания должны быть отключены во время этого кода. Обязательно включите их позже!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Программа обработки прерываний WDT Следующее, о чем нужно беспокоиться, - это обработка WRT ISR. Это сделано так:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU Sleep

Вместо того, чтобы переводить MCU в спящий режим внутри ISD WDT, я рекомендую просто включить спящий режим в конце ISR, а затем заставить MAIN-программу перевести MCU в спящий режим. Таким образом, программа фактически покидает ISR до того, как переходит в спящий режим, и она просыпается и возвращается обратно в WDT ISR.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }
Курт Э. Клотье
источник
ВАУ ... очень подробно. Благодарность! Меня немного смущает раздел сна, где вы показываете основной цикл (если (MCUCR & _BV (SE)) {// Если режим сна включен ... и т. Д.), Я смущен, почему в основном виде вы бы постоянно отключать и разрешать прерывания. И часть в верхней части этого раздела (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Где это должно выполняться?
Адам Хейл
ОК, часть «set_sleep_mode (MODE)» должна быть в основном ДО основного цикла, в идеале в другом коде инициализации, где вы настраиваете порты ввода / вывода, таймеры и т. Д. Вам действительно не нужно enable_sleep (); в этот момент, так как это будет сделано после вашего первого запуска WDT. ВНУТРИ основного цикла, этот код ожидания будет выполняться только в том случае, если включен режим сна, и нет необходимости полностью отключать / включать прерывания там, только если вы выполняете sleep_bod_disable (); Весь этот оператор IF может находиться внизу (но все еще внутри) цикла MAIN после любого другого кода, который вы там выполняете.
Курт Э. Клотье
Хорошо ... теперь это имеет больше смысла. Единственное, на чем я не уверен, это то, что представляет собой эта «временная последовательность» ...
Адам Хейл,
Примечание: я знаю, почему вы хотели бы пойти спать, но я предполагаю, что вам не нужно при использовании WDT?
Адам Хейл
Взгляните на этот маленький раздел таблицы: 8.4.1. По сути, чтобы изменить регистр WDT, вы должны установить бит изменения, а затем установить правильные биты WDTCR в течение стольких тактов. Код, который я указал в разделе «Настройка WDT», сначала активирует бит изменения WD. И нет, вам не нужно использовать функцию сна вообще с WDT. Это может быть стандартный источник прерываний по какой-либо причине.
Курт Э. Клотье
1

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

Вы также можете просто включить прерывание и вообще не включать сброс. Вам придется устанавливать бит WDIE при каждом срабатывании прерывания.

Том Л.
источник
Хммм .... Я думаю, что это имеет смысл. Я сделаю это. Мне всегда нравится использовать вещи за то, что они не были предназначены :)
Адам Хейл
На самом деле я думаю, что это умный дизайн. Сохраняет таймер, сохраняя сторожевой функционал.
Том Л.
2
Этот начальный тайм-аут WDT и последующее прерывание также можно использовать для получения преимущества для некоторых приложений, которые включают WDT для фактического восстановления зависания. Можно посмотреть на адрес возврата в стеке в ISD WDT, чтобы определить, что код пытался сделать, когда произошел «неожиданный тайм-аут».
Майкл Карас
1

Это гораздо проще, чем предлагалось выше и в других местах.

Пока WDTONпредохранитель не запрограммирован (он не запрограммирован по умолчанию), тогда вам нужно только ...

  1. Установите бит разрешения прерывания сторожевого таймера и время ожидания в регистре контроля сторожевого таймера.
  2. Включить прерывания.

Вот пример кода, который будет выполнять ISR один раз в 16 мс ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

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

Если вы хотите, чтобы период отличался от каждой 1 секунды, вы можете использовать эти значения здесь, чтобы установить соответствующие биты в WDTCR...

введите описание изображения здесь

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

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
источник
Невыполнение временной последовательности во время установки экономит одну строку кода - операцию чтения-изменения-записи. Начальная последовательность рекомендуется в таблице «Если сторожевой таймер случайно включен, например, указателем разгона или отключением», а остальная часть моего кода относится к использованию WDT в сочетании с режимом ожидания, как того требует ОП. Ваше решение не проще, вы просто пренебрегли рекомендуемым / необходимым кодом котельной плиты.
Курт Э. Клотье
@ KurtE.Clothier Извините, просто пытаюсь привести самый простой рабочий пример!
bigjosh