引言

简中网上对于QP的说明大部分挺停留在各种概念说明和概念对比,令人一头雾水,看了好像懂了,但是又不知道怎么做。因此本文不会从盘古开天辟地说起,也不会介绍状态机、RTOS、事件驱动啥的各种概念,仅仅介绍入门级别QP框架结合QM图形化工具的使用。

实现项目:密码门禁系统

​​1、用户输入(模拟按键):​​

  • 0-9 : 数字输入
  • *: 确认/开门 (模拟确认键)
  • # : 清除输入/复位 (模拟清除键)

2、​​系统显示(控制台输出):​​

  • 显示当前输入的密码(例如 **** 对应4位输入)
  • 显示系统状态(Ready, Entering, Correct, Wrong, Locked)

3、​​核心逻辑:​​

  • 预设一个简单密码(如 1234)。
  • 用户输入数字:显示 Entering 状态,并显示当前输入密码
  • 用户按 #:清除所有已输入数字,显示 Ready。
  • 用户按 *:检查输入密码。
  • 密码正确:显示 Correct 几秒,然后恢复 Ready。
  • 密码错误:显示 Wrong 几秒,错误计数+1。
  • 连续错误达到3次:系统 Locked(倒计时5秒),期间不接受任何输入。倒计时结束恢复 Ready,错误计数清零。

(其实这个项目用switch更简单)

分析项目

将项目抽象成外部控制输入(事件),项目状态即可,这部分需确保定义能够涵盖需求。
该项目仅有一个活动对象:DoorLock_AO,负责读取按键、处理逻辑、管理状态、控制显示
事件定义:

  • 数字键按下
  • 确认键按下
  • 清除键按下
  • 定时器超时

状态定义:

  • 初始状态(Ready)
  • 输入状态(Entering)
  • 校验状态(Verifying)
  • 正确状态(Correct)
  • 错误状态(Wrong)
  • 锁机状态(Locked)

QM工具使用

在这里插入图片描述
model就是project,包含活动对象、状态机、事件、层次等图形化设计元素。
在这里插入图片描述
package本质是一个分组,用于大型模型的分解,具有命名空间、复用性的特点,一个包可以包含子包、活动对象、状态机、事件、数据类型、端口
在这里插入图片描述
class遵循UML类的定义,包含属性、操作、状态机

在这里插入图片描述
选择为层次状态机

在这里插入图片描述
添加状态机
在这里插入图片描述
打开状态绘制界面,画图(图中代码不重要,后期自己改动很大)
在这里插入图片描述
添加文件夹,这个文件夹用于存放生成的代码
在这里插入图片描述
其中需要手动实现构造函数,实现week定义的QF框架函数。
其中$declare${package1::DoorLock}和$define${package1::DoorLock}表示需要自动生成状态机的声明和定义。
在这里插入图片描述

代码

编写完成后生成代码。
完整代码(通过AI生成了注释)如下:

//$file${.::doorlock.c} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
//
// Model: homework1.qm
// File:  ${.::doorlock.c}
//
// This code has been generated by QM 7.0.1 <www.state-machine.com/qm>.
// DO NOT EDIT THIS FILE MANUALLY. All your changes will be lost.
//
// Copyright (c) 2005 Quantum Leaps, LLC. All rights reserved.
//
//                 ____________________________________
//                /                                   /
//               /    GGGGGGG    PPPPPPPP   LL       /
//              /   GG     GG   PP     PP  LL       /
//             /   GG          PP     PP  LL       /
//            /   GG   GGGGG  PPPPPPPP   LL       /
//           /   GG      GG  PP         LL       /
//          /     GGGGGGG   PP         LLLLLLL  /
//         /___________________________________/
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This generated code is open-source software licensed under the GNU
// General Public License (GPL) as published by the Free Software Foundation
// (see <https://www.gnu.org/licenses>).
//
// NOTE:
// The GPL does NOT permit the incorporation of this code into proprietary
// programs. Please contact Quantum Leaps for commercial licensing options,
// which expressly supersede the GPL and are designed explicitly for
// closed-source distribution.
//
// Quantum Leaps contact information:
// <www.state-machine.com/licensing>
// <info@state-machine.com>
//
//$endhead${.::doorlock.c} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// 定义系统时钟节拍分辨率(每秒10个节拍,即100ms一个节拍)
#define QF_TICKS_PER_SEC 10

