Нравится? Делимся информацией!

воскресенье, 7 апреля 2013 г.

State Machine_реализуем Embedded модель (конечный автомат) цифрового детектора с помощью паттерна State








    Применение паттерна State к программированию цифрового детектора.


То, что конечный автомат является одной из основных парадигм в программировании (автоматное программирование  ) , позволяя описать систему в отдельных ее состояниях, мы можем почерпнуть из этого источника . В нем сравнивается автомат с клеткой в живом организме, из которого мы строим “тело” embedded системы.
<...>
    Разобьем алгоритм детектирования на стадии, а точнее состояния, т.к. детектор  с применяемой в нем децимацией становится похожим на конечным автомат с состояниями и переходами (из-за применения if-else).



В виду конкретной реализации алгоритма децимации - когда мы просчитываем выход фильтра не при каждом входном отсчете, а каждый M-й отсчет (М - коэффициент децимации) -  состояниями у нас будут “каскады” детектора с разной частотой дискретизации (каждое состояние работает на своей частоте дискретизации), переходами - получение выходного отсчета фильтра-дециматора (когда фильтр-дециматор просчитал выходной прореженный отсчет, то функция, его реализующая, выдает true значение). За дополнительной информацией о децимации можно обратиться к моей статье здесь .
Алгоритм на рисунке ниже (хотя он и отличается от представленной выше реализации: сначала децимация, а уж после нахождение модуля I^2+Q^2, - но это не беда для понимания) :


   
    Паттерн State будем реализовывать по алгоритму из Simulink. Он более предпочтителен, т.к. “тяжеловесное” извлечение корня производится не на 15625 S/s , а на 3125 S/s.
    Подберем названия для состояний.
State 1.
    Согласно рисунку 2 с сайта dspLib  к квадратурному гетеродину относятся генераторы ортогональных гармонических сигналов, умножители, фильтры, (правда , это не критично что к чему относится).
    Поэтому присвоим имя:
State 1 = Quadrature_Oscillator_State


State 2.
    Основная функция состояния - слияние квадратурных I-Q каналов в один поток. Из физики мы знаем, что есть два самых фундаментальных закона: закон сохранения импульсов (кинематика) и энергии. Второй к нам применим и он гласит, что мы можем пользоваться операцией сложения для энергий в замкнутой системе. Поэтому с этих позиций термин“слияние каналов” правомерен:
State 2 = IQ_Channels_Merge_State
State 3 = Sqrt_State
State 4
Здесь выделяем составляющие огибающей и применяем первый этап SQRT-алгоритма (возведение в квадрат).
State 4 = Envelop_Components_State
State 5
    Завершающая часть SQRT алгоритма (извлечение корня после фильтрации гармоники с удвоенной частотой огибающей).
State 5 = Components_Detection_State
State 6
    Конечное состояние всего алгоритма детектора. После него начинается все заново


    Диаграмма состояний, отрисована с помощью Visual Paradigm ( В чем рисовать UML, диаграммы? бесплатный UML редактор )




   
Проектирование автомата.


В процессе разработки обычно приходится выбирать между:

  • созданием объектов состояния, когда в них возникает необходимость, и уничтожением сразу после использования;
  • созданием их заранее и навсегда.
