本文介紹以英創(chuàng)公司嵌入式PC模塊為平臺,以事件驅(qū)動為特色的一種通用的嵌入式系統(tǒng)應(yīng)用程序方案,該方案滿足大多數(shù)中、低端嵌入式系統(tǒng)需求,可廣泛應(yīng)用于智能測控設(shè)備、POS終端產(chǎn)品、工業(yè)自動化、網(wǎng)絡(luò)通訊管理等領(lǐng)域。采用英創(chuàng)嵌入式網(wǎng)絡(luò)模塊的客戶,更是可以此為基礎(chǔ),直接進(jìn)入應(yīng)用功能的軟件規(guī)劃及實現(xiàn),從而大大節(jié)省應(yīng)用程序的開發(fā)時間,同時保證應(yīng)用程序的高穩(wěn)定性。本應(yīng)用程序方案的核心是通過對一個簡單的任務(wù)命令隊列進(jìn)行操作,來實現(xiàn)各個不同的應(yīng)用程序功能。下圖是本方案的典型流程框圖。
1、系統(tǒng)流程概述
在上圖中表示了3種不同的流程,它們是程序代碼流程、任務(wù)命令(也稱為事件)流程、以及數(shù)據(jù)的流程,以下對這三種流程做一簡要介紹。
程序流程
應(yīng)用程序啟動后,首先進(jìn)行必要的程序初始化配置,便進(jìn)入系統(tǒng)核心代碼,核心程序?qū)⒁来巫x取系統(tǒng)任務(wù)隊列中的事件代碼,并根據(jù)代碼內(nèi)容轉(zhuǎn)入相應(yīng)的程序功能模塊。不同的程序功能模塊對應(yīng)著不同的任務(wù),即圖中所標(biāo)注的任務(wù)1、任務(wù)2、任務(wù)n等等,這些任務(wù)代碼的特點之一是通過內(nèi)部的狀態(tài)機(jī)機(jī)制來避免程序阻塞,使得程序能快速返回系統(tǒng)任務(wù)調(diào)度單元,從而實現(xiàn)任務(wù)間的切換。
任務(wù)劃分的原則一般是按照應(yīng)用功能或?qū)哟蝸韯澐郑缛蝿?wù)1對原始數(shù)據(jù)進(jìn)行處理,任務(wù)2對處理的結(jié)果數(shù)據(jù)進(jìn)行網(wǎng)絡(luò)傳送,任務(wù)3對數(shù)據(jù)進(jìn)行文件備份。為了提高系統(tǒng)對事件的響應(yīng)速度,每個任務(wù)不宜設(shè)計得過長,就大多數(shù)嵌入式系統(tǒng)應(yīng)用來看,可以把任務(wù)的執(zhí)行時間控制在100ms之內(nèi),對需要更長執(zhí)行時間的功能,可以通過內(nèi)部設(shè)置狀態(tài)機(jī)的方式來化解。
命令流程
系統(tǒng)命令,通常也稱為系統(tǒng)事件,可由系統(tǒng)中多個單元產(chǎn)生,這些單元可以是系統(tǒng)的定時中斷程序,與應(yīng)用相關(guān)的硬件中斷程序以及各個任務(wù)功能程序模塊,它們根據(jù)自身的運行狀況,生成必要的事件并把這些事件推入系統(tǒng)任務(wù)隊列。進(jìn)入系統(tǒng)任務(wù)隊列的事件是完全異步的,它們按照時間順序排列,統(tǒng)一由系統(tǒng)核心代碼讀取,并啟動相應(yīng)的任務(wù)模塊對該事件進(jìn)行處理,這就是所謂的事件驅(qū)動機(jī)制。在程序設(shè)計中采用事件驅(qū)動的一個直接的好處是降低了各任務(wù)間的耦合性,提高了代碼的可靠性及可維護(hù)性。
命令通??啥x成枚舉變量,另可考慮命令參數(shù)段,可存放若干參數(shù)或字符串。系統(tǒng)任務(wù)隊列是一個典型的FIFO數(shù)據(jù)結(jié)構(gòu),系統(tǒng)為中斷程序和普通的任務(wù)模塊提供了發(fā)送事件的API函數(shù)。定時任務(wù)發(fā)生器是一段加載到系統(tǒng)定時中斷中的代碼,在DOS系統(tǒng)中一般可提秒級以上的定時事件,更小時間間隔的事件,可通過系統(tǒng)的其他定時器中斷實現(xiàn),對于一般的嵌入式應(yīng)用,最小定時事件不宜小于5ms,否則會無為增加CPU的開銷,降低系統(tǒng)性能。在命令定義中,一般會定義IDLE或NOP命令,在IDLE任務(wù)中可以放常規(guī)的數(shù)據(jù)處理,也可以放檢查是否有鍵盤、是否有網(wǎng)絡(luò)數(shù)據(jù)來等等,并可形成必要的事件發(fā)送到系統(tǒng)任務(wù)隊列,以啟動相應(yīng)的處理。
數(shù)據(jù)流程
各個任務(wù)模塊的主要功能之一就是對各級應(yīng)用數(shù)據(jù)進(jìn)行必要的加工,并形成新的數(shù)據(jù)。典型的數(shù)據(jù)加工可以是:
對串口來的數(shù)據(jù)進(jìn)行幀格式分析,提取相關(guān)數(shù)據(jù),即通常的通訊規(guī)約分析;
對AD采集的原始數(shù)據(jù)進(jìn)行某種統(tǒng)計處理,提取特征數(shù)據(jù);
讀取數(shù)字輸入狀態(tài),進(jìn)行必要處理;
讀取網(wǎng)絡(luò)報文,進(jìn)行必要的應(yīng)用層規(guī)約解析
應(yīng)用數(shù)據(jù)存文件,文件數(shù)據(jù)處理等等
由于每個任務(wù)的執(zhí)行機(jī)會具有一定的不確定性,因此需要對數(shù)據(jù)開設(shè)一定的緩沖區(qū),對一般的應(yīng)用來說,數(shù)據(jù)處理通常都是順序進(jìn)行的,所以數(shù)據(jù)緩沖區(qū)的結(jié)構(gòu)通常采用FIFO數(shù)據(jù)結(jié)構(gòu),緩沖區(qū)的數(shù)據(jù)單元即可是簡單的字節(jié)、字,也可以是復(fù)合的數(shù)據(jù)結(jié)構(gòu)。在英創(chuàng)提供的程序中,串口的數(shù)據(jù)緩沖區(qū)就是采用的FIFO數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)單元為一個字節(jié),F(xiàn)IFO結(jié)構(gòu)的數(shù)據(jù)緩沖區(qū)也稱為環(huán)型buffer。
可以由一個任務(wù)作數(shù)據(jù)處理,另一個任務(wù)作數(shù)據(jù)傳送,對多任務(wù)共享的單一數(shù)據(jù)單元,可通過設(shè)置信號燈的方法來確保數(shù)據(jù)單元的完整性,對多個數(shù)據(jù)單元,同樣可考慮采用FIFO數(shù)據(jù)結(jié)構(gòu)。對數(shù)據(jù)響應(yīng)時間有嚴(yán)格要求的應(yīng)用,也可以用一個任務(wù)實現(xiàn)數(shù)據(jù)采集處理和網(wǎng)絡(luò)通訊全過程。
以下具體介紹實現(xiàn)上述方案的主要代碼。建議用戶在閱讀本文之前,已對英創(chuàng)嵌入式模塊的功能測試程序有了基本了解。
2、主要程序代碼分析
主控流程與應(yīng)用任務(wù)
#include < stdio.h > // 包含所需的C運行庫
#include < dos.h >
#include “etr_tcp.h” // 英創(chuàng)TCP/IP庫
#include “cmdrive.h” // 事件驅(qū)動API定義
int SysInit( ); // 系統(tǒng)初始化函數(shù)定義
void SysExit( ); // 系統(tǒng)退出處理
int main( )
{
int i1, len, State, ExitFlag; // 局部變量
CMD CmdCode; // 系統(tǒng)命令枚舉變量
char CmdPar[20]; // 系統(tǒng)命令所帶參數(shù)
i1 = SysInit( ); // 首先進(jìn)行初始化
for( ExitFlag=0; ; ) // 系統(tǒng)主循環(huán)
{
ReloadWDT( ); // 加載watchdog
State = NET_Running( ); // 網(wǎng)絡(luò)鏈路管理
CmdCode = CmdQueue.GetCmd( CmdPar ); // 從系統(tǒng)任務(wù)隊列讀取命令
switch( CmdCode )
{
case NOP: // 進(jìn)行常規(guī)處理,如檢查鍵盤、網(wǎng)絡(luò)、串口等
NetPackagePro( ); // 做必要的網(wǎng)絡(luò)低層處理
// 若網(wǎng)絡(luò)接收到數(shù)據(jù),則啟動相應(yīng)任務(wù)進(jìn)行處理
if( NetHasData( ) ) CmdQueue.PushCmd( TASK1 );
break;
case TASK1:
i1 = Task1.Do ( ); // 也可以是普通C函數(shù)
break;
case TASK2:
i1 = Task2. Do ( );
if( i1 ) CmdQueue.PushCmd( TASK2 ); // 發(fā)送命令,以繼續(xù)任務(wù)處理
break;
case TASK3:
i1 = Task3.Do ( );
break;
default: ExitFlag =1; // 非法命令,退出
}
if( ExitFlag ) break;
}
SysExit( );
return 0;
}
系統(tǒng)初始化程序SysInit( ),首先是對系統(tǒng)提供的資源進(jìn)行初始化,如網(wǎng)絡(luò)初始化、串口初始化、LCD顯示初始化等等,然后是對應(yīng)用定義的功能對象進(jìn)行初始化,最后是安裝中斷服務(wù)程序,啟動定時任務(wù)發(fā)生器。相應(yīng)地,SysExit( )函數(shù)則主要是卸載中斷,釋放在初始化中分配的動態(tài)buffer。
在主循環(huán)中的NOP處理,是以網(wǎng)絡(luò)通訊為例,客戶在實際應(yīng)用程序設(shè)計中可以安排其他需要的處理,如處理鍵盤、處理串口數(shù)據(jù)等等。對應(yīng)用級任務(wù),建議采用C++的類來實現(xiàn),每個類對象應(yīng)至少有2個公共函數(shù):Init( )和Do( )函數(shù),主控程序可以通過Do( )函數(shù)的返回值來判斷處理已完成或未完成,若未完成,可發(fā)命令再啟動本函數(shù)進(jìn)行后續(xù)處理,在上面的程序中任務(wù)TASK2的處理就是這樣做的。用C++的類對象來實現(xiàn)應(yīng)用功能,可通過私有變量來定義處理的狀態(tài),在進(jìn)行交互式的通訊處理時,如操作串口設(shè)備,F(xiàn)TP文件上傳等,特別有用,一旦需要處理程序等待對端響應(yīng),程序就返回系統(tǒng)控制進(jìn)行其他處理,等下次再進(jìn)入該任務(wù)模塊時,程序可根據(jù)當(dāng)前狀態(tài)繼續(xù)相應(yīng)的處理,這就是所謂的狀態(tài)機(jī)機(jī)制。下面是應(yīng)用任務(wù)的類定義:
#define ST0 0
#define ST1 1
#define ST2 2
#define ST3 3
class AppTASK
{
int State; // 私有的狀態(tài)變量
int DoST0( ); // 各個分步處理
int DoST1( );
int DoST2( );
int DoST3( );
public:
int Init( ); // 對包括State在內(nèi)的變量進(jìn)行初始化
int Do( ); // 任務(wù)處理函數(shù)
};
在類成員函數(shù)Do( )中實現(xiàn)具體的狀態(tài)轉(zhuǎn)移:
int AppTASK::Do( )
{
int i1;
i1 = 1; // 返回值 = 1:處理未完成;=0:處理完成
switch( State )
{
case ST0:
DoST0( );
State = ST1; // 前進(jìn)到下一狀態(tài)
break;
case ST1:
DoST1( );
State = ST2; // 前進(jìn)到下一狀態(tài)
break;
case ST2:
DoST2( );
State = ST3; // 前進(jìn)到下一狀態(tài)
break;
case ST3:
DoST3( );
State = ST0; // 返回初始狀態(tài)
I1 = 0; // 處理完成!
break;
}
return i1;
}
整個程序方案中,核心的代碼是實現(xiàn)系統(tǒng)的事件驅(qū)動功能,被定義成一個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 }; // 可以根據(jù)應(yīng)用定義更多的命令
#define MaxCmdStack 400 // 定義系統(tǒng)任務(wù)隊列的長度
#define PARLEN 14 // 每個命令所帶參數(shù)的長度
class TaskQueue
{
static unsigned int PutIdx; // 通過2個index的操作,使CmdBuf[ ]成為
static unsigned int GetIdx; // 邏輯上的環(huán)型buffer,即FIFO數(shù)據(jù)結(jié)構(gòu)
static CMD CmdBuf[MaxCmdStack];
static char CmdPar[MaxCmdStack][PARLEN];
static struct time OldTime;
static struct date OldDate;
static unsigned int TickCount; // 定時計數(shù)
static unsigned int TickSize; // 確定最小的定時間隔,可變,初值為0
static void interrupt INT1C_Handler(__CPPARGS); // 通過INT 1C實現(xiàn)定時任務(wù)發(fā)生器
static int ISR_PushCmd( CMD NewCmd, char* pPar=NULL ); // 中斷程序中使用
public:
TaskQueue( );
~TaskQueue( );
CMD GetCmd( char* pPar=NULL ); // 讀取當(dāng)前隊列中的命令
int PushCmd( CMD NewCmd, char* pPar=NULL ); // 填入新的命令到系統(tǒng)任務(wù)隊列
void StartQueue( ); // 啟動定時任務(wù)發(fā)生器
void StopQueue( ); // 關(guān)閉定時任務(wù)發(fā)生器
};
extern class TaskQueue CmdQueue; // 在cmdrive.cpp中定義的類變量實例
#endif
在TaskQueue類的定義中有3個核心API函數(shù),用于實現(xiàn)任務(wù)隊列和定時任務(wù)發(fā)生:
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 ) // 把命令填入任務(wù)隊列
{
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 ); // 填入?yún)?shù)
else memcpy( CmdPar[PutIdx], pPar, PARLEN );
PutIdx = ( PutIdx + 1 ) % MaxCmdStack; // 序號按模加1
enable( );
return 0;
}
環(huán)形緩沖區(qū)的核心是使用了一塊連續(xù)的內(nèi)存,并定義了兩個Index序號:一個是記錄往緩沖區(qū)填數(shù)的PutIdx;一個是記錄從緩沖區(qū)取數(shù)的GetIdx。置數(shù)和取數(shù)是兩個完全異步的過程,所以PutIdx和GetIdx移動的瞬時速度不一定相同,但平均速度一致,當(dāng)PutIdx==GetIdx表明緩沖區(qū)是空的,已經(jīng)無數(shù)可取,而當(dāng)PutIdx-GetIdx=1時,表明緩沖區(qū)已滿,不允許再存數(shù)。
void interrupt TaskQueue::INT1C_Handler(__CPPARGS) // 定時任務(wù)發(fā)生器
{
int i1;
struct time t;
struct date d;
enable( );
TickCount++; // x86的系統(tǒng)時鐘大約55ms中斷一次
if( TickCount >= TickSize )
{
GetSystime( &t ); // get current time
if( t.ti_sec != OldTime.ti_sec ) // 作整秒檢查
{
ISR_PushCmd( TASK1 ); // 每秒執(zhí)行一次TASK1
TickSize = 18; // 整秒對齊
TickCount = 0;
OldTime.ti_sec = t.ti_sec;
if( t.ti_min != OldTime.ti_min ) // 作整分檢查
{
ISR_PushCmd( TASK2 ); // 每分鐘執(zhí)行一次TASK2
OldTime.ti_min = t.ti_min; // update minute then
if( OldTime.ti_hour != t.ti_hour ) // processing hour data
{
ISR_PushCmd( TASK3 ); // 每小時執(zhí)行一次TASK3
OldTime.ti_hour = t.ti_hour; // update hour then
}
}
}
}
}
按照上述代碼實現(xiàn)的方法,用戶很容易實現(xiàn)其他時間間隔的定時任務(wù)。
3、程序程序運行測試分析
建議每個任務(wù)的每次執(zhí)行時間控制在100ms,以便系統(tǒng)合理的分配各任務(wù)的執(zhí)行時間,節(jié)約系統(tǒng)的數(shù)據(jù)buffer開銷。對大多數(shù)應(yīng)用來說,這一要求很容易得到滿足。本應(yīng)用程序方案首先在NetBox-II(CPU主頻24MHz)進(jìn)行了測試,其任務(wù)調(diào)度的時間在90us水平,對100ms的任務(wù)間隔,系統(tǒng)占用時間小于1%,是完全可以接受的。
對于網(wǎng)絡(luò)應(yīng)用,由于存在與對端的交互式操作,所以其整個通訊過程會超過100ms,這時合理的安排是利用等待對端響應(yīng)的時間來處理系統(tǒng)的其它任務(wù),因此需要在相應(yīng)的任務(wù)中采用狀態(tài)機(jī)的方式來實現(xiàn),具體的實現(xiàn)會在后續(xù)的應(yīng)用程序方案中介紹。
-
嵌入式系統(tǒng)
+關(guān)注
關(guān)注
41文章
3593瀏覽量
129482
發(fā)布評論請先 登錄
相關(guān)推薦
評論