真没想到还可以这样写状态机!QP嵌入式实时框架

小麦大叔 2025-03-17 08:01

1 QP嵌入式实时框架

事件驱动型编程

好莱坞原则:和传统的顺序式编程方法例如“超级循环”,或传统的RTOS 的任务不同。绝大多数的现代事件驱动型系统根据好莱坞原则被构造(Don’t call me; I’ll call you.)

QP官网:http://www.state-machine.com/

面向对象

类和单一继承:

图片

工具

QM :一个通过UML类图来描述状态机的软件,并且可以自动生成C代码

图片

QS软件追踪工具:

图片
图片

2 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();
  }

优点:

  • 采用面向对象的设计方法,很好的移植性
  • 实现了进入退出动作
  • 合适的粒度,且事件的粒度可控
  • 状态切换时通过改变指针,效率高
  • 可扩展成为层次状态机

缺点:

  • 对事件的定义以及事件粒度的控制是设计的最大难点,如串口接收到一帧数据,这些变量的更新单独作为某个事件,还是串口收到数据作为一个事件。再或者显示屏,如果使用此种编程方式,如何设计事件。

3 QP实现层次状态机

图片

初始化层次状态机的实现:在初始化时,用户所选取的状态永远是最底层的状态,如上图,我们在计算器开机后,应该进入的是开始状态,这就涉及到一个问题,由最初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 */
    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 */

                    ....

4 QP实时框架的组成

图片
图片

内存管理

使用内存池,对于低性能mcu,内存极为有限,引入内存管理主要是整个架构中,是以事件作为主要的任务通信手段,且事件是带参数的,可能相同类型的事件会多次触发,而事件处理完成后,需要清除事件,无法使用静态的事件,因此是有必要为不同事件创建内存池的。对于不同块大小的内存池,需要考虑的是每个块的起始地址对齐问题。在进行内存池初始化时,我们是根据blocksize+header大小来进行划分内存池的。假设一个2字节的结构,如果以2来进行划分,假设mcu 4字节对齐,那么将有一半的结构起始地址无法对齐,这时需要为每个块预留空间,保证每个块的对齐。

图片

事件队列

每一个活动对象维护一个事件队列,事件都是由基础事件派生的,不同类型的事件只需要将其基础事件成员添加到活动对象的队列中即可,最终在取出的时候通过一个强制转换便能获得附加的参数。

图片

事件派发

  • 直接事件发送 QActive_postLIFO()
  • 发行订阅事件发送 竖轴表示信号(为事件的基类) 活动对象支持64个优先级,每一个活动对象要求拥有唯一优先级 通过优先级的bit位来表示某个事件被哪些活动对象订阅,并在事件触发后根据优先级为活动对象派发事件。
图片

代码风格

图片


一键三连,公众号后台回复【QP】可以获取资料


最后

🫵兄弟们!一个人单打独斗确实能冲得挺快,但要想走得更远、更稳,还得靠一群志同道合的伙伴啊!

👊 麦鸽的知识星球现在已经聚集了一波人,大家都在这里互相学习、共同进步。


如果你也想找个靠谱的学习圈子,赶紧   戳链接 🔗 加入我们吧!

在这里,你能读到星球专栏的干货,优质教程,练手项目,随时向麦鸽提问,还能帮你定制学习计划。别犹豫了,兄弟,一起冲!💪



往期推荐



看完315,拆开了某夕热销的驱鼠神器,我沉默了...

按键驱动别再用delay消抖啦!这个开源库把GPIO玩出花 —— lwbtn

嵌入式开发必学 | 状态机常用的几种骚操作

推荐一款Q弹,丝滑,酸爽的轻量级GUI菜单框架——MiaoUI,完美适配资源紧张的单片机


