Используйте 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

Невыполнение временной последовательности во время установки экономит одну строку кода - операцию чтения-изменения-записи. Начальная последовательность рекомендуется в таблице «Если сторожевой таймер случайно включен, например, указателем разгона или отключением», а остальная часть моего кода относится к использованию WDT в сочетании с режимом ожидания, как того требует ОП. Ваше решение не проще, вы просто пренебрегли рекомендуемым / необходимым кодом котельной плиты.
— Курт Э. Клотье

@ KurtE.Clothier Извините, просто пытаюсь привести самый простой рабочий пример!
— bigjosh
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.