真沒想到還可以這樣寫狀態機!QP嵌入式實時框架
好萊塢原則:和傳統的順序式編程方法例如“超級循環”,或傳統的RTOS 的任務不同。絕大多數的現代事件驅動型系統根據好萊塢原則被構造(Don’t call me; I’ll call you.)
QP官網:http://www.state-machine.com/
面向對象類和單一繼承:

QM :一個通過UML類圖來描述狀態機的軟件,并且可以自動生成C代碼

QS軟件追蹤工具:



/* qevent.h ----------------------------------------------------------------*/
typedef struct QEventTag
{
QSignal sig;
uint8_t dynamic_;
} QEvent;
/* qep.h -------------------------------------------------------------------*/
typedef uint8_t QState; /* status returned from a state-handler function */
typedef QState (*QStateHandler) (void *me, QEvent const *e); /* argument list */
typedef struct QFsmTag /* Finite State Machine */
{
QStateHandler state; /* current active state */
}QFsm;
#define QFsm_ctor(me_, initial_) ((me_)->state = (initial_))
void QFsm_init (QFsm *me, QEvent const *e);
void QFsm_dispatch(QFsm *me, QEvent const *e);
#define Q_RET_HANDLED ((QState)0)
#define Q_RET_IGNORED ((QState)1)
#define Q_RET_TRAN ((QState)2)
#define Q_HANDLED() (Q_RET_HANDLED)
#define Q_IGNORED() (Q_RET_IGNORED)
#define Q_TRAN(target_) (((QFsm *)me)->state = (QStateHandler) (target_),Q_RET_TRAN)
enum QReservedSignals
{
Q_ENTRY_SIG = 1,
Q_EXIT_SIG,
Q_INIT_SIG,
Q_USER_SIG
};
/* file qfsm_ini.c ---------------------------------------------------------*/
#include "qep_port.h" /* the port of the QEP event processor */
#include "qassert.h" /* embedded systems-friendly assertions */
void QFsm_init(QFsm *me, QEvent const *e)
{
(*me->state)(me, e); /* execute the top-most initial transition */
/* enter the target */
(void)(*me->state)(me , &QEP_reservedEvt_[Q_ENTRY_SIG]);
}
/* file qfsm_dis.c ---------------------------------------------------------*/
void QFsm_dispatch(QFsm *me, QEvent const *e)
{
QStateHandler s = me->state; /* save the current state */
QState r = (*s)(me, e); /* call the event handler */
if (r == Q_RET_TRAN) /* transition taken? */
{
(void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]); /* exit the source */
(void)(*me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);/*enter target*/
}
}
實現上面定時器例子
#include "qep_port.h" /* the port of the QEP event processor */
#include "bsp.h" /* board support package */
enum BombSignals /* all signals for the Bomb FSM */
{
UP_SIG = Q_USER_SIG,
DOWN_SIG,
ARM_SIG,
TICK_SIG
};
typedef struct TickEvtTag
{
QEvent super; /* derive from the QEvent structure */
uint8_t fine_time; /* the fine 1/10 s counter */
}TickEvt;
typedef struct Bomb4Tag
{
QFsm super; /* derive from QFsm */
uint8_t timeout; /* number of seconds till explosion */
uint8_t code; /* currently entered code to disarm the bomb */
uint8_t defuse; /* secret defuse code to disarm the bomb */
} Bomb4;
void Bomb4_ctor (Bomb4 *me, uint8_t defuse);
QState Bomb4_initial(Bomb4 *me, QEvent const *e);
QState Bomb4_setting(Bomb4 *me, QEvent const *e);
QState Bomb4_timing (Bomb4 *me, QEvent const *e);
/*--------------------------------------------------------------------------*/
/* the initial value of the timeout */
#define INIT_TIMEOUT 10
/*..........................................................................*/
void Bomb4_ctor(Bomb4 *me, uint8_t defuse) {
QFsm_ctor_(&me->super, (QStateHandler)&Bomb4_initial);
me->defuse = defuse; /* the defuse code is assigned at instantiation */
}
/*..........................................................................*/
QState Bomb4_initial(Bomb4 *me, QEvent const *e) {
(void)e;
me->timeout = INIT_TIMEOUT;
return Q_TRAN(&Bomb4_setting);
}
/*..........................................................................*/
QState Bomb4_setting(Bomb4 *me, QEvent const *e) {
switch (e->sig){
case UP_SIG:{
if (me->timeout < 60) {
++me->timeout;
BSP_display(me->timeout);
}
return Q_HANDLED();
}
case DOWN_SIG: {
if (me->timeout > 1) {
--me->timeout;
BSP_display(me->timeout);
}
return Q_HANDLED();
}
case ARM_SIG: {
return Q_TRAN(&Bomb4_timing); /* transition to "timing" */
}
}
return Q_IGNORED();
}
/*..........................................................................*/
void Bomb4_timing(Bomb4 *me, QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
me->code = 0; /* clear the defuse code */
return Q_HANDLED();
}
case UP_SIG: {
me->code <<= 1;
me->code |= 1;
return Q_HANDLED();
}
case DOWN_SIG: {
me->code <<= 1;
return Q_HANDLED();
}
case ARM_SIG: {
if (me->code == me->defuse) {
return Q_TRAN(&Bomb4_setting);
}
return Q_HANDLED();
}
case TICK_SIG: {
if (((TickEvt const *)e)->fine_time == 0) {
--me->timeout;
BSP_display(me->timeout);
if (me->timeout == 0) {
BSP_boom(); /* destroy the bomb */
}
}
return Q_HANDLED();
}
}
return Q_IGNORED();
}
優點:
- 采用面向對象的設計方法,很好的移植性
- 實現了進入退出動作
- 合適的粒度,且事件的粒度可控
- 狀態切換時通過改變指針,效率高
- 可擴展成為層次狀態機
缺點:
- 對事件的定義以及事件粒度的控制是設計的最大難點,如串口接收到一幀數據,這些變量的更新單獨作為某個事件,還是串口收到數據作為一個事件。再或者顯示屏,如果使用此種編程方式,如何設計事件。

