Gunakan AVR Watchdog seperti ISR ​​normal

17

Saya mencoba membungkus kepala saya di sekitar pengawas waktu pada seri ATTinyX5. Jadi hal-hal yang saya baca membuatnya tampak seperti Anda dapat menggunakannya untuk membuat program melakukan sesuatu yang spesifik selama N detik tetapi tidak pernah benar-benar menunjukkan caranya. Yang lain membuatnya tampak seperti itu hanya akan mereset chip kecuali jika sesuatu dalam kode reset itu dihitung sementara (yang tampaknya merupakan penggunaan "normal").

Apakah ada cara menggunakan WDT seperti yang Anda lakukan TIMER1_COMPA_vect atau serupa. Saya perhatikan bahwa ia memiliki mode batas waktu 1 detik dan saya akan sangat senang dapat menggunakannya untuk membuat sesuatu terjadi setiap 1 detik dalam kode saya (dan lebih baik tidur di antaranya).

Pikiran?

* Pembaruan: * Karena ditanya, yang saya maksud adalah bagian 8.4 dari Lembar Data ATTinyX5 . Bukan berarti saya sepenuhnya memahaminya, yang merupakan masalah saya ...

Adam Haile
sumber
1
+1 untuk berpikir di luar kotak. Jempol ekstra jika Anda menambahkan tautan ke lembar data untuk AVR.
jippie

Jawaban:

23

Anda pasti bisa. Menurut datasheet, pengawas waktu dapat diatur untuk mengatur ulang MCU atau menyebabkan interupsi ketika dipicu. Tampaknya Anda lebih tertarik pada kemungkinan interupsi.

WDT sebenarnya lebih mudah diatur daripada Timer biasa karena alasan yang sama kurang bermanfaat: lebih sedikit opsi. Ini berjalan pada clock 128kHz yang dikalibrasi secara internal, yang artinya waktunya tidak dipengaruhi oleh kecepatan clock utama MCU. Itu juga dapat terus berjalan selama mode tidur terdalam untuk menyediakan sumber bangun.

Saya akan membahas beberapa contoh lembar data serta beberapa kode yang saya gunakan (dalam C).

Termasuk File dan Definisi

Untuk memulai, Anda mungkin ingin menyertakan dua file header berikut ini agar semuanya berfungsi:

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

Juga, saya menggunakan Makro <_BV (BIT)> yang didefinisikan dalam salah satu header AVR standar sebagai berikut (yang mungkin lebih familier bagi Anda):

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

Awal Kode

Ketika MCU pertama kali dimulai, Anda biasanya akan menginisialisasi I / O, mengatur timer, dll. Di suatu tempat di sini adalah waktu yang tepat untuk memastikan WDT tidak menyebabkan reset karena bisa melakukannya lagi, menjaga program Anda tetap di loop tidak stabil.

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
}

Pengaturan WDT

Kemudian, setelah Anda mengatur sisa chip, ulangi WDT. Menyiapkan WDT membutuhkan "urutan waktu", tetapi sangat mudah dilakukan ...

// 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

Tentu saja, interupsi Anda harus dinonaktifkan selama kode ini. Pastikan untuk mengaktifkannya kembali setelahnya!

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

WDT Interrupt Service Routine Hal berikutnya yang perlu dikhawatirkan adalah menangani WDT ISR. Ini dilakukan sebagai berikut:

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
}

Tidur MCU

Daripada menempatkan MCU untuk tidur di dalam WDT ISR, saya sarankan hanya mengaktifkan mode tidur di akhir ISR, kemudian minta program MAIN membuat MCU tertidur. Dengan begitu, program ini benar-benar meninggalkan ISR sebelum tidur, dan akan bangun dan langsung kembali ke 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   
  ****************************/

   }
 }
