为什么当我调用wdt_disable()尝试关闭看门狗定时器时,我的AVR会复位?


34

我遇到了一个问题,即使在计时器上还有足够的时间,在AVR ATtiny84A上执行禁用看门狗序列实际上也会复位芯片。当在许多物理部件上运行相同的代码时,这会不一致地发生。有些每次都会重置,有些有时会重置,有些则永远不会重置。

为了演示该问题,我编写了一个简单的程序,该程序可以...

  1. 使看门狗超时1秒
  2. 重置看门狗
  3. 使白色LED闪烁0.1秒
  4. 闪烁白色LED熄灭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.

    }
}

在启动时,程序将检查先前的重置是否是由看门狗超时引起的,如果是,它将点亮红色LED并清除看门狗重置标志以指示看门狗重置发生。我相信永远不要执行此代码,永远不要点亮红色LED,但是经常这样做。

这里发生了什么?


7
如果您决定在这里写下有关此问题的常见问题解答,我可以想象发现它所需要的痛苦。
弗拉基米尔·克拉韦罗

3
你打赌!这个错误需要12个小时。有一段时间,该错误只会在站点外发生。如果我将板子放到桌面上,那么错误可能会消失,这可能是由于温度影响(我的位置很冷,这使得看门狗振荡器相对于系统时钟而言运行得稍慢)。经过30多次尝试,才能复制并捕捉到视频中。
bigjosh

我几乎可以感觉到痛苦。我不是一个老旧的EE专家,但有时会遇到这种情况。
丰收

Answers:


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.在同一操作中,将逻辑1写入WDCE和WDE。无论WDE位的先前值如何,都必须将逻辑1写入WDE。2.在接下来的四个时钟周期内,以相同的操作,根据需要写入WDE和WDP位,但将WDCE位清零。

不幸的是,该分配具有将看门狗控制寄存器(WDCE)的低3位也设置为0的副作用。这将立即将预分频器设置为其最短的值。如果在执行该指令时已经触发了新的预分频器,则处理器将复位。

由于看门狗定时器运行在物理上独立的128 kHz振荡器上,因此很难预测新的预分频器相对于正在运行的程序的状态。这说明了观察到的行为的广泛范围,在这些行为中,错误可能与电源电压,温度和制造批次相关联,因为所有这些因素都可能非对称地影响看门狗振荡器的速度和系统时钟。这是一个很难发现的错误!

这是避免此问题的更新代码...

__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进行数据表中的建议来解决...

; 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,2015年

1
ps“ josh.com”是真实的……令人印象深刻
vicatcu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.