No.62644 作者:emtronix 邮件:web@emtronix.com ID:116336 登陆:11次 文章数:54篇 最后登陆IP:222.212.37.103 最后登陆:2010/11/10 2:23:27 注册:2008/9/17 22:22:59 财富:418 发帖时间:2009/3/30 10:30:12 发贴者IP:218.88.33.212 标题:emtronix:一种经典的DOS程序框架 -- 成都英创信息技术有限公司 摘要:No.62644一种经典的DOS程序框架 -- 成都英创信息技术有限公司 文章来源: 本文介绍以英创公司嵌入式PC模块为平台,以事件驱动为特色的一种通用的嵌入式系统应用程序方案,该方案满足大多数中、低端嵌入式系统需求,可广泛应用于智能测控设备、POS终端产品、工业自动化、网络通讯管理等领域。采用英创嵌入式网络模块的客户,更是可以此为基础,直接进入应用功能的软件规划及实现,从而大大节省应用程序的开发时间,同时保证应用程序的高稳定性。本应用程序方案的核心是通过对一个简单的任务命令队列进行操作,来实现各个不同的应用程序功能。图1是本方案的典型流程框图。  图1 事件驱动的应用程序流程框图 1.系统流程概述 在图1中表示了3种不同的流程,它们是程序代码流程、任务命令(也称为事件)流程、以及数据的流程,以下对这三种流程做一简要介绍。 程序流程 应用程序启动后,首先进行必要的程序初始化配置,便进入系统核心代码,核心程序将依次读取系统任务队列中的事件代码,并根据代码内容转入相应的程序功能模块。不同的程序功能模块对应着不同的任务,即图中所标注的任务1、任务2、任务n等等,这些任务代码的特点之一是通过内部的状态机机制来避免程序阻塞,使得程序能快速返回系统任务调度单元,从而实现任务间的切换。 任务划分的原则一般是按照应用功能或层次来划分,如任务1对原始数据进行处理,任务2对处理的结果数据进行网络传送,任务3对数据进行文件备份。为了提高系统对事件的响应速度,每个任务不宜设计得过长,就大多数嵌入式系统应用来看,可以把任务的执行时间控制在100ms之内,对需要更长执行时间的功能,可以通过内部设置状态机的方式来化解。 命令流程 系统命令,通常也称为系统事件,可由系统中多个单元产生,这些单元可以是系统的定时中断程序,与应用相关的硬件中断程序以及各个任务功能程序模块,它们根据自身的运行状况,生成必要的事件并把这些事件推入系统任务队列。进入系统任务队列的事件是完全异步的,它们按照时间顺序排列,统一由系统核心代码读取,并启动相应的任务模块对该事件进行处理,这就是所谓的事件驱动机制。在程序设计中采用事件驱动的一个直接的好处是降低了各任务间的耦合性,提高了代码的可靠性及可维护性。 命令通常可定义成枚举变量,另可考虑命令参数段,可存放若干参数或字符串。系统任务队列是一个典型的FIFO数据结构,系统为中断程序和普通的任务模块提供了发送事件的API函数。定时任务发生器是一段加载到系统定时中断中的代码,在DOS系统中一般可提秒级以上的定时事件,更小时间间隔的事件,可通过系统的其他定时器中断实现,对于一般的嵌入式应用,最小定时事件不宜小于5ms,否则会无为增加CPU的开销,降低系统性能。在命令定义中,一般会定义IDLE或NOP命令,在IDLE任务中可以放常规的数据处理,也可以放检查是否有键盘、是否有网络数据来等等,并可形成必要的事件发送到系统任务队列,以启动相应的处理。 数据流程 各个任务模块的主要功能之一就是对各级应用数据进行必要的加工,并形成新的数据。典型的数据加工可以是: 对串口来的数据进行帧格式分析,提取相关数据,即通常的通讯规约分析; 对AD采集的原始数据进行某种统计处理,提取特征数据; 读取数字输入状态,进行必要处理; 读取网络报文,进行必要的应用层规约解析 应用数据存文件,文件数据处理等等 由于每个任务的执行机会具有一定的不确定性,因此需要对数据开设一定的缓冲区,对一般的应用来说,数据处理通常都是顺序进行的,所以数据缓冲区的结构通常采用FIFO数据结构,缓冲区的数据单元即可是简单的字节、字,也可以是复合的数据结构。在英创提供的程序中,串口的数据缓冲区就是采用的FIFO数据结构,数据单元为一个字节,FIFO结构的数据缓冲区也称为环型buffer。 可以由一个任务作数据处理,另一个任务作数据传送,对多任务共享的单一数据单元,可通过设置信号灯的方法来确保数据单元的完整性,对多个数据单元,同样可考虑采用FIFO数据结构。对数据响应时间有严格要求的应用,也可以用一个任务实现数据采集处理和网络通讯全过程。 以下具体介绍实现上述方案的主要代码。建议用户在阅读本文之前,已对英创嵌入式模块的功能测试程序有了基本了解。 2.主要程序代码分析 主控流程与应用任务 #include < stdio.h > // 包含所需的C运行库 #include < dos.h > #include “etr_tcp.h” // 英创TCP/IP库 #include “cmdrive.h” // 事件驱动API定义 int SysInit( ); // 系统初始化函数定义 void SysExit( ); // 系统退出处理 int main( ) { int i1, len, State, ExitFlag; // 局部变量 CMD CmdCode; // 系统命令枚举变量 char CmdPar[20]; // 系统命令所带参数 i1 = SysInit( ); // 首先进行初始化 for( ExitFlag=0; ; ) // 系统主循环 { ReloadWDT( ); // 加载watchdog State = NET_Running( ); // 网络链路管理 CmdCode = CmdQueue.GetCmd( CmdPar ); // 从系统任务队列读取命令 switch( CmdCode ) { case NOP: // 进行常规处理,如检查键盘、网络、串口等 NetPackagePro( ); // 做必要的网络低层处理 // 若网络接收到数据,则启动相应任务进行处理 if( NetHasData( ) ) CmdQueue.PushCmd( TASK1 ); break; case TASK1: i1 = Task1.Do ( ); // 也可以是普通C函数 break; case TASK2: i1 = Task2. Do ( ); if( i1 ) CmdQueue.PushCmd( TASK2 ); // 发送命令,以继续任务处理 break; case TASK3: i1 = Task3.Do ( ); break; default: ExitFlag =1; // 非法命令,退出 } if( ExitFlag ) break; } SysExit( ); return 0; } 系统初始化程序SysInit( ),首先是对系统提供的资源进行初始化,如网络初始化、串口初始化、LCD显示初始化等等,然后是对应用定义的功能对象进行初始化,最后是安装中断服务程序,启动定时任务发生器。相应地,SysExit( )函数则主要是卸载中断,释放在初始化中分配的动态buffer。 在主循环中的NOP处理,是以网络通讯为例,客户在实际应用程序设计中可以安排其他需要的处理,如处理键盘、处理串口数据等等。对应用级任务,建议采用C++的类来实现,每个类对象应至少有2个公共函数:Init( )和Do( )函数,主控程序可以通过Do( )函数的返回值来判断处理已完成或未完成,若未完成,可发命令再启动本函数进行后续处理,在上面的程序中任务TASK2的处理就是这样做的。用C++的类对象来实现应用功能,可通过私有变量来定义处理的状态,在进行交互式的通讯处理时,如操作串口设备,FTP文件上传等,特别有用,一旦需要处理程序等待对端响应,程序就返回系统控制进行其他处理,等下次再进入该任务模块时,程序可根据当前状态继续相应的处理,这就是所谓的状态机机制。下面是应用任务的类定义: #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( ); // 对包括State在内的变量进行初始化 int Do( ); // 任务处理函数 }; 在类成员函数Do( )中实现具体的状态转移: int AppTASK::Do( ) { int i1; i1 = 1; // 返回值 = 1:处理未完成;=0:处理完成 switch( State ) { case ST0: DoST0( ); State = ST1; // 前进到下一状态 break; case ST1: ......
>>返回讨论的主题
|