워치 독 타이머를 끄려고 wdt_disable ()을 호출 할 때 왜 AVR이 재설정됩니까?


34

타이머에 충분한 시간이 남아 있어도 AVR ATtiny84A 에서 디스 에이치 워치 독 시퀀스를 실행하는 것이 실제로 칩을 재설정하는 문제가 있습니다. 이것은 일관되지 않고 많은 물리적 부분에서 동일한 코드를 실행할 때 발생합니다. 일부는 매번 재설정되고 일부는 때때로 재설정되며 일부는 재설정되지 않습니다.

문제를 설명하기 위해 간단한 프로그램을 작성했습니다 ...

  1. 1 초 타임 아웃으로 워치 독을 활성화합니다
  2. 워치 독을 재설정합니다
  3. 0.1 초 동안 흰색 LED를 깜빡입니다.
  4. 0.1 초 동안 흰색 LED가 깜박임
  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.

    }
}

프로그램은 시작시 워치 독 시간 초과로 인해 이전 재설정이 발생했는지 확인하고,이 경우 빨간색 LED가 켜지고 워치 독 재설정 플래그가 지워져 워치 독 재설정이 발생했음을 나타냅니다. 나는이 코드를 실행해서는 안되며 빨간색 LED가 켜지지 않아야한다고 생각하지만 종종 발생합니다.

무슨 일이야?


7
이 문제에 대해 자신의 Q & A를 작성하기로 결정했다면, 그것을 발견하는 데 필요한 고통과 고통을 상상할 수 있습니다.
Vladimir Cravero

3
내기! 이 버그에 12 시간. 잠시 동안 버그는 사이트 외부에서만 발생합니다. 보드를 데스크탑으로 가져 오면 온도 영향으로 인해 버그가 사라질 수 있습니다 (제 위치가 차가워 워치 독 발진기가 시스템 클록에 비해 약간 느려집니다). 그것을 재생하고 비디오의 행위에서 그것을 잡기 위해 30 회 이상의 시험이 필요했습니다.
bigjosh

나는 거의 고통을 느낄 수 있습니다. 나는 낡고 탐색 된 EE가 아니지만 때때로 그러한 상황에서 나 자신을 발견했습니다. 큰 캐치, 맥주를 마시고 문제를 계속 해결;)
Vladimir Cravero

답변:


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)

이 라인의 목적은 WD_CHANGE_BIT에 1을 쓰는 것이며, 다음 라인은 워치 독 인 에이블 비트 (WDE)에 0을 쓸 수 있습니다. 데이터 시트에서 :

활성화 된 감시 타이머를 비활성화하려면 다음 절차를 따라야합니다. 1. 동일한 작업에서 WDCE 및 WDE에 논리 1을 씁니다. 논리 값은 이전의 WDE 비트 값에 관계없이 WDE에 기록되어야합니다. 2. 다음 4 클럭주기 내에서 동일한 작업으로 원하는대로 WDE 및 WDP 비트를 쓰되 WDCE 비트는 지 웁니다.

불행하게도,이 할당은 WDCE ( Watchdog Control Register ) 의 하위 3 비트 를 0으로 설정하는 부작용도 있습니다 . 이것은 즉시 프리스케일러를 최단 값으로 설정합니다. 이 명령이 실행되는 순간에 새로운 프리스케일러가 이미 실행 된 경우 프로세서가 재설정됩니다.

워치 독 타이머는 물리적으로 독립적 인 128kHz 발진기를 사용하므로 실행중인 프로그램과 관련하여 새로운 프리스케일러의 상태를 예측하기가 어렵습니다. 이것은 버그가 공급 전압, 온도 및 제조 배치와 상관 될 수있는 넓은 범위의 관찰 된 동작을 설명합니다. 이러한 모든 것이 워치 독 발진기의 속도와 시스템 클록에 비대칭 적으로 영향을 줄 수 있기 때문입니다. 이것은 찾기가 매우 어려운 버그였습니다!

이 문제를 피하는 업데이트 된 코드는 다음과 같습니다.

__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명령은 워치 독 타이머를 재설정하므로 다음 라인이 다른 프리스케일러로 전환 될 때 아직 시간 초과되지 않았 음을 보장합니다.

데이터 시트에서 제안한대로 WD_CHANGE_BIT 및 WDE 비트를 WD_CONTROL_REGISTER에 OR로 연결하여 문제를 해결할 수도 있습니다.

; 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.