Jump to content

    
dontsov

attiny85: watchdog зависает

Recommended Posts

Прошу помощи!

Счетчик импульсов (watchdog в режиме генерации прерываний) на attiny85 считает импульсы просыпаясь раз в 250мс и раз в сутки выходит из цикла и общается по i2c.
Возникла проблема — очень редко attiny85 уходит в бесконечный цикл.


Как я это понял:
1. Устройство не выходит на связь через 15 мин.
2. Потребление вместо 12-17мкА стабильно 670мкА (без скачков и какой то работы).

Нажатие на кнопку приводит к выходу из цикла. Т.е. цикл работает - состояние кнопки проверяется.

Гипотеза: ISR(WDT_vect) не вызывается.
Подозрения на некорректный ресет watchdog. Проблема проявляется крайне редко и не понятно как отловить.

После зависания wdt_count = 15, т.е. значению иницилизации. Эту информацию вывел только что, поэтому поймал пока 1 зависание.

Странно, что потребление линией 670мкА (измеряю EFM32 Energy monitor), потому что когда Ватериус считает импульсы - там четкие скачки потребления из-за пробуждения из deepsleep.

 

volatile int16_t wdt_count;

/* Вектор прерываний сторожевого таймера watchdog */
ISR( WDT_vect ) { 
	wdt_count--;
	WDTCR |= bit( WDIE );   // так делать написано в datasheet
}  


void resetWatchdog() {
	
	MCUSR = 0; 
	WDTCR = bit( WDCE );

	// Пробуждаемся (проверяем входы) каждые 250 мс
	WDTCR = bit( WDIE ) | bit( WDP2 );     // 250 ms
	
	#define ONE_MINUTE 240  // 1 минута примерно равна 240 пробуждениям		  
	wdt_reset();
} 

inline void counting() {

    power_adc_enable(); //т.к. мы обесточили всё а нам нужен компаратор
    adc_enable();       //после подачи питания на adc

	if (counter0.is_impuls()) {
		info.data.value0++;	  //нужен т.к. при пробуждении запрашиваем данные
		info.states.state0 = counter0.state;
		info.adc.adc0 = counter0.adc;
		storage.add(info.data);
	}
#ifndef LOG_ON
	if (counter1.is_impuls()) {
		info.data.value1++;
		info.states.state1 = counter1.state;
		info.adc.adc1 = counter1.adc;
		storage.add(info.data);
	}
#endif

	adc_disable();
    power_adc_disable();

}

//Запрос периода при инициализции. Также период может изменится после настройки.
// Настройка. Вызывается однократно при запуске.
void setup() {
....
}


void loop() {
	power_all_disable();  // Отключаем все лишнее: ADC, Timer 0 and 1, serial interface

	set_sleep_mode( SLEEP_MODE_PWR_DOWN );  // Режим сна

	resetWatchdog();  
  
	// Выход по прошествию WAKE_EVERY_MIN минут или по нажатию кнопки
	for (unsigned int i = 0; i < ONE_MINUTE && !button.pressed(); ++i)  {
		wdt_count = info.wakeup_period_min;
		
		while ( wdt_count > 0 ) {
			noInterrupts();

			if (button.pressed()) { 
				interrupts(); 
				break;
			} else 	{
				counting(); //Опрос входов. Тут т.к. https://github.com/dontsovcmc/waterius/issues/76

				interrupts();
				sleep_mode();  // Спим (WDTCR)
			}
		}
	}
		
	wdt_disable();        // disable watchdog
	power_all_enable();   // power everything back on

	storage.get(info.data);     // Берем из хранилища текущие значения импульсов
	info.wdt = wdt_count;

	LOG_BEGIN(9600);
	LOG(F("Data:"));
	
    ....

}

Вот здесь была в другом проблема. Теперь не перезагружается... Но вот зависает: 

Если кто-то может потратить чуть больше времени, чтобы отладить проблему за вознаграждение - пишите. 

Спасибо!

Edited by dontsov

Share this post


Link to post
Share on other sites

Кажется я делаю что-то не то при сбросе watchdog.. но вопрос, почему тогда эта ошибка возникла только сейчас? (кстати я апгрейднул attinycore с 1.2.0 до 1.5.2...) сейчас откатил и поставил на тест.

Share this post


Link to post
Share on other sites

Да, работа с вочдогом выглядит странно. Если хочется его именно выключить, то надо не просто WDTCR=(1<<WDCE), а как в даташите написано:

WDTCR = (1<<WDCE)|(1<<WDE);
WDTCR = 0;

Ну и как бы выключать его не обязательно каждый раз, и даже переинициализировать не обязательно, достаточно wdt_reset() вовремя подавать.

 

То что воспроизводится случайно — ну, никто ж не тестирует что будет если с вочдогом работать нештатными методами. Может гонка какая-то внутри его собственной логики, может еще что-то.

Edited by slanted

