Перейти к содержанию
    

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

Счетчик импульсов (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:"));
	
    ....

}

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

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

Спасибо!

Изменено пользователем dontsov

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

 

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

Изменено пользователем slanted

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 minutes ago, slanted said:

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

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

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


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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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. Это гарантирует то что соответствующее прервыание будет подавлено на момент смены конфигурации.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А почему не использовать готовые функции? Я их сам редко в примерах вижу - обычно все напрямую с битами работают.. 
Поставил работать этот вариант, 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();

  ....
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

 

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

 

По поводу этого каунтера: переполнение 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 )
  {
    // работаем
    // спим
  }
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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с...
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 hours ago, slanted said:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я попробовал имитировать задержку в коде, чтобы сработал 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
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Откомментировал прямо в коде (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
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...