Программирование в присутствии сторожевого таймера

Я плохо знаком с программированием встроенных систем, хотя я сделал курсы во время исследований, практическое программирование все еще немного еще дальше.

Вот проблема: Я должен программировать маленькую систему на микроконтроллере NXP LPC2103 (базирующийся ARM 7) без операционной системы. Это имеет сторожевой таймер, который должен регулярно обновляться. Система имеет модем GPRS со стеком TCP/IP, встроенным, и инициализирующий это занимает время дольше, чем сторожевому таймеру нужно к тайм-ауту. Когда я вызываю функцию инициализации, системный сброс.

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

Я не хотел бы называть сторожевой таймер от функции инициализации, я не нахожу эту пользу.

6
задан Bogi 29 June 2010 в 09:23
поделиться

5 ответов

Я бы не хотел вызывать сторожевой таймер из функции инициализации, я не считаю это хорошим.

Это может быть излишним для этой одной ситуации, но общий прием, который я использовал для длительных операций, когда вы, возможно, захотите выполнить другую работу, заключается в том, чтобы длительная функция принимала указатель на функцию обратного вызова, которая будет периодически вызываться. Шаблон, который я обычно использую, состоит в том, чтобы иметь прототип обратного вызова, который мог бы выглядеть следующим образом:

int (callback_t*)(void* progress, void* context);

Долгосрочная функция будет периодически вызывать обратный вызов с некоторой информацией, которая указывает на его прогресс (как этот прогресс представлен, что он означает, зависит от детали конкретной функции) и значение контекста, которое исходный вызывающий объект передал вместе с указателем обратного вызова (опять же - что означает этот параметр и как он интерпретируется, полностью зависит от обратного вызова). в общем, возвращаемое значение функции обратного вызова может использоваться, чтобы указать, что «долго работающая вещь» должна отменить или иным образом изменить поведение.

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

int watchdog_callback( void* progress, void* context)
{
    kick_the_watchdog();

    return 0;  // zero means 'keep going...'
}


void init_modem( callback_t pCallback, void* callback_context)
{
    // do some stuff

    pCallback( 0, callback_context);

    // do some other stuff

    pCallback( 1, callback_context);


    while (waiting_for_modem()) {
         // do work...

         pCallback( 2, callback_context);
    }    
}

В этом шаблоне хорошо то, что его можно использовать в разных ситуациях - у вас может быть функция, которая читает или записывает большой объем данных. Шаблон обратного вызова может использоваться, чтобы что-то отображало ход выполнения.

Обратите внимание, что если вы обнаружите, что у вас есть другие долго работающие функции, можно использовать ту же функцию watchdog_callback () , чтобы позволить им иметь дело с предотвращением сброса сторожевого таймера. Однако, если вы обнаружите, что вам нужно часто полагаться на этот тип вещей, в частности, для сторожевого пса, тогда вам может потребоваться подумать о том, как взаимодействуют ваши задачи, и либо разбить их, либо использовать более сложную схему сторожевого пса, которая управляется сторожевым псом. по своей собственной задаче, с которой взаимодействуют другие задачи, чтобы косвенно поддерживать сторожевой таймер.

6
ответ дан 8 December 2019 в 13:43
поделиться

Как правило, я использовал два подхода для этой ситуации.

Инициализация конечного автомата

Первый вариант во многом такой, как предлагал ваш коллега: реализовал процедуры инициализации в конечном автомате, вызываемом как часть основного цикла, а затем прекратил вызов подпрограмм инициализации и начал вызывать основные подпрограммы.

Это простая и понятная функция, но она может быть немного неудобной, когда речь идет о конкретных длительных процессах, таких как запуск низкочастотного генератора.

Ограниченная по времени обработка сторожевого таймера ISR

Есть еще одна альтернатива, если у вас есть «синтаксис» или эквивалентное прерывание, например прерывание, которое срабатывает каждые 1 мс. В этой ситуации вы можете рассмотреть возможность подачи сторожевого таймера (например) через каждые 50 вызовов прерывания, но ограничив количество раз, когда сторожевой таймер загружается, чтобы равняться максимально допустимому времени для завершения подпрограмм инициализации. В таком случае, как правило, необходимо (если у вас есть сторожевой таймер с оконным интерфейсом, который, на мой взгляд, должен быть) иметь короткий цикл синхронизации в конце инициализации, чтобы гарантировать, что сторожевой таймер не будет загружен до того, как будет достигнуто минимальное время окна, но реализовать это несложно.

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

Обсуждение

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

Я уверен, что другие предложат и другие альтернативные идеи ...

5
ответ дан 8 December 2019 в 13:43
поделиться

Сторожевой таймер в LPC2103 легко настраивается. У вас есть много вариантов для управления им:

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

Вы можете увеличить интервал между кормлениями до очень долгого времени.

Вопрос в том, для чего вы используете сторожевой таймер?

Если он использовался для проверки того, что ваше программное обеспечение работает нормально и не зависает, я не понимаю, как вариант ISR от AI поможет вам (ISR может продолжать работу даже ваша программа зависла).

