dontsov 0 June 26, 2021 Posted June 26, 2021 (edited) · Report post Прошу помощи! Счетчик импульсов (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 June 26, 2021 by dontsov Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 26, 2021 Posted June 26, 2021 · Report post Кажется я делаю что-то не то при сбросе watchdog.. но вопрос, почему тогда эта ошибка возникла только сейчас? (кстати я апгрейднул attinycore с 1.2.0 до 1.5.2...) сейчас откатил и поставил на тест. Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 26, 2021 Posted June 26, 2021 · Report post Гипотеза, что проблема в том что я не поднимаю WDE: WDTCR = bit( WDCE ) | bit( WDE ); Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 June 26, 2021 Posted June 26, 2021 (edited) · Report post Да, работа с вочдогом выглядит странно. Если хочется его именно выключить, то надо не просто WDTCR=(1<<WDCE), а как в даташите написано: WDTCR = (1<<WDCE)|(1<<WDE); WDTCR = 0; Ну и как бы выключать его не обязательно каждый раз, и даже переинициализировать не обязательно, достаточно wdt_reset() вовремя подавать. То что воспроизводится случайно — ну, никто ж не тестирует что будет если с вочдогом работать нештатными методами. Может гонка какая-то внутри его собственной логики, может еще что-то. Edited June 26, 2021 by slanted Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 26, 2021 Posted June 26, 2021 · Report post 7 minutes ago, slanted said: Да, работа с вочдогом выглядит странно. Если хочется его именно выключить, то надо не просто WDTCR=(1<<WDCE), а как в даташите написано: Его хочется просто инициализировать для пробуждения каждые 250мс. А если так делать? wdt_enable(WDTO_250MS); WDTCR |= _BV(WDIE); interrupts(); По выходу из цикла опроса раз в сутки я делаю wdt_disable(); Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 June 26, 2021 Posted June 26, 2021 · Report post Я бы делал немного не так. 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. Это гарантирует то что соответствующее прервыание будет подавлено на момент смены конфигурации. Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 27, 2021 Posted June 27, 2021 · Report post А почему не использовать готовые функции? Я их сам редко в примерах вижу - обычно все напрямую с битами работают.. Поставил работать этот вариант, 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(); .... } Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 June 27, 2021 Posted June 27, 2021 · Report post Я просто забыл что они в avr-libc тоже есть :) Счас посмотрел — вполне адекватно написано, можно и их. PS. Одно последнее замечание — если у нас схема которая должна работать годами, то вочдог все же будет не лишним. Поэтому я бы оставил в WDT_vect остаётся только wdt_count--, а прерывание взводить только перед засыпанием: while ( wdt_count > 0 ) { ... wdt_reset(); WDTCR |= (1<<WDIE); interrupts(); sleep_mode(); } Считать время он будет как и раньше, только теперь если вдруг случится что-то еще, то следующим будет настоящий сброс по вочдогу. Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 June 27, 2021 Posted June 27, 2021 · Report post А, и да, чтоб два раза не вставать. Не очень критично, но всё же. По поводу этого каунтера: переполнение 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 ) { // работаем // спим } } Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 27, 2021 Posted June 27, 2021 · Report post 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с... } Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 June 28, 2021 Posted June 28, 2021 · Report post Да вроде всё правильно. Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 June 28, 2021 Posted June 28, 2021 · Report post 4 hours ago, slanted said: Да вроде всё правильно. Спасибо огромное за помощь! Сутки работают.. Кажется нужно сделать стенд, регулярно дрючащий attiny в кнопку и входы, чтобы проверять такие ситуации =( Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 July 13, 2021 Posted July 13, 2021 · Report post Я попробовал имитировать задержку в коде, чтобы сработал 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 } Quote Share this post Link to post Share on other sites More sharing options...
slanted 0 July 13, 2021 Posted July 13, 2021 · Report post Откомментировал прямо в коде (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 } Quote Share this post Link to post Share on other sites More sharing options...
dontsov 0 July 14, 2021 Posted July 14, 2021 · Report post @slanted wdt_enable в setup убрал. Посмотри, пожалуйста: https://github.com/dontsovcmc/waterius/tree/dev Quote Share this post Link to post Share on other sites More sharing options...