小麦大叔 一位热衷技术的攻城狮,懂点技术,会讲故事,交个朋友?
评论 (0)
  • 文/Leon编辑/cc孙聪颖‍蛇年春晚最有意思的节目,一定非机器人跳舞莫属。就算是遥控或预编程,机器人能够做出如此复杂的动作,在五年前都是不敢想象的事情,其制造商宇树科技也因此火爆全网。就在春节过后不到一个月,会骑自行车的人形机器人诞生了。这背后,是近年来“具身智能”概念的迅猛发展。“我们造了一个跟人一样灵动的机器人!”3月11日,智元机器人联合创始人兼首席技术官彭志辉在微博上说道。在视频中,灵犀X2会骑自行车、能跳《科目三》,还可以与人促膝长谈,甚至拿起葡萄“穿针引线”。在全球人形机器人领域,
    华尔街科技眼 2025-03-17 12:38 112浏览
  • 随着汽车行业逐步迈向电气化和电池动力,位置传感器以及其他长期在车辆中被忽视但却至关重要的小型元器件正逐渐成为关注的焦点。某些电子元器件常常吸引大量关注,例如如今用于训练AI模型的强大GPU几乎每天都出现在新闻中。而其他元器件则默默地执行着重要但鲜为人知的功能。艾迈斯欧司朗一些历史悠久的产品线便隶属于后者,其中包括磁性和电感式位置传感器、电容式传感器和电池监控芯片。工业泵和风扇等产品的制造商利用位置传感器实现电动机高效平稳运行。在车辆的方向盘中安装电容传感器可以保障安全,它可以用于在辅助驾驶模式下
    艾迈斯欧司朗 2025-03-17 22:22 68浏览
  •        在工业视觉检测线上,一台搭载传统图像传感器的机器人因高温导致图像噪点激增,误将合格零件判定为瑕疵品,每小时损失超10万元;在深夜的安防监控画面中,模糊的噪点让犯罪分子身影难以识别,导致案件侦破延迟—— 噪声,已成为图像传感器行业的“无声杀手”。据Yole统计,全球约35%的工业检测误差源于传感器噪声干扰,而安防场景下60%的有效信息因低照度噪点丢失。传统方案试图通过单一优化像素或电路来降噪,却陷入“按下葫芦浮起瓢”的困境。  &nb
    中科领创 2025-03-18 10:24 47浏览
  • 新兴的个人健康监测技术为何在医疗场景和日常生活中越来越受到青睐?为了准确回答这个关键问题,我们首先需要理解三个全球性趋势:如今,几乎人手一部智能手机,这等于随身携带了一台高性能计算机、一个全天候运行的智能医疗传感器中心,还有一块显示屏。发达工业国家的人口正在迅速老龄化,而老年群体的疾病发病率较高。与此同时,年轻人也比过去更加关注如何延长健康寿命。这些人群以及服务他们的医务人员可以利用新技术来优化生活方式,合理调控运动、饮食、睡眠和压力等关键因素,帮助他们作出更健康的生活选择。如摩尔定律所预言,半
    艾迈斯欧司朗 2025-03-17 21:50 73浏览
  • 在招聘合适的人才时,清晰度至关重要。想要找到合适的人选,并确保他们在岗位上取得成功,第一步就是明确职位的关键绩效指标(KPI)和预期成果。但光有这些还不够,如何判断候选人是否具备必要的特质?这时,KSA模型就派上用场了。它是一个简单但强大的方法,能帮助你聚焦于真正影响岗位表现的关键要素。今天,我想和你聊聊这个模型,帮你更轻松地为合适的候选人设定合适的KPI。了解KSA模型KSA代表知识(Knowledge)、技能(Skills)和态度(Attitude),是评估候选人是否适合岗位的三个关键维度。
    优思学院 2025-03-18 15:03 49浏览
  • 在工业4.0与智能制造深度融合的今天,设备实时性、稳定性和成本效益成为企业核心竞争力的关键。触觉智能将基于RK3506平台,分享工业应用方案,本期为大家带来DSMC串行接口在数控行业的应用。DSMC技术解析底层架构突破双倍数据速率:通过上升沿与下降沿双重触发机制,实现单周期内2倍数据吞吐量,较传统SPI接口效率提升300%。多通道并行:支持8线/16线位宽可配置模式,满足多轴协同场景下的同步通信需求。性能实测标杆超低延迟:FPGA互联场景下,写延时小于75ns,读延时小于260ns,相比PCIe
    Industio_触觉智能 2025-03-18 11:46 53浏览
  • 近期,据全球物联网市场调研机构IoT Analytics公布数据显示,2025年全球物联网设备连接数预计将突破200亿,同比增长约14%,物联网技术正以稳定上升态势向工业自动化、智慧城市、智慧农业与智慧家居等领域纵深推进。在多样化的应用场景和复杂环境需求的驱动下,物联网无线通信技术的运行功耗、传输距离和频段兼容性正受到前所未有的关注。为增加物联网通信模块的配置灵活度,消除物联网设备的“连接焦虑”,华普微重磅推出了一款自主研发的超低功耗、可兼容Sub-GHz与2.4GHz 双频段的高性能LoRa
    华普微HOPERF 2025-03-18 15:43 48浏览
  • 一、问题现象:语音播放异常的典型表现在使用WT588F(E)系列语音芯片的开发过程中,工程师常会遇到以下两类典型异常现象:播放不全:语音仅播放前段内容后突然中断,或特定段落无法触发播放断续:音频输出存在明显卡顿、爆音或波形畸变某智能门锁项目实测数据显示,在首批样机中有2%的设备出现语音提示突然中断的情况,经排查发现电源电压在播放瞬间跌落至2.0V(低于芯片工作阈值)。这类问题的根源往往隐藏于硬件设计与系统协同的细节之中。二、核心机理:电压稳定性对语音芯片的影响2.1 电源系统的动态响应特性WT5
    广州唯创电子 2025-03-17 09:18 127浏览
  • 晨穹电子一家专业从事研发、生产、销售各类传感器为一体的高新科技企业。1 人赞同了该文章在工业4.0、智能家居、新能源汽车等场景中,传感器作为数据采集的核心器件,其抗电磁干扰(EMC)能力直接影响系统可靠性。尤其在5G通信、高功率电机、无线充电等复杂电磁环境下,传感器的信号失真问题愈发突出。本文结合MEMS传感器、物联网(IoT)设备、边缘计算等热度技术,解析提升传感器抗干扰能力的6大策略。 一、电磁干扰对传感器的威胁; 1、电磁干扰(EMI)会导致传感器出现 。2、信号跳变(
    传感器晨穹 2025-03-18 09:28 79浏览
  • 失效模式与影响分析(FMEA)失效模式与影响分析(FMEA)是一种系统方法,用于识别和分析系统或过程中的潜在失效,广泛应用于工程和制造领域,以提高产品可靠性和安全性。最新标准由 2019 年发布的 AIAG-VDA FMEA 手册(第一版) 定义,该手册结合了美国和欧洲汽车行业的最佳实践,并引入了 七步法,确保分析全面且结构化。图:优思学院六西格玛新版 FMEA 失效分析的七个步骤1. 规划与准备确定 FMEA 研究的 范围、边界和目标。组建跨职能团队(设
    优思学院 2025-03-17 14:43 114浏览
  • 在制药行业中,生产工艺的精准控制与产品质量安全密切相关。随着制药工业4.0的发展,传感器作为生产流程的"感知器官",在确保合规性、提升效率、降低风险方面发挥着不可替代的作用。本文将以晨穹电子科技(以下简称"晨穹")的压力、温度、流量及液位传感器为例,解析制药厂关键工艺流程中的传感器应用场景及技术要求。一、制药核心工艺流程中的传感器需求1. 原料处理与配液系统液位监测:储罐内原料液位实时监控需使用卫生型液位计。晨穹磁翻板液位计采用316L不锈钢材质,具备CIP/SIP(在线清洗/灭菌)耐受性,符合
    传感器晨穹 2025-03-18 15:51 52浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