找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

409

积分

0

好友

57

主题
发表于 昨天 01:55 | 查看: 7| 回复: 0

状态机是嵌入式系统开发中用于管理复杂逻辑和行为的重要模型。理解其基本语法是设计和实现的基础。

现态:是指当前所处的状态。 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。 动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

图片

传统有限状态机(FSM)实现方法

图片

如图所示,是一个定时计数器,计数器存在两种状态:设置状态和计时状态。

设置状态: “+”、“-”按键对初始倒计时进行设置。当计数值设置完成,点击确认键启动计时,即切换到计时状态。

计时状态: 按下“+”、“-”会进行密码的输入。“+”表示1,“-”表示输入0,密码共有4位。 确认键:只有输入的密码等于默认密码,按确认键才能停止计时,否则计时直接到零,并执行相关操作。

嵌套switch-case实现

这是一种最直观的实现方式。

/***************************************
     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)和退出(Exit)操作。这在复杂的系统设计中尤为重要。
    • 进入事件:只会在刚进入状态时触发一次,主要用于对状态进行必要的初始化。
    • 退出事件:只会在状态切换时触发一次,主要用于清除状态产生的中间参数,为下次进入提供干净环境。

基于状态表的实现

状态机的核心组成包括状态和事件,状态的跃迁由事件驱动,因此可以通过一个二维表格来表示状态跃迁关系,这是一种在网络协议和系统控制中常见的设计模式。

图片 (*) 仅当( code == defuse_code) 时才发生到setting的转换。

二维状态转换表
/*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 (*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 状态相关*/

优点

  • 状态处理独立,易于扩展。
  • 实现了状态的进入和退出动作。
  • 设计灵活,可根据状态跃迁图直观设计,可实现非完全事件驱动和监护条件。

缺点

  • 函数粒度依然偏小,每个状态至少需要3个函数(进入、退出、动作),还需维护转换关系表。

QP嵌入式实时框架

QP (Quantum Platform) 是一个专门为嵌入式系统设计的、基于事件驱动的主动对象框架。

主要特点
  1. 事件驱动型编程:遵循“好莱坞原则”(Don’t call me; I’ll call you),与传统的顺序式“超级循环”或普通RTOS任务不同。
  2. 面向对象:支持类和单一继承,提高代码的模块化和复用性。
  3. 丰富工具链
    • QM:通过UML类图描述状态机,并可自动生成C/C++代码。
    • QS:软件追踪工具,用于系统调试和运行状态监控。

图片 图片 图片 图片

QP实现有限状态机(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*/
   }
}

/****************** 使用QP实现上述定时器例子 ******************/
#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();
}

优点

  • 采用面向对象设计,封装性好,移植性高。
  • 内置支持状态的进入(Q_ENTRY_SIG)和退出(Q_EXIT_SIG)动作。
  • 函数粒度适中(每个状态一个处理函数),且事件处理逻辑集中。
  • 状态切换通过改变函数指针实现,效率高。
  • 易于扩展为层次状态机(Hsm)。

缺点

  • 设计难点在于事件的定义和粒度控制。例如,是将“串口接收一帧数据”作为一个事件,还是将“更新某个变量”作为独立事件,需要根据具体应用权衡。

QP实现层次状态机(Hsm)简介

层次状态机允许状态嵌套,子状态可以继承父状态的行为,从而避免代码重复,是解决复杂状态逻辑的常用设计模式

图片

初始化图片 初始化时,需要从顶状态(Top)钻取(Drill-down)到指定的初始状态(如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;
}

状态切换图片 状态切换时,需要找到源状态和目标状态的最近共同祖先(LCA),然后退出源状态路径上的状态,再进入目标状态路径上的状态。

/*.................................................................*/
QState result(Calc *me, QEvent const *e)
{
    switch (e->sig)
    {
        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 */
   }
   . . . // 查找LCA并执行进入动作的代码
 }
 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实时框架的组成

QP不仅仅是一个状态机实现库,它提供了一个完整的并发模型和运行时框架。 图片 图片

1. 内存管理

使用内存池管理事件对象。对于内存有限的MCU,需要精心设计内存池块的大小和对齐,以避免内存浪费和访问错误。 图片

2. 事件队列

每个活动对象(Active Object)维护一个事件队列(FIFO)。所有事件都派生自基础事件QEvent,入队和出队时通过强制类型转换来传递附加参数。 图片

3. 事件派发
  • 直接事件发送:如QActive_postLIFO(),直接将事件发送给特定活动对象。
  • 发布-订阅事件发送:活动对象可以订阅特定信号。事件发生时,框架根据订阅关系将事件派发给所有订阅者。活动对象具有唯一优先级。 图片
4. 定时事件

通过时间事件链表管理。合作式调度器(QV)在每个循环中检查并触发到期的定时事件。 图片 图片

5. QP-nano简介

QP-nano是QP框架的精简版本,专为资源极其有限的8/16位MCU设计,保留了核心功能:

  • 完全支持层次状态机(最多4层嵌套)。
  • 支持最多8个活动对象。
  • 信号宽度为1字节(255个信号)。
  • 包含合作式(QV)和可抢占式(QK-nano)内核。
  • 内置低功耗支持。

代码风格

QP框架拥有自己一套清晰、一致的代码风格约定。 图片 图片 图片 图片 图片

参考资料

  • QP官方网站: www.state-machine.com



上一篇:Linux设备驱动实战:MMIO内存映射I/O访问指南与嵌入式开发避坑
下一篇:AI项目高失败率根源:破解“大公司病”的组织变革与实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-8 19:33 , Processed in 0.084719 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表