楊振龍 張 偉 付國家 孫天澳
(丹東東方測控技術(shù)股份有限公司)
單片機(jī)通用軟件架構(gòu)在數(shù)據(jù)采集中的應(yīng)用*
楊振龍 張 偉 付國家 孫天澳
(丹東東方測控技術(shù)股份有限公司)
常規(guī)的單片機(jī)系統(tǒng)由于靈活性較差、過于復(fù)雜,用戶使用不方便?;谕ㄓ玫能浖軜?gòu)設(shè)計(jì),用戶可實(shí)現(xiàn)自行設(shè)計(jì)、開發(fā)單片機(jī)系統(tǒng)。介紹了一種單片機(jī)軟件架構(gòu)的基本設(shè)計(jì)思想,闡述了事件的記錄與檢測、類型與產(chǎn)生方式、處理過程、產(chǎn)生實(shí)例、定時(shí)器中斷服務(wù)程序等,并對部分代碼進(jìn)行說明?;谠摷軜?gòu)的單片機(jī)時(shí)序邏輯控制系統(tǒng)具有較高的實(shí)時(shí)性能,同時(shí)降低了程序開發(fā)的復(fù)雜度,提高了系統(tǒng)健壯性,縮短了程序開發(fā)周期,使利用單片機(jī)開發(fā)復(fù)雜的控制系統(tǒng)變得相對簡單、輕松。
單片機(jī) 軟件架構(gòu) 數(shù)據(jù)采集
單片機(jī)做為一種輕量級的微機(jī)系統(tǒng)具有廉價(jià)、方便、開發(fā)周期短等特點(diǎn),在實(shí)時(shí)控制領(lǐng)域具有極其廣泛的應(yīng)用。與PC系統(tǒng)不同,單片機(jī)是一種完全開放的硬件系統(tǒng),沒有相對固定的標(biāo)準(zhǔn)外設(shè)。根據(jù)具體應(yīng)用的不同,用戶需要自主定制系統(tǒng)外設(shè)。這在增加系統(tǒng)靈活性的同時(shí)也使得各種單片機(jī)系統(tǒng)軟件結(jié)構(gòu)千差萬別,用戶必須針對不同的應(yīng)用重新考慮相應(yīng)的軟件結(jié)構(gòu),無形中增加了系統(tǒng)開發(fā)的難度,加大了開發(fā)周期。
近年來,雖然已經(jīng)開發(fā)出一些相對成熟的單片機(jī)操作系統(tǒng),例如Keil公司的RTX51等,但應(yīng)用范圍仍然較小,主要與其靈活性及復(fù)雜度有關(guān)。這些操作系統(tǒng)大多過于復(fù)雜,很多支持多任務(wù)等功能,用戶需要花費(fèi)相當(dāng)多的時(shí)間和精力去學(xué)習(xí)和掌握使用技巧。另外操作系統(tǒng)的靈活性有待提高,用戶的編程方式受到了一定程度的限制。
分析認(rèn)為,單片機(jī)系統(tǒng)開發(fā)并不需要一套額外的軟件系統(tǒng),而是一個(gè)相對靈活、通用的軟件框架和程序開發(fā)準(zhǔn)則?;谠摽蚣芎蜏?zhǔn)則,對于任何單片機(jī)系統(tǒng),用戶都可以按部就班的進(jìn)行設(shè)計(jì)、開發(fā)、編程。論文介紹了基于此類目的開發(fā)的單片機(jī)軟件架構(gòu),能適用于大多數(shù)單片機(jī)控制系統(tǒng)。
單片機(jī)(如無特殊說明,單片機(jī)均指與Intel C51系列單片機(jī)兼容的CPU)歸根結(jié)底就是一款CPU,確切地說是基于馮·諾依曼結(jié)構(gòu)(程序存儲(chǔ)執(zhí)行)的CPU。因此,可以模仿CPU的工作方式來設(shè)計(jì)的軟件架構(gòu)。
CPU的工作過程可簡單的描述如下[1-2]:
(1)CPU按順序逐條執(zhí)行程序存儲(chǔ)器中的指令。
(2)在兩條機(jī)器指令之間,CPU執(zhí)行中斷標(biāo)志的檢測。如果檢測到中斷則跳轉(zhuǎn)到中斷服務(wù)程序中執(zhí)行。
(3)中斷處理完畢后,CPU跳轉(zhuǎn)到先前被中斷的指令處繼續(xù)執(zhí)行。
上述過程是CPU最基本的工作過程,略去了有關(guān)步驟更深入的細(xì)節(jié),因?yàn)槠鋵浖軜?gòu)沒有本質(zhì)影響。
仿照CPU的工作過程,單片機(jī)軟件框架基本結(jié)構(gòu)應(yīng)該是這樣的(基于C語言描述)[3]。
void main()
{
BYTE data e = 0;∥存儲(chǔ)檢測到的事件類型;
init(); onEvtRST();∥初始化系統(tǒng),產(chǎn)生'RST'通知事件;
while ( 1 ) {
if ( getEvt(&e) ){∥檢測事件;
processEvt(e); delEvt(e);//處理事件,刪除處理完的事件;
}
else{
onIdle();∥無事件需要處理時(shí),執(zhí)行空閑代碼;
}
}
}
其中變量e用于存儲(chǔ)getEvt()函數(shù)所獲取的當(dāng)前產(chǎn)生的事件類型。這里的“事件”就相當(dāng)于CPU處理過程中的“中斷”。該框架最多支持8類事件,即變量e的每一個(gè)二進(jìn)制位代表了一類事件。為了保證所有產(chǎn)生的事件都被處理而不發(fā)生事件丟失現(xiàn)象,getEvt()函數(shù)以及事件處理函數(shù)processEvt()并不刪除任何事件,因此,在事件處理完后要調(diào)用delEvt()函數(shù)將事件刪除。否則相同的事件就會(huì)不斷地被處理。當(dāng)沒有事件產(chǎn)生時(shí),系統(tǒng)運(yùn)行一個(gè)空閑代碼onIdle()去執(zhí)行一些實(shí)時(shí)性要求不高的動(dòng)作。
processEvt()函數(shù)用于處理e中記錄的事件,且每次僅獲取一個(gè)事件(如果有的話),即每次檢測,變量e中只會(huì)有一位被置為1。onEvtRST()是一個(gè)“軟事件”,相當(dāng)于CPU處理過程中的“軟中斷”,其中可以放置一些通知代碼,以通知上位機(jī)單片機(jī)產(chǎn)生了一次復(fù)位動(dòng)作。
總體上,框架用“函數(shù)調(diào)用”代替了CPU的“機(jī)器指令”,用“事件”代替了CPU的“中斷”,以完全模仿CPU工作過程的方式構(gòu)建整個(gè)單片機(jī)軟件執(zhí)行架構(gòu)。
框架使用全局變量g_evt記錄系統(tǒng)產(chǎn)生的全部事件,與代碼 1中變量e類型相同,但可以同時(shí)記錄8個(gè)事件。它位于全局存儲(chǔ)區(qū),便于各事件產(chǎn)生代碼(如中斷服務(wù)程序)并記錄各自產(chǎn)生的事件類型。需要注意的是,同一類型的事件在被處理之前只能被記錄一次。如果一類事件從產(chǎn)生到被處理之前又產(chǎn)生了該類事件,將會(huì)發(fā)生事件丟失現(xiàn)象。出現(xiàn)這種情況說明單片機(jī)的處理速度不夠,即事件產(chǎn)生的頻率過快,應(yīng)提高單片機(jī)的主頻。與事件記錄(存儲(chǔ))及檢測有關(guān)的代碼如下:
volatile BYTE data g_evt = 0;∥全局變量;
#define MAX_EVT 8
#define isEvtOn(e) (0 != (g_evt&(e)) )
#define addEvt(e) (g_evt |= (e) )
#define delEvt(e) (g_evt &= (~(e)) )
#define clrEvt() (g_evt = 0 )
BOOL getEvt(BYTE *p)
{
BYTE data i;
for (*p =1, i= 0; i< MAX_EVT;++i, (*p) <<= 1){
if ( isEvtOn(*p) ){
return TRUE;
}
}
return FALSE;
}
單片機(jī)系統(tǒng)的事件主要包含以下幾種:
(1)命令事件,即收到的上位機(jī)命令,由串口中斷服務(wù)程序產(chǎn)生的事件。該事件需串口中斷服務(wù)程序應(yīng)接收到完整的命令后才能產(chǎn)生,因此應(yīng)設(shè)置全局命令緩沖區(qū)用于存儲(chǔ)接收到的單個(gè)字符,同時(shí)設(shè)置緩沖區(qū)指針以指示當(dāng)前接收到的字符應(yīng)存放的緩沖區(qū)位置。命令緩沖區(qū)還用于事件處理函數(shù)中判斷當(dāng)前事件是哪個(gè)具體的命令產(chǎn)生的。因此,串口中斷服務(wù)程序中要首先判斷程序當(dāng)前是否正在處理該類事件,即緩沖區(qū)當(dāng)前是否空閑,才能決定是否將收到的字符放入緩沖區(qū)中,否則將干擾函數(shù)對事件的處理。
(2)外部中斷觸發(fā)事件,由單片機(jī)配置的外設(shè)硬件觸發(fā),由外部中斷服務(wù)程序注冊,由0號定時(shí)器中斷產(chǎn)生。該類事件不直接在中斷服務(wù)程序中產(chǎn)生,主要是由于該類事件的產(chǎn)生具有很大的隨機(jī)性,可能在極短的時(shí)間內(nèi)產(chǎn)生很多個(gè),如果直接在中斷服務(wù)程序中產(chǎn)生并處理,可能導(dǎo)致單片機(jī)無法處理其他事件。同時(shí),該類事件還可能是外設(shè)的誤動(dòng)作如按鍵的抖動(dòng)等。因此,讓定時(shí)器以某一固定的時(shí)間間隔來檢測該類事件比較合適(這有點(diǎn)類似于多任務(wù)系統(tǒng)的時(shí)間片概念),這樣可以抑制該類事件產(chǎn)生的頻度,還能達(dá)到去抖的目的。
(3)自定義事件,即“軟事件”,是由具體應(yīng)用定義的事件。例如當(dāng)檢測到某一端口電平狀態(tài)(開關(guān)量)時(shí),或當(dāng)檢測到某一模擬量輸入時(shí)(比如代碼 1中的“RST”事件),自定義事件直接由軟件在一定條件下通過某種算法產(chǎn)生。
與事件類型及產(chǎn)生有關(guān)的代碼如下:
enum{//事件類型;
EVT_CMD= 0x01,∥00000001,命令事件;
EVT_EX0= 0x02,∥00000010,外部硬件中斷0事件;
EVT_EX1= 0x04,∥00000100,外部硬件中斷1事件;
/*
...∥自定義類型事件。
*/
};
volatile BYTE data g_int = 0;∥全局變量;
#define isIntOn(e) (0 != (g_int&(e)) )
#define addInt(e) (g_int |= (e) )
#define delInt(e) (g_int &= (~(e)) )
#define clrInt() (g_int = 0 )
#define chkInt() ( (g_evt |= g_int), clrInt() )
全局變量g_int用于存儲(chǔ)當(dāng)前產(chǎn)生的中斷類型,含義與g_evt類似。在外部硬件中斷服務(wù)程序中調(diào)用addInt()向g_int中添加(注冊)中斷,在0號定時(shí)器中斷服務(wù)程序中調(diào)用chkInt()檢測產(chǎn)生的中斷并同時(shí)產(chǎn)生相應(yīng)的事件,最后清除注冊的中斷(調(diào)用clrInt())。
各類事件均有其特定的產(chǎn)生機(jī)制,一般與具體應(yīng)用有關(guān)。命令事件是指上位機(jī)發(fā)送給下位機(jī)的命令。大多數(shù)情況下單片機(jī)通過串口(UART)與上位機(jī)進(jìn)行通訊,命令事件在串口中斷服務(wù)程序中產(chǎn)生。這類事件實(shí)時(shí)性要求較高且不存在誤觸發(fā)及“抖動(dòng)”問題,可直接在中斷服務(wù)程序中產(chǎn)生。在此僅給出較有代表性的命令事件的產(chǎn)生過程以供參考,具體代碼如下:
#define INT_COM0 4 /*串口中斷的中斷號*/
#define CMD_SIZE 32 /*命令的最大長度*/
char data g_cmd[CMD_SIZE] = {0};∥命令緩沖區(qū),全局變量;
void isrCOM0 ()interrupt INT_COM0
{
if ( RI0 ){∥處理“讀”中斷;
static int data pos = 0;∥命令緩沖區(qū)指針;
const char data ch = SBUF0;∥讀串口;
if ( !isEvtOn(EVT_CMD) ){∥確保程序當(dāng)前未在處理該類事件;
if ( (pos >= CMD_SIZE)||('?' == ch)||('=' == ch) ){
pos = 0;
}
g_cmd[pos++]= ch;∥將字符存入命令緩沖區(qū);
if ( ';' == ch ){∥';'表示接收到一個(gè)有效的命令;
g_cmd[pos]= '/0';∥構(gòu)造C格式的字符串;
pos = 0;∥復(fù)位緩沖區(qū)指針;
addEvt(EVT_CMD);∥產(chǎn)生事件;
}
}
RI0 = 0;∥清除硬件中斷標(biāo)志;
}
else{∥處理“寫”中斷;
TI0 = 0;∥清除硬件中斷標(biāo)志;
}
}
該事件中命令使用ASCII字符進(jìn)行編碼,所有命令均以“?”或“=”開頭,以“;”結(jié)尾。因此,中斷服務(wù)程序中,每次檢測到“;”字符即可確定當(dāng)前收到了一個(gè)完整的命令,從而產(chǎn)生命令事件。在命令事件處理程序(onEvtCMD())中通過對全局命令緩沖區(qū)g_cmd中的內(nèi)容進(jìn)行字符串比對即可判斷具體的命令類型。
事件處理函數(shù)的定價(jià)代碼如下:
void processEvt(BYTE e)
{
switch ( e ){
case EVT_CMD:onEvtCMD();break;∥命令事件;
case EVT_EX0:onEvtEX0();break;∥外部硬件中斷0事件;
case EVT_EX1:onEvtEX1();break;∥外部硬件中斷1事件;
/*
…∥自定義類型事件。
*/
default: break;
}
}
其中以“onEvt”開頭的函數(shù)為對應(yīng)某一特定事件的處理函數(shù),代碼與具體應(yīng)用有關(guān)。
單片機(jī)程序應(yīng)充分利用0號定時(shí)器中斷將CPU時(shí)間進(jìn)行“分片”,以控制程序的執(zhí)行步調(diào),避免發(fā)生“阻塞”現(xiàn)象,如前面提到的“外部中斷事件”的處理頻度等問題。
選擇0號定時(shí)器中斷的理由有兩個(gè):①幾乎所有的C51單片機(jī)硬件都默認(rèn)配置有該定時(shí)器硬件;②0號定時(shí)器中斷的優(yōu)先級較高,一般不會(huì)被其他中斷干擾。針對某一特定單片機(jī)應(yīng)用系統(tǒng),應(yīng)將定時(shí)器中斷配置成某一最小定時(shí)周期(稱其為系統(tǒng)“滴答”周期),例如1微秒或1毫秒等,然后通過在定時(shí)器中斷內(nèi)部對中斷次數(shù)進(jìn)行計(jì)數(shù)的方式達(dá)到任意定時(shí)時(shí)長的目的。
對外部中斷類事件進(jìn)行定時(shí)檢測的定時(shí)器中斷服務(wù)程序某一實(shí)例代碼如下:
#define INT_T0 1/*0號定時(shí)器中斷號*/
#define TN_EVENT ((WORD)(0.025*TF) )//25ms
void isrT0 ()interrupt INT_T0
{
static WORD data iEvent= 0;
if ((++iEvent) >= TN_EVENT){
chkInt();
iEvent = 0;
}
}
其TF為定時(shí)器中斷的產(chǎn)生頻率,即每秒中發(fā)生中斷的次數(shù),即系統(tǒng)“滴答”的頻率。對于具體的應(yīng)用,TF應(yīng)視實(shí)際情況在系統(tǒng)初始化時(shí)對定時(shí)器進(jìn)行配置。
應(yīng)用該框架,設(shè)計(jì)并開發(fā)了一款數(shù)據(jù)采集系統(tǒng),該系統(tǒng)的原理見圖1[4]。
圖1 某款數(shù)據(jù)采集系統(tǒng)原理框圖
該系統(tǒng)是一個(gè)相對復(fù)雜的數(shù)據(jù)采集應(yīng)用系統(tǒng),涉及較多的外部信號采集、處理,包括模擬量輸入和輸出、數(shù)字量(開關(guān)量,脈沖計(jì)數(shù)等)輸入和輸出、硬件觸發(fā)中斷輸入、數(shù)字通訊等諸多信號輸入、輸出處理[5],如果不對單片機(jī)軟件系統(tǒng)進(jìn)行仔細(xì)的設(shè)計(jì)以選擇合理的軟件架構(gòu),將很難應(yīng)付錯(cuò)綜復(fù)雜的程序流程。
基于靈活、通用的軟件開發(fā)準(zhǔn)則的設(shè)計(jì)該軟件架構(gòu),使單片機(jī)應(yīng)用系統(tǒng)的開發(fā)過程規(guī)范化、結(jié)構(gòu)化、流程化,降低開發(fā)的復(fù)雜程度,使開發(fā)人員可以將主要精力集中于系統(tǒng)功能設(shè)計(jì)方面而不必在軟件實(shí)現(xiàn)方面花費(fèi)過多的精力。該架構(gòu)可在保證功能實(shí)現(xiàn)的前提下,大大縮短單片機(jī)應(yīng)用系統(tǒng)的開發(fā)周期,系統(tǒng)的穩(wěn)定性及可靠性也得到了很大的提高。隨著工業(yè)自動(dòng)化進(jìn)程的不斷深化,該軟件架構(gòu)將在單片機(jī)系統(tǒng)開發(fā)領(lǐng)域具有廣闊的應(yīng)用前景。
[1] 王克義,魯守智,蔡建新.微機(jī)原理與接口技術(shù)教程[M].北京:北京大學(xué)出版社,2004.
[2] Lan McLoughlin.計(jì)算機(jī)體系結(jié)構(gòu):嵌入式方法[M].王 沁,齊 悅譯.北京:機(jī)械工業(yè)出版社,2012.
[3] 徐愛鈞,徐 陽.Keil C51單片機(jī)高級語言應(yīng)用編程與實(shí)踐[M].北京:電子工業(yè)出版社,2013.
[4] 程國鋼,陳躍琴,崔荔蒙.51單片機(jī)典型模塊開發(fā)查詢手冊[M].北京:電子工業(yè)出版社,2012.
[5] 胡曉軍.數(shù)據(jù)采集與分析技術(shù)[M].西安:西安電子科技大學(xué)出版社,2010.
2015-11-23)
*國家重大科學(xué)儀器設(shè)備開發(fā)專項(xiàng)(2012YQ240121)。
楊振龍(1976—),男,工程師,118000 遼寧省丹東市振興區(qū)濱江中路136號。