Share this post


Link to post
Share on other sites
7 minutes ago, slanted said:

Да, работа с вочдогом выглядит странно. Если хочется его именно выключить, то надо не просто WDTCR=(1<<WDCE), а как в даташите написано:

Его хочется просто инициализировать для пробуждения каждые 250мс.
А если так делать?

	wdt_enable(WDTO_250MS);
	WDTCR |= _BV(WDIE); 
	interrupts(); 


По выходу из цикла опроса раз в сутки я делаю wdt_disable();

Share this post


Link to post
Share on other sites

Я бы делал немного не так.

setup()
{
  cli();
  info.service = MCUSR;
  // отключаем WDT
  MCUSR = 0;
  WDTCR = (1<<WDCE) | (1<<WDE);
  WDTCR = 0;
  sei();
  
  // вся остальная инициализация
  ...
}

loop()
{
  cli();
  wdt_reset();
  WDTCR = (1<<WDCE) | (1<<WDE);
  WDTCR = (1<<WDCE) | (1<<WDE) | (1<<WDIF) | (1<<WDIE)
    	  | (1<<WDP2) | (0<<WDP1) | (0<<WDP0);
  sei();
}

Вообще при любых действиях с флагом IE (это относится не только к WDT, а к любой периферии вообще) рекомендуется этим же оператором ставить соответствующий IF. Это гарантирует то что соответствующее прервыание будет подавлено на момент смены конфигурации.

Share this post


Link to post
Share on other sites

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

ISR( WDT_vect ) { 
	wdt_count--;
	WDTCR |= _BV( WDIE ); 
} 

void setup()
{
  noInterrupts();
  info.service = MCUSR; //причина перезагрузки
  wdt_disable();
  interrupts();

  ... работа с EEPROM
}

void loop() 
{
  power_all_disable();  

  set_sleep_mode( SLEEP_MODE_PWR_DOWN ); 

  noInterrupts();
  wdt_enable(WDTO_250MS);
  WDTCR |= _BV(WDIE); 
  interrupts(); 
  
  .... LOOP wdt_count
  
  wdt_disable(); 
  power_all_enable();

  ....
}

Share this post


Link to post
Share on other sites

Я просто забыл что они в avr-libc тоже есть :)

Счас посмотрел — вполне адекватно написано, можно и их.

 

PS. Одно последнее замечание — если у нас схема которая должна работать годами, то вочдог все же будет не лишним. Поэтому я бы оставил в WDT_vect остаётся только wdt_count--, а прерывание взводить только перед засыпанием:

while ( wdt_count > 0 )
{
  ...
    wdt_reset();
    WDTCR |= (1<<WDIE);
    interrupts();
    sleep_mode();
}

Считать время он будет как и раньше, только теперь если вдруг случится что-то еще, то следующим будет настоящий сброс по вочдогу.

Share this post


Link to post
Share on other sites

А, и да, чтоб два раза не вставать. Не очень критично, но всё же.

 

По поводу этого каунтера: переполнение signed int — это undefined behavior, поэтому очередной чрезмерно оптимизирующий компилятор может что-нибудь такое решить и, к примеру, вообще выкинуть этот счётчик. Поэтому я обычно делаю все подобные счётчики unsigned, считающими вверх, и не сбрасываю их никогда, только сравниваю при ожидании. Заодно это даёт глобальный счётчик тиков, который можно переиспользовать где-нибудь еще. Примерно так:

volatile uint16_t wdt_count;

ISR(WDT_vect)
{
  ++wdt_count;
}

{
  // цикл ожидания
  uint16_t const now = wdt_count;
  while ( (wdt_count - now) < wakeup_period )
  {
    // работаем
    // спим
  }
}

 

Share this post


Link to post
Share on other sites
12 hours ago, slanted said:

А, и да, чтоб два раза не вставать. Не очень критично, но всё же.

 

Огромное спасибо!! 
За wdt_count тоже - люблю когда behavior defined!

Корректно, я понял вас?

volatile uint16_t wdt_count;

ISR( WDT_vect ) { 
	++wdt_count;
}  

void setup() {

	noInterrupts();
	info.service = MCUSR;
	wdt_disable();
	interrupts();
	....
}

void loop() {
	power_all_disable();  // Отключаем все лишнее: ADC, Timer 0 and 1, serial interface

	set_sleep_mode( SLEEP_MODE_PWR_DOWN );  // Режим сна

	noInterrupts();
	wdt_enable(WDTO_250MS); 
	interrupts(); 

	// Цикл опроса входов
	// Выход по прошествию wakeup_period_min минут или по нажатию кнопки
	// ONE_MINUTE = 240 для 250мс
	for (uint16_t i = 0; i < ONE_MINUTE && !button.pressed(); ++i)  {
		wdt_count = 0;
		
		while (wdt_count < info.wakeup_period_min)
		{
			noInterrupts();

			if (button.pressed()) { // Пользователь нажал кнопку
				interrupts();  
				break;
			} else 	{
				counting(); //Опрос входов. Тут т.к. https://github.com/dontsovcmc/waterius/issues/76

				wdt_reset();
				WDTCR |= _BV(WDIE); 
				interrupts();
				sleep_mode(); 
			}
		}
	}
		
	wdt_disable();       
	power_all_enable(); 
	
	...пробуждаемся и общаемся по i2с...
}

 