Первый вариант предпочтителен, когда заранее неизвестно, в какие состояния будет попадать система, и переход по состояниям происходит сравнительно редко. При этом мы не создаем объектов, которые никогда не будут использованы, что существенно, если в объектах состояния хранится много информации.
Когда изменения состояния происходят часто, поэтому не хотелось бы уничтожать представляющие их объекты (ибо они могут очень скоро понадобиться вновь), следует воспользоваться вторым подходом.
Поэтому мы воспользуемся вторым подходом.
В разработке реализации программы цифрового детектора нам поможет паттерн проектирования State.  Этот паттерн имеет множество реализаций (об этом почитать статью The Anthology of the Finite State Machine Design Patterns , а именно: про Real-Time State Pattern). Меня же интересует Real-Time State Pattern, который описан в статье Hierarchical State Machine . На их основе постигался дзен использования данного паттерна и ваялся свой.
Машина состояний (конечный автомат) будет классом с именем  CAsyncDetector.  В паттерне применяется композиция (включение), т.е. класс CAsyncDetector будет композитным - содержать внутри себя классы состояний конечного автомата. А эти классы  мы уже реализуем посредством наследования от некого базового, интерфейсного класса. Реализация состояний путем наследования очень практична, т.к., как описано в статье “Hierarchical State Machine”, можно реализовать и иерархическую версию конечного автомата в случае большого числа состояний.
Расширение количества состояний автомата достигается объявлением в теле внешнего класса CAsyncDetector дополнительных новых состояний с указанием его родителей и переопределением виртуальных методов родителя, которые будет использовать данное новое состояние.
В моем варианте паттерна классы состояний знают друг о друге, т.к. именно в их методах реализованы переходы из одного состояния в другое. Считаю, это не беда, т.к. алгоритм работы цифрового детектора более чем детерминированный.
Есть также реализация паттерна Состояние, где переходом управляет класс Контекст, которому классы состояний передают сигнал о наступлении  события, а уж контекст на основе текущего состояния и полученного события решает куда перейти. Об этих тонких различиях почитать в State Machine — новый паттерн ООП .


Один из минусов паттерна State:

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


    Итак, Диаграмма Классов у меня получается следующей:




    Примечания к заголовочному файлу.

  • По диаграмме видно - используем композицию. Классы Состояний являются вложенными в класс Машины Состояний (CAsyncDetector). Они не видны для других классов, т.к. являются закрытыми.
  • Все Классы Состояний объявлены дружественными. А т.к. дружественность не наследуется, то ключевое слово friend мы указываем для всех Классов Состояний. Это никак не нарушает прицип инкапсуляции, т.к все Классы Состояний - вложенные, да и к тому же объявлены в поле действия спецификатора доступа private.
  • Экземпляры Классов Состояний (Объекты состояний) обозначены как статические. Это и логично, т.к. код, который будет в этих состояниях - это код детектора, который должен существовать всегда.
  • Вложенные состояния наследуются от одного базового, интерфейсного класса CUnitState. Здесь есть небольшое сходство с паттерном Strategy.
  • общедоступным объявлен только метод Launch(), чтобы один раз запустить систему, которая потом будет работать в автономном режиме.
  • Машина состояний хранит указатель p_current_state_ на текущее состояние. Тип указателя - это тип базового интерфейсного класса Состояния CUnitState.  В соответствии с механизмом позднего связывания реализуется полиморфизм, состоящий в том, что с помощью одного и того же обращения к методу  Behave():
