Mengapa AVR saya direset ketika saya memanggil wdt_disable () untuk mencoba mematikan pengawas waktu?

34

Saya mengalami masalah ketika menjalankan urutan watchdog penonaktifan pada AVR ATtiny84A sebenarnya mengatur ulang chip meskipun timer harus memiliki banyak waktu tersisa di atasnya. Ini terjadi secara tidak konsisten dan ketika menjalankan kode yang sama pada banyak bagian fisik; beberapa reset setiap kali, kadang-kadang reset beberapa, dan beberapa tidak pernah.

Untuk menunjukkan masalah, saya telah menulis sebuah program sederhana yang ...

  1. Mengaktifkan watchdog dengan timeout 1 detik
  2. Mereset pengawas
  3. Mengedipkan LED putih menyala selama 0,1 detik
  4. Mematikan LED putih selama 0,1 detik
  5. Menonaktifkan pengawas

Total waktu antara pengawas mengaktifkan dan menonaktifkan adalah kurang dari 0,3 detik, namun terkadang pengawas reset terjadi ketika urutan penonaktifan dijalankan.

Ini kodenya:

#define F_CPU 1000000                   // Name used by delay.h. We are running 1Mhz (default fuses)

#include <avr/io.h>
#include <util/delay.h>
#include <avr/wdt.h>


// White LED connected to pin 8 - PA5

#define WHITE_LED_PORT PORTA
#define WHITE_LED_DDR DDRA
#define WHITE_LED_BIT 5


// Red LED connected to pin 7 - PA6

#define RED_LED_PORT PORTA
#define RED_LED_DDR DDRA
#define RED_LED_BIT 6


int main(void)
{
    // Set LED pins to output mode

    RED_LED_DDR |= _BV(RED_LED_BIT);
    WHITE_LED_DDR |= _BV(WHITE_LED_BIT);


    // Are we coming out of a watchdog reset?
    //        WDRF: Watchdog Reset Flag
    //        This bit is set if a watchdog reset occurs. The bit is reset by a Power-on Reset, or by writing a
    //        logic zero to the flag

    if (MCUSR & _BV(WDRF) ) {

        // We should never get here!


        // Light the RED led to show it happened
        RED_LED_PORT |= _BV(RED_LED_BIT);

        MCUCR = 0;        // Clear the flag for next time
    }

    while(1)
    {
        // Enable a 1 second watchdog
        wdt_enable( WDTO_1S );

        wdt_reset();          // Not necessary since the enable macro does it, but just to be 100% sure

        // Flash white LED for 0.1 second just so we know it is running
        WHITE_LED_PORT |= _BV(WHITE_LED_BIT);
        _delay_ms(100);
        WHITE_LED_PORT &= ~_BV(WHITE_LED_BIT);
        _delay_ms(100);

        // Ok, when we get here, it has only been about 0.2 seconds since we reset the watchdog.

        wdt_disable();        // Turn off the watchdog with plenty of time to spare.

    }
}

Pada saat startup, program memeriksa untuk melihat apakah reset sebelumnya disebabkan oleh timeout anjing pengawas, dan jika demikian itu menyalakan LED merah dan mengosongkan flag pengaturan ulang pengawas untuk menunjukkan bahwa pengaturan ulang pengawas terjadi. Saya percaya bahwa kode ini seharusnya tidak pernah dieksekusi dan LED merah tidak boleh menyala, namun sering terjadi.

Apa yang terjadi disini?

bigjosh
sumber
7
Jika Anda memutuskan untuk menulis T&J Anda sendiri di sini tentang masalah ini, saya bisa membayangkan rasa sakit dan penderitaan yang diperlukan untuk menemukannya.
Vladimir Cravero
3
Anda bertaruh! 12 jam pada bug ini. Untuk sementara, bug HANYA akan terjadi di luar situs. Jika saya membawa papan ke desktop saya maka bug akan hilang, kemungkinan karena efek suhu (tempat saya dingin yang membuat osilator pengawas berjalan sedikit lebih lambat relatif terhadap jam sistem). Butuh 30+ percobaan untuk mereproduksi dan menangkapnya dalam aksi di video.
bigjosh
Aku hampir bisa merasakan sakitnya. Saya bukan EE yang lama dan dinavigasi tetapi kadang-kadang saya menemukan diri saya dalam situasi seperti itu. Tangkapan hebat, minum bir dan terus memecahkan masalah;)
Vladimir Cravero

