Development for Embedded Systems Based on Quantum Leaps

    Quantum Leaps or QP is a family of open source frameworks for developing embedded systems. QP can work with the main OS (Linux, Windows, FreeRTOS, etc.) or without it. QP is ported to a huge number of different processor families .
    A QP application is designed as several state machines based on UML statecharts.
    The following is an example of designing a very simple finite state machine application.


    1. Get the framework
    To get started, go to the project website and download the library sources . Next, download the SDK with examples for Win32. The example shows the solution to the problem of dining philosophersin the console, on the Win32 API and MFC. The document folder also contains a complete description of the console application design process.
    The example is quite complex, therefore, to start with something very simple, I decided to come up with and solve my own problem.

    2. Statement of the problem
    Bob loader works on the conveyor, which delivers the boxes to the warehouse. If there is a box on the conveyor, that Bob takes it and piles it. If there is no box, then Bob stands and waits. When the box appears on the conveyor, a bell rings to attract Bob's attention. When the bean picks up the box, he presses a button and shows that the next box can be conveyed along the conveyor.

    3. State diagram
    Let us describe the problem in the form of a state diagram. There are two automata (active objects) in the problem.

    image

    The first BOB machine begins its life in the FREE state (the loader is not busy with work and is ready to take the box). The BOB periodically generates a Timeout_sig signal. In doing so, it must transmit a Timeout signal to the CONVEYOR. When receiving a Timeout signal, CONVEYOR should go to FULL (a box has appeared on the conveyor) and send a BOB notification in the form of a Ready signal. BOB having received the Ready signal should go into the WORK state (took the box) and send the Get signal back. Upon receiving Get, the CONVEYOR object enters the EMPTY state. BOB when generating Timeout_sig returns to the FREE state.

    4. Application development
    The main task of a QP developer is to write classes of active objects using a state diagram. Objects must process the received signals and send the necessary signals under certain conditions.

    Project Content
    A QP project is usually divided into the following parts:
    1) Global variables and constants - qpbob.h
    2) Hardware-dependent code - bsp.h, bsp.cpp
    3) Active objects - bob.cpp, conveyor.cpp
    4) Point input - main.cpp

    We determine the signals
    From the state diagram we determine the signals that are in the system and determine their type of enumeration in the qpbob.h file.

    #ifndef qpbob_h
    #define qpbob_h
    //(1) Сигналы
    enum DPPSignals {
      READY = Q_USER_SIG,
      GET,
      TIMEOUT,
      TERMINATE_SIG, 
      MAX_PUB_SIG,                 
      MAX_SIG                         
    };

    //(2) Глобальные указатели на активные объекты
    extern QActive * const AO_BOB; 
    extern QActive * const AO_CONVEYOR; 

    #endif                               

    * This source code was highlighted with Source Code Highlighter.


    Explanations:
    1) The first user signal must begin with the constant Q_USER_SIG. The enumeration contains a number of service signals: TERMINATE_SIG - application completion signal; upon receipt, active objects must correctly stop their work; MAX_PUB_SIG - signals announced after this can be transmitted only directly from object to object, and the rest of the signals are available to all objects of the system; MAX_SIG - the maximum signal number in the system. The TIMEOUT_SIG signal is not specified here since it is used only inside the active Bob object.
    2) Global pointers to active system objects. Initialized when creating objects.

    Writing Hardware Dependent Code
    QP can be used with a large number of hardware platforms. Description of active objects and their interaction does not depend on a specific platform. Code that depends on the hardware is better to write separately from active objects. In our example, active objects use functions open in the bsp.h file

    #ifndef bsp_h
    #define bsp_h
    //(1) Частота работы внутреннего таймера
    #define BSP_TICKS_PER_SEC  1
    //(2) Функции приложения привязанные к апаратной части
    void BSP_init(int argc, char *argv[]);
    void BSP_print(char const *str);
    #endif                                // bsp_h

    * This source code was highlighted with Source Code Highlighter.


    Explanations:
    1) We set the frequency of the internal timer of the system.
    2) The BSP_init function initializes the hardware. The BSP_print function prints text to the console by putting a time stamp in the form of a number of tenths of a second.
    The implementation of the functions is in the bsp.cpp file.

    #include "qp_port.h"
    #include "qpbob.h"
    #include "bsp.h"

    #include
    #include
    #include
    #include

    Q_DEFINE_THIS_FILE

    //(1)Счетчик времени
    unsigned int TimeCounter;
    //(2)Функция ставит штамп времени
    void BSP_timeStamp()
    {
      printf("%d - ",TimeCounter);
    }

    //(3) Признак работы idle-потока
    static uint8_t l_running;
    //(4) Потоковая функция idle-потока
    static DWORD WINAPI idleThread(LPVOID par) { 
      TimeCounter=0;
      (void)par;
      l_running = (uint8_t)1;
      while (l_running) {
        Sleep(100); 
        TimeCounter++;
        //(5)Если нажат Esc то программа посылает завершающий сигнал
        if (_kbhit()) {                  
          if (_getch() == '\33') {      
            QF::publish(Q_NEW(QEvent, TERMINATE_SIG));
            BSP_print("Esc exit");
          }
        }

      }
      return 0;                        
    }
    //(6) Инициализация приложения
    void BSP_init(int argc, char *argv[]) {
      HANDLE hIdle;
      //(7)Установка частоты внутреннего таймера
      QF_setTickRate(BSP_TICKS_PER_SEC); 
      //(8) Создание idle потока
      hIdle = CreateThread(NULL, 1024, &idleThread, (void *)0, 0, NULL);
      Q_ASSERT(hIdle != (HANDLE)0);          
      SetThreadPriority(hIdle, THREAD_PRIORITY_IDLE);

      printf("QP example"
          "\nQEP %s\nQF %s\n"
          "Press ESC to quit...\n",
          QEP::getVersion(),
          QF::getVersion());
    }
    //............................................................................
    void QF::onStartup(void) {
    }
    //(9)..........................................................................
    void QF::onCleanup(void) {
      l_running = (uint8_t)0;              
    }

    //............................................................................
    void BSP_print(char const *str)
    {
      BSP_timeStamp();
      printf("%s\n", str);
    }
    //(10)
    void Q_onAssert(char const Q_ROM * const Q_ROM_VAR file, int line) {
      fprintf(stderr, "Assertion failed in %s, line %d", file, line);
      exit(0);
    }


    * This source code was highlighted with Source Code Highlighter.


    Explanations:
    1) Variable for calculating the operating time from starting the system.
    2) Displays the value of the time counter.
    3) While the value of the variable is not 0, the idle thread is working.
    4) The idle thread is launched in the system to monitor the keyboard.
    5) When Esc is pressed, a message is sent to the system to terminate TERMINATE_SIG.
    6) The function initializes the application.
    7) The frequency of the system timer.
    8) Start idle-stream.
    9) After the system stops, the idle stream closes.
    10) Implementation of a function for outputting debugging information.

    Starting the system
    The system starts in the main function of main.cpp file. You need to create all the necessary objects and transfer control to the active objects.

    #include "qp_port.h"
    #include "qpbob.h"
    #include "bsp.h"

    //(1) Очереди сигналов
    static QEvent const *l_bobQueueSto[10];
    static QEvent const *l_conQueueSto[10];
    //(2) Список подписчиков на сигналы
    static QSubscrList  l_subscrSto[MAX_PUB_SIG];
    //(3) Пул событий, здесь не используется
    static union SmallEvents {
      void *min_size;
      
    } l_smlPoolSto[10];        

    int main(int argc, char *argv[]) {

      //(4) Инициализируем BSP
      BSP_init(argc, argv);               

      //(5) Инициализируем фреймворк
      QF::init();   

      //(6) Настройка
      QS_FILTER_ON(QS_ALL_RECORDS);
      QS_FILTER_OFF(QS_QF_INT_LOCK);
      QS_FILTER_OFF(QS_QF_INT_UNLOCK);
      QS_FILTER_OFF(QS_QF_ISR_ENTRY);
      QS_FILTER_OFF(QS_QF_ISR_EXIT);
      QS_FILTER_OFF(QS_QF_TICK);
      QS_FILTER_OFF(QS_QK_SCHEDULE);

      //(7) Регистрируем пул событий                        
      QS_OBJ_DICTIONARY(l_smlPoolSto);
     
      //(8) Инициализируем список подписчиков
      QF::psInit(l_subscrSto, Q_DIM(l_subscrSto));  

      //(9) Инициализируем пул событий
      QF::poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0]));

      //(10) Запускаем активные объекты
      AO_CONVEYOR->start(2,
              l_conQueueSto, Q_DIM(l_conQueueSto),
              (void *)0, 1024, (QEvent *)0);
      AO_BOB->start(3,
              l_bobQueueSto, Q_DIM(l_bobQueueSto),
              (void *)0, 1024, (QEvent *)0);
      

      //(11)Запускаем приложение
      QF::run();                   
      
      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.


    Explanations:
    1) Each active object needs an array to store signals.
    2) We need an array to store subscriptions of objects to signals. If the object wants to receive a public signal, it must be subscribed to it.
    3) Signals with additional data need a special pool (not used here).
    4) We initialize the hardware.
    5) We initialize the system.
    6) System setup.
    7) Register the event pool.
    8) We initialize the list of subscribers.
    9) Initialize the event pool.
    10) Run active objects.
    11) We start the system. Now the active objects interact with each other and the application is running.

    Active objects
    Active system objects are written based on a state diagram. Let us consider in detail the implementation of the Bob object, the bob.cpp file. Active objects inherit from the QActive class.

    #include "qp_port.h"
    #include "qpbob.h"
    #include "bsp.h"

    Q_DEFINE_THIS_FILE

    class Bob : public QActive {

    private:
      //(1) собственное событие таймера
      QTimeEvt m_timeEvt;
    public:
      Bob();

    private:
      //(2) Состояния
      //Функции вызываются в определенном состоянии при получении сигнала
      static QState initial(Bob *me, QEvent const *e);
      static QState free(Bob *me, QEvent const *e);
      static QState work(Bob *me, QEvent const *e);
    };

    static Bob l_Bob;                  

    #define TIMEOUT_TIME 1

    //(3) Перечисление для маркера внутреннего события таймера
    enum InternalSignals {                  
      TIMEOUT_SIG = MAX_SIG
    };

    QActive * const AO_BOB = &l_Bob;          

    //............................................................................
    Bob::Bob() : QActive((QStateHandler)&Bob::initial),
    m_timeEvt(TIMEOUT_SIG){//(4) Тут ставим конструктор внутреннего события таймера

      
      
    }
    //............................................................................
    QState Bob::initial(Bob *me, QEvent const *) {

      BSP_print("Bob initial");
      //Перечисляем объекты
      QS_OBJ_DICTIONARY(&l_Bob);
      QS_OBJ_DICTIONARY(&l_Bob.m_timeEvt);
      //Пеерчисляем состояния
      QS_FUN_DICTIONARY(&QHsm::top);
      QS_FUN_DICTIONARY(&Bob::initial);
      QS_FUN_DICTIONARY(&Bob::free);
      QS_FUN_DICTIONARY(&Bob::work);
      //Перечиляем сигналы
     
      QS_SIG_DICTIONARY(READY,  me);
      QS_SIG_DICTIONARY(TIMEOUT_SIG, me);
      //(5)Подписываемся на сигналы
      
      me->subscribe(READY);
      me->subscribe(TERMINATE_SIG);
      
    //(6)Запускаем внутренне событие таймера с задданым таймаутом
      me->m_timeEvt.postIn(me, TIMEOUT_TIME);

      //(7) Инициируем переход в другое состояние
      return Q_TRAN(&Bob::free);
    }
    //............................................................................
    QState Bob::free(Bob *me, QEvent const *e) {
      
    BSP_print("Bob free");
    //(8) Обработка сигналов  
    switch (e->sig) {
        //(9)Стандартный сигнал входа в состояние  
        case Q_ENTRY_SIG: {
          
          return Q_HANDLED();
        }
        //(10)Обработка нашего сигнала
        
        case READY:
        {
          BSP_print("O my box READY. Going work");
          
          return Q_TRAN(&Bob::work);
        }
        case TIMEOUT_SIG:
        {
          BSP_print("Tick Free - Bob");
          //(11)Посылаем следующий тик таймера
          me->m_timeEvt.postIn(me, TIMEOUT_TIME);
          //(12)Посылаем событие без параметров
          QEvent* be=Q_NEW(QEvent,TIMEOUT);
          QF::publish(be);
          
          
          return Q_HANDLED();
        }
        //(13)Обработка сигнала выключения
        case TERMINATE_SIG: {
          BSP_print("Bob terminate.----------------------------------------------");
          QF::stop();//(14) Остановка приложения
          //(15) Возвращаем признак обработаного сигнала
          return Q_HANDLED();
        }
      }
      return Q_SUPER(&QHsm::top);
    }

    QState Bob::work(Bob *me, QEvent const *e) {
      
    BSP_print("Bob work");
     
    switch (e->sig) {
        
        case Q_ENTRY_SIG: {
            QEvent* be=Q_NEW(QEvent,GET);
          QF::publish(be);
          BSP_print("Bob public GET");

          
          return Q_HANDLED();
        }
          
        case TIMEOUT_SIG:
        {
          BSP_print("Tick Work - Bob");
          BSP_print("I am going to free.");
          me->m_timeEvt.postIn(me, TIMEOUT_TIME);
          QEvent* be=Q_NEW(QEvent,TIMEOUT);
          QF::publish(be);
               
          return Q_TRAN(&Bob::free);
        }
        
        case TERMINATE_SIG: {
          BSP_print("Bob terminate.----------------------------------------------");
          QF::stop();
          return Q_HANDLED();
        }
      }
      return Q_SUPER(&QHsm::top);
    }

    * This source code was highlighted with Source Code Highlighter.


    Explanations
    1) Bob should periodically generate a TIMEOUT_SIG message for this we use our own timer.
    2) The active object is always in one of its states. When a signal is received, it is transferred to the function that needs to be called in this state. State functions have their own special signature. A Bob object has two states, FREE and WORK, but you also need to describe the initial state of INITIAL to start the object.
    3) For the internal timer you need to create an internal signal.
    4) The constructor of the object must call the constructor of the timer.
    5) The object must subscribe to the public signals that it wants to receive.
    6) The timer starts and after a specified interval sends a signal TIMEOUT_SIG.
    7) From the INITIAL state we transfer the object to the FREE state.
    8) In the FREE state, when a signal is received, the free function is called in which you need to process the signal.
    9) In addition to user signals, there are standard signals for entering and exiting a state.
    10) An example of processing a user signal. After processing the signal, go to the WORK state.
    11) when receiving the TIMEOUT_SIG signal, you need to recharge the timer so that it sends TIMEOUT_SIG at the specified interval.
    12) An open signal is also sent to the system. This signal waits for the Conveyor object.
    13) If the object takes up some resources, then it must process the system stop signal and correctly release the resources. The active object that was last run (in our case Bob) should process the completion signal and stop the system.
    14) System shutdown.
    15) If the signal is processed but does not change the state of the object, then the processing flag should be returned.

    The Conveyor object from the conveyor.cpp file is simpler and you can figure it out yourself.

    #include "qp_port.h"
    #include "qpbob.h"
    #include "bsp.h"

    Q_DEFINE_THIS_FILE

    class Conveyor : public QActive {

    private:
      
    public:
      Conveyor();

    private:
      //состояния
      static QState initial(Conveyor *me, QEvent const *e);
      static QState empty(Conveyor *me, QEvent const *e);
      static QState full(Conveyor *me, QEvent const *e);
    };

    static Conveyor l_Conveyor;                  

    QActive * const AO_CONVEYOR = &l_Conveyor;          

    //............................................................................
    Conveyor::Conveyor() : QActive((QStateHandler)&Conveyor::initial)
    {

    }
    //............................................................................
    QState Conveyor::initial(Conveyor *me, QEvent const *) {

      BSP_print("Conveyor initial");
     
      QS_OBJ_DICTIONARY(&l_Conveyor);
      
      
      QS_FUN_DICTIONARY(&QHsm::top);
      QS_FUN_DICTIONARY(&Conveyor::initial);
      QS_FUN_DICTIONARY(&Conveyor::empty);
      QS_FUN_DICTIONARY(&Conveyor::full);
      
      QS_SIG_DICTIONARY(GET,  me);
      QS_SIG_DICTIONARY(TIMEOUT, me);
      
      me->subscribe(GET);
      me->subscribe(TIMEOUT);
      
      return Q_TRAN(&Conveyor::empty);
    }
    //............................................................................
    QState Conveyor::empty(Conveyor *me, QEvent const *e) {
      
    BSP_print("Conveyor empty");
     
    switch (e->sig) {
        
        
        case TIMEOUT:
        {
          BSP_print("Tick Empty - Conveyor");
          BSP_print("Conveyor going to full.");
          
          
          return Q_TRAN(&Conveyor::full);
        }
        
      }
      return Q_SUPER(&QHsm::top);
    }

    QState Conveyor::full(Conveyor *me, QEvent const *e) {
      
    BSP_print("Conveyor full");

    switch (e->sig) {
        //(1) Стандартный сигнал входа в состояние  
        
        case Q_ENTRY_SIG: {
          QEvent* be=Q_NEW(QEvent,READY);
          QF::publish(be);
          BSP_print("Conveyor READY.");
                 }
        
        case GET:
          {
            BSP_print("Bob GET box.");
          return Q_TRAN(&Conveyor::empty);
          }
        
      }
      return Q_SUPER(&QHsm::top);
    }


    * This source code was highlighted with Source Code Highlighter.


    This example of developing an application based on state machines for embedded systems using QP does not pretend to be realistic or completely correct. But I think it will help those wishing to master QP. Further study of QP can be continued based on materials from the official website .

    Sources:
    only code;
    project in VS.

    Also popular now: