Применение SWITCH-технологии при разработке прикладного программного обеспечения для микроконтроллеров.
Часть 3. Обмен сообщениями и таймеры
Обмен сообщениями между автоматами: заключение
Перед тем как приступить к обсуждению вопроса, вынесенного в подзаголовок, подведем краткие итоги материала, изложенного в предыдущей статье цикла.
Итак, мы условно делим сообщения на два типа: «обычные» (далее — просто сообщения) и широковещательные. Широковещательное сообщение может быть принято неограниченным количеством автоматов в цикле, в то время как обычное сообщение — только одним автоматом, после чего оно удаляется (сбрасывается в неактивное состояние). Реализация широковещательных сообщений довольно проста, но обычным сообщениям мы уделим еще немного нашего внимания, отметив ряд нюансов.
Нюанс первый: на программисте лежит обязанность организовать программу таким образом, чтобы в каждый конкретный момент времени каждое сообщение могло приниматься только одним автоматом в цикле (или не приниматься ни одним). Несоблюдение этого правила ведет к следующему. Допустим, некоторое сообщение MSG_SOME_MESSAGE принимается двумя автоматами FSM1 и FSM3 в одном цикле программы. При этом первый автомат, приняв сообщение, сбросит его, а второй автомат, соответственно, его не примет. Очевидно, что в данном случае поведение программы будет определяться последовательностью вызовов автоматов в главном цикле, если автоматы будут вызываться в последовательности FSM3, … FSM1, поведение программы станет другим. Между тем одним из основных свойств предлагаемой концепции является именно независимость поведения программы от порядка вызовов функций автоматов (потоков).
Итак, каждое сообщение представлено конечным автоматом (КА), имеющим три состояния:
- Состояние 0: состояние неактивно.
- Состояние 1: состояние установлено, но неактивно.
- Состояние 2: состояние активно.
Автомат, посылающий сообщение, фактически переводит соответствующий КА из состояния 0 в состояние 1, а в конце цикла программы функция ProcessMessages переводит все сообщения, находящиеся в состоянии 1, в состояние 2. Таким образом, сообщение является активным с начала цикла, следующего после его генерации, до момента его приема либо до конца цикла, если оно не было принято. Также функция ProcessMessages сбрасывает все не принятые сообщения в конце цикла (переход 2–0).
Благодаря такому разделению «жизненного цикла» сообщения на фазы состояния, отладка программы становится простым и удобным процессом, так как мы можем постоянно отслеживать в отладчике, какой автомат и в каком состоянии сгенерировал сообщение, а какой его принял. Процесс отладки можно сделать еще более удобным, если организовать передачу отладочной информации из функций GetMessage и SendMessage через, например, порт RS-232, тогда мы сможем получить протокол работы программы на компьютере.
Существуют еще некоторые нюансы, связанные с обработкой сообщений. Допустим, мы записали условия переходов из некоторого состояния следующим образом:
Здесь должны выполняться следующие условия переходов: если принято сообщение MSG_SOME_MESSAGE и выполнено некоторое дополнительное условие GetInput(IN0), то автомат выполняет действие DoSomething() и переходит в состояние NEW_STATE, а при выполнении дополнительного условия GetInput(IN1) выполняется действие DoSomethingElse() и осуществляется переход в состояние ANOTHER_STATE. Однако ясно, что второе условие никогда не сработает, так как сообщение MSG_SOME_MESSAGE будет сброшено первой проверкой перехода при вызове GetMessage, вне зависимости от выполнения дополнительного условия GetInput(IN0). Поэтому так писать нельзя. Правильной реализацией переходов с такими условиями будет следующий код:
И, разумеется, нельзя использовать условия переходов типа следующего:
Другими словами, никогда нельзя исходить из предположения, что два разных сообщения поступят одновременно, в одном цикле.
Особенностью предлагаемой модели взаимодействия конечных автоматов является то, что они в определенном смысле синхронны, и если автомат генерирует сообщение, мы можем с уверенностью утверждать, что его сможет принять любой автомат в следующем цикле работы программы. Но в том случае, если мы передаем сообщение из главного цикла программы в обработчик прерывания, мы не можем быть уверены, что прерывание наступит в течение следующей итерации главного цикла, или в течение определенного времени, или вообще когда-либо. Поэтому такие передачи сообщений нужно применять с крайней осторожностью. Автору представляется, что в случае, если необходимо управление процессами в обработчике прерывания их главного цикла, лучше пожертвовать чистотой концепции и обойтись обычным набором флагов. Передача сообщений в обратном направлении, из обработчика прерывания в главный цикл, допускает применение сообщений (а вот применение простых флагов здесь крайне нежелательно в силу того, что из значения могут изменяться обработчиком прерываний асинхронно, в самые непредсказуемые моменты). Однако в силу асинхронности процессов в такой передаче сообщений скрываются некоторые «подводные камни». Представим себе такую ситуацию: обработчик прерывания послал сообщение MSG_SOME_MESSAGE автомату FSM3 в главном цикле программы. При этом КА, соответствующий данному сообщению, перешел в состояние 1. В конце цикла функция ProcessMessages перевела его в состояние 2 (активное состояние), в котором он должен находиться до окончания следующего цикла либо до приема сообщения автоматом FSM3. Теперь представим себе, что обработчик прерывания снова послал это же сообщение до его приема автоматом. В результате оно снова переводится в неактивное состояние и не может быть принято автоматом. Если обработчик прерывания будет повторять отправку сообщения с тем же периодом, с которым выполняется главный цикл программы, или чаще, сообщение никогда не перейдет в активное состояние и, соответственно, никогда не будет принято. Выход из данного положения очень прост. Немного изменим функцию SendMessage, чтобы не допустить повторной активизации сообщения:
На этом тему манипуляций с сообщениями пока оставим и перейдем к рассмотрению другого важного раздела — программных («виртуальных») таймеров.
Таймеры
Механизм таймеров может быть эффективно использован в SWITCH-программировании для эффективного описания условий переходов между состояниями автомата в том случае, если переход должен произойти по истечении определенного промежутка времени.
Под таймером мы будем подразумевать не аппаратное устройство микроконтроллера, а «виртуальный» объект, имеющий уникальный идентификатор и представляющий, по сути, переменную, увеличивающую свое значение на единицу через определенные интервалы времени. Этот интервал определяется настройками аппаратного таймера. Все «виртуальные» таймеры программы обслуживаются одним прерыванием аппаратного таймера.
Полную процедуру инициализации аппаратного таймера мы здесь приводить не будем, так как она зависит от типа применяемого микроконтроллера и реализуется для каждого типа микроконтроллеров по-разному (вы можете обратиться к фирменной документации, в которой вопросы подобного рода, как правило, рассматриваются очень подробно).
Инициализация виртуальных таймеров состоит в их обнулении:
Функция GetTimer позволяет получить текущее значение таймерной переменной, а ResetTimer сбрасывает таймер в начальное (нулевое) состояние. Заголовочный файл модуля таймеров может выглядеть, например, следующим образом:
В следующей статье мы продолжим рассмотрение механизма действия виртуальных таймеров согласно SWITCH-технологии и приведем конкретные примеры их применения.
Автор благодарит Анатолия Абрамовича Шалыто за поддержку при написании данного цикла статей.