Jawaban:

41

Ada bug di rutin pustaka wdt_reset ().

Ini kodenya ...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "out %0, %1" "\n\t" \
   "out %0, __zero_reg__" "\n\t" \
   "out __SREG__,__tmp_reg__" "\n\t" \
   : /* no outputs */ \
   : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
   "r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
   : "r0" \
)

Baris keempat berkembang menjadi ...

out _WD_CONTROL_REG, _BV(_WD_CHANGE_BIT) | _BV(WDE)

Maksud dari baris ini adalah untuk menulis 1 ke WD_CHANGE_BIT, yang akan memungkinkan baris berikut untuk menulis 0 ke watchdog enable bit (WDE). Dari lembar data:

Untuk menonaktifkan Timer Watchdog yang diaktifkan, prosedur berikut harus diikuti: 1. Dalam operasi yang sama, tuliskan logika ke WDCE dan WDE. Logika harus ditulis ke WDE terlepas dari nilai bit WDE sebelumnya. 2. Dalam empat siklus jam berikutnya, dalam operasi yang sama, tulis bit WDE dan WDP seperti yang diinginkan, tetapi dengan bit WDCE dihapus.

Sayangnya, penugasan ini memiliki efek samping yaitu menetapkan 3 bit terbawah Watchdog Control Register (WDCE) menjadi 0's. Ini segera menetapkan prescaler ke nilai terpendeknya. Jika prescaler baru telah dipecat pada saat instruksi ini dijalankan, prosesor akan diatur ulang.

Karena pengawas waktu menjalankan osilator 128 kHz independen secara fisik, sulit untuk memprediksi keadaan prescaler baru dalam kaitannya dengan program yang sedang berjalan. Ini menjelaskan berbagai perilaku yang diamati di mana bug dapat dikorelasikan dengan tegangan suplai, suhu, dan batch produksi karena semua hal ini dapat mempengaruhi kecepatan osilator pengawas dan jam sistem secara asimetris. Ini adalah bug yang sangat sulit ditemukan!

Berikut adalah kode yang diperbarui yang menghindari masalah ini ...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "wdr" "\n\t" \
   "out %0, %1" "\n\t" \
   "out %0, __zero_reg__" "\n\t" \
   "out __SREG__,__tmp_reg__" "\n\t" \
   : /* no outputs */ \
   : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
   "r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
   : "r0" \
)

wdrInstruksi tambahan me-reset timer anjing pengawas, jadi ketika baris berikut berpotensi beralih ke prescaler yang berbeda, itu dijamin belum waktunya habis.

Ini juga bisa diperbaiki dengan ORing WD_CHANGE_BIT dan bit WDE ke dalam WD_CONTROL_REGISTER seperti yang disarankan dalam lembar data ...

; Write logical one to WDCE and WDE
; Keep old prescaler setting to prevent unintentional Watchdog Reset
in r16, WDTCR
ori r16, (1<<WDCE)|(1<<WDE)
out WDTCR, r16

... tetapi ini membutuhkan lebih banyak kode dan register awal tambahan. Karena penghitung pengawas diatur ulang ketika dinonaktifkan, pengaturan ulang tambahan tidak akan merusak apa pun dan tidak memiliki efek samping yang tidak disengaja.

bigjosh
sumber
7
Saya juga ingin memberi Anda alat peraga karena ketika saya pergi untuk memeriksa daftar masalah avr-libc, tampaknya Anda (mungkin Anda) mengirimkannya di sana savannah.nongnu.org/bugs/?44140
vicatcu
1
ps "josh.com" adalah nyata ... mengesankan
vicatcu