QP-nano:实现轻量级嵌入式系统开发
1. 主动对象结构声明原则
在开发中,不建议全局声明主动对象结构,而是在特定主动对象模块的文件作用域内声明。例如,
struct TunnelTag
在
tunnel.c
文件作用域内声明,这样能确保每个主动对象完全封装。
2. 主动对象构造函数
系统中的每个主动对象都必须提供一个“构造函数”,用于初始化主动对象实例。这些构造函数不接受“me”指针,因为它们可以访问全局主动对象实例,但可以接受其他初始化参数。例如,
Missile_ctor()
接受导弹速度参数,构造函数在
main()
开始时被调用。
3. 在 QP-nano 中实现 Ship 主动对象
3.1 实现概述
在 QP-nano 中实现主动对象与完整版 QP 非常相似,需要从 QP-nano 提供的
QActive
基础结构派生具体的主动对象结构,主要工作是详细设计主动对象的状态机。与完整版 QP 的重要区别在于,QP-nano 中的状态处理函数只接受一个“me”指针作为参数,当前事件嵌入在状态机内部,可通过“me”指针访问。QP-nano 提供了
Q_SIG()
和
Q_PAR()
宏,分别用于方便地访问当前事件的信号和标量参数。
3.2 Ship 主动对象代码实现
#include "qpn_port.h"
#include "bsp.h"
#include "game.h"
/* local objects ----------------------------------------------------------*/
typedef struct ShipTag {
QActive super;
/* extend the QActive class */
uint8_t x;
uint8_t y;
uint8_t exp_ctr;
uint16_t score;
} Ship;
/* the Ship active object */
static QState Ship_initial(Ship *me);
static QState Ship_active(Ship *me);
static QState Ship_parked(Ship *me);
static QState Ship_flying(Ship *me);
static QState Ship_exploding(Ship *me);
/* global objects -------------------------------------------------------*/
Ship AO_Ship;
void Ship_ctor(void) {
Ship *me = &AO_Ship;
QActive_ctor(&me->super, (QStateHandler)&Ship_initial);
me->x = GAME_SHIP_X;
me->y = GAME_SHIP_Y;
}
/* HSM definition -------------------------------------------------------*/
QState Ship_initial(Ship *me) {
return Q_TRAN(&Ship_active);
/* top-most initial transition */
}
QState Ship_active(Ship *me) {
switch (Q_SIG(me)) {
case Q_INIT_SIG: {
/* nested initial transition */
return Q_TRAN(&Ship_parked);
}
case PLAYER_SHIP_MOVE_SIG: {
me->x = (uint8_t)Q_PAR(me);
me->y = (uint8_t)(Q_PAR(me) >> 8);
return Q_HANDLED();
}
}
return Q_SUPER(&QHsm_top);
}
QState Ship_flying(Ship *me) {
switch (Q_SIG(me)) {
case Q_ENTRY_SIG: {
me->score = 0;
/* reset the score */
QActive_post((QActive *)&AO_Tunnel, SCORE_SIG, me->score);
return Q_HANDLED();
}
case TIME_TICK_SIG: {
/* tell the Tunnel to draw the Ship and test for hits */
QActive_post((QActive *)&AO_Tunnel, SHIP_IMG_SIG,
((QParam)SHIP_BMP << 16)
| (QParam)me->x
| ((QParam)me->y << 8));
++me->score;
/* increment the score for surviving another tick */
if ((me->score % 10) == 0) {
/* is the score "round"? */
QActive_post((QActive *)&AO_Tunnel, SCORE_SIG, me->score);
}
return Q_HANDLED();
}
case PLAYER_TRIGGER_SIG: {
/* trigger the Missile */
QActive_post((QActive *)&AO_Missile, MISSILE_FIRE_SIG,
(QParam)me->x
| (((QParam)me->y + SHIP_HEIGHT - 1) << 8));
return Q_HANDLED();
}
case DESTROYED_MINE_SIG: {
me->score += Q_PAR(me);
/* the score will be sent to the Tunnel by the next TIME_TICK */
return Q_HANDLED();
}
case HIT_WALL_SIG:
case HIT_MINE_SIG: {
return Q_TRAN(&Ship_exploding);
}
}
return Q_SUPER(&Ship_active);
}
3.3 代码解释
-
Ship结构体定义了Ship主动对象,派生自QActive结构。 -
Ship_initial()函数定义了Ship状态机的最顶层初始转换。 -
状态处理函数通过
Q_SIG()宏根据事件信号进行switch语句判断,使用Q_TRAN()宏指定转换目标,使用Q_PAR()宏访问事件参数。 -
QActive_post()函数用于直接将指定的事件信号和参数发送到接收方主动对象。
3.4 状态机操作步骤
| 操作步骤 | 描述 |
|---|---|
| 1 |
定义
Ship
结构体,继承
QActive
结构。
|
| 2 |
实现
Ship
状态机的初始转换函数
Ship_initial()
。
|
| 3 | 实现状态处理函数,根据事件信号进行不同处理。 |
| 4 |
使用
Q_TRAN()
宏指定转换目标,
Q_PAR()
宏访问事件参数。
|
| 5 |
使用
QActive_post()
函数发送事件。
|
3.5 状态机流程图
graph TD;
A[Ship_initial] --> B[Ship_active];
B --> C[Ship_parked];
B --> D[Ship_flying];
D --> E[Ship_exploding];
D -->|TIME_TICK_SIG| D;
D -->|PLAYER_TRIGGER_SIG| D;
D -->|DESTROYED_MINE_SIG| D;
D -->|HIT_WALL_SIG/HIT_MINE_SIG| E;
4. QP-nano 中的时间事件
4.1 时间事件概述
QP-nano 为系统中的每个主动对象维护一个私有时间事件(定时器),这些定时器可以被编程(激活),在指定的时钟滴答数后生成保留的
Q_TIMEOUT
事件。内部,QP-nano 仅将时间事件表示为一个递减计数器(通常为 2 字节的 RAM),只支持单次时间事件,因为周期性时间事件需要两倍的 RAM 来存储周期。
Q_TIMEOUT
信号在 QP-nano 中是预定义的保留信号之一。
4.2 时间事件代码示例
QState Tunnel_game_over(Tunnel *me) {
switch (Q_SIG(me)) {
case Q_ENTRY_SIG: {
QActive_arm((QActive *)me, BSP_TICKS_PER_SEC/2);
/* 1/2 sec */
me->blink_ctr = 5*2;
/* 5s timeout */
BSP_drawNString((GAME_SCREEN_WIDTH - 6*9)/2, 0, "Game Over");
return (QState)0;
}
case Q_EXIT_SIG: {
QActive_disarm((QActive *)me);
BSP_updateScore(0);
/* clear the score on the display */
return (QState)0;
}
case Q_TIMEOUT_SIG: {
QActive_arm((QActive *)me, BSP_TICKS_PER_SEC/2);
/* 1/2 sec */
BSP_drawNString((GAME_SCREEN_WIDTH - 6*9)/2, 0,
(((me->blink_ctr & 1) != 0)
? "Game Over"
: " "));
if ((--me->blink_ctr) == 0) {
/* blinked enough times? */
Q_TRAN(&Tunnel_demo);
}
return (QState)0;
}
}
return (QState)&Tunnel_active;
}
4.3 代码解释
-
在
Q_ENTRY_SIG中激活时间事件,设置超时时间和闪烁计数器,并显示“Game Over”文本。 -
在
Q_EXIT_SIG中停用时间事件,清除显示上的分数。 -
在
Q_TIMEOUT_SIG中再次激活时间事件,根据闪烁计数器的值显示或隐藏“Game Over”文本,当闪烁次数达到上限时,进行状态转换。
4.4 时间事件操作步骤
| 操作步骤 | 描述 |
|---|---|
| 1 |
在状态进入时,使用
QActive_arm()
函数激活时间事件。
|
| 2 |
在状态退出时,使用
QActive_disarm()
函数停用时间事件。
|
| 3 |
处理
Q_TIMEOUT_SIG
事件,根据需要重新激活时间事件或进行状态转换。
|
4.5 时间事件流程图
graph TD;
A[Q_ENTRY_SIG] --> B[QActive_arm];
B --> C[设置 blink_ctr];
C --> D[显示 Game Over];
E[Q_EXIT_SIG] --> F[QActive_disarm];
F --> G[清除分数];
H[Q_TIMEOUT_SIG] --> I[QActive_arm];
I --> J[判断 blink_ctr];
J -->|!=0| K[显示或隐藏 Game Over];
J -->|==0| L[Q_TRAN 到 Tunnel_demo];
5. “Fly ‘n’ Shoot” 应用的板级支持包(BSP)
5.1 BSP 概述
QP-nano 会调用几个特定于平台的回调函数,这些函数通常需要在板级支持包(BSP)中定义。除了回调函数,还必须定义所有的中断服务例程(ISR)。
5.2 BSP 代码示例
#include "qpn_port.h"
#include "game.h"
#include "bsp.h"
/* Local-scope objects ---------------------------------------------------*/
static void interrupt (*l_dosTmrISR)(void);
static void interrupt (*l_dosKbdISR)(void);
#define TMR_VECTOR 0x08
#define KBD_VECTOR 0x09
/*.......................................................................*/
static void interrupt tmrISR(void) {
/* 80x86 enters ISRs with int. locked */
QF_tick();
/* process all armed time events */
QActive_postISR((QActive *)&AO_Tunnel, TIME_TICK_SIG, 0);
QActive_postISR((QActive *)&AO_Ship, TIME_TICK_SIG, 0);
QActive_postISR((QActive *)&AO_Missile, TIME_TICK_SIG, 0);
outportb(0x20, 0x20);
/* write EOI to the master PIC */
}
/*.......................................................................*/
void BSP_init(void) {
...
}
/*.......................................................................*/
void BSP_drawBitmap(uint8_t const *bitmap, uint8_t width, uint8_t height) {
Video_drawBitmapAt(0, 8, bitmap, width, height);
}
/*.......................................................................*/
void BSP_drawNString(uint8_t x, uint8_t y, char const *str) {
Video_drawStringAt(x, 8 + y*8, str);
}
/*.......................................................................*/
void BSP_updateScore(uint16_t score) {
if (score == 0) {
Video_clearRect(68, 24, 72, 25, VIDEO_BGND_RED);
}
Video_printNumAt(68, 24, VIDEO_FGND_YELLOW, score);
}
/*.......................................................................*/
void QF_onStartup(void) {
/* save the original DOS vectors ... */
l_dosTmrISR = getvect(TMR_VECTOR);
l_dosKbdISR = getvect(KBD_VECTOR);
QF_INT_LOCK();
setvect(TMR_VECTOR, &tmrISR);
setvect(KBD_VECTOR, &kbdISR);
QF_INT_UNLOCK();
}
/*.......................................................................*/
void QF_stop(void) {
/* restore the original DOS vectors ... */
if (l_dosTmrISR != (void interrupt (*)(void))0) { /* DOS vectors saved? */
QF_INT_LOCK();
setvect(TMR_VECTOR, l_dosTmrISR);
setvect(KBD_VECTOR, l_dosKbdISR);
QF_INT_UNLOCK();
}
_exit(0);
/* exit to DOS */
}
/*.......................................................................*/
void QF_onIdle(void) {
/* see NOTE01 */
QF_INT_UNLOCK();
}
/*-----------------------------------------------------------------------*/
void Q_onAssert(char const Q_ROM * const Q_ROM_VAR file, int line) {
...
QF_stop();
/* stop QF and cleanup */
}
5.3 代码解释
-
tmrISR()是定时器中断服务例程,调用QF_tick()处理所有激活的时间事件,并将TIME_TICK事件直接发送给需要接收的主动对象。 -
QF_onStartup()回调函数在所有主动对象状态机启动后但事件循环开始执行前被调用,用于配置和启动中断。 -
QF_stop()函数停止 QP-nano 并将控制权返回给操作系统,在 DOS 版本中,该函数会恢复原始中断并退出到 DOS。 -
QF_onIdle()回调函数在中断锁定的情况下被调用,需要解锁中断。 -
Q_onAssert()是断言失败回调函数,用于处理断言失败的情况。
5.4 BSP 操作步骤
| 操作步骤 | 描述 |
|---|---|
| 1 |
实现定时器中断服务例程
tmrISR()
,调用
QF_tick()
并发送
TIME_TICK
事件。
|
| 2 |
实现
QF_onStartup()
函数,配置和启动中断。
|
| 3 |
实现
QF_stop()
函数,停止 QP-nano 并恢复中断。
|
| 4 |
实现
QF_onIdle()
函数,解锁中断。
|
| 5 |
实现
Q_onAssert()
函数,处理断言失败。
|
5.5 BSP 流程图
graph TD;
A[系统启动] --> B[QF_onStartup];
B --> C[设置中断向量];
D[定时器中断] --> E[tmrISR];
E --> F[QF_tick];
F --> G[发送 TIME_TICK 事件];
H[系统停止] --> I[QF_stop];
I --> J[恢复中断向量];
K[空闲状态] --> L[QF_onIdle];
L --> M[解锁中断];
N[断言失败] --> O[Q_onAssert];
O --> P[停止 QF 并清理];
6. 构建 “Fly ‘n’ Shoot” QP-nano 应用
6.1 构建概述
构建 QP-nano 应用比完整版 QP 更简单,只需要将两个 QP-nano 源文件
qepn.c
和
qfn.c
添加到项目中,并指示编译器在
<qp>\qpn\include\
目录中搜索头文件。如果使用 QK-nano,还需要添加
qkn.c
源文件。
6.2 构建操作步骤
| 操作步骤 | 描述 |
|---|---|
| 1 |
将
qepn.c
和
qfn.c
源文件添加到项目中。
|
| 2 |
指示编译器在
<qp>\qpn\include\
目录中搜索头文件。
|
| 3 |
如果使用 QK-nano,添加
qkn.c
源文件。
|
| 4 | 根据不同的编译器和平台,使用相应的项目文件进行构建。 |
6.3 构建流程图
graph TD;
A[创建项目] --> B[添加源文件];
B --> C[设置头文件搜索路径];
C --> D[选择编译器和平台];
D --> E[使用项目文件构建];
7. QP-nano 结构
7.1 QP-nano 结构概述
QP-nano 提供了中央基类
QActive
用于派生具体的主动对象。
QActive
类是抽象的,默认从
QHsm
层次状态机类派生,这意味着主动对象是 HSMs,并继承了
init()
和
dispatch()
状态机接口。
QActive
还包含主动对象优先级、事件队列和时间事件计数器。
7.2 QP-nano 类结构
| 类名 | 描述 |
|---|---|
QEvent
|
表示 QP-nano 中的事件,事件信号
QSignal
被 typedef 为字节(
uint8_t
),还包含一个标量事件参数。
|
QHsm
| 层次状态机类,包含当前事件。 |
QActive
| 抽象基类,用于派生具体的主动对象,包含优先级、事件队列和时间事件计数器。 |
QActiveCB
| 主动对象控制块结构,包含在编译时已知的只读数据元素。 |
7.3 QP-nano 结构流程图
graph TD;
A[QEvent] --> B[sig: QSignal];
A --> C[par: QParam];
D[QHsm] --> E[init()];
D --> F[dispatch()];
D --> G[state: QState Handler];
D --> H[evt: QEvent];
I[QActive] --> J[start()];
I --> K[post()];
I --> L[postISR()];
I --> M[arm()];
I --> N[disarm()];
I --> O[prio: uint8_t];
I --> P[head: uint8_t];
I --> Q[tail: uint8_t];
I --> R[nUsed: uint8_t];
I --> S[tickCtr: QTimeEvtCtr];
T[QActiveCB] --> U[act: * QActive];
T --> V[queue: * QEvent];
T --> W[end: uint8_t];
X[QF_active[]] --> Y[QActiveCB];
Z[应用程序] --> I[QActive];
Z --> A[QEvent];
Z --> D[QHsm];
Z --> T[QActiveCB];
通过以上内容,我们详细介绍了 QP-nano 在嵌入式系统开发中的应用,包括主动对象的实现、时间事件的处理、板级支持包的编写、应用的构建以及 QP-nano 的结构。这些知识可以帮助开发者更好地利用 QP-nano 进行轻量级嵌入式系统的开发。
QP-nano实现轻量级嵌入式系统开发
超级会员免费看
611

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



