0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

英创:经典的DOS程序框架流程图分析

英创信息技术 作者:英创信息技术 2019-10-17 11:23 次阅读

本文介绍以英创公司嵌入式PC模块为平台,以事件驱动为特色的一种通用的嵌入式系统应用程序方案,该方案满足大多数中、低端嵌入式系统需求,可广泛应用于智能测控设备、POS终端产品工业自动化网络通讯管理等领域。采用英创嵌入式网络模块的客户,更是可以此为基础,直接进入应用功能的软件规划及实现,从而大大节省应用程序的开发时间,同时保证应用程序的高稳定性。本应用程序方案的核心是通过对一个简单的任务命令队列进行操作,来实现各个不同的应用程序功能。下图是本方案的典型流程框图。

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:

DoST1( );

State = ST2; // 前进到下一状态

break;

case ST2:

DoST2( );

State = ST3; // 前进到下一状态

break;

case ST3:

DoST3( );

State = ST0; // 返回初始状态

I1 = 0; // 处理完成!

break;

}

return i1;

}


整个程序方案中,核心的代码是实现系统的事件驱动功能,被定义成一个C++类如下:

#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 }; // 可以根据应用定义更多的命令

#define MaxCmdStack 400 // 定义系统任务队列的长度

#define PARLEN 14 // 每个命令所带参数的长度

class TaskQueue

{

static unsigned int PutIdx; // 通过2个index的操作,使CmdBuf[ ]成为

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; // 确定最小的定时间隔,可变,初值为0

static void interrupt INT1C_Handler(__CPPARGS); // 通过INT 1C实现定时任务发生器

static int ISR_PushCmd( CMD NewCmd, char* pPar=NULL ); // 中断程序中使用

public:

TaskQueue( );

~TaskQueue( );

CMD GetCmd( char* pPar=NULL ); // 读取当前队列中的命令

int PushCmd( CMD NewCmd, char* pPar=NULL ); // 填入新的命令到系统任务队列

void StartQueue( ); // 启动定时任务发生器

void StopQueue( ); // 关闭定时任务发生器

};

extern class TaskQueue CmdQueue; // 在cmdrive.cpp中定义的类变量实例

#endif

在TaskQueue类的定义中有3个核心API函数,用于实现任务队列和定时任务发生:

CMD TaskQueue::GetCmd( char* pPar ) // 从FIFO读取命令

{

CMD CmdCode;

if( GetIdx != PutIdx )

{

disable( );

CmdCode = (CMD)CmdBuf[GetIdx];

if( pPar != NULL ) memcpy( pPar, CmdPar[GetIdx], PARLEN );

GetIdx = ( GetIdx + 1 ) % MaxCmdStack;

enable( );

return CmdCode;

}

return NOP;

}

// return = -1: command aborted

// = 0: command pushed

int TaskQueue::PushCmd( CMD NewCmd, char* pPar ) // 把命令填入任务队列

{

unsigned int Idx;

if( GetIdx == 0 ) Idx = MaxCmdStack - 1;

else Idx = GetIdx - 1;

disable( );

if( PutIdx == Idx ) return -1; // 表明队列已满

CmdBuf[PutIdx] = NewCmd; // 填入命令码

if( pPar == NULL ) memset( CmdPar[PutIdx], 0, PARLEN ); // 填入参数

else memcpy( CmdPar[PutIdx], pPar, PARLEN );

PutIdx = ( PutIdx + 1 ) % MaxCmdStack; // 序号按模加1

enable( );

return 0;

}

环形缓冲区的核心是使用了一块连续的内存,并定义了两个Index序号:一个是记录往缓冲区填数的PutIdx;一个是记录从缓冲区取数的GetIdx。置数和取数是两个完全异步的过程,所以PutIdx和GetIdx移动的瞬时速度不一定相同,但平均速度一致,当PutIdx==GetIdx表明缓冲区是空的,已经无数可取,而当PutIdx-GetIdx=1时,表明缓冲区已满,不允许再存数。

void interrupt TaskQueue::INT1C_Handler(__CPPARGS) // 定时任务发生器

{

int i1;

struct time t;

struct date d;

enable( );

TickCount++; // x86的系统时钟大约55ms中断一次

if( TickCount >= TickSize )

{

GetSystime( &t ); // get current time

if( t.ti_sec != OldTime.ti_sec ) // 作整秒检查

{

ISR_PushCmd( TASK1 ); // 每秒执行一次TASK1

TickSize = 18; // 整秒对齐

TickCount = 0;

OldTime.ti_sec = t.ti_sec;

if( t.ti_min != OldTime.ti_min ) // 作整分检查

{

ISR_PushCmd( TASK2 ); // 每分钟执行一次TASK2

OldTime.ti_min = t.ti_min; // update minute then

if( OldTime.ti_hour != t.ti_hour ) // processing hour data

{

ISR_PushCmd( TASK3 ); // 每小时执行一次TASK3

OldTime.ti_hour = t.ti_hour; // update hour then

}

}

}

}

}