// 包含QP框架核心头文件
#include "qpc.h"

// 标准库头文件
#include <stdlib.h>
#include <stdio.h>
#include "safe_std.h"  // 安全标准I/O函数
#include <time.h>      // 时间函数
#include <conio.h>     // 控制台输入函数(_kbhit, _getch)
#include <windows.h>   // Windows API(Sleep函数)

/*
 * 信号定义 - QP框架中的事件标识符
 *
 * QP框架使用信号(Signals)来标识不同类型的事件。
 * 所有用户自定义信号必须从Q_USER_SIG开始定义,以避免与系统保留信号冲突。
 */
enum DoorLockSignals {
    TIMEOUT_SIG = Q_USER_SIG, // 定时器超时信号(必须从Q_USER_SIG开始)
    DIGIT_KEY_SIG,            // 数字键按下事件
    ENTER_KEY_SIG,            // 确认键(*)按下事件
    CLEAR_KEY_SIG,            // 清除键(#)按下事件
    MAX_PUB_SIG               // 最后一个信号(用于边界检查)
};

//$declare${package1::DoorLock} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

/*
 * DoorLock 结构体 - 状态机对象
 *
 * 在QP框架中,每个状态机都是一个对象,包含:
 * 1. 继承自QHsm基类(层次状态机)
 * 2. 私有数据成员(状态机内部使用的变量)
 * 3. 公有数据成员(可被外部访问的变量)
 */
typedef struct {
    // protected: (QP框架要求)
    QHsm super; // 继承自QHsm基类(层次状态机)
    
    // private: (状态机内部使用)
    clock_t timer_start;     // 定时器开始时间(clock_t类型)
    uint32_t timer_duration; // 定时器持续时间(毫秒)
    uint32_t input;          // 当前输入的数字序列
    
    // public: (可被外部访问)
    uint8_t err_count;       // 错误计数(连续错误次数)
    uint32_t password;       // 系统预设密码
} DoorLock;

/*
 * 状态处理函数声明 - QP框架状态机核心
 *
 * 在QP框架中,每个状态对应一个处理函数,函数签名如下:
 * static QState 函数名(DoorLock *const me, QEvt const *const e);
 *
 * 参数:
 *   me - 指向状态机实例的指针
 *   e  - 指向事件的指针
 *
 * 返回值:
 *   QState - 状态处理结果(使用QP提供的宏)
 */
static QState DoorLock_initial(DoorLock *const me, void const *const par);
static QState DoorLock_Ready(DoorLock *const me, QEvt const *const e);
static QState DoorLock_Entering(DoorLock *const me, QEvt const *const e);
static QState DoorLock_Verifying(DoorLock *const me, QEvt const *const e);
static QState DoorLock_Correct(DoorLock *const me, QEvt const *const e);
static QState DoorLock_Wrong(DoorLock *const me, QEvt const *const e);
static QState DoorLock_Locked(DoorLock *const me, QEvt const *const e);
//$enddecl${package1::DoorLock} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

/*
 * DoorLock 构造函数
 *
 * 在QP框架中,构造函数用于:
 * 1. 初始化状态机基类(QHsm)
 * 2. 设置状态机的初始状态
 * 3. 初始化状态机的数据成员
 */
void DoorLock_ctor(DoorLock *const me) {
    /*
     * QHsm_ctor - QP框架的层次状态机构造函数
     *
     * 参数:
     *   &me->super - 指向状态机基类的指针
     *   Q_STATE_CAST(&DoorLock_initial) - 初始状态函数(使用Q_STATE_CAST宏转换)
     */
    QHsm_ctor(&me->super, Q_STATE_CAST(&DoorLock_initial));
    
    // 初始化定时器字段
    me->timer_start = 0;
    me->timer_duration = 0;
    
    // 初始化其他字段
    me->input = 0;
    me->err_count = 0;
    me->password = 1234; // 设置默认密码
}