Share this post


Link to post
Share on other sites
4 hours ago, slanted said:

Да вроде всё правильно.

Спасибо огромное за помощь! Сутки работают.. Кажется нужно сделать стенд, регулярно дрючащий attiny в кнопку и входы, чтобы проверять такие ситуации =(

Share this post


Link to post
Share on other sites

Я попробовал имитировать задержку в коде, чтобы сработал watchdog на перезагрузку - он срабатывает, но плата уходит в зависание. Кажется потому что не выставлял MCUSR = 0.
@slanted:
Вот такой код корректно перезагружает если wdt_reset не наступит за 250мс:

volatile uint32_t wdt_count;

ISR( WDT_vect ) { 
    ++wdt_count;
    WDTCR |= _BV(WDIE); 
} 

void setup() {

    noInterrupts();
    info.service = MCUSR; // причина перезагрузки
    MCUSR = 0;            // без этого не работает после перезагрузки по watchdog
    wdt_disable();        // а нужно ли тут, если у меня код быстро дойдёт до инициализации watchdog?
    interrupts();

    set_sleep_mode( SLEEP_MODE_PWR_DOWN ); // достаточно же 1 раз сделать?

    wakeup_period = WAKEUP_PERIOD_DEFAULT;
    
    .....
}



void loop() {
    power_all_disable(); 
    
    //NoInterrupts не нужно? т.к. в wdt_enable есть ассемблерная команда cli
    wdt_enable(WDTO_250MS); 
    interrupts(); 

    wdt_count = 0;
    while ((wdt_count < wakeup_period) && !button.pressed())
    {        
        counting();
        WDTCR |= _BV(WDIE);  //пока не понял, обязательно ли здесь или можно после wdt_enable раз в WDT_vect есть. без этого не работает.
        sleep_mode();
     }

     power_all_enable();
     // Далее: я не отключаю watchdog, для надежности. wdt_reset() вызывается в button.wait_release() где я могу долго ждать пока пользователь отпустит кнопку.

    ... //взаимоотношения с ESP
}

 

Share this post


Link to post
Share on other sites

Откомментировал прямо в коде (SL). В любом случае это первое что я буду смотреть как соберу свою железку, т.к. то что есть мне как-то в целом не нравится.

void setup() {

    noInterrupts();
    info.service = MCUSR; // причина перезагрузки
    MCUSR = 0;            // без этого не работает после перезагрузки по watchdog
                          // SL: да, это требуется для выключения WDT (см 8.5.2/bit 3 wde в даташите)
    wdt_disable();        // а нужно ли тут, если у меня код быстро дойдёт до инициализации watchdog?
                          // SL: по хорошему да, потому что мы не знаем сколько займёт инициализация.
  			  //     Если там еще и бутлоадер будет, к примеру, то это надо прямо там выключать.
  	 		  //     Надо это еще раз продумать.
    interrupts();

    set_sleep_mode( SLEEP_MODE_PWR_DOWN ); // достаточно же 1 раз сделать?
  					   // SL: В теории да, но мне встречалась рекомендация перезаписывать
  					   //     эти биты при каждом входе в sleep mode

    wakeup_period = WAKEUP_PERIOD_DEFAULT;
    
    .....
}



void loop() {
    power_all_disable(); 
    
    //NoInterrupts не нужно? т.к. в wdt_enable есть ассемблерная команда cli
    // SL: и interrupts() тоже не нужно, в wdt_enable есть сладкая парочка
    //     in tmp, SREG / out SREG, tmp, т.е. флаг прерывания оно восстановит
    //     как было
    wdt_enable(WDTO_250MS); 
    interrupts(); 

    wdt_count = 0;
    while ((wdt_count < wakeup_period) && !button.pressed())
    {        
        counting();
        WDTCR |= _BV(WDIE);  //пока не понял, обязательно ли здесь или можно после wdt_enable раз в WDT_vect есть. без этого не работает.
      			     //SL: WDIE нужно обновлять при каждом срабатывании таймера, потому что аппаратура его сбрасывает.
      			     //    Возможно, она его сбрасывает _при_выходе_ из прерывания, поэтому установка флага там не имеет эффекта.
        sleep_mode();
     }

     power_all_enable();
     // Далее: я не отключаю watchdog, для надежности. wdt_reset() вызывается в button.wait_release() где я могу долго ждать пока пользователь отпустит кнопку.

    ... //взаимоотношения с ESP
}

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.