Программный сброс AVR без ватчдога

Я написал обычную программу-оболочку avr, которая уже использует сторожевой таймер для перезапуска, а после сброса сторожевого таймера - восстанавливает плохую ситуацию.

После этого процесса восстановления я должен перезапустить avr. Все, что я нашел в теме «программный сброс arduino», устанавливает кратчайший тайм-аут сторожевого таймера (15 мс), а затем вращает процессор (for(;;);), пока не вызовет сброс сторожевого таймера.

Но это приводит к входу в процесс восстановления, который уже выполнен на этом этапе.

Можно ли добиться этого элегантным способом?

Какой-то не очень или менее элегантный способ:

  • Я могу привязать цифровой пин к сбросу и подтянуть резистором.

  • Я могу записать на фиксированный адрес во флэш-памяти или в EEPROM (но это может происходить часто) перед программным перезапуском, чтобы предотвратить вход в режим восстановления.


Есть ли какие-либо регистры, которые "выживают" при программном сбросе?

Изменить:

Я добавляю фиктивный пример кода, чтобы намекнуть, что я пытаюсь сделать.

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

/*************************** Оболочка приложения **************** ****************/

// определяем call_application и call_recover как слабые пустые функции
//чтобы их можно было переписать.

bool isWatchdogReset()
{
    return MCUSR == 8;
}

void softwareReset()
{
    // wdt_enable(WDTO_15MS);for(;;);// (таким образом входит в режим восстановления)
    //или
    // asm("jmp 0x0");// (таким образом регистры функции не сбрасываются)
}

bool isSoftwareReset()
{
    //???
}

int main()
{
    bool needRecover = isWatchdogReset() && !isSoftwareReset();
    MCUSR = 0;
    init_board();
    wdt_enable(WDTO_1S);
    while(1)
    {
        if(!needRecover)
        {
            call_application();
        }
        else
        {
            call_recover();
        }
        wdt_reset();
    }   
}


/********************************* Application ********************************/

void call_application()
{
    if(randomTrue())
    {
        //зависание более 1 секунды, что приводит к сбросу WDT
    }
    else
    {
        //правильная работа
    }
}

/********************************** Recover ***********************************/

void call_recover()
{
    // инициализируем UART, подключаем прерывания, записываем flash и EEPROM, модифицируем
    //регистры функций.

    //восстановление завершено, возврат к исходному приложению вроде как
    // ничего не произошло.
    softwareReset();
}

Изменить: Я пытаюсь объяснить кратко, чтобы понять суть:

1) Я уже использую сторожевой таймер для обнаружения ошибок в коде приложения.

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

3) Чтобы правильно выполнить сброс, нужно установить тайм-аут сторожевого таймера. Но смотрите пункт 1), он уже используется, так как же вы можете отличить запрошенный сброс от сброса для восстановления ошибочного кода?

, 👍2

Обсуждение

а почему со вторым сбросом wdt проблема? или почему нельзя продолжить без перезагрузки?, @Juraj

Я прочитал ваш вопрос три раза, но до сих пор не понимаю, что вы описываете. О каком «восстановительном процессе» вы говорите? «Восстанавливают» данные в процедуре прерывания WDT., @Gerben


2 ответа


2

Сброс в основном выполняется по адресу вектора сброса 0x0000 (если у вас нет загрузчика или вы не хотите его запускать).

Есть также регистр MCUSR, указывающий, какой источник сброса вызвал сброс. Он не будет установлен, если вы сделаете простой переход на адрес сброса.

Память также сохраняется, но код инициализации C/C++ имеет место и инициализирует ваши глобальные переменные. Этого можно избежать, поместив переменную/переменные в раздел .noinit. Но не определяется, что там после сброса. В сочетании с регистром MCUSR вы можете инициализировать переменные для обычных источников сброса и оставить их неизменными, если вы перейдете к адресу вектора сброса.

Кроме того, обычные источники сброса переходят к загрузчику, поэтому он может использовать те же области памяти, что и ваши переменные .noinit, и уничтожать их.

,

Я проверяю регистр MCUSR сразу после запуска, чтобы определить источник сброса/включения питания. Теперь я попробовал этот способ сброса (void sw_reset(){asm("jmp 0x0");}). Это работает, но, как я разобрал, он просто сбрасывает указатель стека, что приводит к повторному запуску программы из main(), но настройки порта, таймеров и UART остаются., @Dankó Dávid

@DankóDávid Так ты не хочешь перезапускать его? Может быть, это [проблема XY](https://meta.stackexchange.com/a/66378)?, @KIIV

Также обратите внимание, что аппаратный сброс устанавливает все регистры ввода-вывода в исходное значение, чего не происходит при переходе на 0x0000. Лишь очень немногие регистры, такие как MCUSR, не имеют определенного начального значения., @Edgar Bonet

@EdgarBonet да, это может быть проблемой в процессе связи TWI или около того. Но код инициализации надо писать, так как что-то уже настроено. Определенно в этом случае., @KIIV

Я отредактировал вопрос и добавил пример того, какую именно проблему я пытаюсь решить. 1) Я уже использую сторожевой таймер для обнаружения неисправности. 2) Я хотел бы полностью сбросить AVR (сбросить все регистры функций, к которым прикасалась функция восстановления, и я не могу отследить), но единственный способ, который я нашел, - это использовать тайм-аут WDT, что приводит не только к перезапуску, но и к распознать как «неисправность перезапуска». Более упрощенно: мне нужен сброс неисправности (WDT) и намеренный программный сброс, которые не используют один и тот же механизм сброса. Или различайте два способа сброса AVR., @Dankó Dávid


2

Логика ваших приложений неверна. Вы устанавливаете needRecover в начале main, но проверяете его в бесконечном цикле while (1). Значение needRecover не изменится в цикле.

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

Вы можете поместить инициализацию в функцию с именем setup, а бесконечный цикл — в функцию с именем loop. И есть такая функция main:

int main( void )
{
  setup();

  for (;;)
  {
    loop();
  }

  return 0;
}

подождите, где-то это уже видели :-)

,

Процесс восстановления должен перезапустить микроконтроллер. Но теперь для этого используется сторожевой таймер. Что приводит к входу снова в режим рекавери, а не в режим приложений., @Dankó Dávid

@DankóDávid, используйте прерывание сторожевого таймера вместо сторожевого таймера. настройте прерывание, а затем выполните сброс с помощью wdt_enable(WDTO_15MS); мой здесь: https://github.com/jandrassy/Regulator/blob/b6ef120c6f8963979268d8d17b56e5632336f317/Regulator/Watchdog.ino#L24, @Juraj

вы можете использовать эту библиотеку https://github.com/nadavmatalon/WatchDog. Я планирую заменить ею свою обработку сторожевого таймера., @Juraj