嵌入式系统应用程序方案之一——基于事件驱动的应用程序框架
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
进行处理,这就是所谓的事件驱动机制。在程序设计中采用事件驱动的一个直接的好处是降
低了各任务间的耦合性,提高了代码的可靠性及可维护性。
命令通常可定义成枚举变量,另可考虑命令参数段,可存放若干参数或字符串。系统任
务队列是一个典型的 FIFO 数据结构,系统为中断程序和普通的任务模块提供了发送事件的
API 函数。定时任务发生器是一段加载到系统定时中断中的代码,在 DOS 系统中一般可提
break;
case TASK1:
i1 = Task1.Do ( );
// 也可以是普通 C 函数
break;
case TASK2:
i1 = Task2. Do ( );
if( i1 ) CmdQueue.PushCmd( TASK2 );
// 发送命令,以继续任务处理
break;
case TASK3:
static unsigned int GetIdx;
// 逻辑上的环型 buffer,即 FIFO 数据结构
static CMD
CmdBuf[MaxCmdStack];
static char
CmdPar[MaxCmdStack][PARLEN];
static struct time OldTime; static struct date OldDate; static unsigned int TickCount; static unsigned int TickSize;
列,以启动相应的处理。
数据流程
技术支持:028-85136173
2
http://www.emtronix.com
嵌入式系统应用程序方案之一
各个任务模块的主要功能之一就是对各级应用数据进行必要的加工,并形成新的数据。 典型的数据加工可以是:
对串口来的数据进行帧格式分析,提取相关数据,即通常的通讯规约分析; 对 AD 采集的原始数据进行某种统计处理,提取特征数据; 读取数字输入状态,进行必要处理; 读取网络报文,进行必要的应用层规约解析 应用数据存文件,文件数据处理等等 由于每个任务的执行机会具有一定的不确定性,因此需要对数据开设一定的缓冲区,对 一般的应用来说,数据处理通常都是顺序进行的,所以数据缓冲区的结构通常采用 FIFO 数 据结构,缓冲区的数据单元即可是简单的字节、字,也可以是复合的数据结构。在英创提供 的程序中,串口的数据缓冲区就是采用的 FIFO 数据结构,数据单元为一个字节,FIFO 结 构的数据缓冲区也称为环型 buffer。 可以由一个任务作数据处理,另一个任务作数据传送,对多任务共享的单一数据单元, 可通过设置信号灯的方法来确保数据单元的完整性,对多个数据单元,同样可考虑采用 FIFO 数据结构。对数据响应时间有严格要求的应用,也可以用一个任务实现数据采集处理和网络 通讯全过程。 以下具体介绍实现上述方案的主要代码。建议用户在阅读本文之前,已对英创嵌入式模 块的功能测试程序有了基本了解。
i1 = Task3.Do ( );
break;
default: ExitFlag =1;
// 非法命令,退出
}
技术支持:028-85136173
4
http://www.emtronix.com
嵌入式系统应用程序方案之一
if( ExitFlag ) break; } SysExit( ); return 0; }
应用程序启动后,首先进行必要的程序初始化配置,便进入系统核心代码,核心程序将
依次读取系统任务队列中的事件代码,并根据代码内容转入相应的程序功能模块。不同的程
序功能模块对应着不同的任务,即图中所标注的任务 1、任务 2、任务 n 等等,这些任务代
码的特点之一是通过内部的状态机机制来避免程序阻塞,使得程序能快速返回系统任务调度
嵌入式系统应用程序方案之一
嵌入式系统应用程序方案之一
——基于事件驱动的应用程序框架
英创信息技术有限公司 2005 年 10 月
本文介绍以英创公司嵌入式 PC 模块为平台,以事件驱动为特色的一种通用的嵌入式系 统应用程序方案,该方案满足大多数中、低端嵌入式系统需求,可广泛应用于智能测控设备、 POS 终端产品、工业自动化、网络通讯管理等领域。采用英创嵌入式网络模块的客户,更 是可以此为基础,直接进入应用功能的软件规划及实现,从而大大节省应用程序的开发时间, 同时保证应用程序的高稳定性。本应用程序方案的核心是通过对一个简单的任务命令队列进 行操作,来实现各个不同的应用程序功能。图 1 是本方案的典型流程框图。
系统初始化程序 SysInit( ),首先是对系统提供的资源进行初始化,如网络初始化、串 口初始化、LCD 显示初始化等等,然后是对应用定义的功能对象进行初始化,最后是安装 中断服务程序,启动定时任务发生器。相应地,SysExit( )函数则主要是卸载中断,释放在 初始化中分配的动态 buffer。
} return i1; }
// 返回值 = 1:处理未完成;=0:处理完成
// 前进到下一状态 // 前进到下一状态 // 前进到下一状态 // 返回初始状态 // 处理完成!
技术支持:028-85136173
6
http://www.emtronix.com
嵌入式系统应用程序方案之一
整个程序方案中,核心的代码是实现系统的事件驱动功能,被定义成一个 C++类如下:
单元,从而实现任务间的切换。
任务划分的原则一般是按照应用功能或层次来划分,如任务 1 对原始数据进行处理,
任务 2 对处理的结果数据进行网络传送,任务 3 对数据进行文件备份。为了提高系统对事
件的响应速度,每个任务不宜设计得过长,就大多数嵌入式系统应用来看,可以把任务的执
行时间控制在 100ms 之内,对需要更长执行时间的功能,可以通过内部设置状态机的方式
// 定时计数 // 确定最小的定时间隔,可变,初值为 0
static void interrupt INT1C_Handler(__CPPARGS);
// 通过 INT 1C 实现定时任务发生器
static int ISR_PushCmd( CMD NewCmd, char* pPar=NULL );// 中断程序中使用
在主循环中的 NOP 处理,是以网络通讯为例,客户在实际应用程序设计中可以安排其 他需要的处理,如处理键盘、处理串口数据等等。对应用级任务,建议采用 C++的类来实 现,每个类对象应至少有 2 个公共函数:Init( )和 Do( )函数,主控程序可以通过 Do( )函数 的返回值来判断处理已完成或未完成,若未完成,可发命令再启动本函数进行后续处理,在 上面的程序中任务 TASK2 的处理就是这样做的。用 C++的类对象来实现应用功能,可通过 私有变量来定义处理的状态,在进行交互式的通讯处理时,如操作串口设备,FTP 文件上 传等,特别有用,一旦需要处理程序等待对端响应,程序就返回系统控制进行其他处理,等 下次再进入该任务模块时,程序可根据当前状态继续相应的处理,这就是所谓的状态机机制。 下面是应用任务的类定义:
秒级以上的定时事件,更小时间间隔的事件,可通过系统的其他定时器中断实现,对于一般
的嵌入式应用,最小定时事件不宜小于 5ms,否则会无为增加 CPU 的开销,降低系统性能。
在命令定义中,一般会定义 IDLE 或 NOP 命令,在 IDLE 任务中可以放常规的数据处理,
也可以放检查是否有键盘、是否有网络数据来等等,并可形成必要的事件发送到系统任务队
int i1;
i1 = 1; switch( State ) {
case ST0: DoST0( ); State = ST1; break;
case ST1: DoST1( ); State = ST2; break;
case ST2: DoST2( ); State = ST3; break;
case ST3: DoST3( ); State = ST0; I1 = 0; break;
技术支持:028-85136173
// 私有的状态变量 // 各个分步处理
// 对包括 State 在内的变量进行初始化 // 任务处理函数
5
http://www.emtronix.com
嵌入式系统应用程序方案之一
在类成员函数 Do( )中实现具体的状态转移:
int AppTASK::Do( ) {
#define
ST0
0
#define
ST1
1
#define
ST2
2
#define
ST3
3
class AppTASK {
int State; int DoST0( ); int DoST1( ); int DoST2( ); int DoST3( ); public: int Init( ); int Do( ); };
// 局部变量 // 系统命令枚举变量 // 系统命令所带参数
i1 = SysInit( );
// 首先进行初始化
for( ExitFlag=0; ; )
// 系统主循环
{
ReloadWDT( );
// 加载 watchdog
State = NET_Running( );
// 网络链路管理
CmdCode = CmdQueue.GetCmd( CmdPar );
技术支持:028-85136173
3
http://www.emtronix.com
嵌入式系统应用程序方案之一
2.主要程序代码分析
主控流程与应用任务
#include #include #include #include
<stdio.h> <dos.h> “etr_tcp.h” “cmdrive.h”
// 读取当前队列中的命令 // 填入新的命令到系统任务队列 // 启动定时任务发生器 // 关闭定时任务发生器
// 可以根据应用定义更多的命令
#define #define
MaxCmdStack 400
PARLEN
14
// 定义系统任务队列的长度 // 每个命令所带参数的长度
class TaskQueue
{
static unsigned int PutIdx;
// 通过 2 个 index 的操作,使 CmdBuf[ ]成为
// 从系统任务队列读取命令
switch( CmdCode )
{
case NOP:
// 进行常规处理,如检查键盘、网络、串口等
NetPackagePro( );
// 做必要的网络低层处理
// 若网络接收到数据,则启动相应任务进行处理
If( NetHasData( ) ) CmdQueue.PushCmd( TASK1 );
技术支持:028-85136173
图 1 事件驱动的应用程序流程框图
1
ห้องสมุดไป่ตู้
http://www.emtronix.com
嵌入式系统应用程序方案之一
1.系统流程概述
在图 1 中表示了 3 种不同的流程,它们是程序代码流程、任务命令(也称为事件)流
程、以及数据的流程,以下对这三种流程做一简要介绍。
程序流程
public:
TaskQueue( );
~TaskQueue( );
CMD GetCmd( char* pPar=NULL ); int PushCmd( CMD NewCmd, char* pPar=NULL ); void StartQueue( ); void StopQueue( ); };
// 包含所需的 C 运行库
// 英创 TCP/IP 库 // 事件驱动 API 定义
int SysInit( ); void SysExit( ); int main( ) {
int i1, len, State, ExitFlag; CMD CmdCode; char CmdPar[20];
// 系统初始化函数定义 // 系统退出处理
来化解。
命令流程
系统命令,通常也称为系统事件,可由系统中多个单元产生,这些单元可以是系统的定
时中断程序,与应用相关的硬件中断程序以及各个任务功能程序模块,它们根据自身的运行
状况,生成必要的事件并把这些事件推入系统任务队列。进入系统任务队列的事件是完全异
步的,它们按照时间顺序排列,统一由系统核心代码读取,并启动相应的任务模块对该事件
#if
!defined(_CMDRIVE_H)
#define _CMDRIVE_H
#ifdef __cplusplus #define __CPPARGS ...
#else #define __CPPARGS
#endif
#include <dos.h>
enum CMD { NOP, TASK1, TASK2, TASK3, EXIT };