公式アカウントをフォロー+スターを付けて、楽しいコンテンツをお見逃しなく
ソース | CSDN
著者 |
ステート マシンは組み込み開発で一般的な手法ですが、ステート マシンにはさまざまな形式があります。ここでは、古典的な QP フレームワークの原理を説明します。
ステートマシンの基本用語
現在のステータス: 現在の状態を指します。条件: 「イベント」とも呼ばれ、条件が満たされると、アクションがトリガーされるか、状態遷移が実行されます。アクション: 条件が満たされた後に実行されるアクション。アクションの実行後、新しい状態に移行することも、元の状態にとどまることもできます。
アクションは必要ありません。条件が満たされると、アクションを実行せずに新しい状態に直接移行できます。次の状態: 条件が満たされた後に移行する新しい状態。「二次状態」は「現在の状態」に対して相対的なものであり、「二次状態」が活性化されると新たな「現在の状態」に変化します。
従来の有限ステートマシンFsmの実装方法
図に示すようにタイミングカウンタであり、設定状態とタイミング状態の2つの状態を持ちます。
状態を設定する
「+」と「-」ボタンは、初期カウントダウンを設定するために使用されます。カウント値が設定されたら、「OK」ボタンをクリックして計時を開始します。つまり、計時状態に切り替わります。
タイミングステータス
「+」「-」を押してパスワードを入力します。「+」は 1、「-」は入力 0、パスワードは 4 桁です
確認キー: 入力されたパスワードがデフォルトのパスワードと等しい場合にのみ、確認キーを押してタイミングを停止します。そうでない場合、タイミングは直接ゼロになり、関連する操作が実行されます。
ネストされたスイッチ
/***************************************
1.列出所有的状态
***************************************/
typedef enum{
SETTING,
TIMING
}STATE_TYPE;
/***************************************
2.列出所有的事件
***************************************/
typedef enum{
UP_EVT,
DOWN_EVT,
ARM_EVT,
TICK_EVT
}EVENT_TYPE;
/***************************************
3.定义和状态机相关结构
***************************************/
struct bomb
{
uint8_t state;
uint8_t timeout;
uint8_t code;
uint8_t defuse_code;
}bomb1;
/***************************************
4.初始化状态机
***************************************/
void bomb1_init(void)
{
bomb1.state = SETTING;
bomb1.defuse_code = 6; //0110
}
/***************************************
5. 状态机事件派发
***************************************/
void bomb1_fsm_dispatch(EVENT_TYPE evt ,void* param)
{
switch(bomb1.state)
{
case SETTING:
{
switch(evt)
{
case UP_EVT: // "+" 按键按下事件
if(bomb1.timeout< 60) ++bomb1.timeout;
bsp_display(bomb1.timeout);
break;
case DOWN_EVT: // "-" 按键按下事件
if(bomb1.timeout > 0) --bomb1.timeout;
bsp_display(bomb1.timeout);
break;
case ARM_EVT: // "确认" 按键按下事件
bomb1.state = TIMING;
bomb1.code = 0;
break;
}
} break;
case TIMING:
{
switch(evt)
{
case UP_EVT: // "+" 按键按下事件
bomb1.code = (bomb1.code <<1) |0x01;
break;
case DOWN_EVT: // "-" 按键按下事件
bomb1.code = (bomb1.code <<1);
break;
case ARM_EVT: // "确认" 按键按下事件
if(bomb1.code == bomb1.defuse_code){
bomb1.state = SETTING;
}
else{
bsp_display("bomb!")
}
break;
case TICK_EVT:
if(bomb1.timeout)
{
--bomb1.timeout;
bsp_display(bomb1.timeout);
}
if(bomb1.timeout == 0)
{
bsp_display("bomb!")
}
break;
}
}break;
}
}
アドバンテージ:
シンプルで一貫したコードの読み取り、理解しやすい
欠点がある
ステートやイベントの数が増加すると、コードステート関数を頻繁に変更する必要があり、ステートイベント処理関数のコード量は増加し続けます。
ステートマシンはカプセル化されていないため、移植性が劣ります。
状態の開始および終了操作は実装されていません。ステートマシンでは入口と出口が特に重要です
Entry イベント: 入力されたときに 1 回だけトリガーされ、その主な機能は必要な状態を初期化することです。
Exit イベント: 状態が切り替わるときに 1 回だけトリガーされます。主な機能は、状態によって生成された中間パラメータをクリアし、次のエントリにクリーンな環境を提供することです。
状態テーブル
2次元状態遷移表
ステート マシンは状態とイベントに分けることができ、状態遷移はイベントによって駆動されるため、2 次元テーブルを使用して状態遷移を表現できます。
(*) 設定への移行は、(code == defuse_code) の場合にのみ発生します。
/*1.列出所有的状态*/
enum
{
SETTING,
TIMING,
MAX_STATE
};
/*2.列出所有的事件*/
enum
{
UP_EVT,
DOWN_EVT,
ARM_EVT,
TICK_EVT,
MAX_EVT
};
/*3.定义状态表*/
typedef void (*fp_state)(EVT_TYPE evt , void* param);
static const fp_state bomb2_table[MAX_STATE][MAX_EVENT] =
{
{setting_UP , setting_DOWN , setting_ARM , null},
{setting_UP , setting_DOWN , setting_ARM , timing_TICK}
};
struct bomb_t
{
const fp_state const *state_table; /* the State-Table */
uint8_t state; /* the current active state */
uint8_t timeout;
uint8_t code;
uint8_t defuse_code;
};
struct bomb bomb2=
{
.state_table = bomb2_table;
}
void bomb2_init(void)
{
bomb2.defuse_code = 6; // 0110
bomb2.state = SETTING;
}
void bomb2_dispatch(EVT_TYPE evt , void* param)
{
fp_state s = NULL;
if(evt > MAX_EVT)
{
LOG("EVT type error!");
return;
}
s = bomb2.state_table[bomb2.state * MAX_EVT + evt];
if(s != NULL)
{
s(evt , param);
}
}
/*列出所有的状态对应的事件处理函数*/
void setting_UP(EVT_TYPE evt, void* param)
{
if(bomb1.timeout< 60) ++bomb1.timeout;
bsp_display(bomb1.timeout);
}
アドバンテージ
ユーザーにとって各状態は比較的独立しているため、イベントや状態を追加しても、既存の状態イベント関数を変更する必要はありません。
ステート マシンはカプセル化できるため、移植性が優れています 関数ポインタの安全な変換 以下の機能を使用して、プライベート属性を持つステート マシンとイベントを拡張し、統一された基本的なステート マシン インターフェイスを使用できます
typedef void (*Tran)(struct StateTableTag *me, Event const *e); void Bomb2_setting_ARM (Bomb2 *me, Event const *e); typedef struct Bomb { struct StateTableTag *me; //必须为第一个成员 uint8_t private; }
欠点がある
最も明らかな欠点は、関数の粒度が小さすぎることです。1 つのステートと 1 つのイベントから関数が生成されます。ステートやイベントが多数になると、処理関数が急激に増加します。コードを読むときにロジックが分散します。
入口および出口アクションは実装されていません。
1次元状態遷移表
実装原則:
typedef void (*fp_action)(EVT_TYPE evt,void* param);
/*转换表基础结构*/
struct tran_evt_t
{
EVT_TYPE evt;
uint8_t next_state;
};
/*状态的描述*/
struct fsm_state_t
{
fp_action enter_action; //进入动作
fp_action exit_action; //退出动作
fp_action action;
tran_evt_t* tran; //转换表
uint8_t tran_nb; //转换表的大小
const char* name;
}
/*状态表本体*/
#define ARRAY(x) x,sizeof(x)/sizeof(x[0])
const struct fsm_state_t state_table[]=
{
{setting_enter , setting_exit , setting_action , ARRAY(set_tran_evt),"setting" },
{timing_enter , timing_exit , timing_action , ARRAY(time_tran_evt),"timing" }
};
/*构建一个状态机*/
struct fsm
{
const struct state_t * state_table; /* the State-Table */
uint8_t cur_state; /* the current active state */
uint8_t timeout;
uint8_t code;
uint8_t defuse_code;
}bomb3;
/*初始化状态机*/
void bomb3_init(void)
{
bomb3.state_table = state_table; //指向状态表
bomb3.cur_state = setting;
bomb3.defuse_code = 8; //1000
}
/*状态机事件派发*/
void fsm_dispatch(EVT_TYPE evt , void* param)
{
tran_evt_t* p_tran = NULL;
/*获取当前状态的转换表*/
p_tran = bomb3.state_table[bomb3.cur_state]->tran;
/*判断所有可能的转换是否与当前触发的事件匹配*/
for(uint8_t i=0;i<x;i++)
{
if(p_tran[i]->evt == evt)//事件会触发转换
{
if(NULL != bomb3.state_table[bomb3.cur_state].exit_action){
bomb3.state_table[bomb3.cur_state].exit_action(NULL); //执行退出动作
}
if(bomb3.state_table[_tran[i]->next_state].enter_action){
bomb3.state_table[_tran[i]->next_state].enter_action(NULL);//执行进入动作
}
/*更新当前状态*/
bomb3.cur_state = p_tran[i]->next_state;
}
else
{
bomb3.state_table[bomb3.cur_state].action(evt,param);
}
}
}
/*************************************************************************
setting状态相关
************************************************************************/
void setting_enter(EVT_TYPE evt , void* param)
{
}
void setting_exit(EVT_TYPE evt , void* param)
{
}
void setting_action(EVT_TYPE evt , void* param)
{
}
tran_evt_t set_tran_evt[]=
{
{ARM , timing},
}
/*timing 状态相关*/
アドバンテージ
ユーザーにとって各状態は比較的独立しているため、イベントや状態を追加しても、既存の状態イベント関数を変更する必要はありません。
状態の開始と終了を実装しました
状態遷移図に従って設計するのが簡単です(状態遷移図は各状態の遷移可能性を列挙したもので、ここでは遷移表となります)
実装は柔軟で、最終状態などの複雑なロジックも実現でき、監視条件を増やすことでイベント数を削減できます。完全なイベント駆動型ではない
欠点がある
関数の粒度は小さい (2 次元より小さく、成長が遅い) 各状態には少なくとも 3 つの関数が必要であり、すべての変換関係をリストする必要があることがわかります。
QP 組み込みリアルタイム フレームワーク
特徴
イベント駆動型プログラミング
ハリウッドの原則: 「ハイパーループ」などの従来の逐次プログラミング手法や従来の RTOS タスクとは異なります。最新のイベント駆動型システムのほとんどは、ハリウッドの原則に従って構造化されています (電話しないでください。電話します。)。
オブジェクト指向
クラスと単一継承。
道具
QM は、UML クラス図を通じてステート マシンを記述し、C コードを自動的に生成できるソフトウェアです。
QS ソフトウェア追跡ツール:
QEP は有限状態マシン Fsm を実装します
/* 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();
}
アドバンテージ
オブジェクト指向設計手法、優れた移植性
実装されたエントリおよびエグジットアクション
適切な粒度、およびイベントの粒度は制御可能
状態切り替え時にポインタを変更することで効率が高くなります
階層型ステートマシンになるように拡張可能
欠点がある
設計における最大の困難は、イベントの定義とイベント粒度の制御です。たとえば、シリアル ポートがデータのフレームを受信した場合、これらの変数の更新は単独のイベント、またはシリアル ポートが受信したデータとみなされます。イベントとして扱われます。または、このプログラミング方法を使用する場合の表示画面、イベントのデザイン方法。
QP は階層型ステート マシン Hsm の簡単な紹介を実装します。
初期化:
初期化レベル ステート マシンの実装: 初期化中、ユーザーが選択した状態は常に一番下の状態です。上図に示すように、電卓をオンにした後、開始状態に入らなければなりませんが、これには問題があります。(先頭の状態) 開始する状態切り替えパスがあります。開始する状態を設定する場合、このパスをどのように検索するかが鍵になります (パスが正しく開始できること、および遷移状態の開始イベントと終了イベントがわかっていること)パスを実行する必要があります)
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 */
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 */
....
QPリアルタイムフレームワークの構成
メモリ管理
メモリ プールを使用すると、低パフォーマンスの MCU ではメモリが非常に制限されます。メモリ管理の導入は主にアーキテクチャ全体に行われます。イベントはタスク通信の主な手段として使用され、イベントにはパラメータがあります。同じ種類のイベントイベント処理完了後はイベントをクリアする必要があり、静的イベントは使用できないため、イベントごとにメモリプールを作成する必要があります。
ブロック サイズが異なるメモリ プールの場合、考慮する必要があるのは、各ブロックの開始アドレスの位置合わせです。メモリプールを初期化する際、ブロックサイズ+ヘッダーサイズに応じてメモリプールを分割します。2バイトの構造体を想定し、MCUが4バイトアラインだと仮定して2で割ると、構造体の開始アドレスの半分がアラインされなくなります。このとき、ブロックごとにスペースを確保する必要があります。各ブロックが揃っていることを確認してください。
イベントキュー
各アクティブ オブジェクトはイベント キューを維持します。イベントは基本イベントから派生します。さまざまなタイプのイベントは、基本イベント メンバーをアクティブ オブジェクト キューに追加するだけで済み、最後に、それらを取り出すときに必須の変換を渡します。追加のパラメーターを取得します。
イベントディスパッチ
イベントの直接送信:
QActive_postLIFO()
パブリッシュ・サブスクライブ・イベント送信:
縦軸は信号 (イベントの基本クラス) を表します。
アクティブ オブジェクトは 64 の優先順位をサポートし、各アクティブ オブジェクトには一意の優先順位が必要です
優先順位ビットは、どのアクティブ オブジェクトがイベントにサブスクライブされているかを示し、イベントがトリガーされた後、優先順位に従ってイベントをアクティブ オブジェクトにディスパッチします。
時限イベント
順序付けされていないリンク リスト:
協調スケジューラ QV:
QP nano の紹介
最大 4 レベルの状態ネストまでのあらゆる状態遷移トポロジーの入口/出口アクションの保証を含む、階層状態ネストの完全なサポート
最大 8 つの同時実行、確定的、スレッドセーフなイベント キューをサポートするアクティブ オブジェクト57
1 バイト幅の信号 (255 信号) と、0 (パラメータなし)、1、2、または 4 バイトとして構成できるスケーラブルなパラメータをサポートします。
先入れ先出し FIFO キューイング戦略を使用した直接イベント ディスパッチ メカニズム
各アクティブ オブジェクトには、0 (タイム イベントなし)、1、2、または 4 バイトの構成可能なダイナミック レンジを持つワンショット タイム イベント (タイマー) があります。
組み込みの協調バニラカーネル
QK-nano という名前の組み込みプリエンプティブル RTC カーネル (第 6 章、セクション 6.3.8 を参照)
省電力モードを簡単に実装できるアイドル コールバック関数を備えた低電力アーキテクチャ。
一般的なローエンド CPU アーキテクチャ用の C コンパイラへの非標準拡張用のコードで準備されています (例: コード空間での定数オブジェクトの割り当て、リエントラント関数など)
アサーションベースのエラー処理戦略
コードスタイル:
この記事はここで共有されていますので、お役に立てれば幸いです。
出典: https://blog.csdn.net/qq_36969440/article/details/110387716
免責事項:この記事の内容はインターネットから得たものであり、著作権は元の著者に属します。著作権上の問題が含まれる場合は、削除するよう私に連絡してください。
------------ 終了 ------------
公式アカウントに注意し、ルールに従って技術交流グループに参加するには「 Jiagroup 」と返信し、さらに多くのコンテンツを表示するには「 1024」と返信します。
さらに共有を確認するには、「元のテキストを読む」をクリックしてください。