Почему мой AVR сбрасывается, когда я вызываю wdt_disable (), чтобы попытаться выключить сторожевой таймер?


34

У меня возникла проблема, когда выполнение последовательности сторожевого таймера отключения на AVR ATtiny84A фактически сбрасывает микросхему, даже несмотря на то, что на таймере должно быть достаточно времени. Это происходит непоследовательно и при запуске одного и того же кода на многих физических частях; некоторые сбрасываются каждый раз, некоторые сбрасываются иногда, а некоторые никогда.

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

  1. Включает сторожевой таймер с задержкой в ​​1 секунду
  2. Сбрасывает сторожевой таймер
  3. Мигает белым светодиодом на 0,1 секунды
  4. Вспыхнул белый светодиод на 0,1 секунды
  5. Отключает сторожевой таймер

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

Вот код:

#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.

    }
}

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

Что здесь происходит?


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

3
Вы держите пари! 12 часов на эту ошибку. Какое-то время ошибка будет происходить ТОЛЬКО вне сайта. Если бы я принес платы на рабочий стол, то ошибка исчезла бы, вероятно, из-за температурных эффектов (у меня холодное место, из-за которого генератор сторожевого таймера работает немного медленнее, чем системные часы). Потребовалось более 30 испытаний, чтобы воспроизвести его и поймать в акте на видео.
bigjosh

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

Ответы:


41

В процедуре библиотеки wdt_reset () есть ошибка.

Вот код ...

__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" \
)

Четвертая строка расширяется до ...

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

Цель этой строки - записать 1 в WD_CHANGE_BIT, что позволит следующей строке записать 0 в бит включения сторожевого таймера (WDE). Из таблицы данных:

Чтобы отключить включенный сторожевой таймер, необходимо выполнить следующую процедуру: 1. В той же операции записать логическую единицу в WDCE и WDE. Логическая единица должна быть записана в WDE независимо от предыдущего значения бита WDE. 2. В течение следующих четырех тактов, в той же операции, запишите биты WDE и WDP по желанию, но с очищенным битом WDCE.

К сожалению, у этого назначения есть побочный эффект также установки младших 3 битов регистра управления сторожевым таймером (WDCE) в 0. Это сразу устанавливает прескалер к его кратчайшему значению. Если новый прескалер уже запущен в момент выполнения этой инструкции, процессор будет сброшен.

Поскольку сторожевой таймер работает от физически независимого генератора частотой 128 кГц, трудно предсказать, каким будет состояние нового прескалера относительно работающей программы. Это объясняет широкий диапазон наблюдаемого поведения, при котором ошибка может быть соотнесена с напряжением питания, температурой и производственной партией, поскольку все эти вещи могут асимметрично влиять на скорость сторожевого генератора и тактовых импульсов системы. Это было очень трудно найти ошибку!

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

__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" \
)

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

Это также может быть исправлено с помощью OR, добавляя биты WD_CHANGE_BIT и WDE в WD_CONTROL_REGISTER, как предложено в таблицах данных ...

; 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

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


7
Я также хотел бы дать вам реквизит, потому что, когда я пошел, чтобы проверить список проблем avr-libc, кажется, что вы (предположительно, вы) представили его там уже savannah.nongnu.org/bugs/?44140
vicatcu

1
ps "josh.com" реально ... впечатляет
vicatcu
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.