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

电器工程 Arduino avr 服装 看门狗
2022-01-03 11:30:55

我有一个问题,在 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 也不应该亮起,但它经常会亮起。

这里发生了什么?

1个回答

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. 在同一操作中,将逻辑 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 位 ORing 到 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

...但这需要更多代码和额外的临时寄存器。由于看门狗计数器在被禁用时会被重置,因此额外的重置不会破坏任何东西,也不会产生意外的副作用。