mcu由于內(nèi)部資源的限制,軟件設(shè)計有其特殊性,程序一般沒有復(fù)雜的算法以及數(shù)據(jù)結(jié)構(gòu),代碼量也不大,通常不會使用OS(OperatingSystem),因為對于一個只有若干KROM,一百多byteRAM的mcu來說,一個簡單OS也會吃掉大部分的資源。
對于無os的系統(tǒng),流行的設(shè)計是主程序(主循環(huán))+(定時)中斷,這種結(jié)構(gòu)雖然符合自然想法,不過卻有很多不利之處,首先是中斷可以在主程序的任何地方發(fā)生,隨意打斷主程序。其次主程序與中斷之間的耦合性(關(guān)聯(lián)度)較大,這種做法使得主程序與中斷纏繞在一起,必須仔細(xì)處理以防不測。
那么換一種思路,如果把主程序全部放入(定時)中斷中會怎么樣?這么做至少可以立即看到幾個好處:系統(tǒng)可以處于低功耗的休眠狀態(tài),將由中斷喚醒進入主程序;如果程序跑飛,則中斷可以拉回;沒有了主從之分(其他中斷另計),程序易于模塊化。
(題外話:這種方法就不會有何處喂狗的說法,也沒有中斷是否應(yīng)該盡可能的簡短的爭論了)
為了把主程序全部放入(定時)中斷中,必須把程序化分成一個個的模塊,即任務(wù),每個任務(wù)完成一個特定的功能,例如掃描鍵盤并檢測按鍵。設(shè)定一個合理的時基(tick),例如5,10或20ms,每次定時中斷,把所有任務(wù)執(zhí)行一遍,為減少復(fù)雜性,一般不做動態(tài)調(diào)度(最多使用固定數(shù)組以簡化設(shè)計,做動態(tài)調(diào)度就接近os了),這實際上是一種無優(yōu)先級時間片輪循的變種。來看看主程序的構(gòu)成:
voidmain()
{
….//Initialize
while(true){
IDLE;//sleep
}
}
這里的IDLE是一條sleep指令,讓mcu進入低功耗模式。中斷程序的構(gòu)成
voidTimer_Interrupt()
{
SetTimer();
ResetStack();
Enable_Timer_Interrupt;
….
進入中斷后,首先重置Timer,這主要針對8051,8051自動重裝分頻器只有8-bit,難以做到長時間定時;復(fù)位stack,即把stack指針賦值為棧頂或棧底(對于pic,TIDSP等使用循環(huán)棧的mcu來說,則無此必要),用以表示與過去決裂,而且不準(zhǔn)備返回到中斷點,保證不會保留程序在跑飛時stack中的遺體。Enable_Timer_Interrupt也主要是針對8051。8051由于中斷控制較弱,只有兩級中斷優(yōu)先級,而且使用了如果中斷程序不用reti返回,則不能響應(yīng)同級中斷這種偷懶方法,所以對于8051,必須調(diào)用一次reti來開放中斷:
_Enable_Timer_Interrupt:
acall_reti
_reti:reti
下面就是任務(wù)的執(zhí)行了,這里有幾種方法。第一種是采用固定順序,由于mcu程序復(fù)雜度不高,多數(shù)情況下可以采用這種方法:
…
Enable_Timer_Interrupt;
ProcessKey();
RunTask2();
…
RunTaskN();
while(1)IDLE;
可以看到中斷把所有任務(wù)調(diào)用一遍,至于任務(wù)是否需要運行,由程序員自己控制。另一種做法是通過函數(shù)指針數(shù)組:
#defineCountOfArray(x)(sizeof(x)/sizeof(x[0]))
typedefvoid(*FUNCTIONPTR)();
constFUNCTIONPTR[]tasks={
ProcessKey,
RunTask2,
…
RunTaskN
};
voidTimer_Interrupt()
{
SetTimer();
ResetStack();
Enable_Timer_Interrupt;
for(i=0;i<CountOfArray(tasks),i++)
(*tasks
)();
while(1)IDLE;
}
使用const是讓數(shù)組內(nèi)容位于codesegment(ROM)而非datasegment(RAM)中,8051中使用code作為const的替代品。
(題外話:關(guān)于函數(shù)指針賦值時是否需要取地址操作符&的問題,與數(shù)組名一樣,取決于compiler.對于熟悉匯編的人來說,函數(shù)名和數(shù)組名都是常數(shù)地址,無需也不能取地址。對于不熟悉匯編的人來說,用&取地址是理所當(dāng)然的事情。VisualC++2005對此兩者都支持)
這種方法在匯編下表現(xiàn)為散轉(zhuǎn),一個小技巧是利用stack獲取跳轉(zhuǎn)表入口:
movA,state
acallMultiJump
ajmpstate0
ajmpstate1
...
MultiJump:popDPH
popDPL
rlA
jmp@A+DPTR
還有一種方法是把函數(shù)指針數(shù)組(動態(tài)數(shù)組,鏈表更好,不過在mcu中不適用)放在datasegment中,便于修改函數(shù)指針以運行不同的任務(wù),這已經(jīng)接近于動態(tài)調(diào)度了:
FUNCTIONPTR[COUNTOFTASKS]tasks;
tasks[0]=ProcessKey;
tasks[0]=RunTaskM;
tasks[0]=NULL;
...
FUNCTIONPTRpFunc;
for(i=0;i<COUNTOFTASKS;i++){
pFunc=tasks);
if(pFunc!=NULL)
(*pFunc)();
}
通過上面的手段,一個中斷驅(qū)動的框架形成了,下面的事情就是保證每個tick內(nèi)所有任務(wù)的運行時間總和不能超過一個tick的時間。為了做到這一點,必須把每個任務(wù)切分成一個個的時間片,每個tick內(nèi)運行一片。這里引入了狀態(tài)機(statemachine)來實現(xiàn)切分。關(guān)于statemachine,很多書中都有介紹,這里就不多說了。
(題外話:實踐升華出理論,理論再作用于實踐。我很長時間不知道我一直沿用的方法就是statemachine,直到學(xué)習(xí)UML/C++,書中介紹tachniquesforidentifyingdynamicbehvior,方才豁然開朗。功夫在詩外,掌握C++,甚至C#JAVA,對理解嵌入式程序設(shè)計,會有莫大的幫助)
狀態(tài)機的程序?qū)崿F(xiàn)相當(dāng)簡單,第一種方法是用swich-case實現(xiàn):
voidRunTaskN()
{
switch(state){
case0:state0();break;
case1:state1();break;
…
caseM:stateM();break;
default:
state=0;
}
}
另一種方法還是用更通用簡潔的函數(shù)指針數(shù)組:
constFUNCTIONPTR[]states={state0,state1,…,stateM};
voidRunTaskN()
{
(*states[state])();
}
下面是statemachine控制的例子:
voidstate0(){}
voidstate1(){state++;}//nextstate;
voidstate2(){state+=2;}//gotostate4;
voidstate3(){state--;}//gotopreviousstate;
voidstate4(){delay=100;state++;}
voidstate5(){delay--;if(delay<=0)state++;}//delay100*tick
voidstate6(){state=0;}//gotothefirststate
一個小技巧是把第一個狀態(tài)state0設(shè)置為空狀態(tài),即:
voidstate0(){}
這樣,state=0可以讓整個task停止運行,如果需要投入運行,簡單的讓state=1即可。
以下是一個鍵盤掃描的例子,這里假設(shè)tick=20ms,ScanKeyboard()函數(shù)控制口線的輸出掃描,并檢測輸入轉(zhuǎn)換為鍵碼,利用每個state之間20ms的間隔去抖動。
enumEnumKey{
EnumKey_NoKey=0,
…
};
structStructKey{
intkeyValue;
boolkeyPressed;
};
structStructKeyProcesskey;
voidProcessKey(){(*states[state])();}
voidstate0(){}
voidstate1(){key.keyPressed=false;state++;}
voidstate2(){if(ScanKey()!=EnumKey_NoKey)state++;}//nextstateifakeypressed
voidstate3()
{//debouncingstate
key.keyValue=ScanKey();
if(key.keyValue==EnumKey_NoKey)
state--;
else{
key.keyPressed=true;
state++;
}
}
voidstate4(){if(ScanKey()==EnumKey_NoKey)state++;}//nextstateifthekeyreleased
voidstate5(){ScanKey()==EnumKey_NoKey?state=1:state--;}
上面的鍵盤處理過程顯然比通常使用標(biāo)志去抖的程序簡潔清晰,而且沒有軟件延時去抖的困擾。以此類推,各個任務(wù)都可以劃分成一個個的state,每個state實際上占用不多的處理時間。某些任務(wù)可以劃分成若干個子任務(wù),每個子任務(wù)再劃分成若干個狀態(tài)。
(題外話:對于常數(shù)類型,建議使用enum分類組織,避免使用大量#define定義常數(shù))
對于一些完全不能分割,必須獨占的任務(wù)來說,比如我以前一個低成本應(yīng)用中紅外遙控器的軟件解碼任務(wù),這時只能犧牲其他的任務(wù)了。兩種做法:一種是關(guān)閉中斷,完全的獨占;
voidRunTaskN()
{
Disable_Interrupt;
…
Enable_Interrupt;
}
第二種,允許定時中斷發(fā)生,保證某些時基register得以更新;
voidTimer_Interrupt()
{
SetTimer();
Enable_Timer_Interrupt;
UpdateTimingRegisters();
if(watchDogCounter=0){
ResetStack();
for(i=0;i<CountOfArray(tasks),i++)
(*tasks)();
while(1)IDLE;
}
else
watchDogCounter--;
}
只要watchDogCounter不為0,那么中斷正常返回到中斷點,繼續(xù)執(zhí)行先前被中斷的任務(wù),否則,復(fù)位stack,重新進行任務(wù)循環(huán)。這種狀況下,中斷處理過程極短,對獨占任務(wù)的影響也有限。
中斷驅(qū)動多任務(wù)配合狀態(tài)機的使用,我相信這是mcu下無os系統(tǒng)較好的設(shè)計結(jié)構(gòu)。對于絕大多數(shù)mcu程序設(shè)計來說,可以極大的減輕程序結(jié)構(gòu)的安排,無需過多的考慮各個任務(wù)之間的時間安排,而且可以讓程序簡潔易懂。缺點是,程序員必須花費一定的時間考慮如何切分任務(wù)。
下面是一段用C改寫的CDPlayer中檢測disc是否存在的偽代碼,用以展示這種結(jié)構(gòu)的設(shè)計技巧,原源代碼為Z8mcu匯編,基于Sony的DSP,ServoandRF處理芯片,通過送出命令字來控制主軸/滑板/聚焦/尋跡電機,并讀取狀態(tài)以及CD的subQ碼。這個處理任務(wù)只是一個大任務(wù)下用statemachine切開的一個二級子任務(wù),tick=20ms。
state1(){InitializeMotor();state++;}
state2(){
if(innerSwitch!=ON){
SendCommand(EnumCommand_SlidingMotorBackward);
timeout=MILLISECOND(10000);
state++;//滑板電機向內(nèi)運動,直至觸及最內(nèi)開關(guān)。
}
else
state+=2;
}
state3(){
if((--timeout)==0){//note:someCcompliersdonotsupport(--timeout)==
SendCommand(EnumCommand_SlidingMotorStop)
systemErrorCode=EnumErrorCode_InnerSwitch;
state=0;//10s超時錯誤,
}
else{
if(innerSwitch==ON){
SendCommand(EnumCommand_SlidingMotorStop)
timeout=MILLISECOND(200);//200ms電機停止時間
state++;
}
}
}
state4(){if((--timeout)==0)state++;}//等待電機完全停止
state5(){
SendCommand(EnumCommand_SlidingMotorForward);
timeout=MILLISECOND(2000);
state++;
}//滑板電機向外運動,脫離innerswitch
state6(){
if((--timeout)==0){
SendCommand(EnumCommand_SlidingMotorStop)
systemErrorCode=EnumErrorCode_InnerSwitch;
state=0;//2s超時錯誤,
}
else{
if(innerSwitch==OFF){
SendCommand(EnumCommand_SlidingMotorStop)
timeout=MILLISECOND(200);//200ms電機停止時間
state++;
}
}
}
state7(){state4();}
state8(){LaserOn();state++;retryCounter=3;}//打開激光器
state9(){
SendCommand(FocusUp);
state++;
timeout=MILLISECOND(2000);
}//光頭上舉,檢測聚焦過零3次,判斷cd是否存在
state10(){
if(FocusCrossZero){
systemStatus.Disc=EnumStatus_DiscExist;
SendCommand(EnumCommand_AutoFocusOn);//有cd,打開自動聚焦。
state=0;//本任務(wù)結(jié)束。
playProcess.state=1;//啟動play任務(wù)
}
elseif((--timeout)==0){
SendCommand(EnumCommand_FocusClose);//光頭聚焦復(fù)位
if((--retryCounter)==0){
systemStatus.Disc=EnumStatus_Nodisc;//無盤
displayProcess.state=EnumDisplayState_NoDisc;//顯示閃爍的無盤
LaserOff();
state=0;//任務(wù)停止
}
else
state--;//再試
}
}
stateStop(){
SendCommand(EnumCommand_SlidingMotorStop);
SendCommand(EnumCommand_FocusClose);
state=0;
}
網(wǎng)友評論:^_^
網(wǎng)友評論:如果象LZ這樣的高手被你一句話氣跑了,豈不是讓很多新手失去請教的機會?。!
網(wǎng)友評論:為什么害死的是一只貓而不是一只老鼠或者別的???
網(wǎng)友評論:感謝樓主分享
網(wǎng)友評論:雖然很欣賞這種模式,可以降低功耗,但還是不愿意嘗試
貌似中斷服務(wù)程序還是簡單點好
網(wǎng)友評論:不看好!
網(wǎng)友評論:處理一個跑馬燈程序可能這樣做可以。但是真的需要處理一些實時度要求高一點的程序就不可行了。
網(wǎng)友評論:這是典型的協(xié)作式,但是這樣做不安全,容易丟失時間片。建議中斷里面設(shè)置標(biāo)志,把那一大套移到外面,這樣更安全。
網(wǎng)友評論:不錯!不錯。。。。。。。
網(wǎng)友評論:1.不是什么單片機都支持中斷嵌套,咱的意思,能放到外面的就不要放到中斷中,盡可能快的釋放中斷是中斷機制的本意
2.低功耗啥的和處理時間有關(guān),在終端中處理同樣不低功耗
網(wǎng)友評論:學(xué)習(xí)學(xué)習(xí)。。。。。。。。。。。!
網(wǎng)友評論:非常歡迎來自五湖四海的朋友,電子技術(shù)交流群:29303696
網(wǎng)友評論:還沒有認(rèn)真地看,不過,粗略地看一下有點像RTX51的辦法。
RTX51就是用T0作時間中斷計時器,主程序就可以是一個maintask,然后給每個任務(wù)分配一定的運行時間片,到時就中斷執(zhí)行別的任務(wù),
不知道我說的對不對,我現(xiàn)在就新接手一個這樣的項目,遇到的問題是每個任務(wù)的運行時間有比較嚴(yán)格的要求,不可以太長,想請問一下樓主你怎么預(yù)算一下每個task的執(zhí)行時間?
網(wǎng)友評論:收藏
網(wǎng)友評論:中斷復(fù)雜化與簡約化并不矛盾,應(yīng)用不同,靈活選擇了
網(wǎng)友評論:沒法細(xì)細(xì)看完,樓主的意思是不是這樣的?(我不懂什么OS,操作系統(tǒng))
假設(shè)每5MS中斷一次,
中斷一次,做第1件事,要在5MS內(nèi)做完.做完空循環(huán).
中斷第2次,做第2件事,要在5MS內(nèi)做完.做完,做完空循環(huán).
中斷第3次,做第3件事,要在5MS內(nèi)做完.做完,做完空循環(huán).
...
中斷第N次,做第N件事,要在5MS內(nèi)做完.做完,清除中斷記數(shù)標(biāo)記.做完空循環(huán).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
我在沒中斷的系統(tǒng)中這樣做:
計數(shù)器如果等于1,做第1件事,完成剛好200US,然后記數(shù)器加1.
計數(shù)器如果等于2,做第2件事,完成剛好200US,然后記數(shù)器加1.
計數(shù)器如果等于1,做第3件事,完成剛好200US,然后記數(shù)器加1.
...
計數(shù)器如果等于N,做第N件事,完成剛好200US,然后記數(shù)器清0.然后記數(shù)器加1.
網(wǎng)友評論:雖然我的觀點和樓主不同,我支持能在主程序做的絕不用中斷。例如按鍵去抖就用主程序周期,反正去抖有50%甚至更大的偏差都沒關(guān)系。
不過還是支持一下樓主原創(chuàng)。
網(wǎng)友評論:IC庫存管理軟件第一品牌,您的最佳選擇
要用就用“易管理”
“易管理”庫存管理系統(tǒng),輕松實現(xiàn)采購、銷售、詢價、客戶關(guān)系管理,銷售訂單合同等信息一體化管理,輕松實現(xiàn)庫存與貿(mào)易統(tǒng)一運行。這是在國內(nèi)首次提出進銷存貿(mào)易一體化的平臺,這是領(lǐng)先全球的專享網(wǎng)絡(luò)平臺。
在激烈的市場競爭中,如何提升企業(yè)市場競爭力和自身形象,適應(yīng)社會各方面發(fā)展的需求,以及如何應(yīng)對新形勢、新挑戰(zhàn)?通過合理的企業(yè)信息化建設(shè)就可以讓你的企業(yè)插上騰飛的翅膀,它不但可以讓你的企業(yè)變得更高效,還可以讓您的企業(yè)具有更高的競爭力!
隨著信息技術(shù)的發(fā)展,決戰(zhàn)IC市場,信息在IC行業(yè)起著舉足輕重的作用,我們致力于專為IC行業(yè)的公司提供專業(yè)的全方位信息技術(shù)服務(wù)。
“易管理”,使您發(fā)現(xiàn)弊端,果斷解決
我們分析您的管理煩惱,為您尋求解決方案
想實時掌握公司的運作情況嗎?
想節(jié)約時間將更多精力用于提升業(yè)務(wù)競爭力嗎?
想隨時掌握客戶的銷售情況嗎?
想更有效的掌握庫存情況嗎?
想掌握復(fù)雜工作流程,從容知道處理方案嗎?
想排除加班處理繁雜財務(wù)工作的煩惱嗎??
企業(yè)在管理中遇到的問題
1、供應(yīng)商庫存管理混亂!
2、商業(yè)秘密的供應(yīng)商庫存信息給員工輕易帶走!
3、從前使用的庫存管理系統(tǒng),麻煩又不好用!
4、分公司數(shù)據(jù)無法共享!
5、詢報價記錄沒有存底,潛在客戶無法抓!
。。。。。。。。。
我們擁有全國幾千客戶的優(yōu)異口碑見證,幫助你實時準(zhǔn)確的掌握公司庫存、客戶、財務(wù),業(yè)務(wù)的狀況。
聯(lián)系人:陳明亮
電子郵件:cml@eguanli.com
電 話:+86-755-83910101839102028306712183067044
移動電話:13510964861
地 址:深圳市福田區(qū)深南中路6002號人民大廈5樓
網(wǎng) 址:http://www.iclook.cn
QQ:860736028
網(wǎng)友評論:想法不錯.
網(wǎng)友評論:還是比較贊成所長的簡約中斷
網(wǎng)友評論:板凳
網(wǎng)友評論:好像有處筆誤constFUNCTIONPTR[]tasks={
constFUNCTIONPTRtasks[]={
網(wǎng)友評論:不好理解,個人感覺:
時序的主次將完全顛倒;
而且系統(tǒng)可能變得很復(fù)雜;
是否放在中斷中CPU運行的時間基本一樣,低功耗從何而來?
主程序往往耗時都很長,時序豈不是要大亂?
中斷中多安排一些例行性的動作,如顯示,讀健,發(fā)聲等,這個是提倡的,但整個主程序搬過來,實在看不出什么好處。
網(wǎng)友評論:技術(shù)資料中心
匯集全球各大廠商的產(chǎn)品資料,從技術(shù)參數(shù)到應(yīng)用筆記、解決方案、應(yīng)用筆記等一應(yīng)俱全,搜索方式靈活,各種技術(shù)資料相互關(guān)聯(lián),資源集成度極高;同時,為了幫助工程師獲得更多新技術(shù),ICBuy還將聯(lián)合各技術(shù)代理商開展在線技術(shù)培訓(xùn)。
億芯網(wǎng),簡稱ICBuy,立足于通過互聯(lián)網(wǎng)為電子行業(yè)的用戶提供元器件相關(guān)信息服務(wù),內(nèi)容包括技術(shù)資料中心、工程樣片零售商城、市場供貨信息查詢?nèi)齻方面,為客戶提供從技術(shù)開發(fā)到小批量采購以及量產(chǎn)供應(yīng)商的選擇等一系列產(chǎn)品和信息服務(wù)。
相關(guān)鏈接:http://www.icbuy.com/
網(wǎng)友評論:樓主實在是把一件簡單的事情復(fù)雜化了。如果能合理應(yīng)用“狀態(tài)機”的概念,所謂的多任務(wù)處理就能輕松實現(xiàn)。
網(wǎng)友評論:不錯,努力學(xué)習(xí)中
網(wǎng)友評論:這樣能保證時鐘的準(zhǔn)確?你能保證中斷里的任務(wù)能在這么短的時間內(nèi)完成?假如你用了液晶,有刷寫液晶任務(wù),他所耗用的時間早已經(jīng)夠定時器中段多次了。用這種方式,只能是任務(wù)少,且耗時短。
假如使用了ps2接口,而此時正在定時器中斷中處理任務(wù),當(dāng)ps2設(shè)備有請求時,無法響應(yīng),應(yīng)此會錯過這個響應(yīng)。此類對響應(yīng)要求苛刻的設(shè)備在這種系統(tǒng)中會很不可靠。
網(wǎng)友評論:雖然做過多年的單片機程序,還沒有想過原來可以這樣做,呵呵。。。
就像習(xí)慣了右手,卻忽視了左手也可以做一些事情,建議大家學(xué)習(xí)LZ變相思考的做法,沒準(zhǔn)能想出更好的辦法
與諸君共勉
網(wǎng)友評論:記號
網(wǎng)友評論:只是很多具體的情況是任務(wù)不多不復(fù)雜,這時所有任務(wù)都可以在中斷中完成。。。
網(wǎng)友評論:感覺不是個很好的一個構(gòu)架
當(dāng)然了,比沒有構(gòu)架好
網(wǎng)友評論:偶也是這么做的,就是有的觀點不敢茍同,個人認(rèn)為,一個時間片已經(jīng)足夠程序執(zhí)行了,想想12M晶振,平均每條語句算他2us,10ms一個時間片,可以執(zhí)行5000條語句,在狀態(tài)機的情況下并不是每條語句都執(zhí)行到的,一般的項目一個狀態(tài)執(zhí)行不可能5000條語句,好!執(zhí)行完跳出我可以讓它休眠,降低耗電,更重要的是增加的抗干擾性!偶覺得開門狗拉回來沒用,如果開門狗溢出,重啟,需要利用狀態(tài)機來返回,返回到原來執(zhí)行的狀態(tài)
網(wǎng)友評論:應(yīng)用的場合要求實時性高,用個狀態(tài)機機制好點。事件驅(qū)動,我不認(rèn)為中斷內(nèi)做太多的動作,一般在中斷做個時間標(biāo)志或者處理一些響應(yīng)程度高的動作。沒有必要復(fù)雜化
網(wǎng)友評論:中斷程序應(yīng)該做盡可能少的事情。
前后臺系統(tǒng)中,我喜歡這樣。
voidmain(void)
{
while(1)
{
Sleep();
SystemCounter++;
switch(status)
{
//各個任務(wù)......
}
}
}
網(wǎng)友評論:學(xué)習(xí)了。多謝LS
網(wǎng)友評論:不過用中斷要小心!
網(wǎng)友評論:超宏達(dá)科技是一家專業(yè)的電子元器件代理商(www.super-grand.net),主要是NXP、ATMEL、ST、FAIRCHILD、NS、MICROCHIP、Winbond、Rohm、JRC、SAMSUNG、IR、UTC的代理,公司強大的技術(shù)支持和電子商務(wù)平臺,可以解決電子元器件BOM表一站式打樣和在線詢價,詳細(xì)資料請登陸www.super-grand.net,電話:83219636傳真:83261186
網(wǎng)友評論:
voidmain(void)
{
ucharbuffer=SmartOption[0];//SmartOption
system_init();
receiver_init();
while(1)
{
keyscan_thread();
key_handle_therad();
minder_thread();
RF_encode_thread();
idle();
};
}
網(wǎng)友評論:有點操作系統(tǒng)的味道了