状态机是嵌入式开发中常见的一种方法,但状态机的形式有很多,这里给大家分享一下经典的qp框架原理。
状态机基本术语
现态:是指当前所处的状态。条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。 动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
传统有限状态机fsm实现方法
如图,是一个定时计数器,计数器存在两种状态,一种为设置状态,一种为计时状态
设置状态
“+” “-” 按键对初始倒计时进行设置当计数值设置完成,点击确认键启动计时 ,即切换到计时状态
计时状态
按下“+” “-” 会进行密码的输入。“+”表示1 ,“-”表示输入0 ,密码共有4位 确认键:只有输入的密码等于默认密码,按确认键才能停止计时,否则计时直接到零,并执行相关操作
嵌套switch
/*************************************** 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 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 < 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.timeouttran; /*判断所有可能的转换是否与当前触发的事件匹配*/ for(uint8_t i=0;ievt == 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嵌入式实时框架
特点
事件驱动型编程
好莱坞原则:和传统的顺序式编程方法例如“超级循环”,或传统的rtos 的任务不同。绝大多数的现代事件驱动型系统根据好莱坞原则被构造,(don’t call me; i’ll call you.)
面向对象
类和单一继承。
工具
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 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 code 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简介
初始化:
初始化层次状态机的实现:在初始化时,用户所选取的状态永远是最底层的状态,如上图,我们在计算器开机后,应该进入的是开始状态,这就涉及到一个问题,由最初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)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 */ }img 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,内存极为有限,引入内存管理主要是整个架构中,是以事件作为主要的任务通信手段,且事件是带参数的,可能相同类型的事件会多次触发,而事件处理完成后,需要清除事件,无法使用静态的事件,因此是有必要为不同事件创建内存池的。
对于不同块大小的内存池,需要考虑的是每个块的起始地址对齐问题。在进行内存池初始化时,我们是根据blocksize+header大小来进行划分内存池的。假设一个2字节的结构,如果以2来进行划分,假设mcu 4字节对齐,那么将有一半的结构起始地址无法对齐,这时需要为每个块预留空间,保证每个块的对齐。
事件队列
每一个活动对象维护一个事件队列,事件都是由基础事件派生的,不同类型的事件只需要将其基础事件成员添加到活动对象的队列中即可,最终在取出的时候通过一个强制转换便能获得附加的参数。
事件派发
直接事件发送:
qactive_postlifo()
发行订阅事件发送:
竖轴表示信号(为事件的基类)
活动对象支持64个优先级,每一个活动对象要求拥有唯一优先级
通过优先级的bit位来表示某个事件被哪些活动对象订阅,并在事件触发后根据优先级为活动对象派发事件。
定时事件
非有序链表:
合作式调度器qv:
qp nano的简介
完全支持层次式状态嵌套,包括在最多4 层状态嵌套情况下,对任何状态转换拓扑的可保证的进入/ 退出动作
支持高达8 个并发执行的,可确定的,线程安全的事件队列的活动对象57
支持一个字节宽( 255 个信号)的信号,和一个可伸缩的参数,它可被配置成0 (没有参数), 1 , 2 或4 字节
使用先进先出fifo排队策略的直接事件派发机制
每个活动对象有一个一次性时间事件(定时器),它的可配置动态范围是0(没有时间事件) , 1 , 2 或4 字节
内建的合作式vanilla 内核
内建的名为qk-nano 的可抢占型rtc内核(见第六章6.3.8节)
带有空闲回调函数的低功耗架构,用来方便的实现节省功耗模式。
在代码里为流行的低端cpu架构的c编译器的非标准扩展进行了准备(例如,在代码空间分配常数对象,可重入函数,等等)
基于断言的错误处理策略
代码风格:
numactl内存绑定中代码段的问题
继CES 2023之后,研扬科技宣布增加基于最新NVIDIA Jetson Orin系统化模块的AI边缘 Box PC系列
腾讯优图TNN助力深度学习提速增效,在手机端进行AI APP开发
硬盘性能指标概述
CMOS开发多模应用的RF收发器
经典的QP框架原理
WT588F02A-16S录音芯片的功能特点
英特尔人工智能创新应用大赛开启!为更多用户带来 AI PC 生产力及娱乐体验跃升
Via与May Mobility合作试点自动驾驶出行服务
如何计算DC-DC的输入电容Cin与输出电容Cout
物联网技术在工业领域加速落地,众企业携手合作成为未来的发展方向
风向风速仪主机设计原理
采用Zynq UltraScale+ MPSoC EV器件进行4K编码和解码操作
单片机痴狂者的DIY:LED光立方制作
面向1200V功率应用的异质衬底横向和垂直GaN器件发展趋势
在博途中DB块不重新初始化,怎么进行下载呢?
Apple Watch系列产品都将载入睡眠跟踪功能
工程师盘点MacBook与PC厂商的竞逐
自动驾驶系统功能自车运动与路径规划介绍
断路器的作用