Данная статья является наброском к главе "5.4 Сторожевой таймер" книги "Отказоустойчивое ПО для МК".
WDT - сторожевой таймер (watchdog timer) - аппаратный механизм защиты от зависания программы. В самом простом случае WDT представляет собой счетчик, тактируемый системным или независимым генератором. При переполнении счетчика схема WDT генерирует сигнал сброса микроконтроллера, который приводит к его перезагрузке. Программа должна периодически посылать схеме WDT сигнал о том, что все в порядке; этот сигнал говорит сторожевому таймеру, что программа работает корректно, и счетчик сторожевого таймера обнуляется (в некоторых контроллерах обновляется константой).
Если программа перестает посылать сигналы сторожевому таймеру, т.е. сигналы обнуления WDT отсутствуют в течение времени, достаточного для переполнения счетчика, то это свидетельствует о том, что программа зависла, и ситуация воспринимается как аварийная. В результате длительного отсутствия сигналов от программы схема WDT формирует сигнал сброса микроконтроллера (в некоторых типах микроконтроллеров WDT генерирует еще немаскируемое прерывание).
Также многие WDT могут работать в оконном режиме, когда сигнал от программы на обнуление WDT не допускается раньше какого-то времени. Т.е. он не должен приходить слишком рано или слишком поздно. Такая функция может оказаться полезной для более быстрого реагирования на некоторые виды сбоя. Например, если функция обнуления WDT вызывается раньше времени, то это говорит о том, что какие-то функции отработали быстрее, чем ожидалось, что может свидетельствовать о наличии сбоя. Из такого режима нужно выйти как можно быстрее, не дожидаясь переполнения WDT (т.е. не давая программе еще какое-то время работать во внештатном режиме). Оконные WDT удобно применять в тех случаях, когда нет возможности контролировать время выполнения участков кода с помощью аппаратного таймера.
WDT бывает встроенным в МК, внешним в виде специализированной микросхемы или внешним в виде отдельного электронного устройства (или узла). Обычно счетчик в составе WDT тактируется собственным генератором, не зависящим от основного системного генератора контроллера. Чаще всего это внутренний RC-генератор. Отдельный генератор применяется с той целью, чтобы WDT имел возможность продолжать счет (а в последствии - сформировать сигнал сброса) даже если произойдет срыв генерации основного генератора, тактирующего МК.
В зависимости от типа WDT обнуление его счетчика может производиться по-разному. Например, счетчик внешнего WDT обычно сбрасывается переключением управляющего входа из одного состояния в другое (в некоторых - только по одному фронту). Для сброса внутренних WDT может использоваться специальная инструкция микроконтроллера, или запись в какой-нибудь регистр (или отдельный бит регистра), или выполнение микроконтроллером последовательной записи двух констант (так называемого пароля) в определенный регистр WDT.
Встроенный в МК сторожевой таймер является самым ненадежным из перечисленных и на него можно полагаться только в малоответственных системах. Есть две причины:
Стоит добавить, что некоторые микроконтроллеры допускают тактирование внутреннего WDT от системного генератора, что делает его неэффективным при срыве генерации или при случайном входе в Sleep-режим.
Поэтому внутренний WDT можно использовать только в малоответственных системах или, на худой конец, там, где критична стоимость продукта, и лишний элемент в виде внешнего WDT будет непозволительной роскошью, или где есть ограничения по габаритам или массе конечного устройства. Однако не следует забывать и о достоинствах внутреннего сторожевого таймера:
Более эффективным с точки зрения надежности (и в большинстве случаев достаточным) является внешний WDT, выполненный в виде отдельной микросхемы. Обычно такие микросхемы комбинируют в себе функции сторожевого таймера и монитора напряжения питания. Такие микросхемы (они называются супервизорами, микропроцессорными мониторами или просто сторожевыми таймерами) выпускаются разными фирмами в различных вариантах. Сигналом "все хорошо" сторожевому таймеру в составе такой микросхемы обычно служит изменение логического уровня ("0"→"1" или "1"→"0") на специальном входе. Изменение уровня сбрасывает внутренний счетчик. Супервизоры имеют выход для управления сбросом микроконтроллера (обычно такие микросхемы имеют два выхода для управления сбросом: прямой и инверсный, - т.к. микроконтроллеры различных производителей имеют различные активные уровни входа сброса). Когда супервизор обнаруживает, что напряжение питания опустилось ниже какого-то предустановленного уровня или сигнал "все хорошо" отсутствует длительное время, он формирует активный уровень на выходе управления сбросом и удерживает его в активном состоянии либо в течение какого-то времени (если произошел сброс по переполнению счетчика сторожевого таймера) либо до того момента, как напряжение питания не придет в норму. Это общая функция, присущая всем супервизорам со встроенным WDT, в остальном микросхемы-супервизоры могут отличаться:
В зависимости от задач, которые мы хотим возложить на WDT, помимо его основной функции, мы можем выбирать ту или иную микросхему. Для управления WDT в составе такой микросхемы нам понадобится один вывод контроллера, настроенный на выход, для генерации сигналов обнуления WDT (в случае контроля выхода WDO потребуется еще один вывод МК, настроенный на вход).
Также при выборе микросхемы супервизора следует обращать внимание на следующие параметры:
Ниже приведены примеры микросхем-супервизоров, в составе которых имеется WDT (это краткий список, на самом деле таких микросхем великое множество):
MCP131x - 4 интервала WDT (6.3, 102, 1600, 25600 мс), потребление 5мкА, нет ножки WDO ADM699 - фиксированное и довольно длительное время переполнения (тип. 1.6с), имеет выход ~WDO, позволяющий определить, что сброс был вызван переполнением WDT. Нет внешнего тактирования. Нет входа для батарейки. 600 мкА. ADM705 MAX69x (x - четный) - Имеет выход ~WDO. Можно подключать батарейку. 2мА. MAX69x (x - нечетный) - есть внешнее тактирование, два режима счета (1.6сек или 100 ms). Имеет выход ~WDO. Можно подключать батарейку. 2мА. EM6151 - оконный (67/33 или 33/67), без батарейки, 35 мкА (+ слип-режим 25 мкА), нет WDO TC1232, ADM1232, MAX1232 - три режима интервала (150мс, 600мс, 1200мс), не имеет выхода WDO MAX6746-6753 - 5 мкА, регулируемое время WDT, маленький корпус (sot23-8), оконный MAX6369 - 8мкА, регулируемое время, корпус sot23-8, есть WDO
TODO: дополнить и разъяснить
Отдельное устройство разрабатывается индивидуально с учетом особенностей конкретного устройства, используемого в нем микроконтроллера и конкретной программы. Сердцем такого устройства может быть маленький, например, 8-разрядный микроконтроллер (чаще OTP, например, PIC12CE518), который, помимо стандартного набора функций WDT, может иметь ряд дополнительных возможностей. Т.е. преимущества таких WDT в расширенном функционале и гибкости настройки.
Недостатками таких WDT являются габариты, а также то, что программа в маленьком МК должна соответствовать всем требованиям безопасности (начиная с самодиагностики и заканчивая контролем собственного внутреннего WDT), а также требует тщательнейшей отладки. Кроме того, сам микроконтроллер, используемый в качестве WDT, также подвержен всем видам сбоев, вызванных помехами. Но это компенсируется, во-первых, тем, что такие контроллеры выполнены по более грубой технологии, чем тот, которым они будут управлять, следовательно, они помехам подвержены гораздо меньше; во-вторых, есть возможность организовать "круговую поруку", когда маленький МК следит за большим, а большой следит за маленьким, что во много раз повышает вероятность самовосстановления работоспособности системы после сбоя.
Но построение схем-мониторов (не называю их WDT, т.к. функционал может быть гораздо шире простого счетчика) дает массу дополнительных возможностей, таких как:
К WDT этого типа можно также отнести одновибраторы, собранные на дискретных компонентах (иногда с использованием компараторов). Такие WDT наименее всего подвержены помехам, но они требуют много места на плате, не очень экономичны в энергопотреблении и более сложны в настройке и отладке.
Как описано в предыдущем параграфе, все ошибки и сбои программы, возникающие во время ее работы, должны по возможности отслеживаться в ходе ее выполнения средствами самой программы. При обнаружении некорректного ее поведения следует делать анализ состояния программы с целью определить критичность сбоя и, если это возможно, принять адекватные меры по восстановлению работоспособности программы. WDT является последним рубежом при обеспечении отказоустойчивости ПО, он генерирует сигнал сброса в том случае, если средствами программы обнаружить сбой не удалось, или если программа попала в непредусмотренное зацикливание. Причинами такого поведения программы могут быть несколько факторов:
(Детально о причинах отказов ПО см. "2. Причины и последствия сбоев")
Следует обратить внимание, что многие факторы не зависят ни от программистов, ни от схемотехников, ни от ОТК.
Важное замечание: сбой всегда предпочтительнее обнаружить средствами самой программы (это достигается введением в логику программы обработки сигнатур, проверки данных, проверки рассчетов и пр.), т.к. в этом случае есть возможность безопасного восстановления работоспособности, даже если придется для этого произвести программный сброс контроллера, перед которым имеется возможность перевести все внешнее оборудование в безопасный или нейтральный режим.
Но, во-первых, средствами программы всего проверить и перепроверить нельзя. И ограничения здесь не только в памяти, используемой для тестов, и времени, которое тесты отнимут, но и в том, что сами тесты, являясь частью программы, нужно бы проверять. А потом проверять и эти проверщики и т.д. Поэтому run-time тесты всегда неполные. Во-вторых, существуют непредвиденные сбои, при которых программный счетчик может прыгнуть в такую точку программы, в которой при текущем состоянии ячеек RAM-памяти и текущих настройках периферии, можно задержаться очень надолго. Например, сбоем программного счетчика, программа попала в код передачи байта в программной реализации интерфейса SPI:
void spi_send_byte (unsigned char Data) { int i; i = 8; <---- Сюда попали из-за сбоя PC do { if (Data & 0x80) PIN_DO = 1; else PIN_DO = 0; spi_delay(); PIN_CLK = 1; spi_delay(); PIN_CLK = 0; Data <<= 1; } while (--i); }
На момент сбоя в ячейке памяти, где располагается переменная i, могло находиться любое значение, и безобидный цикл на 8 проходов может превратиться в цикл на 50000 проходов. В общем случае от подобных сбоев может спасти только WDT. В предыдущем параграфе (5.3 run-time контроль) было сказано о контроле стека и сигнатурах, которые могли бы сигнализировать о том, что что-то идет не так, но подобные тесты, во-первых, нецелесообразно применять во всех функциях без исключения, а во-вторых, при большом значении числа в ячейках памяти, занятых переменной i, WDT может успеть переполниться раньше, чем программа дойдет до проверок в конце функции.
Два слова стоит сказать об ошибках программы, которые могут привести к зависанию. Само собой разумеется, что надо стремиться к написанию не зависающих программ, уделяя внимание не только качественной проработке проектируемого алгоритма, но также и качественному кодированию, и качественному тестированию. Однако, существует несколько факторов, которые по отдельности или все вмести затрудняют выполнение какого-либо этапа. Например, не всегда заранее известен точный алгоритм будущего устройства (для инновационных проектов), когда изменения в алгоритм вносятся на этапе разработки. Такие изменения могут быть продиктованы различными обстоятельствами, начиная маркетинговыми исследованиями и заканчивая статусом фирм-производителей компонентов (банкротство, перепродажа и пр.). Алгоритм в результате внесения изменений в него может оказаться несогласованным.
Также повлиять на качественное выполнение какого-либо этапа разработки могут факторы организационного характера: над программой трудятся несколько человек, каждый из которых может по-своему интерпретировать какие-либо моменты, описанные в ТЗ, или по-своему додумать то, что в ТЗ не указано явно; программа или ее часть может быть поручена программисту, не имеющему достаточной квалификации или достаточного опыта для качественной реализации. Не следует забывать о факторе времени, выделенного на разработку. Программы, имеющие большое количество состояний, режимов работы и параллельных процессов, не могут быть протестированы целиком, т.к. время тестирования может превысить экономически целесообразные сроки выпуска устройства; поэтому к конечному потребителю программа может попасть, грубо говоря, недоотлаженной. Это не означает, что она будет сбоить и виснуть на каждом шагу, просто при совокупности внештатных ситуаций и стечении каких-то обстоятельств, она может зависнуть.
Как уже было сказано, нельзя исключать инструментальные ошибки, т.е. ошибки компиляторов, программаторов, наконец, самих микроконтроллеров. Они составляют лишь малую толику от ошибок, допускаемых самими разработчиками, но, тем не менее, имеют место быть.
Итак, даже если разработчик уверен в своей аккуратности, методичности и пунктуальности, существуют факторы, которые могут привести к непредвиденным ошибкам в производимом им ПО, вызывающих в том числе и непредусмотренное зависание программы. Поэтому использование сторожевого таймера в качестве системного монитора является обязательным, особенно, когда речь идет об ответственных системах, сбои в которых могут привести к убыткам или нанесению вреда здоровью людей.
Примечание: в данном параграфе применяюся вызовы псевдо-функций wdt_clear() и soft_reset().
Порядок обнуления WDT - это правила, по которым программа выполняет проверки и принимает решение о том, что на данный момент времени она выполняется корректно и, следовательно, можно давать команду WDT на обнуление счетчика. Т.е. счетчик WDT можно обнулять только тогда, когда программа считает, что все в порядке. Здесь кроются две проблемы:
Частью таких контрольных точек можно считать описанные в предыдущем параграфе run-time тесты. Но эти тесты призваны уже заметить ошибку, т.е. на месте засечь некорректное поведение программы, из-за которого нужно либо попытаться восстановить работоспособность программы, либо произвести сброс, либо перейти в безопасный режим. Срабатывание run-time теста однозначно нам говорит о том, что далее выполнять программу нельзя. Однако, надо учесть, что и успешное прохождени конкретного теста не говорит о правильноси функционирования программы в целом. Также контрольными точками могут считаться факты выполнения конкретных участков кода, т.е. те факты, что программа дошла до какого-то места с правильными параметрами. В некоторых случаях приходится учитывать еще и последовательность выполнения таких участков во времени. Следует добавить, что существуют участки кода, в течение выполнения которых нельзя точно сказать, корректно все выполняется или нет; типичный пример - библиотечные функции (см. ниже).
Умение программиста создавать набор контрольных точек, способных заметить наибольшее число отклонений в поведении программы, акцентируя внимание на ответственных участках, во многом определяет насколько WDT окажется полезным в обнаружении сбоя для конкретной программы.
Довольно часто можно встретить такой подход, при котором счетчик WDT безусловно обнуляется в главном цикле программы, в прерывании по таймеру, возникающему раз в миллисекунду, или несколько раз в программе с интервалом в 5-10 инструкций (или операторов). Другими словами, программист делает все, чтобы сторожевой таймер не успел переполниться ни при каких условиях. Доходит даже до комичного кода:
char uart_getch (void) { while (!USART_RX) wdt_clear(); return USART_RXREG; }
Думаю, не надо пояснять, чем этот код плох. Достаточно предположить, что в эту функцию мы можем попасть при отключенном модуле USART, в результате чего повиснем на все время жизни программы.
Такие подходы эквивалентны неиспользованию WDT с одной разницей: у программиста появляется иллюзия по поводу отказоустойчивости программы и, следовательно, сильно снижается бдительность в предвидении возможных причин отказов (что там думать? если что - сторожевой таймер спасет!). Ведь, например, при обнулении WDT в прерывании, программа может просто повиснуть в вечном цикле ожидая условие, которое никогда не выполнится, в то время как прерывания будут исправно выполняться. Или для систем с одним вектором прерывания (например, младшие семейства PIC-контроллеров) может получиться так, что по случайности будет разрешено прерывание, которое не предусмотрено обработчиком, в результате чего программа никогда не выйдет из обработчика (сразу же при выходе опять произойдет вход, т.к. сброс флага не был предусмотрен), а обработчик таймера, в котором производится сброс WDT, по-прежнему будет выполняться с заданным периодом. Основня программа в этом случае управление уже не получит.
Часто можно встретить такую рекомендацию (в литературе, в интернет-статьях, на технических форумах): основные функции программы должны после своего выполнения устанавливать флажок, подтверждающий, что функция выполнена; в главный цикл в main() или в прерывание добавляется код, который проверяет, что все нужные функции отработали, и только после этого обнуляет WDT.
Для этого в программе заводится переменная, содержащая по одному биту на каждую критическую функцию:
union { struct { // Каждый бит обнуляется в соответствующей подпрограмме UINT8 bKeyboard : 1; // Обработка кнопок UINT8 bLCD : 1; // Ввывод данных на экран UINT8 bRelay : 1; // Включение/выключение реле UINT8 bClock : 1; // Часы } Bits; UINT8 NotDone; // Когда все биты сброшены, эта переменная = 0 } g_WDT;
В главном цикле main() (или в обработчике периодического перрывания) вставляется код проверки флагов:
void main (void) { g_WDT.NotDone = 0;// При инициалиации считаем, что программа выполняется корректно ... while (1) // Основной цикл программы { if (!g_WDT.NotDone) // Первый проход цикла или все подпрограммы отработали { // Устанавливаем флаги функций, требуемых к выполнению g_WDT.Bits.bKeyboard = 1; g_WDT.Bits.bLCD = 1; g_WDT.Bits.bRelay = 1; g_WDT.Bits.bClock = 1; // Обнуляем счетчик WDT wdt_clear(); } KeyboardWork(); LCDWork(); RelayWork(); ClockWork(); } }
Каждая критическая функция сбрасывает соответствующий ей бит, если она выполнилась корректно, например:
void KeyboardWork (void) { if (!KeyboardTimeout) return; // Обрабатываем кнопки с определенным периодом. Если время еще не // настало, то выходим из функции (примечание: данный флаг может // устанавлиается, например, в прерывании по таймеру // Читаем кнопки и обрабатываем дребезг ... g_WDT.Bits.bKeyboard = 0; // Обнуляем флаг, сообщая, что функция отработала успешно }
Примечание: флаги можно заводить не только для тех функций, которые вызываются из основного цикла main(), но иногда и для вложенных функций и даже для обработчиков прерываний. Коду проверки важно знать, что ответственные участки кода были выполнены.
Для простых приложений такой подход можно считать очень удачным, т.к. он имеет очевидные преимущества:
Однако он имеет и свои недостатки:
Также часто встречается рекомендация в вызываемой функции выставлять переменной конкретное значение, а после выхода проверять его и, если оно соответствует ожидаемому, производить обнуление WDT:
uint16 g_WDT_Temp; // глобальная переменная ... g_WDT_Temp = 0; process_func1(); // В теле этой функции к WDT_Temp прибавляется 0x1111 if (g_WDT_Temp == 0x1111) wdt_clear(); else soft_reset(); process_func2(); // В теле этой функции к WDT_Temp прибавляется 0x2222 if (g_WDT_Temp == 0x3333) wdt_clear(); else soft_reset(); process_func3(); // В теле этой функции к WDT_Temp прибавляется 0x3333 if (g_WDT_Temp == 0x6666) wdt_clear(); else soft_reset(); ...
Метод является комбинацией с одним из видов run-time проверок. Если мы случайно пропускаем выполнение какой-либо функции (например из-за сбоя PC), то run-time проверка сигнатуры функции вызовет обработчик soft_reset(). Вне комбинации с run-time проверками этот метод не только малоэффективен, но даже опасен, т.к. если убрать во всех условиях ветку else, то даже после обнаружения сбоя (т.е. сбой не только произошел, но уже замечен) мы позволим программе выполнить еще несколько функций, уже находясь во внештатном режиме.
Т.е. данный метод является дополнением к run-time проверкам. На мой взгляд, он не очень эффективен, т.к.:
Однако он имеет два преимущества:
Суть метода заключается в том, что обработчик WDT, анализируя специально заведенные для этих целей глобальные переменные, знает, в каком режиме работает программа, что она должна делать и чего она делать не должна. Вызов wdt_clear() производится при прохождении всех проверок внутри обработчика периодического прерывания (чаще всего от таймера). Это позволяет нам вести постоянный контроль за состоянием программы с определенным периодом вне зависимости от времени выполнения отдельных функций. В сущности этот метод является расширенной версией метода обнуления с контролем флагов за исключением того, что он только в редких случаях может использоваться вне прерывания.
Метод хорош тем, что позволяет выполнять различные проверки для различных режимов работы, т.е. позволяет контролировать правильность работы программы в целом (например, для диктофона не могут быть одновременно активны режимы записи и воспроизведения; автосигнализация не может запоминать новые серийные номера брелоков, если она не в режиме программирования и т.п.). В зависимости от текущего режима работы и выпоняемых в данный момент действий, порядок и набор проверок может меняться. К примеру, электрическая плита может работать в 3х режимах: режим сна, режим готовки и режим настройки. В каждом из них выполняются разные наборы функций, которые было бы неудобно контролировать скопом, поэтому для контроля каждого набора создается своя ветвь проверок в зависимости от режима.
int g_Mode = 0; void TIMER1_ISR (void) { static bool bAllowClearWDT; switch (g_Mode) { case MODE_IDLE: ... break; case MODE_COOKING: ... break; case MODE_SETUP: ... break; default: // Несуществующий режим // Выполняем какие-то действия по оживлению устройства, если это // возможно, или производим программный сброс, предварительно // запротоколировав сбой break; } if (bAllowClearWDT) wdt_clear(); ... }
Этот метод лишен основных недостатков двух предыдущих методов обнуления (с контролем флагов и контролем функций), а именно:
Но главное преимущество этого метода в том, что он может быть применен тогда, когда программа использует внешние библиотеки, учитывая, что в них нет четкого регламента по времени выполнения отдельных функций, а также при работе программы под управлением RTOS, где каждый из нескольких процессов живет своей жизнью (см. ниже).
Однако, при своей универсальности этот метод не лишен недостатков:
Иногда бывает удобно выхватить из каждого метода что-нибудь одно и использовать в комбинации с другим. Например, может понадобится написать функцию (скажем, автомат состояний), для которой сам алгоритм проверки состояний превратится в сложный ветвящийся комплекс операторов ветвлений и циклов. В таком случае может оказаться целесообразным не учитывать эту функцию в проверках внутри прерывания, а воспользоваться для нее методом обнуления с контролем функций или обнулением в цикле, т.е. в критических узлах функции расставить вызовы wdt_clear() (обязательно с проверкой текущего состояния). Это может усложнить анализ причин сброса, но иногда намного упрощает проработку алгоритма контроля выполнения программы.
При проектировании алгоритма обнуления WDT перед прораммистом встает вопрос выбора интревала WDT. Диапазон временных интервалов, проедоставляемый сторожевыми таймерами разных производителей, а также внутренним WDT, довольно широк: от единиц миллисекунд до единиц секунд (для внешних) или до нескольких суток (для втутренних). Интревал следует выбирать исходя из возлагаемых на WDT задач. Например, если мы планируем использовать WDT только для вывода МК из зависания, то редко могут понадобиться интервалы длиннее секунды-двух. Если же WDT будет использован для периодической активации контроллера, работающего в режиме пониженного энергопотребления, то тут, в зависимости от конкретной задачи, могут быть уместны интервалы в несколько часов или даже суток. В некоторых случаях целесообразно изменять интервал в ходе работы: например, его можно делать короче при выполнении ответственных функций или, наоборот, делать длиннее при переходе в режим пониженного энергопотребления.
Интервалы, касающиеся специфичных приемнений WDT (об этом ниже), выбираются по-своему в каждом случае, и тут нужно руководствоваться особенностями конкретного применения. То же самое касается интервала для SLEEP-режима: в зависимости от области применения может быть выбран различный интервал. А вот выбирая интервал для использования WDT в качестве монитора прораммы (т.е. фактически с точки зрения программы - интервала обработки процедуры обнуления WDT), следует руководствоваться тремя основными критериями:
Чем быстрее произойдет сброс контроллера после возникновения сбоя, тем лучше. Это и понятно, раз произошел сбой и программа начала выполняться по внештатному расписанию, т.е. перестала соответствовать спецификации, то нужно это прекратить как можно быстрее. Например, для программы, контролирующей целостность настила ступеней движущегося эскалатора, непозволительно находиться в зависшем состоянии в течение времени, соизмеримого со временем прохода одной ступени над датчиком.
Кроме того, нужно помнить, что зависшая программа - это далеко не всегда программа, которая ничего не делает. Бывает, конечно, что собьестя генерация кварца тактирующего микроконтроллер. Но в большинстве случаев программа продожает выполняться, причем она может не просто зациклиться на каком-то небольшом участке (по каким-то причинам не выполняется условие выхода из цикла), но у нее может сбиться указатель стека, в результате чего после выполнения первой же встретившейся инструкции RETURN программа продолжит выполнение с произвольного адреса (в стеке могли находиться данные, которые после сбоя указателя стека ошибочно были восприняты как адрес возврата).
Если программа является многопоточным приложением, многие процессы в которой живут большей частью собственной жизнью, то код проверок правильности состояния программы может оказаться сложным и выполняться довольно длительное время (в худшем случае может достигать десятков и сотен микросекунд). Становится очевидным, что если делать эти проверки сравнительно часто, то снизится эффективность самой программы. И не только из-за того, что 1% или 10% времени будет уходить на проверки, а еще и из-за того, что проверка состояния программы нарушает время ее реакции на какое-либо событие (особенно, если проверка находится в обработчике прерывания высокого уровня).
Программа может содержать участки кода, критичные к выполнению с точностью до такта, где инструкции для обнуления счетчика сторожеваого таймера будет просто некуда втиснуть. Сами инструкции обнуления вставить, возможно, удастся, но на код проверки правильности состояния программы может уже не остаться места, а без этого кода WDT может сослужить плохую службу (мы уже говорили о зависании в цикле с безусловным обнулением WDT). Таким кодом может оказаться, например, обработка какого-то внешнего синала, требующая точной выборки по времени. Проблема может быть частично решена применением DMA-модуля, если он есть. Но в общем случае надо учитывать наличие таких фрагментов кода при выборе интервала обнуления WDT.
Также стоит отметить, что некоторые контроллеры имеют возможность переключаться на резервный генератор при срыве генерации основного. Обычно резервный генератор имеет частоту на два-три порядка ниже частоты основного генератора (часто 32768 Гц). Обычно при переходе на резервный генератор генерируется немаскируемое прерывание, которое позволяет нам выполнить код по переводу устройства в резервный режим перед выполнением сброса. Но следует учесть, что этот код будет выполняться в сотни или тысячи раз медленнее, чем при тактировании от основного генератора, а тактирование сторожевого таймера при этом останется без изменений. И при неправильном выборе интервала сторожевого таймера или при неучете особенностей выполнения кода при тактировании от резервного генератора может получиться ситуация, в которой счетчик WDT успеет переполниться до того, как будут выполнены все действия по подготовке к сбросу. Т.е. в обработчике немаскируемого прерывания, возникающего при срыве генерации основного генератора, следует применять обнуление WDT по особым правилам. Разумеется, в таком случае контролировать правильность программы уже не имеет смысла, но кое-какие проверки перед обнулением WDT выполнять, скорее всего, придется. В данном случае можно применять метод обнуления с контролем отдельных функций.
Отдельно стоит вопрос порядка обнуления сторожевого таймера, когда в программе используются библиотеки сторонних производителей. Сами библиотечные функции никогда не производят обнуление WDT, с другой стороны в общем случае неизвестно, сколько времени может потребоваться на выполнение какой-либо библиотечной функции. Простая строка:
printf(" %5.3f\n", sin(t));
может выполняться многие тысячи тактов, и даже обрамление ее вызовами wdt_clear():
wdt_clear(); printf(" %5.3f\n", sin(t)); wdt_clear();
не всегда может спасти от переполнения WDT. Выхода здесь два:
Увеличение интервала - довольно сомнительное решение в данном случае. Тому есть несколько причин:
Обнуление в прерывании с контролем состояния намного лучше подходит для решения этой задачи. Такой подход позволяет нам учесть длительное время выполнения быблиотечной функции, не увеличивая при этом интервал самого сторожевого таймера. Для этого нужно всего лишь завести глобальную переменную, в которую перед вызовом библиотечной функции можно записывать предполагаемое время ее выполнения с каким угодно запасом. А в обработчике прерывания к коду проверок состояния программы добавить обработку этой переменной.
volatile struct { UINT8 ucActive; INT16 nCounter; } g_WDT_Library; void TIMER1_ISR (void) { static bool bAllowClearWDT; ... bAllowClearWDT = true; if (g_WDT_Library.ucActive == 0x55) // Проверяем, не выполнятеся ли сейчас библиотечная функция { if (g_WDT_Library.nCounter <= 0) bAllowClearWDT = false; else g_WDT_Library.nCounter--; } ... if (bAllowClearWDT) wdt_clear(); ... }
Перед вызовом библиотечных функций, время выполнения которых вывает сомнения, переменная g_WDT_Library.nCounter устанавливается в соответствии с ожидаемым временем выполнения (это удобно организовать в виде функции, т.к. могут потребоваться действия для обеспечения атомарного доступа к счетчику):
void wdt_lib_start (UINT16 value) { g_WDT_Library.ucActive = 0xAA; // Блокируем моификацию счетчика в прервании (любое число != 0x55) g_WDT_Library.nCounter = value; g_WDT_Library.ucActive = 0x55; // Запускаем отсчет } void wdt_lib_stop (UINT16 value) { g_WDT_Library.ucActive = 0xAA; // останавливаем контроль WDT для библиотечной функции // Пишем любое число != 0x55 } void main (void) { ... while (1) { ... wdt_lib_start(100); // Выделяем 100 мс на выполнение функции printf(" %5.3f\n", sin(t)); wdt_lib_stop(); ... } }
При работе программы под управлением RTOS порядок обнуления WDT несколько усложняется, из-за того, что потребуется следить сразу за несколькими процессами (задачами). Построение полноценного алгоритма обнуления WDT в случае использования RTOS - это совсем нетривиальная задача; сложности, с которыми мы сталкиваемся приведены ниже:
Ввиду этих сложностей обычно ограничиваются построением простых индивидуальных тестов с контролем состояний для каждой активной задачи и выполняют обнуление при успешном прохождении их всех. Т.е. своего рода надстройка над этими тестами в виде обнуления с проверкой флагов. Причем в случае с RTOS эти проверки можно делать только в прерывании высокого приоритета (выше системного). Все более детальные проверки лучше реализовывать в виде run-time тестов. Это, во-первых, очень разгрузит код проверки условий обнуления WDT, а во-вторых, как уже было сказано, что сбой всегда предпочтительнее заметить на программном уровне, чтобы иметь возможность попытаться восстановить работоспособность или, в крайнем случае, произвести сброс безопасно, предварительно переведя все внешнее оборудование в безопасный режим. А сторожевому таймеру оставить функцию сброса только в самых критических ситуациях.
Но, даже реализуя проверки по упрощенной схеме, не стоит забывать о некоторых особенностях, превносимых RTOS:
В особо ответственных системах WDT как контролирующий узел обязательно должен быть протестирован (иначе полагаться на него нельзя). Такие проверки предписывают делать европейский стандарт IEC 60730 (Annex H 11.12.7 п. 8) или американский стандарт UL1998 (A2.1 п.8). В зависимости от условий работы тестирование может производиться единожды при включении питания или с каким-то периодом, но не нарушая при этом целостность выполнения программы (т.е. в случае периодического тестирования перед проведением теста все внешнее оборудование должно быть переведено в безопасный режим, сохранены в энергонезависимую память данные, которые необходимо восстановить после перезагрузки.
Более детально тестирование WDT описано в "5.2 Самодиагностика", здесь повторю только основные принципы проверки:
Основной параграф, описывающий поведение программы при сбросе, это "5.5 Что делать при обнаружении сбоя". Здесь опишу только основные моменты, касающиеся сброса контроллера, вызванного переполнением WDT.
Все действия делятся на два этапа:
Установить причины сброса, вызванного переполнением WDT, программными средствами довольно затруднительно. Даже если мы проанаизируем контрольные переменные в памяти (такие переменные должны быть размещены в сегментах RAM, не подвергающихся начальной инициализации или обнулению; для этого в разных компиляторах есть специальные директивы), то в лучшем случае получим только последствия сбоя, т.е. несоответствие значений этих переменных предполагаемым, а саму причину таким способом не установить. Кроме того, все эти переменные могут содержать достоверные значения, т.к. сбой мог произойти из-за срыва генерации, или из-за изменения значения программного счетчика, или из-за изменения триггеров самого WDT (об этом ниже).
Но причины сбоя можно попытаться проанализировать вручную. Для этого программа должна иметь возможность при старте сохранять в энергонезависимую память или передавать по внешним каналам связи набор значимых переменных, регистров, а также участка стека. Все эти данные могут в дальнейшем помочь нам установить в каком месте программы и в каком ее состоянии произошел сбой. Причем по возможности при каждом сбое лучше сохранять все эти данные в разные участки энергонезависимой памяти. Это наберет статистикку сбоев и увеличит наши шансы установить причину, что особенно полезно, если причина была в недостаточно хорошо проработанном алгоритме программы, т.е. если сбой был вызван самой программой, а не внешними причинами. Но даже при невозможности сохранять данные в разные участки энергонезависимой памяти (например, если не позволяет ее свободный объем), то обязательно нужно сохранять счетчик сбоев. При обслуживании устройства по этому счетчику можно будет делать выводы о том, был ли это случайный сбой, или же систематический, что является сигналом к срочной переработке устройства.
Какие данные нужно сохранять:
Чем больше данных мы сохраним, тем больше возможностей установить причину сбоя и, если это возможно, устранить ее.
TODO:
- Восстановление работы - - Зависание и преждевременный сброс WDT - - RC-генератор и температура - - Другие применения WDT -
Виктор Тимофеев, сентябрь 2010