⬆️ ⬇️

Development for embedded systems based on Quantum Leaps

Quantum Leaps or QP is an open source framework for embedded systems development. 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 (Statecharts).

The following is an example of designing a very simple finite state machine based application.





1. Get the framework

To start, go to the project site and download the source library . Next, download the SDK with examples for Win32. The example shows the solution of the dining philosophers problem in the console, on the Win32 API and MFC. The document folder also contains a complete description of the console application design process.

An example is quite complicated, therefore, to start with something very simple, I decided to invent and solve my own problem.



2. Statement of the problem

Loader Bob works on a conveyor belt that delivers boxes to the warehouse. If there is a box on the conveyor, that Bob takes it and puts it in a pile. If there is no box, then Bob is standing and waiting. When a box appears on the conveyor, the bell rings to catch Bob’s attention. When the bob picks up the box, he presses a button and shows that the next box can be passed through the pipeline.

')

3. State diagram

We describe the problem in the form of a state diagram. There are two automata (active objects) in the task.



image



The first BOB machine starts its life in the FREE state (the loader is not busy with work and is ready to take the box). BOB periodically generates a timeout_sig signal. At the same time, it must transmit a Timeout signal to the CONVEYOR machine. CONVEYOR when receiving a signal Timeout should go to the FULL state (a box appeared on the conveyor) and send a BOB notification as a Ready signal. BOB receiving the signal Ready should go to the WORK state (took the box) and send the Get signal back. Upon receiving Get, the CONVEYOR object enters the EMPTY state. BOB returns to the FREE state when generating Timeout_sig.



4. Application development

The main task of a QP developer is to write the classes of active objects according to the state diagram. Objects must process 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) Entry point - main.cpp



Determine the signals

According to the state diagram, we determine the signals that are in the system and determine their type of enumeration in the file qpbob.h.



#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 start with the constant Q_USER_SIG. The enumeration contains a number of service signals: TERMINATE_SIG - an application termination signal; upon receipt, active objects must correctly stop their work; MAX_PUB_SIG - the signals declared after this can be transmitted only directly from the object to the object, and the remaining signals are available to all objects of the system; MAX_SIG - the maximum signal number in the system. There is no TIMEOUT_SIG signal as it is used only inside the active Bob object.

2) Global pointers to active system objects. Initialized when creating objects.



We write a hardware-dependent code

QP can be used with a large number of hardware platforms. The description of active objects and their interaction does not depend on a specific platform. The code that depends on the hardware is better to write separately from the active objects. In our example, active objects use functions opened in bsp.h



#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) Set the frequency of the internal timer of the system.

2) The BSP_init function initializes the hardware. The BSP_print function displays the text in the console by stamping a time stamp in the form of the 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 <conio.h>

#include <stdlib.h>

#include <stdio.h>

#include <time.h>



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 counting the running time from system startup.

2) Displays the time counter value.

3) While the value of the variable is not 0, the idle thread is running.

4) The idle-stream is launched in the system to monitor the keyboard.

5) When you press Esc, a message is sent to the system to complete the work TERMINATE_SIG.

6) The function initializes the application.

7) The frequency of the system timer.

8) Run idle stream.

9) After the system stops, the idle stream closes.

10) The implementation of the function to display debug information.



System startup

The system runs in the main function of the main.cpp file. You need to create all the necessary objects and transfer control to 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 for storing signals.

2) You need an array to store subscriptions of objects to signals. If an 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) Initialize the hardware.

5) Initialize the system.

6) System configuration.

7) Register the event pool.

8) Initialize the list of subscribers.

9) Initialize the event pool.

10) We start active objects.

11) Start the system. Now the active objects interact with each other and the application is running.



Active Objects

Active objects of the system are written on the basis of a state diagram. Consider in detail the implementation of the Bob object file bob.cpp. Active objects are inherited 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 must 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 passed to the function to be called in this state. State functions have their own special signature. The Bob object has two states, FREE and WORK, but you also need to describe the initial state of the INITIAL to run the object.

3) For the internal timer, you need to create an internal signal.

4) The object constructor must call the timer constructor.

5) The facility must subscribe to public signals that it wants to receive.

6) The timer starts and at a specified interval will send a signal TIMEOUT_SIG.

7) From the state INITIAL translate the object into the state FREE.

8) In the FREE state, when the signal is received, the free function is called in which you need to process the signal.

9) In addition to custom signals, there are standard signals for entering the state and for exiting the state.

10) An example of custom signal processing. After processing the signal, go to the WORK state.

11) when receiving a TIMEOUT_SIG signal, you need to recharge the timer, so that it sends TIMEOUT_SIG at a specified interval.

12) An open signal is also sent to the system. This signal waits for a Conveyor object.

13) If an object takes up some resources, then it must process the system stop signal and correctly release resources. The active object that was last launched (in our case, Bob) should process the termination signal and stop the system.

14) Stop the system.

15) If the signal is processed but does not change the state of the object, then you need to return the sign of processing.



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 finite automata for embedded systems using QP does not pretend to realism and complete correctness. But I think it will help those who wish to master QP. Further study of QP can be continued on the basis of materials from the official site .



Sources:

code only;

project in VS.

Source: https://habr.com/ru/post/102024/



All Articles