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