Kurt E. Clothier
sumber
WOW ... sangat detail. Terima kasih! Saya sedikit bingung dengan bagian sleep di mana Anda menunjukkan loop utama (if (MCUCR & _BV (SE)) {// Jika Sleep Diaktifkan ... dll. Saya bingung mengapa dalam tampilan utama Anda akan terus menonaktifkan dan mengaktifkan interupsi. Dan bagian di atas bagian itu (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Di mana itu seharusnya dijalankan?
Adam Haile
OK, bagian "set_sleep_mode (MODE)" harus berada di utama SEBELUM loop utama, idealnya dalam kode inisialisasi lainnya di mana Anda mengatur port I / O, timer, dll. Anda tidak benar-benar perlu mengaktifkan_sleep (); pada saat itu, karena akan dilakukan setelah pemicu WDT pertama Anda. Di dalam loop utama, kode tidur itu hanya akan menjalankan JIKA tidur diaktifkan, dan itu tidak sepenuhnya diperlukan untuk menonaktifkan / mengaktifkan kembali interupsi di sana, hanya jika Anda melakukan sleep_bod_disable (); Seluruh pernyataan IF itu bisa berada di bagian bawah (tetapi masih di dalam) dari loop MAIN setelah kode lain yang Anda jalankan di sana.
Kurt E. Clothier
Oke ... itu lebih masuk akal sekarang. Hanya hal terakhir yang saya tidak jelas tentang apa "urutan waktu" ini adalah ...
Adam Haile
Catatan: Saya tahu mengapa Anda ingin tidur, tetapi saya berasumsi bahwa Anda tidak harus menggunakan WDT?
Adam Haile
Lihatlah bagian kecil lembar data ini: 8.4.1. Pada dasarnya, untuk mengubah register WDT, Anda harus mengatur bit perubahan dan kemudian mengatur bit WDTCR yang tepat dalam begitu banyak siklus jam. Kode yang saya berikan di bagian Pengaturan WDT melakukan ini dengan terlebih dahulu mengaktifkan bit perubahan WD. Dan tidak, Anda tidak harus menggunakan fungsi tidur sama sekali dengan WDT. Ini bisa menjadi sumber interupsi berjangka waktu standar untuk alasan apa pun yang Anda inginkan.
Kurt E. Clothier
1

Menurut datasheet itu mungkin. Anda bahkan dapat mengaktifkan keduanya, interupsi dan reset. Jika keduanya diaktifkan, batas waktu Watchdog pertama akan memicu interupsi yang menyebabkan bit Interrupt Enable menjadi tidak disetel (interrupt nonaktif). Batas waktu berikutnya akan mengatur ulang CPU Anda. Jika Anda mengaktifkan Interupsi secara langsung setelah dieksekusi, batas waktu berikutnya hanya akan (lagi) memicu interupsi.

Anda juga bisa mengaktifkan interupsi dan tidak mengaktifkan reset sama sekali. Anda harus mengatur bit WDIE setiap kali interupsi terpicu.

Tom L.
sumber
Hmmm .... Saya pikir itu masuk akal. Saya akan mencobanya. Saya selalu suka menggunakan hal-hal yang tidak dimaksudkan untuk mereka :)
Adam Haile
Sebenarnya saya pikir ini desain yang pintar. Menghemat waktu Anda dengan tetap menjaga fungsi pengawas.
Tom L.
2
Waktu tunggu awal WDT ini dan interupsi berikutnya juga dapat digunakan untuk memanfaatkan beberapa aplikasi yang mengaktifkan WDT untuk pemulihan hangup aktual. Orang dapat melihat alamat balik yang ditumpuk di WDT ISR untuk menyimpulkan apa yang coba dilakukan kode ketika "batas waktu tak terduga" terjadi.
Michael Karas
1

Ini jauh lebih mudah daripada yang disarankan di atas dan di tempat lain.

Selama WDTONsekring tidak diprogram (tidak diprogram secara default), maka Anda hanya perlu ...

  1. Atur bit interrupt watchdog interrupt dan timeout di register kontrol watchdog.
  2. Aktifkan interupsi.

Berikut adalah contoh kode yang akan menjalankan ISR sekali per 16ms ...

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);
}

Benar-benar itu. Karena kami tidak pernah mengaktifkan reset pengawas, kami tidak perlu dipusingkan dengan urutan waktunya untuk menonaktifkannya. Bendera interrupt pengawas secara otomatis akan dihapus ketika ISR dipanggil.

Jika Anda menginginkan periode yang berbeda dari setiap 1 detik, Anda dapat menggunakan nilai-nilai ini di sini untuk mengatur bit yang sesuai di WDTCR...

masukkan deskripsi gambar di sini

Perhatikan bahwa Anda perlu menjalankan urutan waktu untuk mengubah batas waktu. Berikut adalah kode yang menetapkan batas waktu menjadi 1 detik ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
sumber
Tidak melakukan urutan waktu selama pengaturan menyimpan satu baris kode - operasi baca-modifikasi-tulis. Urutan awal direkomendasikan dalam lembar data "Jika Watchdog diaktifkan secara tidak sengaja, misalnya oleh penunjuk pelarian atau kondisi brown-out" dan sisa kode saya khusus untuk menggunakan WDT dalam kombinasi dengan mode tidur, seperti yang diminta oleh OP. Solusi Anda tidak sederhana, Anda hanya mengabaikan kode pelat ketel yang direkomendasikan / diperlukan.
Kurt E. Clothier
@ KurtE.Clothier Maaf, hanya mencoba memberikan contoh kerja yang paling sederhana!
bigjosh