初始化層次狀態機的實現:在初始化時,用戶所選取的狀態永遠是最底層的狀態,如上圖,我們在計算器開機后,應該進入的是開始狀態,這就涉及到一個問題,由最初top(頂狀態)到begin 是有一條狀態切換路徑的,當我們設置狀態為begin如何搜索這條路徑成為關鍵(知道了路徑才能正確的進入begin,要執行路徑中過渡狀態的進入和退出事件)。
void QHsm_init(QHsm *me, QEvent const *e)
{
Q_ALLEGE((*me->state)(me, e) == Q_RET_TRAN);
t = (QStateHandler)&QHsm_top; /* HSM starts in the top state */
do { /* drill into the target... */
QStateHandler path[QEP_MAX_NEST_DEPTH_];
int8_t ip = (int8_t)0; /* transition entry path index */
path[0] = me->state; /* 這里的狀態為begin */
/*通過執行空信號,從底層狀態找到頂狀態的路徑*/
(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
while (me->state != t) {
path[++ip] = me->state;
(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
}
/*切換為begin*/
me->state = path[0]; /* restore the target of the initial tran. */
/* 鉆到最底層的狀態,執行路徑中的所有進入事件 */
Q_ASSERT(ip < (int8_t)QEP_MAX_NEST_DEPTH_);
do { /* retrace the entry path in reverse (desired) order... */
QEP_ENTER_(path[ip]); /* enter path[ip] */
} while ((--ip) >= (int8_t)0);
t = path[0]; /* current state becomes the new source */
} while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN);
me->state = t;
}
狀態切換:

/*.................................................................*/
QState result(Calc *me, QEvent const *e)
{
switch (e->sig)
{you
case ENTER_SIG:{
break;
}
case EXIT_SIG:{
break;
}
case C_SIG:
{
printf("clear");
return Q_HANDLED();
}
case B_SIG:
{
return Q_TRAN(&begin);
}
}
return Q_SUPER(&reday);
}
/*.ready為result和begin的超狀態................................................*/
QState ready(Calc *me, QEvent const *e)
{
switch (e->sig)
{
case ENTER_SIG:{
break;
}
case EXIT_SIG:{
break;
}
case OPER_SIG:
{
return Q_TRAN(&opEntered);
}
}
return Q_SUPER(&on);
}
void QHsm_dispatch(QHsm *me, QEvent const *e)
{
QStateHandler path[QEP_MAX_NEST_DEPTH_];
QStateHandler s;
QStateHandler t;
QState r;
t = me->state; /* save the current state */
do { /* process the event hierarchically... */
s = me->state;
r = (*s)(me, e); /* invoke state handler s */
} while (r == Q_RET_SUPER); //當前狀態不能處理事件 ,直到找到能處理事件的狀態
if (r == Q_RET_TRAN) { /* transition taken? */
int8_t ip = (int8_t)(-1); /* transition entry path index */
int8_t iq; /* helper transition entry path index */
path[0] = me->state; /* save the target of the transition */
path[1] = t;
while (t != s) { /* exit current state to transition source s... */
if (QEP_TRIG_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {/*exit handled? */
(void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* find superstate of t */
}
t = me->state; /* me->state holds the superstate */
}
. . .
}
me->state = t; /* set new state or restore the current state */
}

t = path[0]; /* target of the transition */4 QP實時框架的組成
if (s == t) { /* (a) check source==target (transition to self) */
QEP_EXIT_(s) /* exit the source */
ip = (int8_t)0; /* enter the target */
}
else {
(void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* superstate of target */
t = me->state;
if (s == t) { /* (b) check source==target->super */
ip = (int8_t)0; /* enter the target */
}
else {
(void)QEP_TRIG_(s, QEP_EMPTY_SIG_); /* superstate of src */
/* (c) check source->super==target->super */
if(me->state == t) {
QEP_EXIT_(s) /* exit the source */
ip = (int8_t)0; /* enter the target */
}
else {
/* (d) check source->super==target */
if (me->state == path[0]) {
QEP_EXIT_(s) /* exit the source */
}
else { /* (e) check rest of source==target->super->super..
* and store the entry path along the way */
....


使用內存池,對于低性能mcu,內存極為有限,引入內存管理主要是整個架構中,是以事件作為主要的任務通信手段,且事件是帶參數的,可能相同類型的事件會多次觸發,而事件處理完成后,需要清除事件,無法使用靜態的事件,因此是有必要為不同事件創建內存池的。對于不同塊大小的內存池,需要考慮的是每個塊的起始地址對齊問題。在進行內存池初始化時,我們是根據blocksize+header大小來進行劃分內存池的。假設一個2字節的結構,如果以2來進行劃分,假設mcu 4字節對齊,那么將有一半的結構起始地址無法對齊,這時需要為每個塊預留空間,保證每個塊的對齊。

每一個活動對象維護一個事件隊列,事件都是由基礎事件派生的,不同類型的事件只需要將其基礎事件成員添加到活動對象的隊列中即可,最終在取出的時候通過一個強制轉換便能獲得附加的參數。

- 直接事件發送 QActive_postLIFO()
- 發行訂閱事件發送 豎軸表示信號(為事件的基類) 活動對象支持64個優先級,每一個活動對象要求擁有唯一優先級 通過優先級的bit位來表示某個事件被哪些活動對象訂閱,并在事件觸發后根據優先級為活動對象派發事件。


一鍵三連,公眾號后臺回復【QP】可以獲取資料

*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。