按照上述代码实现的方法,用户很容易实现其他时间间隔的定时任务。

3、程序程序运行测试分析

建议每个任务的每次执行时间控制在100ms,以便系统合理的分配各任务的执行时间,节约系统的数据buffer开销。对大多数应用来说,这一要求很容易得到满足。本应用程序方案首先在NetBox-II(CPU主频24MHz)进行了测试,其任务调度的时间在90us水平,对100ms的任务间隔,系统占用时间小于1%,是完全可以接受的。

对于网络应用,由于存在与对端的交互式操作,所以其整个通讯过程会超过100ms,这时合理的安排是利用等待对端响应的时间来处理系统的其它任务,因此需要在相应的任务中采用状态机的方式来实现,具体的实现会在后续的应用程序方案中介绍。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式系统
    +关注

    关注

    40

    文章

    3437

    浏览量

    128238
收藏 人收藏

    评论

    相关推荐

    【求助】如何破解labview生成程序流程图

    那位大侠能提供破解labview生成的程序流程图,因为要看流程图必须有程序作者的密码权限,
    发表于 02-09 17:05

    能教教我程序流程图怎么画吗?有中断程序的。

    我刚开始学习单片机,对程序流程图不怎么熟悉,今天老师给了一个有中断程序程序,我想知道。画程序流程图
    发表于 10-18 18:04

    程序流程图

    这个程序流程图,请大神们帮帮我#include"config.h"/*************************clock data*************************************/#define c_ox 63#define c_oy
    发表于 07-05 11:07

    分享一个好的工具 单片机C语言流程图分析

    分享一个好的工具 单片机C语言流程图分析器,可以分析整理程序流程对看懂别人的程序非常管用!
    发表于 02-08 14:45

    Verilog用什么软件画程序流程图

    想用Viso画Verilog流程图,但是画了一点就觉得画不下去。之前用UML画,由于基本不太会,所以放弃。请问大家,Verilog流程图用什么画好呢?谢谢!
    发表于 11-03 18:13

    Labview流程图

    Labview能够生成流程图吗,Labview的程序能画流程图吗?
    发表于 07-21 10:09

    程序流程图

    求大神根据下面的程序帮我画一个程序流程图,越详细越好。#include "reg52.h"***it IN = P1^0;***it LED = P2^6;***it RELAY
    发表于 11-21 20:47

    基于labview温度湿度测试流程图

    急急急,请问有没有哪位大神做过基于labview温度湿度测试流程图,(注意不是看程序,是看流程图),小弟第一次写labview流程图不是太懂写法和格式以及注意的地方,有没有大神可以分享
    发表于 10-16 18:33

    visio画程序流程图

    弄清图形符号: 运行Visio,新建一个基本流程图。在形状窗口中用基本流程图的图形符号来表示程序算法。表1给出了较常用的“流程图”所用的基本符号。 举例使用: 一、制作顺序结构
    发表于 07-03 00:19

    怎样绘制流程图

    流程图进行新建使用。3.在左侧面板中选择我们需要的流程图图形,移动至右面面板中进行使用,然后会用线段将搭建的流程图图形进行连接,这样框架就算搭建完成。4.现在就是对内容进行输入,双击
    发表于 01-15 11:38

    控制算法设计及程序流程图

    \z \u一、设计要求及内容 3二、实验原理 3三、功能模块简介 3四、系统原理 7五、控制算法设计及程序流程图 8六、调试结果与分析 10七、心得体会 10参考文献 10附录 1
    发表于 09-10 07:34

    TL2543 AD采样程序流程图制作

    多多指教。程序流程图:TL2543 AD采样程序流程图制作出来的实物如下:单片机参考源代码如下:/*接好线后记得共地,根据
    发表于 01-07 07:58

    8253中断服务程序流程图

    8253中断服务程序流程图 希望发电机P =
    发表于 05-03 00:12 4755次阅读
    8253中断服务<b class='flag-5'>程序</b><b class='flag-5'>流程图</b>

    直流无刷电机控制器程序流程图

    直流无刷电机控制器程序流程图(新型电源技术论文)-直流无刷电机控制器程序流程图                         
    发表于 09-18 18:02 123次下载
    直流无刷电机控制器<b class='flag-5'>程序</b><b class='flag-5'>流程图</b>

    27个非常经典的设备工作流程图

    今天给大家分享27个非常经典的设备工作流程图解。
    的头像 发表于 06-02 17:16 1093次阅读
    27个非常<b class='flag-5'>经典</b>的设备工作<b class='flag-5'>流程图</b>解