p_current_state_->Behave(*this);      //обращаемся к методу
    выполняются различные действия в зависимости от типа (от того Класса Состояния), на который ссылается указатель в данный момент. Этим и реализуется внешне кажущаяся изменчивость поведения паттерна State в зависимости от текущего состояния.

  • данная версия программы предназначена только для исследования и  для отладки на компьютере. Для этого в классе CUnitClass добавлено защищенное поле  counter_ и определенным образом реализованы конструкторы, а так же - вывод в консоль сообщений о том, в каком состоянии мы находимся.
  • Стиль оформления программы основывался на рекомендациях  Гугла: Google C++ Style  


  1. * async_detector.h */
  2. #ifndef ASYNC_DETECTOR_H
  3. #define ASYNC_DETECTOR_H
  4. #include <iostream>
  5. /**
  6. @file async_detector.h
  7. @author Bachinin Eugene
  8. @version Version 2.0
  9. @note Version 1.0 is realized as if-else statements
  10. @full this is the asynchronous detector model realized
  11. as the State Machine by means of State Pattern. It's only the model
  12. that's why we don't use real code for realizing detector
  13. */
  14. class CAsyncDetector { //State Machine
  15. public:
  16.     void Launch();
  17. protected:
  18. private:
  19.     class CUnitState {
  20.     public:
  21.                         CUnitState() : counter_( 0 ) {}
  22.                         CUnitState( long num ) : counter_( num ) {}
  23.         virtual            ~CUnitState() {}
  24.         virtual void    Behave( CAsyncDetector &async_detector ){}
  25.     protected:
  26.         //только для целей моделирования переходов конечного автомата!!!
  27.         //Удалить при реализации настоящего детектора (+изменить конструкторы)
  28.         long counter_;
  29.     };
  30.     friend CUnitState;
  31.     class CQuadratureOscillator : public CUnitState {
  32.     public:
  33.         CQuadratureOscillator() : CUnitState( 0 ) {}
  34.         void Behave( CAsyncDetector &async_detector );
  35.     };
  36.     friend CQuadratureOscillator;    //дружественность не наследуется
  37.     class CIQChannelsMerge : public CUnitState {
  38.     public:
  39.         CIQChannelsMerge() : CUnitState( 0 ) {}
  40.         void Behave( CAsyncDetector &async_detector );
  41.     };
  42.     friend CIQChannelsMerge;
  43.     class CSqrt : public CUnitState {
  44.     public:
  45.         CSqrt() : CUnitState( 0 ) {}
  46.         void Behave( CAsyncDetector &async_detector );
  47.     };
  48.     friend CSqrt;
  49.     class CEnvelopComponents : public CUnitState {
  50.     public:
  51.         CEnvelopComponents() : CUnitState( 0 ) {}
  52.         void Behave( CAsyncDetector &async_detector );
  53.     };
  54.     friend CEnvelopComponents;
  55.     class CComponentsDetection : public CUnitState {
  56.     public:
  57.         CComponentsDetection() : CUnitState( 0 ) {}
  58.         void Behave( CAsyncDetector &async_detector );
  59.     };
  60.     friend CComponentsDetection;
  61. private:
  62.     static CQuadratureOscillator    quadrature_oscillator_state_;
  63.     static CIQChannelsMerge            iq_channels_merge_state_;
  64.     static CSqrt                    sqrt_state_;
  65.     static CEnvelopComponents        envelop_components_state_;
  66.     static CComponentsDetection        components_detection_state_;
  67.     CUnitState *p_current_state_;
  68. private:
  69.     void NextState( CUnitState &ref_state );
  70.     void NextStateLaunch();
  71. }; //CAsyncDetector
  72. #endif //ASYNC_DETECTOR_H



    Реализация поведения паттерна State и необходимые разъяснения.

  • т.к. Классы Состояния объявлены, как дружественные, то в их методы (здесь он один - Behave()) мы можем передать по ссылке экземпляр нашей Машины Состояний (т.е. класса CAsyncDetector) и иметь доступ к закрытым методам и полям класса, т.е.  CAsyncDetector::NextState() и указателю p_current_state_.



  1. /* async_detector.cpp */
  2. #include "async_detector.h"
  3. //блок объявления статических переменных
  4. CAsyncDetector::CQuadratureOscillator    CAsyncDetector::quadrature_oscillator_state_;
  5. CAsyncDetector::CIQChannelsMerge        CAsyncDetector::iq_channels_merge_state_;
  6. CAsyncDetector::CSqrt                    CAsyncDetector::sqrt_state_;
  7. CAsyncDetector::CEnvelopComponents        CAsyncDetector::envelop_components_state_;
  8. CAsyncDetector::CComponentsDetection    CAsyncDetector::components_detection_state_;
  9. //может быть ошибка: может лучше писать (CAsyncDetector::CUnitState &ref_state )
  10. void CAsyncDetector::NextState( CUnitState &ref_state ) {
  11.     p_current_state_ = &ref_state;
  12. }
  13. void CAsyncDetector::NextStateLaunch( ) {
  14.     this->p_current_state_->Behave( *this );
  15. }
  16. void CAsyncDetector::Launch() {
  17.     //т.к. начинаем всегда с первого состояния, то устанавливаем его явно
  18.     p_current_state_ = &quadrature_oscillator_state_;
  19.     //Дальше конечный автомат цифрового детектора должен сам переходить
  20.     //из одного состояния в  другое
  21.     //Впервый раз запускаем так
  22.     p_current_state_->Behave(*this);
  23.     std::cin.get();
  24. }
  25. void CAsyncDetector::CQuadratureOscillator::Behave( CAsyncDetector &async_detector ) {
  26.     std::cout << "We are now in the: t quadrature_oscillator_state_ t Entries: " << this->counter_ << std::endl;
  27.     std::cin.get();
  28.     /*
  29.     *
  30.     Выполняем действия этого Состояния
  31.     *
  32.     */
  33.     this->counter_++;
  34.     if( (this->counter_ % 10) != 0 ) {
  35.         //переход на начало
  36.         async_detector.NextState( async_detector.quadrature_oscillator_state_); //????????
  37.     }
  38.     else {
  39.         //переключаемся в следующее состояние
  40.         async_detector.NextState( async_detector.iq_channels_merge_state_ );
  41.     }
  42.     //запускаем
  43.     async_detector.NextStateLaunch();
  44. }
  45. void CAsyncDetector::CIQChannelsMerge::Behave( CAsyncDetector &async_detector ) {
  46.     std::cout << "We are now in the: t iq_channels_merge_state_ t Entries: " << this->counter_ << std::endl;
  47.     std::cin.get();
  48.     /*
  49.     *
  50.     Выполняем действия этого Состояния
  51.     *
  52.     */
  53.     this->counter_++;
  54.     if( (this->counter_ % 5) != 0 ) {
  55.         async_detector.NextState( async_detector.quadrature_oscillator_state_);
  56.     }
  57.     else {
  58.         async_detector.NextState( async_detector.sqrt_state_ );
  59.     }
  60.     async_detector.NextStateLaunch();
  61. }
  62. void CAsyncDetector::CSqrt::Behave( CAsyncDetector &async_detector ) {
  63.     std::cout << "We are now in the: t sqrt_state_ t Entries: " << this->counter_ << std::endl;
  64.     std::cin.get();
  65.     /*
  66.     *
  67.     Выполняем действия этого Состояния
  68.     *
  69.     */
  70.     this->counter_++;
  71.     if( (this->counter_ % 5) != 0 ) {
  72.         async_detector.NextState( async_detector.quadrature_oscillator_state_);
  73.     }
  74.     else {
  75.         async_detector.NextState( async_detector.envelop_components_state_ );
  76.     }
  77.     async_detector.NextStateLaunch();
  78. }
  79. void CAsyncDetector::CEnvelopComponents::Behave( CAsyncDetector &async_detector ) {
  80.     std::cout << "We are now in the: t envelop_components_state_ t Entries: " << this->counter_ << std::endl;
  81.     std::cin.get();
  82.     /*
  83.     *
  84.     Выполняем действия этого Состояния
  85.     *
  86.     */
  87.     this->counter_++;
  88.     if( (this->counter_ % 5) != 0 ) {
  89.         async_detector.NextState( async_detector.quadrature_oscillator_state_);
  90.     }
  91.     else {
  92.         async_detector.NextState( async_detector.components_detection_state_ );
  93.     }
  94.     async_detector.NextStateLaunch();
  95. }
  96. void CAsyncDetector::CComponentsDetection::Behave( CAsyncDetector &async_detector ) {
  97.     std::cout << "We are now in the: t components_detection_state_ t Entries: " << this->counter_  << std::endl;
  98.     std::cin.get();
  99.     /*
  100.     *
  101.     Выполняем действия этого Состояния
  102.     *
  103.     */
  104.     //закольцовываем конечный автомат на начальное состояние
  105.     async_detector.NextState( async_detector.quadrature_oscillator_state_ );
  106.     //запускаем
  107.     async_detector.NextStateLaunch();
  108. }



Ну и главная функция:


  1. #include <iostream>
  2. #include "async_detector.h"
  3. int main() {
  4.     CAsyncDetector async_detector;
  5.     async_detector.Launch();
  6.     std::cin.get();
  7.     return 0;
  8. }



Результаты:






Комментариев нет:

Отправить комментарий