什么是QP?

上一个博客我提到了在复杂程序中使用状态机的好处,可以使复杂的逻辑在编程时更加清晰,上文中时用到的是FSM有限状态机框架,但是在更加复杂的情况下,可以使用层次状态机,说白了就是状态机中的状态机,最近这两天都在看这个框架,但是以后估计不会用到了,写一篇博客来记录一下学习过程。

使用在STM32上可以使用QPC或者是QPN(QP-nano),这篇博客主要是讲一下怎样移植到STM32上,其中QP-nano作为一个精简版的QP,API使用数量大大降低,资源消耗也大大减少,适合运行在8位机或者是16位机上。

QP-nano移植,先把git上面的代码包下载先来https://github.com/QuantumLeaps/qpn,文件夹中主要包含这几个代码文件夹:
1.png

其中3rd_party是关于第三方平台移植的,与三方硬件相关,我们是将其移植到我们的工程中,所以不用管他;
doxygen是与某种注释相关,我没了解过,既然是注释,也不用管他;
examples是在各个平台上的示例,我们可以参考他写我们自己的demo;
include是我们需要的,里面包含头文件;
ports也是我们需要的,需要保留;
src就不用说了,源码文件夹,我们也需要。

需要注意的是,QPn有两种模式,QK与QV,其中QV是以优先级为主,QK是以抢占为主。
我们在这里使用QV,我们在工程中新建qpn文件夹,创建include、ports、src文件夹,将git里面的include文件夹、ports中的对应平台文件夹中QV文件夹中的代码分别复制到我们工程中的include、ports里面,src需要注意,因为我们使用的是QV所以QKN文件夹中的源码就不要往过放了,避免造成冲突,最终src文件夹中的目录应该是这样的:

2.png

在example文件夹中找到对应的qpn_conf.h,然后在工程中导入一下头文件夹与源文件夹路径,编译一下,应该不会报错,到了这里我们应该就把大体框架移植过来了,剩下的就是需要做开发了,新建APP文件夹,添加这些文件,其实也可以参考着历程里面对应的框架写。

3.png

这个例程完成的是一个点灯的demo,首先完成相应的偷吻加调用,这里就不多解释了,打字挺累人的,然后需要给他一个心跳,我把它放在系统时钟中断里面。

4.png

然后疯狂的ctrl+c+v,其中自己先新建一个状态机,必须要继承super,根据需求增加自己的参数

typedef struct BlinkyTag {  
    QActive super;         
    uint16_t score;
} Blinky;

并且新建一个对象:Blinky AO_Blinky;

建立状态机创建函数:

void Blinky_ctor(void) {
    Blinky * const me = &AO_Blinky;
    QActive_ctor(&me->super, Q_STATE_CAST(&Blinky_initial));
}

创建完成后会自动进入一个Blinky_initial初始化状态,在这个函数中启动一个定时器,每500ms触发一次,然后转移状态:

QState Blinky_initial(Blinky * const me) {
    QActive_armX((QActive *)me, 0U,BSP_TICKS_PER_SEC/2U, BSP_TICKS_PER_SEC/2U);
    return Q_TRAN(&Blinky_off);
}

下面分别是开关灯的状态机:

QState Blinky_off(Blinky * const me) {
    QState status;
    switch (Q_SIG(me)) {
        case Q_ENTRY_SIG: {
            BSP_ledOff();
            status = Q_HANDLED();
            break;
        }
        case Q_TIMEOUT_SIG: {
            printf("led on!!!\r\n");
            status = Q_TRAN(&Blinky_on);
            break;
        }
        default: {
            status = Q_SUPER(&QHsm_top);
            break;
        }
    }
    return status;
}
QState Blinky_on(Blinky * const me) {
    QState status;
    switch (Q_SIG(me)) {
        case Q_ENTRY_SIG: {
            BSP_ledOn();
            status = Q_HANDLED();
            break;
        }
        case Q_TIMEOUT_SIG: {
            printf("led off!!!\r\n");
            status = Q_TRAN(&Blinky_off);
            break;
        }
        default: {
            status = Q_SUPER(&QHsm_top);
            break;
        }
    }
    return status;
}

第一次看着有点懵,其实也就是超时完成后触发状态转移,然后在第一次进入的时候改变灯光的状态,然后再次等待超时出发,熟悉了使用时还是很简单的,在任务中想给别的状态机发送消息使用QACTIVE_POST函数,第一个参数是目标状态机、第二个参数是状态、第三个参数是参数。到这里已经大致完成了状态机的使用方法,今天下班了先回家了,拜拜~

补充一下:还有其他的一个问题,用户自定义的状态量不能和QP库的状态量重复,可以这样定义:

enum BlinkySignals {
    DUMMY_SIG = Q_USER_SIG,
    MAX_PUB_SIG,          /* the last published signal */
    GET_MSG,
    TIMEOUT_SIG,
    MAX_SIG               /* the last signal */
};