У меня возникла проблема, когда выполнение последовательности отключения сторожевого таймера на AVR ATtiny84A фактически сбрасывает чип, хотя на таймере должно быть достаточно времени. Это происходит непоследовательно и при запуске одного и того же кода на многих физических частях; некоторые сбрасываются каждый раз, некоторые сбрасываются иногда, а некоторые никогда.
Чтобы продемонстрировать проблему, я написал простую программу, которая...
Общее время между включением и отключением сторожевого таймера составляет менее 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.
}
}
При запуске программа проверяет, не был ли предыдущий сброс вызван тайм-аутом сторожевого таймера, и если да, то зажигает красный светодиод и сбрасывает флаг сброса сторожевого таймера, указывая на то, что произошел сброс сторожевого таймера. Я считаю, что этот код никогда не должен выполняться, и красный светодиод никогда не должен загораться, хотя это часто происходит.
Что здесь происходит?
В процедуре библиотеки 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
инструкция сбрасывает сторожевой таймер, поэтому, когда следующая строка потенциально переключается на другой прескалер, это гарантирует, что время ожидания еще не истекло.
Это также можно исправить, объединив биты 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
... но для этого требуется больше кода и дополнительный временной регистр. Поскольку сторожевой счетчик сбрасывается, когда он отключен, дополнительный сброс ничего не стирает и не имеет непреднамеренных побочных эффектов.
Владимир Краверо
большой джош
Владимир Краверо