/*
 * QF_onStartup - QP框架启动回调函数
 *
 * 当使用完整QP框架时,此函数在QF_run()之前调用。
 * 在本简化实现中,我们不需要它,但保留它以满足编译要求。
 */
void QF_onStartup(void) {}

/*
 * QF_onCleanup - QP框架清理回调函数
 *
 * 当使用完整QP框架时,此函数在QF_stop()之后调用。
 * 在本简化实现中,我们不需要它,但保留它以满足编译要求。
 */
void QF_onCleanup(void) {}

/*
 * QF_onClockTick - QP框架时钟节拍回调函数
 *
 * 当使用完整QP框架时,此函数由系统定时器中断调用。
 * 在本简化实现中,我们不需要它,但保留它以满足编译要求。
 */
void QF_onClockTick(void) {}

/*
 * Q_onError - QP框架错误处理函数
 *
 * 当QP框架检测到错误时调用此函数。
 * 参数:
 *   module - 发生错误的模块名
 *   id     - 错误ID
 */
Q_NORETURN Q_onError(char const *const module, int_t const id) {
    FPRINTF_S(stderr, "ERROR in %s:%d", module, id);
    exit(-1);
}

/*
 * 主函数 - 程序入口点
 */
int main() {
    DoorLock doorLock; // 创建状态机实例
    
    /*
     * QF_init - QP框架初始化函数
     *
     * 在完整QP框架中,此函数初始化框架内部数据结构。
     * 在本简化实现中,我们调用它以满足某些QP组件的需求。
     */
    QF_init();
    
    // 调用构造函数初始化状态机
    DoorLock_ctor(&doorLock);
    
    /*
     * QASM_INIT - QP框架状态机初始化宏
     *
     * 功能:初始化状态机并执行初始转换
     * 参数:
     *   &doorLock.super - 指向状态机基类的指针
     *   (QEvt *)0       - 初始事件(通常为NULL)
     *   0               - 状态机ID(通常为0)
     */
    QASM_INIT(&doorLock.super, (QEvt *)0, 0);
    
    // 主事件循环
    while (1) {
        // 1. 检查并处理定时器超时
        if (doorLock.timer_duration > 0) {
            clock_t current = clock();
            // 计算已过时间(毫秒)
            clock_t elapsed_ms = (current - doorLock.timer_start) * 1000 / CLOCKS_PER_SEC;
            
            // 检查是否超时
            if (elapsed_ms >= doorLock.timer_duration) {
                /*
                 * 创建超时事件
                 *
                 * QEvt是QP框架的事件基类:
                 *   struct QEvt {
                 *       QSignal sig; // 事件信号
                 *       // ... 其他框架内部成员
                 *   };
                 */
                QEvt e = {TIMEOUT_SIG};
                
                /*
                 * QASM_DISPATCH - QP框架事件分发宏
                 *
                 * 功能:将事件分发给状态机处理
                 * 参数:
                 *   &doorLock.super - 指向状态机基类的指针
                 *   &e             - 事件指针
                 *   0               - 状态机ID(通常为0)
                 */
                QASM_DISPATCH(&doorLock.super, &e, 0);
                
                // 重置定时器
                doorLock.timer_duration = 0;
            }
        }
        
        // 2. 非阻塞处理用户输入
        if (_kbhit()) {
            char input = _getch(); // 获取按键
            QSignal sig = 0;       // QP信号类型(本质是整数)
            
            // 根据按键设置信号
            switch (input) {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                // 处理数字输入
                doorLock.input = doorLock.input * 10 + (input - '0');
                printf("Input: %lu\n", doorLock.input);
                sig = DIGIT_KEY_SIG;
                break;
            case '*':
                sig = ENTER_KEY_SIG;
                break;
            case '#':
                sig = CLEAR_KEY_SIG;
                break;
            }
            
            // 如果有有效信号,创建并分发事件
            if (sig != 0) {
                QEvt e = {sig};
                QASM_DISPATCH(&doorLock.super, &e, 0);
            }
        }
        
        // 3. 短暂休眠减少CPU占用
        Sleep(10); // 休眠10ms
    }
    
    return 0;
}

//$skip${QP_VERSION} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// 检查QP框架版本要求
#if (QP_VERSION < 730U) || (QP_VERSION != ((QP_RELEASE^4294967295U)%0x2710U))
#error qpc version 7.3.0 or higher required
#endif
//$endskip${QP_VERSION} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

//$define${package1::DoorLock} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

//${package1::DoorLock} ......................................................

//${package1::DoorLock::SM} ..................................................
/*
 * DoorLock_initial - 状态机初始状态
 *
 * 在QP框架中,这是状态机的入口点。
 * 当状态机初始化时,第一个调用的状态函数。
 */
static QState DoorLock_initial(DoorLock *const me, void const *const par) {
    // 初始化错误计数
    me->err_count = 0;
    
    /*
     * Q_TRAN - 状态转换宏
     *
     * 功能:请求转换到新状态
     * 参数:目标状态的处理函数
     * 返回值:Q_RET_TRAN(告诉框架需要状态转换)
     */
    return Q_TRAN(&DoorLock_Ready);
}

//${package1::DoorLock::SM::Ready} ...........................................
/*
 * Ready状态 - 系统就绪状态
 */
static QState DoorLock_Ready(DoorLock *const me, QEvt const *const e) {
    QState status_; // 状态处理结果
    
    switch (e->sig) {
    // Q_ENTRY_SIG - 进入状态事件(由框架自动发送)
    case Q_ENTRY_SIG: {
        printf("ready\n");
        // 重置输入和密码
        me->input = 0;
        me->password = 1234;
        
        /*
         * Q_HANDLED - 事件已处理宏
         *
         * 返回值:Q_RET_HANDLED(告诉框架事件已处理)
         */
        status_ = Q_HANDLED();
        break;
    }
    // DIGIT_KEY_SIG - 数字键按下事件
    case DIGIT_KEY_SIG: {
        // 转换到输入状态
        status_ = Q_TRAN(&DoorLock_Entering);
        break;
    }
    default: {
        /*
         * Q_SUPER - 传递给父状态宏
         *
         * 功能:将事件传递给父状态处理
         * 参数:父状态的处理函数(QHsm_top是顶级状态)
         * 返回值:Q_RET_SUPER(告诉框架将事件传递给父状态)
         */
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}

//${package1::DoorLock::SM::Entering} ........................................
/*
 * Entering状态 - 密码输入状态
 */
static QState DoorLock_Entering(DoorLock *const me, QEvt const *const e) {
    QState status_;
    switch (e->sig) {
    case Q_ENTRY_SIG: {
        printf("entering\n");
        status_ = Q_HANDLED();
        break;
    }
    case DIGIT_KEY_SIG: {
        // 数字键按下时保持在当前状态
        status_ = Q_TRAN(&DoorLock_Entering);
        break;
    }
    case CLEAR_KEY_SIG: {
        // 清除键按下时返回就绪状态
        status_ = Q_TRAN(&DoorLock_Ready);
        break;
    }
    case ENTER_KEY_SIG: {
        // 确认键按下时转换到验证状态
        status_ = Q_TRAN(&DoorLock_Verifying);
        break;
    }
    default: {
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}

//${package1::DoorLock::SM::Verifying} .......................................
/*
 * Verifying状态 - 密码验证状态
 */
static QState DoorLock_Verifying(DoorLock *const me, QEvt const *const e) {
    QState status_;
    switch (e->sig) {
    case Q_ENTRY_SIG: {
        printf("verifying\n");
        status_ = Q_HANDLED();
        break;
    }
    case Q_INIT_SIG: {
        /*
         * Q_INIT_SIG - 状态初始化事件
         *
         * 在QP框架中,进入状态后自动发送此事件。
         * 用于执行状态初始化逻辑。
         */
        if (me->password == me->input) {
            printf("-> c\n");
            // 密码正确,转换到正确状态
            status_ = Q_TRAN(&DoorLock_Correct);
        } else {
            printf("-> n \n");
            // 密码错误,转换到错误状态
            status_ = Q_TRAN(&DoorLock_Wrong);
        }
        break;
    }
    default: {
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}

//${package1::DoorLock::SM::Correct} .........................................
/*
 * Correct状态 - 密码正确状态
 */
static QState DoorLock_Correct(DoorLock *const me, QEvt const *const e) {
    QState status_;
    switch (e->sig) {
    case Q_ENTRY_SIG: {
        printf("correct\n");
        // 重置错误计数
        me->err_count = 0;
        // 启动2秒定时器
        me->timer_start = clock();
        me->timer_duration = 2000;
        status_ = Q_HANDLED();
        break;
    }
    case TIMEOUT_SIG: {
        // 超时后返回就绪状态
        status_ = Q_TRAN(&DoorLock_Ready);
        break;
    }
    default: {
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}

//${package1::DoorLock::SM::Wrong} ...........................................
/*
 * Wrong状态 - 密码错误状态
 */
static QState DoorLock_Wrong(DoorLock *const me, QEvt const *const e) {
    QState status_;
    switch (e->sig) {
    case Q_ENTRY_SIG: {
        printf("wrong\n");
        me->err_count++;
        
        me->timer_start = clock();
        me->timer_duration = 2000;
        status_ = Q_HANDLED();
        break;
    }
    case Q_INIT_SIG: {
        // 在初始化事件中检查错误计数
        if (me->err_count >= 3) {
            me->err_count = 0;
            status_ = Q_TRAN(&DoorLock_Locked);
        } else {
            status_ = Q_HANDLED();
        }
        break;
    }
    case TIMEOUT_SIG: {
        status_ = Q_TRAN(&DoorLock_Ready);
        break;
    }
    default: {
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}
//${package1::DoorLock::SM::Locked} ..........................................
/*
 * Locked状态 - 系统锁定状态
 */
static QState DoorLock_Locked(DoorLock *const me, QEvt const *const e) {
    QState status_;
    switch (e->sig) {
    case Q_ENTRY_SIG: {
        printf("Locked\n");
        // 启动5秒定时器
        me->timer_start = clock();
        me->timer_duration = 5000;
        status_ = Q_HANDLED();
        break;
    }
    case TIMEOUT_SIG: {
        // 超时后返回就绪状态
        status_ = Q_TRAN(&DoorLock_Ready);
        break;
    }
    default: {
        status_ = Q_SUPER(&QHsm_top);
        break;
    }
    }
    return status_;
}
//$enddef${package1::DoorLock} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

QP框架介绍

本介绍以RTOS进行对比进行,大量内容使用AI生成

​​特性​ QP框架 RTOS​
​​基本单元​ 活动对象(Active Objects) 线程(Threads)
​​单元结构​ 状态机 + 事件队列 + 私有数据 执行上下文 + 栈空间
​​设计哲学​​ 事件驱动计算模型 过程式编程模型
​​问题解决视角​ 状态与行为建模 任务与资源管理

QP调度框架
在这里插入图片描述
RTOS调度框架
在这里插入图片描述
QP以​​事件驱动状态机​​为核心,专注于状态行为建模;RTOS以​​任务调度​​为核心,专注于执行流管理。理解这些差异有助于根据应用需求选择最佳方案,或在复杂系统中实现两者的协同应用。

QP状态机高级技术

  • 层次状态嵌套
  • 历史状态
  • 正交区域
  • 事件转换
  • 事件发布\订阅
  • 超时状态转换
  • 状态模式扩展
  • 延迟信号
  • 状态机代理模式
  • ​​状态机组合模式
  • 。。。

在复杂逻辑场景的时候,通过更加高级的功能,实现花里胡哨的状态机功能,涵盖所有业务场景,并且运行起来较RTOS更安全,而且调试复杂度更低。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