單片機裸機系統(tǒng),通常又被稱為前后臺系統(tǒng)。
百度百科中,對前后臺系統(tǒng)有一段解釋:
?前后臺系統(tǒng),即計算機前后臺系統(tǒng),早期的嵌入式系統(tǒng)中沒有操作系統(tǒng)的概念,程序員編寫嵌入式程序通常直接面對裸機及裸設備,在這種情況下,通常把嵌入式程序分成兩部分,即前臺程序和后臺程序。 ?
實現(xiàn)模式如下:
應用程序是一個無限的循環(huán),循環(huán)中調用相應的函數(shù)完成相應的操作,這部分可以看成后臺行為,也就是while主循環(huán);前臺程序通過中斷來處理事件,也就是我們常見的中斷。
一般情況下,后臺程序也叫事件處理任務,前臺程序也叫中斷級任務。在程序運行時,后臺程序檢查每個任務是否具備運行條件,通過一定的調度算法來完成相應的操作(按照先后順序依次調度)。對于實時性要求特別嚴格的操作通常由中斷來完成,僅在中斷服務程序中標記事件的發(fā)生,不再做任何工作就退出中斷,這樣就不會造成在中斷服務程序中處理費時的事件而影響后續(xù)和其他中斷。
實際上,前后臺系統(tǒng)的實時性比預計的要差。這是因為前后臺系統(tǒng)認為所有的任務具有相同的優(yōu)先級別,即是平等的,而且任務的執(zhí)行又是通過FIFO隊列排隊,因而對那些實時性要求高的任務不可能立刻得到處理。另外,由于后臺程序是一個無限循環(huán)的結構,一旦在這個循環(huán)體中正在處理的任務崩潰,使得整個任務隊列中的其他任務得不到機會被處理,從而造成整個系統(tǒng)的崩潰。由于這類系統(tǒng)結構簡單,幾乎不需要RAM/ROM的額外開銷,因而在簡單的嵌入式應用被廣泛使用。
可以看出,前后臺系統(tǒng)是在輪詢系統(tǒng)的基礎上加入了中斷。
外部事件的響應在中斷里面完成,事件的處理還是回到輪詢系統(tǒng)中完成。
中斷在這里稱為前臺,main()函數(shù)中的無限循環(huán)稱為后臺。
示例說明:
int flag1 = 0; int flag2= 0; int flag3 = 0; int main(){ hardwareInit();//硬件初始化 for(;;){ if(flag1){ doSomething1();//處理事件 flag1=0;//清除標志位 } if(flag2){ doSomething2();//處理事件 flag2=0;//清除標志位 } if(flag3){ doSomething3();//處理事件 flag3=0;//清除標志位 } } } //中斷處理程序 void ISR1(void ){ flag1=1//置位標志位 } //中斷處理程序 void ISR2(void ){ flag2=1//置位標志位 } //中斷處理程序 void ISR3(void ){ flag3=1//置位標志位 }
在順序執(zhí)行后臺程序時,如果有中斷,那么中斷會打斷后臺程序的正常執(zhí)行流,轉而去執(zhí)行中斷服務程序,在中斷服務程序中標記事件。當然,并不是說不能在中斷里處理程序,如果事件要處理的事情很簡短,則可在中斷服務程序里面處理,如果事件要處理的事情比較多,則建議返回后臺程序處理。雖然事件的響應和處理分開了,但是事件的處理還是在后臺順序執(zhí)行的,但相比輪詢系統(tǒng),前后臺系統(tǒng)確保了事件不會丟失,再加上中斷具有可嵌套的功能,這可以大大提高程序的實時響應能力。
通常,我們會在中斷里執(zhí)行一些緊急的任務,對實時性要求比較高的任務,最直觀的就是,速度很快的任務,需要及時處理的任務。
舉個例子說明
直接參考:前后臺系統(tǒng)的精髓
總之就是
緊急的事務一定要用中斷處理!中斷只處理緊急事務!
比如底層的數(shù)據(jù)收發(fā),速度太快,放在后臺處理太慢了。對于數(shù)據(jù)的處理,就沒有那么著急了??梢栽谥袛嗬锝邮胀瓿珊笤O置標志位,然后在主循環(huán)里根據(jù)標志位完成處理,之后手動開啟相應中斷去發(fā)送。
這其中,注意全局變量的使用。
主循環(huán)和中斷的關系梳理
首先要明確的是,嵌入式程序中必須要有主循環(huán),這樣才能保證程序的正確反復執(zhí)行。
之前學習時講的是,不要在中斷里做過多的操作,以免中斷時間過長,影響其他中斷和主循環(huán)的程序執(zhí)行。
關于這點可參考:
1,中斷處理時間太長或導致正常的應用程序不能按時被執(zhí)行;
2,不能嵌套的cpu中,執(zhí)行中斷時不能響應其他中斷,中斷執(zhí)行時間太長容易丟其他中斷;
3,能嵌套的cpu中,某個中斷服務程序執(zhí)行時間太長,可能導致中斷嵌套非常深,容易導致棧溢出,也容易出現(xiàn)邏輯問題;
ISR會阻斷正常的CPU調度機制。
在最簡單的嵌入式系統(tǒng)上,可能沒有操作系統(tǒng),所謂CPU調度就是狀態(tài)輪詢加上各種中斷響應。如果一個ISR占用時間太長,勢必影響輪詢的及時性穩(wěn)定性。如果ISR一直關中斷,不允許本身再入或者響應其他中斷,那麻煩就更多了。所以正確的ISR應該只做那些必須立即完成的事情,然后把不必立即處理的數(shù)據(jù)保存下來,設置一個狀態(tài)指示,然后退出,讓輪詢機制去處理。
在有操作系統(tǒng)的情況下,ISR怎么寫,必須符合操作系統(tǒng)規(guī)定的規(guī)范,不能因為ISR運行特權級別高就亂來。
注意:
并非所有的任務都不能放在中斷里,一些緊急任務,比如數(shù)據(jù)收發(fā)等,就需要放在中斷里,如果放在主循環(huán)里很可能來不及處理。
我的理解:
主循環(huán)和中斷本身是獨立的,即CPU和外設是可以同步工作的。
二者的配合就是,外設的工作完成后,會通知CPU,這時候,主循環(huán)里可以根據(jù)標志位情況進行相應的處理。
這樣一來,二者的分工就相對明確了,中斷里處理數(shù)據(jù)傳輸?shù)木o急任務或者完成標志位,然后主循環(huán)里根據(jù)標志位去處理不緊急的任務。
這樣一來,中斷和主循環(huán)的某個任務在某種程度上就是一種先后的順序結構,只有中斷完成了,主循環(huán)里才能根據(jù)標志位來執(zhí)行任務。
因為主循環(huán)是循環(huán)執(zhí)行的,所以不管一個任務放在主循環(huán)的哪個位置,都是會被循環(huán)執(zhí)行到的,這就是CPU的輪詢調度。
中斷是外設在硬件層面上的實時性任務。
注意:
以最近學到的一種思路來舉例。
之前,我用定時器時,都是一個任務一個定時器,這樣,有幾個任務就需要幾個定時器,每個任務時間改變時,就去改變初始化的內容。
后來學到一種思路,定時器只用一個,然后設定1ms的基準值,接著在對應的中斷里面計數(shù),并且設置計數(shù)完成標志位,然后在主循環(huán)里面,根據(jù)完成標志位再去執(zhí)行相應的業(yè)務代碼。這樣一來,中斷里就只處理了標志位,而且,因為定時器里幾乎沒有業(yè)務邏輯,所以一個定時器中斷里可以執(zhí)行判斷好多個任務的定時,然后主循環(huán)里依次執(zhí)行各業(yè)務邏輯。
中斷最大的優(yōu)勢就是及時響應。
主循環(huán)里是順序執(zhí)行的,所以,沒辦法及時響應,但是可以處理多任務。
因此二者需要配合使用。
主循環(huán)優(yōu)先級是最低的,會被所有中斷打斷。
參考:
STM32的滴答定時器中斷打斷主函數(shù)while循環(huán)嗎
這里面可能會涉及到一些原子操作的概念。
全局變量在中斷和主循環(huán)中共同使用時異常問題
如果中斷函數(shù)沒執(zhí)行完成時,又來了該中斷源的中斷請求,會發(fā)生什么情況?
參考:
如果中斷函數(shù)沒執(zhí)行完成時,又來了該中斷源的中斷請求,會發(fā)生什么情況? - STM32/STM8單片機論壇 - ST MCU意法半導體官方技術支持論壇 - 21ic電子技術開發(fā)論壇
stm32代碼執(zhí)行時間的查看
參考:
單片機的裸機系統(tǒng)和多任務系統(tǒng)總結
前后臺系統(tǒng)中,中斷只進行硬件層面的數(shù)據(jù)處理以及置標志位,其他的事情讓主循環(huán)去做。?
任務要有執(zhí)行條件
裸機開發(fā)時,主循環(huán)里的任務都需要有執(zhí)行條件,只有滿足執(zhí)行條件,才會進入執(zhí)行。
而這個執(zhí)行條件,直觀來說其實就是判斷標志位。
如果沒有執(zhí)行條件,就會不斷地循環(huán)進入程序執(zhí)行,可能會導致程序卡頓。
另外一方面,如果沒有執(zhí)行條件,則不管什么情況都會執(zhí)行,那么在不需要執(zhí)行的時候也會執(zhí)行,那么就加大了主循環(huán)中的執(zhí)行時間,實時性更加無法得到保證。
關于標志位補充
在裸機編程中,會大量用到各種狀態(tài)標志位。
一方面,這些標志位是表示機器的某些狀態(tài),另一方面,因為主程序和中斷的執(zhí)行,很多時候是有先后順序的,所以,需要通過一些標志位來判斷某個任務當前是否執(zhí)行。
比如某個任務有些地方實時性較高,有些地方實時性要求沒那么高,此時,就可以將實時性要求高的放在中斷里,實時性要求不高的放在主循環(huán)里。
這種情況下,就需要有個標志位,讓中斷里先執(zhí)行,執(zhí)行后置個標志位,主循環(huán)里,當標志位被置位時才會開始執(zhí)行接下來的程序部分。
另外要注意的是,當判斷標志位并且執(zhí)行了相關代碼后,就需要復位標志位,防止每次都會進入執(zhí)行。
其實,中斷里的標志位判斷正確后需要復位標志位,就是為了防止條件還沒滿足時也能不斷地進入執(zhí)行。
比如,接收數(shù)據(jù)然后處理,接收數(shù)據(jù)就需要放在中斷里,因為這個對實時性要求較高,但是對數(shù)據(jù)的處理就沒那么急了,可以放在主循環(huán)里,此時,就有先后順序的要求,
需要先接收完數(shù)據(jù),然后才能處理。所以,就需要一個標志位。接收完數(shù)據(jù)后,標志位置位,然后在主循環(huán)里面,根據(jù)標志位置位去執(zhí)行處理程序,同時,需要復位標志位,
要不然,就會不斷進入主循環(huán)程序,而不會按照既定的順序去執(zhí)行,自然就會出錯了。有個問題就是,在一個1秒中斷里寫10個任務,和把10個任務分別寫在10個1秒中斷里,有區(qū)別嗎?
前者的好處是,只需要一個定時器就能搞定,而后者需要10個定時器。
后者看起來貌似可以讓程序執(zhí)行更快,但仔細想一想,他10個定時器也不是并行執(zhí)行的,就算10個定時器可以同時觸發(fā),但是CPU一次也只能
執(zhí)行一個中斷,中斷的執(zhí)行也是要排隊的。
所以,前者在一個中斷里排隊,后者分成了好多個中斷在排隊,二者的執(zhí)行實時度并沒有明顯區(qū)別。
定時器的使用技巧
有時候,需要很多時間定時,比如100ms,200ms,300ms,500ms,1s,5s,1min等等,試想一下,如果每個時間都定個定時器,那單片機僅有的定時器肯定不夠用。
怎么辦呢?
那就是開啟一個定時器時基,比如定個1ms定時器,然后可以在中斷處理函數(shù)中,對每個定時任務都進行計數(shù),只要是1ms的整數(shù)倍,都可以計數(shù)到達目標之后才執(zhí)行任務,這樣一個時基定時器的處理函數(shù)中就可以處理多個任務。
比如:
這里示例的時基設置的是10ms
但是要注意,中斷里只進行標志位的設置,不要進行任何業(yè)務操作。
標志位的設計
一開始,我在設計標志位的時候,都是用一個字節(jié)的0和1來表示的,這樣太浪費了。
其實有更好的方式,這種方式也是單片機設計中的一種設計思想,那就是每個標志位用1個位來表示,如果一個位不夠表示,就用多個位來表示。
比如:
#define FLG_UART 0x01
#define FLG_TMR 0x02
#define FLG_EXI 0x04
#define FLG_KEY 0x08
然后由此就有一些計算方式
定義u8FlgTmp為這個標志位字節(jié)的表示變量
那么u8FlgTmp & FLG_UART位與就能表示u8FlgTmp里是不是置位了?FLG_UART這個標志位
if(u8FlgTmp & FLG_UART)
{
????action_uart(); /*處理串口事件*/
}
g_u8EvntFlgGrp |= FLG_UART; /*設置 UART 事件標志*/位或就可以將FLG_UART標志位疊加到g_u8EvntFlgGrp上。
以下舉例說明:
g_u8EvntFlgGrp一開始為0
volatile INT8U g_u8EvntFlgGrp = 0; /*事件標志組*/
然后接連發(fā)生了UART/TMR/EXI/KEY中斷,那么:
g_u8EvntFlgGrp?|= FLG_UART,此時g_u8EvntFlgGrp為0x01
g_u8EvntFlgGrp?|= FLG_TMR,此時g_u8EvntFlgGrp為0x03
g_u8EvntFlgGrp?|= FLG_EXI,此時g_u8EvntFlgGrp為0x07
g_u8EvntFlgGrp?|= FLG_KEY,此時g_u8EvntFlgGrp為0x0F
此時g_u8EvntFlgGrp就記錄了所有事件的觸發(fā)標志,然后如何判斷當前事件有沒有發(fā)生?
那就是用g_u8EvntFlgGrp去位與對應的標志位
g_u8EvntFlgGrp?& FLG_UART
g_u8EvntFlgGrp?& FLG_TMR
g_u8EvntFlgGrp?& FLG_EXI
g_u8EvntFlgGrp?& FLG_KEY
標準庫舉例
程序進行位操作的效率也更高。
不過,這樣用1位表示1個標志,一個字節(jié)也只能表示8個,數(shù)量利用不大夠。
如果標志較多,也可以直接使用一個字節(jié)從0—255的數(shù)值來表示各種標志。
需要注意的是,這種方式一個變量同一時間只能表示一種類型的狀態(tài),而位方式一次能表示8種類型的狀態(tài),相當于8個變量。
實際中根據(jù)具體需要去進行設計。
仔細想了想,以上更適合驅動層面的設計,業(yè)務層面復雜且不固定,設計起來相對困難。
如果標志位太多,就用位操作,前提是事件標志,邏輯或可以傳入多個標志位。
標志位引起的隱蔽BUG
遇到一個很隱蔽的問題。
我有個需求,是這樣的,我需要每隔一段時間就往屏幕發(fā)數(shù)據(jù),于是我在定時器里計數(shù),然后在主循環(huán)根據(jù)計數(shù)值來判斷是否執(zhí)行發(fā)送函數(shù)。
我以為這樣在滿足條件后只會執(zhí)行一次,其實會進入執(zhí)行很多次。
為什么?上面圖片中,我理想情況下,當計數(shù)值templateTimeCount為5的整數(shù)倍時才會進入,0和5都會進入,但是因為這個是放在主循環(huán)里循環(huán)執(zhí)行的,當templateTimeCount=0的時候,因為等待templateTimeCount=1之前,會進入這個函數(shù)很多很多次,而且每次判斷都是滿足條件的。
這里其實就是1個定時周期內能執(zhí)行這個程序多少次,這里就會執(zhí)行多少次。
這就是為什么主循環(huán)中以及中斷中的每個任務函數(shù)都要有判斷條件,而且,進入后要馬上清標志位,要不然會不斷進入,所以,裸機中主函數(shù)中判斷標志位和清除標志位都是必須的操作,如果沒有這個環(huán)節(jié),就要注意很有可能會出錯。
任務同步
用標志位實現(xiàn)的,兩個程序先后執(zhí)行的情況,可以稱之為任務同步。也就是說,兩個任務必須按照既定的先后順序來執(zhí)行,而不是隨機地誰先誰后。?
為什么中斷里不建議有耗時較長的操作??文章來源:http://www.zghlxwxcb.cn/news/detail-532586.html
就以直接延時來考慮這個問題。
如果中斷里延時了,那么因為主循環(huán)和相同優(yōu)先級中斷沒法打斷延時的中斷,所以其他中斷和主循環(huán)里的其他任務都得不到及時執(zhí)行。
主循環(huán)優(yōu)先級最低,主循環(huán)不會阻塞中斷,只有中斷會阻塞中斷。
所以中斷里不能時間過長的最大原因是,會阻礙其他同優(yōu)先級的中斷執(zhí)行。
不管是主循環(huán)還是中斷,都不能有長時間延時,都會阻礙其他主循環(huán)任務的執(zhí)行,中斷里更嚴重的會影響其他中斷的執(zhí)行。
那對于較耗時的工作,為什么不推薦放在中斷里,但是可以放在主循環(huán)里?
因為中斷本來就是為了響應實時性要求高的任務而設定的,執(zhí)行的任務實時性要求都很高,所以不能有較長時間的阻塞,但是主循環(huán)里的任務優(yōu)先級沒有那么高,有稍微的耗時也問題不大,這個耗時有時也不要想得太夸張,一般也就是從ns級別轉到了us或者ms級別罷了。
所以
為什么中斷里不建議有耗時較長的操作??
因為影響實時性。文章來源地址http://www.zghlxwxcb.cn/news/detail-532586.html
到了這里,關于關于單片機的前后臺系統(tǒng)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!