QP-nano:极致精简的嵌入式开发框架
1. QP-nano概述
QP-nano是一款专门为低端8位和16位微控制器(MCU)设计的通用、可移植、超轻量级的事件驱动基础设施。它由以下几个主要部分组成:
-
QEP-nano
:分层事件处理器。
-
QF-nano
:最小实时框架。
-
内核选择
:可以选择使用内置的合作式“普通”内核,或者是抢占式的QK-nano内核。
QP-nano具有以下关键特性:
1.
分层状态嵌套支持
:完全支持分层状态嵌套,包括在任意状态转换拓扑上保证执行进入/退出动作,最多支持四层状态嵌套。
2.
多活动对象支持
:支持最多八个并发执行的活动对象,配备确定性、线程安全的事件队列。
3.
事件参数配置
:支持具有单字节信号(255个信号)和一个标量参数的事件,参数大小可配置为0(无参数)、1、2或4字节。
4.
事件传递机制
:采用先进先出(FIFO)排队策略的直接事件传递机制。
5.
时间事件支持
:每个活动对象有一个单次触发的时间事件(定时器),动态范围可配置为0(无时间事件)、1、2或4字节。
6.
低功耗架构
:具备低功耗架构,带有空闲回调函数,便于实现节能模式。
7.
非标准扩展处理
:代码中提供了处理流行低端CPU架构C编译器中非标准扩展的功能,例如在代码空间中分配常量对象、可重入函数等。
8.
错误处理策略
:基于断言的错误处理策略。
QP-nano设计面临的最大挑战是极其有限的RAM预算,假设只有大约100字节(包括C栈)。因此,在设计时需要仔细计算每一个字节的RAM使用。这与完整版QP不同,完整版QP在不降低编程便利性、灵活性或性能的前提下,不会刻意节省每一个字节的RAM。
由于RAM严重受限,QP-nano不支持具有任意大小参数的事件,只允许固定大小的事件,且只有一个标量参数,可配置为1、2或4字节(或0字节,表示无事件参数)。这种设计带来了一些简化的结果:
-
事件队列存储
:QP-nano的事件队列存储整个事件,而不是像完整版QP那样只存储事件指针。小的、固定大小的事件以值传递的方式复制进出事件队列,本质上是线程安全的。
-
无需事件池
:值传递策略消除了对事件池的需求,因为事件池无法适应有限的RAM。
-
无需引用计数
:在这种设计中,事件的引用计数是不必要的。
目前,QP-nano不支持软件跟踪,因为可用的RAM太小,无法容纳合理的跟踪缓冲区。此外,大多数低端MCU的引脚数量非常有限,为开发目的分配一个额外的输出引脚可能是一个挑战。
QP-nano的组件及其与目标硬件、板级支持包(BSP)和应用程序的关系如下:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(Target Hardware):::process --> B("Vanilla Cooperative Kernel or QK-nano Preemptive Kernel"):::process
B --> C(QF-nano Real - Time Framework):::process
C --> D(QEP-nano Hierarchical Event Processor):::process
A --> E(BSP):::process
D --> F(Application):::process
E --> F
2. 使用QP-nano实现“Fly ‘n’ Shoot”示例
学习QP-nano的功能以及它与完整版QP的区别的最佳方法之一,是在QP-nano中重新实现一个非平凡的QP示例。这里将介绍使用QP-nano实现的“Fly ‘n’ Shoot”游戏。
代码中包含了针对两个嵌入式目标和两种不同内核的四个“Fly ‘n’ Shoot”游戏的QP-nano实现。这里使用的是DOS版本,使用非抢占式内核,通过旧版Turbo C++ 1.01编译器编译,可以直接在任何Windows PC上运行。该版本的代码位于目录
<qp>\qpn\examples\80x86\tcpp101\game\
。同样的应用程序代码(除了BSP)也适用于Cortex - M3 EV - LM3S811开发板,其代码位于目录
<qp>\qpn\examples\cortex - m3\iar\game - ev - lm3s811\
。
2.1 main()函数
以下是“Fly ‘n’ Shoot”应用程序的main.c源文件,包含了main()函数以及QP-nano所需的一些重要数据结构:
#include "qpn_port.h" /* QP-nano port */
#include "bsp.h" /* Board Support Package (BSP) */
#include "game.h" /* application interface */
/*.....................................................................*/
static QEvent l_tunnelQueue[GAME_MINES_MAX + 4];
static QEvent l_shipQueue[2];
static QEvent l_missileQueue[2];
/* QF_active[] array defines all active object control blocks ----*/
QActiveCB const Q_ROM Q_ROM_VAR QF_active[] = {
{ (QActive *)0, (QEvent *)0, 0 },
{ (QActive *)&AO_tunnel, l_tunnelQueue, Q_DIM(l_tunnelQueue) },
{ (QActive *)&AO_ship, l_shipQueue, Q_DIM(l_shipQueue) },
{ (QActive *)&AO_missile, l_missileQueue, Q_DIM(l_missileQueue) }
};
/* make sure that the QF_active[] array matches QF_MAX_ACTIVE in qpn_port.h */
Q_ASSERT_COMPILE(QF_MAX_ACTIVE == Q_DIM(QF_active) - 1);
/*.....................................................................*/
void main (void) {
Tunnel_ctor ();
Ship_ctor ();
Missile_ctor(GAME_MISSILE_SPEED_X);
BSP_init(); /* initialize the board */
QF_run(); /* transfer control to QF-nano */
}
main()函数的具体操作步骤如下:
1.
包含必要的头文件
:
-
qpn_port.h
:包含QP-nano针对特定处理器和编译器的适配信息,即端口信息,通常位于应用程序目录中。
-
bsp.h
:包含板级支持包的接口,位于应用程序目录中。
-
game.h
:包含“Fly ‘n’ Shoot”游戏组件之间共享的事件声明和其他设施,后续会详细讨论,位于应用程序目录中。
2.
为事件队列分配存储
:应用程序需要为所有活动对象的事件队列提供存储。在QP-nano中,通过静态分配的事件数组在编译时提供存储。事件由
<qp>\qpn\include\qepn.h
头文件中声明的QEvent结构实例表示。每个活动对象的事件队列长度可以不同,需要根据应用程序的需求来确定。
3.
定义活动对象控制块数组
:每个QP-nano应用程序必须提供常量数组QF_active[],该数组定义了应用程序中所有活动对象的控制块。控制块QActiveCB结构包含三个元素:对应活动对象实例的指针、活动对象事件队列缓冲区的指针以及队列缓冲区的长度。
-
QF_active[0]
:对应活动对象优先级为零,保留给空闲任务,不能用于任何活动对象。
-
QF_active[1]
及后续元素:按相对优先级顺序定义活动对象控制块,QP-nano中活动对象的最大数量不能超过八个。活动对象控制块在QF_active[]数组中的顺序定义了活动对象的优先级,这是代码中唯一分配活动对象优先级的地方。
- 编译时断言
Q_ASSERT_COMPILE(QF_MAX_ACTIVE == Q_DIM(QF_active) - 1)
确保QF_active[]数组的维度与qpn_port.h头文件中定义的活动对象数量QF_MAX_ACTIVE相匹配。在QP-nano中,QF_MAX_ACTIVE表示应用程序中使用的活动对象的确切数量,而完整版QP中,QF_MAX_ACTIVE仅表示可配置的最大活动对象数量。所有活动对象必须在编译时定义,这意味着所有活动对象从一开始就存在,不能像完整版QP那样在后期启动或停止。
4.
调用活动对象构造函数
:main()函数必须首先显式调用所有活动对象的构造函数。
5.
初始化板级支持包
:调用BSP_init()函数初始化板级支持包。
6.
将控制权传递给QF-nano框架
:调用QF_run()函数将控制权传递给QF-nano框架。
与完整版QP相比,QP-nano的应用程序启动要简单得多。它不支持事件池和发布 - 订阅列表,因此不需要初始化它们,也不需要显式启动活动对象。QF-nano框架在QF_run()函数获得控制权后,会自动启动QF_active[]数组中定义的所有活动对象。
2.2 qpn_port.h头文件
qpn_port.h头文件定义了QP-nano的端口以及特定应用程序的所有配置参数。与完整版QP不同,QP-nano的端口通常在应用程序级别定义,并且通常整个QP-nano端口只由qpn_port.h头文件组成。以下是“Fly ‘n’ Shoot”游戏DOS版本的完整qpn_port.h文件:
#ifndef qpn_port_h
#define qpn_port_h
#define Q_PARAM_SIZE 4
#define QF_TIMEEVT_CTR_SIZE 2
#define Q_NFSM
#define QF_MAX_ACTIVE 3
#define QF_INT_LOCK() disable()
#define QF_INT_UNLOCK() enable()
typedef signed char int8_t;
typedef signed int int16_t;
typedef signed long int32_t;
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
#include <dos.h>
#undef outportb
#include "qepn.h"
#include "qfn.h"
#endif /* qpn_port_h */
该头文件中的配置参数说明如下:
1.
Q_PARAM_SIZE
:定义标量事件参数的大小(以字节为单位),允许的值为0(无参数)、1、2或4字节。如果未在qpn_port.h中定义该宏,则默认值为0(无参数)。
2.
QF_TIMEEVT_CTR_SIZE
:定义时间事件计数器的大小(以字节为单位),允许的值为0(无时间事件)、1、2或4字节。如果未在qpn_port.h中定义该宏,则默认值为0(无时间事件)。
3.
Q_NFSM
:定义该宏将消除简单非分层有限状态机(FSM)的代码。
4.
QF_MAX_ACTIVE
:必须定义为应用程序中使用的活动对象的确切数量,值必须在1到8之间,并且必须与QF_active[]数组的定义一致。
5.
QF_INT_LOCK()/QF_INT_UNLOCK()
:定义QP-nano的任务级中断锁定策略。
6.
精确宽度类型定义
:由于使用的旧版Turbo C++ 1.01编译器是预标准编译器,不提供
<stdint.h>
头文件,因此需要手动定义QP-nano使用的六个精确宽度整数类型。
7.
包含必要的头文件
:
-
qepn.h
:QP-nano事件处理器的平台无关头文件。
-
qfn.h
:QF-nano实时框架的平台无关头文件。
需要注意的是,上述qpn_port.h头文件隐式配置QP-nano使用内置的合作式“普通”内核。如果要使用抢占式的QK-nano内核,需要在qpn_port.h头文件中包含qkn.h接口。
2.3 “Fly ‘n’ Shoot”游戏中的信号、事件和活动对象
在QP-nano中,事件信号的枚举方式与完整版QP相同,但信号值不能超过255,因为信号始终用单字节表示。QP-nano不支持指定任意事件参数,所有事件都是QEvent结构的实例,包含根据Q_PARAM_SIZE定义配置的固定大小标量参数。
活动对象在QP-nano中同样从QActive基结构派生。在所有QP-nano示例中,包括“Fly ‘n’ Shoot”游戏,展示了一种将活动对象结构和状态机完全封装的技术。以下是“Fly ‘n’ Shoot”游戏的game.h头文件:
#ifndef game_h
#define game_h
enum GameSignals {
TIME_TICK_SIG = Q_USER_SIG,
PLAYER_TRIGGER_SIG,
PLAYER_QUIT_SIG,
GAME_OVER_SIG,
PLAYER_SHIP_MOVE_SIG,
BLINK_TIMEOUT_SIG,
SCREEN_TIMEOUT_SIG,
TAKE_OFF_SIG,
HIT_WALL_SIG,
HIT_MINE_SIG,
SHIP_IMG_SIG,
MISSILE_IMG_SIG,
MINE_IMG_SIG,
MISSILE_FIRE_SIG,
DESTROYED_MINE_SIG,
EXPLOSION_SIG,
MINE_PLANT_SIG,
MINE_DISABLED_SIG,
MINE_RECYCLE_SIG,
SCORE_SIG
};
extern struct TunnelTag AO_Tunnel;
extern struct ShipTag AO_Ship;
extern struct MissileTag AO_Missile;
void Tunnel_ctor (void);
void Ship_ctor (void);
void Missile_ctor(uint8_t speed);
#endif /* game_h */
该头文件的具体说明如下:
1.
信号枚举
:所有信号在一个枚举中定义,自动保证信号的唯一性。用户信号必须从Q_USER_SIG偏移开始,以避免与QP-nano保留的信号重叠。
2.
活动对象声明
:将系统中的所有活动对象实例声明为外部变量,这些声明对于初始化QF_active[]数组是必要的。需要注意的是,活动对象结构(如struct TunnelTag)不需要在应用程序头文件中全局定义,QF_active[]数组只需要活动对象的指针,编译器可以在不知道活动对象结构完整定义的情况下解析这些指针。
通过以上对QP-nano的介绍以及“Fly ‘n’ Shoot”游戏示例的实现,我们可以看到QP-nano在资源受限的嵌入式系统中具有独特的优势,它通过简化设计和优化内存使用,为低端MCU提供了一个高效的事件驱动开发框架。
QP-nano:极致精简的嵌入式开发框架
3. QP-nano的优势与应用场景总结
QP-nano作为一款专为低端8位和16位MCU设计的事件驱动基础设施,具有诸多显著优势,使其在特定的应用场景中表现出色。以下是对其优势和适用场景的总结:
| 优势 | 说明 |
|---|---|
| 轻量级设计 | QP-nano具有超轻量级的特点,对RAM的需求极低,假设仅需约100字节(包括C栈)。这使得它能够在资源极度受限的MCU上运行,而完整版QP可能因资源需求过高而无法使用。 |
| 分层状态嵌套支持 | 支持最多四层的分层状态嵌套,并能保证在任意状态转换拓扑上执行进入/退出动作。这一特性使得复杂的状态机设计变得更加容易和可靠,有助于实现复杂的业务逻辑。 |
| 多活动对象管理 | 支持最多八个并发执行的活动对象,每个活动对象都有自己的事件队列,且队列具有确定性和线程安全性。通过合理配置活动对象的优先级,可以高效地处理多个任务。 |
| 事件参数配置灵活 | 允许配置事件的标量参数大小为0、1、2或4字节,这种固定大小的参数配置方式避免了对任意大小参数的支持,从而简化了设计,减少了内存占用。 |
| 低功耗架构 | 具备低功耗架构,带有空闲回调函数,方便实现节能模式。对于电池供电的设备或对功耗敏感的应用场景,这一特性尤为重要。 |
| 代码扩展性好 | 代码中提供了处理流行低端CPU架构C编译器中非标准扩展的功能,如常量对象的分配和可重入函数的支持。这使得QP-nano能够更好地适应不同的硬件平台和编译器环境。 |
QP-nano适用于以下应用场景:
-
资源受限的嵌入式系统
:如一些低成本的传感器节点、小型智能设备等,这些设备通常具有有限的RAM和ROM资源,QP-nano的轻量级设计能够满足其需求。
-
对功耗要求较高的设备
:例如无线传感器网络中的节点、便携式医疗设备等,QP-nano的低功耗架构可以有效延长设备的电池续航时间。
-
需要实现复杂状态机的应用
:如工业自动化中的控制设备、智能家居系统中的控制器等,QP-nano的分层状态嵌套支持可以帮助开发者更方便地实现复杂的状态转换逻辑。
4. QP-nano开发的注意事项
在使用QP-nano进行开发时,需要注意以下几个方面:
4.1 内存管理
由于QP-nano的RAM资源非常有限,因此在开发过程中必须严格控制内存的使用。以下是一些内存管理的建议:
-
合理配置事件参数大小
:根据应用的实际需求,选择合适的事件参数大小(0、1、2或4字节),避免不必要的内存浪费。
-
优化事件队列长度
:仔细评估每个活动对象的事件队列长度,避免队列过长导致内存占用过多。可以根据活动对象的处理能力和事件产生的频率来确定队列长度。
-
避免使用不必要的全局变量
:全局变量会占用宝贵的RAM空间,尽量使用局部变量和静态变量来减少内存开销。
4.2 活动对象的使用
- 活动对象数量限制 :QP-nano最多支持八个活动对象,在设计应用程序时,需要确保活动对象的数量不超过这个限制。如果需要处理更多的任务,可以考虑将一些任务合并或采用其他方式进行优化。
- 活动对象的优先级配置 :活动对象的优先级由QF_active[]数组的顺序决定,这是唯一分配优先级的地方。在配置优先级时,需要根据任务的重要性和实时性要求来合理安排,以确保系统的稳定性和性能。
4.3 中断处理
QP-nano的中断处理需要特别注意,以下是一些相关的注意事项:
-
中断锁定策略
:通过QF_INT_LOCK()和QF_INT_UNLOCK()宏来定义任务级的中断锁定策略,确保在关键代码段中不会被中断干扰。在使用这些宏时,需要确保中断锁定的时间尽可能短,以避免影响系统的实时性。
-
中断服务程序(ISR)的设计
:ISR应该尽量简洁,避免在ISR中执行复杂的操作。如果需要处理大量的数据或执行复杂的计算,可以将这些任务交给活动对象来处理,ISR只负责触发相应的事件。
5. 未来展望
随着物联网和嵌入式系统的不断发展,对资源受限设备的需求也越来越大。QP-nano作为一款轻量级的事件驱动开发框架,具有很大的发展潜力。未来,QP-nano可能会在以下几个方面得到进一步的发展:
5.1 功能扩展
- 支持更多的硬件平台 :目前QP-nano主要针对8位和16位MCU,未来可能会扩展到支持更多类型的处理器,如32位MCU甚至一些低功耗的微处理器。
- 增加新的功能模块 :例如支持更多的通信协议、提供更丰富的定时器功能等,以满足不同应用场景的需求。
5.2 性能优化
- 进一步降低内存占用 :通过优化代码结构和算法,进一步降低QP-nano对RAM和ROM的需求,使其能够在更资源受限的设备上运行。
- 提高系统的实时性 :通过改进调度算法和中断处理机制,提高系统的实时响应能力,满足一些对实时性要求较高的应用场景。
5.3 开发工具的完善
- 提供更友好的开发工具 :开发专门针对QP-nano的集成开发环境(IDE)或调试工具,使开发者能够更方便地进行代码编写、调试和测试。
- 完善文档和示例代码 :提供更详细的文档和丰富的示例代码,帮助开发者更快地掌握QP-nano的使用方法,降低开发门槛。
6. 总结
QP-nano是一款专为资源受限的嵌入式系统设计的轻量级事件驱动开发框架。通过对其关键特性、“Fly ‘n’ Shoot”游戏示例的实现以及优势和应用场景的分析,我们可以看到QP-nano在处理复杂状态机和多任务管理方面具有独特的优势,同时通过优化内存使用和简化设计,为低端MCU提供了一个高效的开发解决方案。
在使用QP-nano进行开发时,需要注意内存管理、活动对象的使用和中断处理等方面的问题。未来,随着技术的不断发展,QP-nano有望在功能扩展、性能优化和开发工具完善等方面取得更大的进步,为嵌入式系统开发带来更多的便利和可能性。
通过深入学习和掌握QP-nano的使用方法,开发者可以在资源受限的环境中开发出高效、稳定的嵌入式应用程序,满足不同领域的需求。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(资源受限嵌入式系统):::process --> B(QP-nano):::process
C(对功耗要求高的设备):::process --> B
D(需要复杂状态机的应用):::process --> B
B --> E(轻量级设计):::process
B --> F(分层状态嵌套支持):::process
B --> G(多活动对象管理):::process
B --> H(事件参数配置灵活):::process
B --> I(低功耗架构):::process
B --> J(代码扩展性好):::process
以上就是关于QP-nano的详细介绍,希望能为开发者在嵌入式系统开发中提供有益的参考。
超级会员免费看
1万+

被折叠的 条评论
为什么被折叠?