Подробные сведения о параметрах сторожевого таймера см. В главе (17) «Сторожевой таймер» (WDT) в руководстве пользователя для вашего MCU. http://www.nxp.com/documents/user_manual/UM10161.pdf

3
ответ дан 8 December 2019 в 13:43
поделиться

Вы можете пересмотреть код обслуживания таймера WD.

Обычно таймер WD должен обслуживаться во время простоя (цикл простоя или задача ожидания) и в драйверах самого нижнего уровня (например, когда вы читаете / записываете с / на модем GPRS или MAC для вашего TCP / IP-соединения. , так далее.).

Если этого недостаточно, ваша микропрограмма также может ничего не делать, кроме как сжигать циклы ЦП в программе задержки. Можно добавить сюда службу таймера WD, но вам, возможно, придется настроить таймер задержки, чтобы учесть время обслуживания WD.

Если в вашем приложении просто есть какие-то длинные задачи с интенсивной загрузкой процессора, выполнение которых требует больше времени, чем позволяет период таймера WD, вы можете подумать о том, чтобы увеличить интервал таймера WD. Это не всегда возможно, но я предпочитаю, чтобы ссылки на таймер WD не попадали на верхние уровни прошивки, чтобы уровень приложений оставался максимально переносимым. Таймеры WD обычно зависят от оборудования, поэтому любые ссылки на таймер WD в вашем коде редко переносимы. В любом случае низкоуровневые драйверы редко переносятся, поэтому обычно это лучшее место для обслуживания таймера WD.

1
ответ дан 8 December 2019 в 13:43
поделиться

Сторожевые псы - это замечательно, но они также являются проблемой, когда ваша программа или система не подходят для этого. Лучше всего работать, когда у вас есть код, который выглядит (обычно) следующим образом:

Watchdog_init();

hardware_init();
subsystem1_init();
subsystem2_init();
subsystem3_init();
...
subsystemN_init();

forever {
   Watchdog_tickle();

   subsystem1_work();
   subsystem2_work();
   subsystem3_work();
   ...
   subsystemN_work();
}

Очень часто вы можете разработать свою программу таким образом, чтобы это работало, и, как правило, это очень надежно (но не полностью).

Но в случаях, подобных вашему, это работает не так хорошо. В конечном итоге вам придется спроектировать и создать (или, возможно, использовать библиотеку) структуру, которая имеет различные условия, которые должны выполняться, чтобы контролировать, если / когда сторожевого пса ласкают. Однако это может быть очень непросто. Сложность этого кода может сама по себе вносить ошибки. Вы могли бы написать идеальное приложение, за исключением сторожевой схемы, и ваш проект может сильно сбрасываться, или весь ваш код может быть плохим и просто постоянно ласкать сторожевой таймер, заставляя его никогда не сбрасываться.

Один из хороших способов изменить приведенный выше код для обработки более сложных ситуаций - это изменить функции subsystemX_work, чтобы они не отставали от состояния. Это можно сделать с помощью статических переменных в функциях или с помощью указателей функций, а не функций, и изменить фактическую функцию, которая выполняется, чтобы отразить текущее состояние этой подсистемы.Каждая подсистема становится конечным автоматом.

Другой способ обойти длительное преднамеренное ожидание с помощью быстро кусающегося сторожевого пса - разбить долгосрочную функцию на более короткие части. Вместо:

slow_device_init();
Watchdog_tickle();

Вы могли бы сделать:

slow_device_init_begin();
Watchdog_tickle();
slow_device_init_finish();
Watchdog_tickle();

А затем расширить это, чтобы растянуть сторожевой таймер, выполнив:

slow_device_init_begin();
for ( i = SLOW_DEV_TRIES; i ; i--) {
   Watchdog_tickle();
   if (slow_device_init_done()) {
       break;
   }
}
Watchdog_tickle();

И все же это может становиться все более и более сложным. Часто вам приходится создавать делегата сторожевого пса, который просто проверяет выполнение условий и выполняет или не ласкает сторожевой таймер на основе этих условий. Это начинает усложняться. Это может быть реализовано путем создания объекта для каждой из ваших подсистем, у которого есть какой-либо метод / функция для вызова для проверки работоспособности подсистемы. Методы работоспособности могут быть очень сложными и даже изменяться по мере изменения состояния этой подсистемы, хотя они должны быть как можно более простыми, чтобы было как можно проще проверить правильность кода, а также из-за изменений в том, как Работа подсистемы потребует изменений в способах измерения здоровья.

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

Watchdog_periodic() {
   for_each subsustem in subsystem_list { // not C, but you get the idea
      if ( 0 > --(subsystem->count_down) ) {
           // Do something that causes a reset. This could be returning and not petting
           // the hardware watchdog, doing a while(1);, or something else
      }
   }
   Watchdog_tickle();
}

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

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

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

int x;
fscanf(input, "%i", x); // Passed uninitialized x rather than address of x

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

2
ответ дан 8 December 2019 в 13:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: