|
轉(zhuǎn)自:http://www.zg4o1577.cn/bbs/dpj-59966-1.html
作者: 朱有田
本文僅討論筆者在實(shí)踐分時(shí)系統(tǒng)過程中的方法和思想,不拘泥于編程語言工具及其技巧,不用程序語言描述,全文采用敘事的方法,引出工程實(shí)踐過程中解決一些問題的過程和思路。
第一部分: 單CPU計(jì)算機(jī)運(yùn)行的模型
計(jì)算機(jī)最基本模型是圖靈模型(相關(guān)知識自行腦補(bǔ)),計(jì)算機(jī)將所有要處理的復(fù)雜任務(wù)分解到有限的基本的操作(指令),這個操作的集合就是指令集,指令集被設(shè)計(jì)固化到硬件(CPU或處理器)中。程序是為了解決特定問題而編制的一個指令序列,計(jì)算機(jī)的運(yùn)行就是一個在時(shí)間上串行的一個指令流。
如果有2個程序需要在一臺計(jì)算機(jī)上運(yùn)行,常見的場景是先運(yùn)行其中一個程序,運(yùn)行結(jié)束后,再運(yùn)行第二個程序。
第二部分: 單CPU計(jì)算機(jī)中多個程序并發(fā)執(zhí)行
多個程序并行的概念出現(xiàn)的很早,早期為了共享昂貴的計(jì)算機(jī)資源,人們試圖使一個計(jì)算機(jī)為多個用戶同時(shí)提供服務(wù)。多個程序并發(fā)執(zhí)行,采取分時(shí)的方法來實(shí)現(xiàn),稱分時(shí)系統(tǒng)。
分時(shí)即將時(shí)間視為資源進(jìn)行分配,將時(shí)間切分為人們感知上較小的一個單位,比如20毫秒(0.02秒),稱為一個時(shí)間片。若程序1和程序2都要被執(zhí)行,每個程序輪流被執(zhí)行一個時(shí)間片。從宏觀(感官)上來看,程序1和程序2是同時(shí)一起執(zhí)行的,就像兩臺計(jì)算機(jī)在同步工作一樣。
若程序1和程序2同步運(yùn)行,計(jì)算機(jī)是這樣進(jìn)行的:先運(yùn)行程序1,20毫秒后,切換到程序2,20毫秒后又切換到程序1,20毫秒后再次切換到程序2,如此反復(fù)… 一秒鐘切換了50次,程序1和程序2都在運(yùn)行、暫停、運(yùn)行、暫停這樣的狀態(tài)中進(jìn)行,由于切換時(shí)間夠快,人的感官認(rèn)為程序1和程序2是同步運(yùn)行的。
第三部分: 中斷和定時(shí)器的作用
要實(shí)現(xiàn)分時(shí)的機(jī)制,離不開定時(shí)器和中斷機(jī)制。定時(shí)器就是定時(shí)發(fā)出中斷信號讓計(jì)算機(jī)能夠進(jìn)入切換程序;中斷機(jī)制是指計(jì)算機(jī)的硬件要能夠支持在執(zhí)行過程中被中斷,跳轉(zhuǎn)到指定的中斷程序中去運(yùn)行,并在運(yùn)行結(jié)束后能夠返回到被中斷的點(diǎn)繼續(xù)運(yùn)行原來的程序。如果計(jì)算機(jī)硬件沒有中斷機(jī)制,則無法實(shí)現(xiàn)分時(shí)。
對于信號的輸入處理,程序總是以順序的方式進(jìn)行的。比如按下一個按鍵,程序并不是立即就知道有按鍵被按下,而是要等到程序指針運(yùn)行到按鍵處理程序時(shí)才會被發(fā)現(xiàn)。按鍵響應(yīng)的速度取決于程序指針本身的運(yùn)行速度和按鍵處理程序的長短及間隔距離,前者依賴于硬件主頻,后者依賴于程序設(shè)計(jì)的水平。這種依賴程序主動去發(fā)現(xiàn)的信號輸入方式一般稱查詢方式,與查詢方式相對應(yīng)的是中斷方式。
中斷方式是設(shè)計(jì)在硬件級別的,假如按下一個按鍵采用中斷的方式處理,那么當(dāng)按鍵按下時(shí)觸發(fā)中斷,系統(tǒng)執(zhí)行完當(dāng)前指令后保存當(dāng)前的程序指針位置(返回時(shí)候用),然后將程序指針跳轉(zhuǎn)到指定的點(diǎn),程序設(shè)計(jì)時(shí)在這個點(diǎn)編寫好按鍵處理程序,程序結(jié)束后返回到被中斷的點(diǎn)繼續(xù)運(yùn)行。很顯然,中斷方式的響應(yīng)時(shí)間很小且容易估算,程序也不需要“定時(shí)的去查詢”。中斷機(jī)制依賴于硬件提供的中斷資源(可接受的中斷源數(shù)量),往往比較有限;其次當(dāng)多個中斷信號發(fā)生時(shí),要考慮優(yōu)先次序和中斷嵌套(中斷過程中再響應(yīng)中斷)的問題。
事實(shí)上,中斷的實(shí)現(xiàn)也是一種查詢方式,當(dāng)中斷信號發(fā)生時(shí),計(jì)算機(jī)并不是真正的立即響應(yīng),而是要等待當(dāng)前的指令被執(zhí)行完,硬件才會去查詢是否有中斷信號存在。中斷也可以理解為在微指令級別(一個指令由一段微指令組成)下進(jìn)行查詢處理的,其顆粒度是一條指令。
定時(shí)器是一種二進(jìn)制計(jì)數(shù)器,其硬件上原理非常簡單:用一些邊沿觸發(fā)器串接起來就能構(gòu)成一個二進(jìn)制計(jì)數(shù)器,只要輸入一個方波,就能使計(jì)數(shù)器的數(shù)值加一。當(dāng)計(jì)數(shù)器加到溢出時(shí),將溢出信號引出到中斷信號,就能起到定時(shí)的作用。我們通過給定時(shí)器設(shè)置一個初始值來設(shè)置定時(shí)器時(shí)間的長短。
比如在程序中要等待一秒鐘的延時(shí),如不用定時(shí)器,則必須讓程序指針在一個循環(huán)體中空轉(zhuǎn)N次,達(dá)到延時(shí)的作用。這種方式有兩個弊端:一是程序空轉(zhuǎn)浪費(fèi)計(jì)算機(jī)資源,在這個延時(shí)過程中,程序啥也干不了,無法響應(yīng)按鍵,無法刷新顯示,如同死機(jī)一樣;二是延時(shí)精度難控制,由于程序指令本身執(zhí)行時(shí)間有長短之分,要達(dá)到精確的延時(shí),程序設(shè)計(jì)要反復(fù)調(diào)教循環(huán)體空轉(zhuǎn)的次數(shù)。
如采用定時(shí)器進(jìn)行一秒延時(shí),只要在定時(shí)器內(nèi)設(shè)置好初始值(使初始值到溢出的時(shí)間剛好為1秒),開啟定時(shí)器后,程序可以繼續(xù)執(zhí)行其他工作,如按鍵掃描等。當(dāng)1秒時(shí)間到達(dá),定時(shí)器溢出發(fā)出中斷信號,使程序指針跳轉(zhuǎn)到中斷響應(yīng)程序,程序在此進(jìn)行延時(shí)后的處理。由此可以發(fā)現(xiàn),定時(shí)器相當(dāng)于硬件級別并行的設(shè)備,程序指針的跑動和定時(shí)器內(nèi)計(jì)數(shù)值的增加是并行的。
言歸正傳,定時(shí)器和中斷是實(shí)現(xiàn)分時(shí)系統(tǒng)的基礎(chǔ),其中中斷機(jī)制尤為重要,70年代流行的PDP系列小型機(jī)上,分時(shí)系統(tǒng)的定時(shí)中斷是由一個從工頻電源上獲取的50-60Hz的定時(shí)中斷外設(shè)產(chǎn)生,稱之為電源時(shí)鐘,令人腦洞大開。現(xiàn)代操作系統(tǒng)的祖宗UNIX就是在這種機(jī)型上產(chǎn)生的一種分時(shí)操作系統(tǒng),UNIX最初的用戶是貝爾實(shí)驗(yàn)室的文員們,他們用它來處理專利文書,由于多個終端上可以同步進(jìn)行操作,又能方便進(jìn)行共享和調(diào)用,得到當(dāng)時(shí)女職員們的好評。
摘要:UNIX是用于DEC PDP-11及Interdata 8/32計(jì)算機(jī)的一個通用的交互作用的分時(shí)操作系統(tǒng)。從1971年開始以來,使用日趨廣泛。UNIX所論及的內(nèi)容有以下領(lǐng)域: 1.文件結(jié)構(gòu):一個統(tǒng)一的、可隨機(jī)尋址的字節(jié)序列。取消“記錄”的概念。文件尋址的效率。2.文件系統(tǒng)設(shè)備的結(jié)構(gòu):目錄與文件。3.I/O設(shè)備與文件系統(tǒng)的一體化。4.用戶接口:外殼程序原理,I/O重新定向以及管道。5.進(jìn)程環(huán)境:系統(tǒng)調(diào)用、信號、以及地址空間。6.可靠性:癱瘓,文件的丟失。7.安全性:損壞與檢查時(shí)數(shù)據(jù)保護(hù);阻塞情況下的系統(tǒng)保護(hù)。8.一個高級語言的使用—收益與代價(jià)。9.UNIX系統(tǒng)沒有一般意義上的“實(shí)時(shí)”、進(jìn)程間通訊、異步的I/O等功能。
UNIX內(nèi)核由10,000行左右的C語言代碼和1,000行左右的匯編語言代碼所組成。匯編語言代碼又可進(jìn)一步分成兩部份:一部份為200行,包括為提高系統(tǒng)效率而設(shè)計(jì)的那些代碼(可以用C語言來寫);另一部份為800行,包括不能用C語言寫的、執(zhí)行硬件功能的那些代碼。
第四部分: 分時(shí)的意義
從程序設(shè)計(jì)角度,程序是一個單線順序的指令流(語句流),在程序里,指令(語句)一個接一個被執(zhí)行,從開始到結(jié)束,或條件轉(zhuǎn)移,或反復(fù)循環(huán)。程序員安排好這一切前后處理的次序。在應(yīng)用角度,用戶打開程序,運(yùn)行程序,直到結(jié)束退出,再打開另一個程序。在DOS時(shí)代,人們就是這么做的,那真是一個單純的時(shí)代。
在分時(shí)系統(tǒng)下,多個程序并行,程序員設(shè)計(jì)程序時(shí)可以設(shè)計(jì)多個線程,比如一個線程負(fù)責(zé)顯示,一個線程負(fù)責(zé)鍵盤輸入,一個線程負(fù)責(zé)數(shù)據(jù)處理。每一個線程都是單獨(dú)的程序,最終他們將同步運(yùn)行,程序員除了要考慮單個線程的運(yùn)行,還要考慮線程之間的同步、通信和互斥。這改變了傳統(tǒng)的程序設(shè)計(jì)思維,使軟件的表現(xiàn)和可能性更加豐富多彩,程序設(shè)計(jì)手段也更加多樣。
在應(yīng)用角度,當(dāng)你在編輯文檔時(shí),同時(shí)又在下載電影,耳機(jī)里又能播放著音樂。這些當(dāng)下看起來稀松平常的事情,在計(jì)算機(jī)還是單任務(wù)時(shí)代真是難以想象的。
第五部分: 在80c52單片機(jī)上實(shí)踐分時(shí)系統(tǒng)
分時(shí)系統(tǒng)并不高不可攀,了解以上的概念后,任何有經(jīng)驗(yàn)的程序員都能對它進(jìn)行實(shí)踐,筆者就是在最便宜和古老的51單片機(jī)上實(shí)踐了分時(shí)系統(tǒng)。
80c52是intel的一款增強(qiáng)型mcs-51單片機(jī),51系列8位單片機(jī)自80年代開始至今仍被廣泛使用在各種場景,這種芯片很便宜,也很容易就能搞到。后來Intel將mcs-51授權(quán)給了多個芯片制造商。以ATMEL生產(chǎn)的AT89S52為例,它支持總計(jì)111條的51指令集,256字節(jié)RAM,8k字節(jié)程序閃存(可反復(fù)刷寫),3個16位計(jì)時(shí)器,5個中斷源,1個串口,4個8位IO口。要使她運(yùn)行起來非常的方便:只需在XTAL管腳之間加一個晶振和2個27pF電容(晶振的頻率決定運(yùn)行速度),在VCC腳加5V電源,然后把RESET管腳對地短路一下(復(fù)位),計(jì)算機(jī)便開始從程序閃存的0地址開始取指令執(zhí)行..
沒玩過51單片機(jī)的讀者一定好奇程序是怎么編制到芯片的閃存里面去的。單片機(jī)程序的開發(fā)主要依賴個人電腦,首先通過文本編輯器編寫源程序,然后通過51的編譯器程序編譯成目標(biāo)文件,最后通過芯片燒錄器將目標(biāo)文件復(fù)制(燒入)到51芯片內(nèi)。然后你的程序就可以在單片機(jī)跑動了。
事實(shí)上,無論是偉福,還是uvsion,都提供了包括編輯器、編譯器、仿真和調(diào)試為一體的開發(fā)環(huán)境,程序在編制完成后,直接在開發(fā)環(huán)境里面仿真調(diào)試,經(jīng)過排錯后,將問題降到最低再燒入到單片機(jī)運(yùn)行,這比反復(fù)燒寫芯片驗(yàn)證程序效率要高的多。
最要緊的事情是:AT89S52具備實(shí)現(xiàn)分時(shí)系統(tǒng)的必要條件:1支持中斷,2具備計(jì)時(shí)器,3勉強(qiáng)夠用的RAM空間。
第六部分: 時(shí)間片的分配
實(shí)現(xiàn)分時(shí)系統(tǒng)的第一要考慮怎么進(jìn)行時(shí)間片的分配,也就是程序之間的切換問題。這個很簡單,可利用一個定時(shí)器產(chǎn)生一個固定的延時(shí),當(dāng)延時(shí)到達(dá),進(jìn)入定時(shí)器中斷,將這個中斷的入口跳轉(zhuǎn)到切換程序。注意:程序的切換不是簡單的跳轉(zhuǎn),而是要先保存當(dāng)前程序的執(zhí)行狀態(tài)、被中斷的地址,不至于下次回來繼續(xù)當(dāng)前程序時(shí)變量、狀態(tài)都被改變,那就會亂套了。然后再選擇下一個要運(yùn)行的程序,將被選中程序的狀態(tài)恢復(fù)到當(dāng)前的各狀態(tài)寄存器當(dāng)中,恢復(fù)到這個程序上次被打斷的點(diǎn)繼續(xù)運(yùn)行,等待下一個時(shí)間片用完,進(jìn)中斷,重復(fù)以上過程。
既然每個程序都要保存和恢復(fù)它的運(yùn)行狀態(tài),那么問題來了。一是要保存哪些信息,稱之為保護(hù)的范圍;二是需要多少內(nèi)存,能夠支撐多少個程序進(jìn)行分時(shí)。
第一個問題,保護(hù)范圍取決于用戶程序的斷點(diǎn)地址、狀態(tài)字、寄存器組,以及堆棧空間。第二個問題,支撐多少個程序進(jìn)行分時(shí)取決于內(nèi)存的大小,89C52的256字節(jié)支撐8個線程后,留給應(yīng)用程序的內(nèi)存只有36個字節(jié),因此80c51的128個字節(jié)RAM是不夠的。
在此,為了區(qū)別于系統(tǒng)程序,改稱以上所謂被分時(shí)的程序?yàn)槿蝿?wù)或線程。
第七部分: 內(nèi)存組織的方法
我們能設(shè)想到最原始的方法是通過數(shù)組(數(shù)據(jù)表)的方式,將每一個線程的保護(hù)內(nèi)容固定的保存起來。這種方法的缺點(diǎn)是不靈活,要增補(bǔ)被保護(hù)的內(nèi)容就要重新定義數(shù)組,改動操作數(shù)據(jù)的代碼。當(dāng)然還有更值得推薦的方法:用堆棧來組織內(nèi)存。
堆棧是一種數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)如被壓入沖鋒步槍AK47彈匣的子彈,最先被壓進(jìn)去的子彈,最后才被彈出到槍膛。在計(jì)算機(jī)內(nèi)一般設(shè)有一個SP寄存器,存放堆棧指針,如果執(zhí)行PUSH指令,SP先向前推一格,數(shù)據(jù)被寫入SP所指向的內(nèi)存;如果執(zhí)行POP指令,數(shù)據(jù)從SP所指向的內(nèi)存讀出,SP再向后退一格。這種先入后出的結(jié)構(gòu)能夠很好的支撐子程序嵌套調(diào)用時(shí)的數(shù)據(jù)存儲。舉一個栗子:
主程序運(yùn)行時(shí)調(diào)用子程序A,當(dāng)前的程序指針PC被壓入堆棧(必須留下跳轉(zhuǎn)點(diǎn)的腳印,否則子程序完成后返回不到原來被跳過去的點(diǎn)),接著PC跳轉(zhuǎn)到子程序A中開始運(yùn)行,在子程序A里面又有調(diào)用子程序B,此刻的程序指針PC也被壓入堆棧,接著跳到子程序B中,子程序B完成后返回,將堆棧中最近保存的PC彈出到PC寄存器,程序返回到子程序A,子程序A完成后返回,從堆棧彈出最早壓入的PC,程序返回到主程序。
系統(tǒng)在線程之間切換時(shí),將當(dāng)前線程的所有需要保護(hù)的內(nèi)容全部壓入堆棧,最后僅需要保存好它的堆棧指針(就像把東西全扔箱子里,只需保管好鑰匙)。將下一個線程的堆棧指針取出來,從堆棧彈出所有要恢復(fù)的數(shù)據(jù),然后返回到新線程運(yùn)行下一個時(shí)間片。
第八部分: 關(guān)于線程調(diào)度
當(dāng)一個時(shí)間片用完,定時(shí)器發(fā)生中斷后,程序指針跳轉(zhuǎn)到線程切換程序,首先是保護(hù)現(xiàn)場。然后是選擇下一個線程。那么問題又來了,我們打算怎么來選擇下一個線程?這就是所謂的線程調(diào)度,操作系統(tǒng)的教課書里叫進(jìn)程調(diào)度。
最原始粗暴的方法就是順序循環(huán)調(diào)度,所有線程先來后到排個隊(duì),挨個輪著,實(shí)行平均主義的制度。在一些實(shí)時(shí)系統(tǒng)里面,比如μCOS,是按優(yōu)先級來確定調(diào)度權(quán)的:永遠(yuǎn)只選擇優(yōu)先級最高的線程,也就是說,如果線程一直處于最高優(yōu)先級,那么它就一直占著CPU不放,就不會被搶占。如果要讓線程立即得到執(zhí)行,就必須設(shè)置最高優(yōu)先級。這種方法能夠提供手段讓用戶設(shè)計(jì)的任務(wù)滿足實(shí)時(shí)的要求(所謂實(shí)時(shí),就是對響應(yīng)時(shí)間可評估,可控制,不是字面理解上的實(shí)時(shí)。)
除了優(yōu)先級方式,還可以設(shè)定一種搶占的機(jī)制,比如在順序調(diào)度的過程中來一個半道插隊(duì)方式的搶占,也是一種可以滿足實(shí)時(shí)要求的方法。半路殺出個陳咬金,需要提前設(shè)置一個信號通知給調(diào)度程序,表示當(dāng)前有某個線程需要插隊(duì)搶占。這里要考慮到原有的順序調(diào)度不能被打亂,或者被搶占的線程正好又是被輪到的線程,那么下次調(diào)度應(yīng)繼續(xù)往后調(diào)度。
調(diào)度算法有很多,應(yīng)按工程實(shí)際需求來設(shè)計(jì),讀者不妨也可以設(shè)想更有創(chuàng)新意義的方法。
第九部分: 系統(tǒng)效率和延遲
分時(shí)總是要付出一些代價(jià)的,假如一顆CPU全力運(yùn)行一個單程序,那么CPU利用率是100%。如果這顆CPU要分時(shí)運(yùn)行兩個程序,那么每個程序?qū)PU的利用率都不足50%,因?yàn)榍袚Q要耗費(fèi)CPU時(shí)間,切換次數(shù)越多越耗費(fèi),“切換可是要上稅的”。切換次數(shù)和時(shí)間片的設(shè)置相關(guān),一般來說時(shí)間片設(shè)置范圍在1ms到20ms之間,時(shí)間片太小,浪費(fèi)了大量CPU時(shí)間在上下文切換上,時(shí)間片太大,線程被輪到的時(shí)間就長,響應(yīng)變差。一個線程的運(yùn)行延遲在分時(shí)系統(tǒng)里很難被準(zhǔn)確的估算,它與已就緒的線程數(shù)量、CPU主頻、時(shí)間片長短、中斷響應(yīng)等各種因素相關(guān),這些因素動態(tài)影響著單個線程的運(yùn)行延遲。
分時(shí)系統(tǒng)中單個線程性能的一種簡單的估算方法:假如一個CPU工作在30MHz的主頻下,有3個已就緒的線程被平均調(diào)度,時(shí)間片相等,那么單個線程的運(yùn)行性能相當(dāng)于它獨(dú)占了一個10MHz不到的CPU的效果,相當(dāng)于這個CPU被拆分成了3個10MHz不到的CPU在同步運(yùn)行。由于現(xiàn)場就緒與否是動態(tài)變化的,因此很難完全準(zhǔn)確估算。
總而言之,分時(shí)盡管耗費(fèi)了一些CPU時(shí)間,但相對它所帶來驚奇效果和并行思維給程序設(shè)計(jì)帶來更多的可能性而言,微不足道。分時(shí)就像變魔術(shù)一樣,能把一顆CPU拆分成N個弱小的CPU同步運(yùn)行不同的程序。從宏觀上,分時(shí)能夠充分挖掘CPU資源,當(dāng)一個線程需要等待或有目的的延時(shí),完全可以把CPU時(shí)間讓給其他線程,而不是白白的空等。
第十部分: 線程的休眠和就緒
當(dāng)一個線程需要等待或延時(shí),或者索性暫停掉,可以通過狀態(tài)標(biāo)記,使調(diào)度程序跳過對它的調(diào)度,那么這個線程就是被休眠了,或稱該線程處于休眠態(tài)。反過來,如果線程等著被調(diào)度,稱該線程處于就緒態(tài)。這個功能在調(diào)度程序內(nèi)做一個判斷不難實(shí)現(xiàn)。
如果要使一個線程休眠,系統(tǒng)可以提供一個函數(shù)(子程序)來修改線程的休眠標(biāo)志(變量)。任何線程,包括線程自己都可以進(jìn)行休眠操作。所謂想睡就睡,但不是想醒就醒,喚醒一定是其他線程或時(shí)鐘服務(wù)來幫助喚醒你。
除了休眠,還可以設(shè)置一個殺死的操作,所謂殺死線程,就是讓線程的狀態(tài)信息恢復(fù)初始化,下次再喚醒該線程,相當(dāng)于要從頭開始執(zhí)行。殺死線程,或線程自殺,可以理解為線程的復(fù)位并休眠。
可以設(shè)想一種簡單的設(shè)計(jì)場景:用線程1來處理用戶輸入,線程2、3、4為不同功能的獨(dú)立任務(wù),線程1可通過用戶輸入選擇喚醒或休眠線程2、3、4來調(diào)取不同的功能。對于任務(wù)的開發(fā)來說,確實(shí)提供了與往常不同的手段,用的好,絕對順手。
第十一部分: 線程之間通信和互斥
感官上,線程是獨(dú)立運(yùn)行的,微觀上,線程跑著跑著不知道什么時(shí)候就咔的被喊停,CPU被強(qiáng)行切換到別的線程去運(yùn)行。也就是說,線程的運(yùn)行是隨時(shí)隨刻隨地都可能被中斷的。
線程和線程之間要通信,一般通過全局變量、全局的數(shù)據(jù)結(jié)構(gòu)(內(nèi)存的一塊空間)來共享信息。比如線程A往全局變量內(nèi)寫信息,線程B從該變量讀信息。以達(dá)到讓線程A的信息傳達(dá)給線程B的目的。那么問題又來了,如果線程A對全局變量寫了一半,咔,時(shí)間片到了,輪到線程B運(yùn)行,這時(shí)候線程B從該變量讀到的信息就是一個錯誤的信息。
先來分析這個問題:
中斷是在指令和指令之間響應(yīng)的,也就是其最小顆粒度是單條指令,如果寫變量是一條指令搞定的,由于指令在執(zhí)行過程中不會被中斷,所以不會發(fā)生以上情況。單條指令的操作也稱為原子操作。
可惜的是,一條指令至多可寫一個字。對于沒有接觸過匯編的程序員來說,一條高級語言的語句很容易被認(rèn)為是一個原子操作,其實(shí)不然,經(jīng)過編譯后,一條高級語言的語句往往都由若干條指令組成。
思考解決問題:
要解決寫變量時(shí)候不被破壞的問題,就要排斥其他線程對這塊變量的操作,或防止在寫的過程中被中斷。方法有幾種:
最簡單粗暴的方法是開始寫共享變量之前直接把中斷給關(guān)了,寫完以后再開。還有更流氓的做法,就是在寫共享變量之前直接讓計(jì)時(shí)器停擺,定住,結(jié)束后再繼續(xù)往前計(jì)數(shù)。方法雖然簡單,但是這么做會讓整個系統(tǒng)短暫停滯,明顯有副作用。
推薦的方法是讓調(diào)度程序帶上鎖功能,如果處于上鎖狀態(tài),調(diào)度程序不執(zhí)行切換。線程在寫共享變量之前先加鎖,結(jié)束后再解鎖。在加鎖期間,雖然中斷和計(jì)時(shí)都不受影響,但是其他所有線程都被禁止了,還是有些缺憾。
更好的辦法是采用信號量,相當(dāng)于給共享變量加一個紅綠燈。設(shè)置紅綠燈的操作必須是原子操作,線程在操作共享變量時(shí)先查一下是不是綠燈,如果是綠燈就把它變?yōu)榧t燈,然后操作變量,結(jié)束后再設(shè)為綠燈;如果是紅燈就排隊(duì)等待,直到綠燈來了再進(jìn)行操作。
名詞摘要:在教材中,把對需要互斥的共享變量和設(shè)備進(jìn)行獨(dú)占操作,叫臨界區(qū)操作,結(jié)束后要退出臨界區(qū)。
第十二部分: 掛鐘和鬧鈴叫醒服務(wù)
線程在運(yùn)行過程中經(jīng)常會用到等待相對精確的延時(shí)、以及超時(shí)判斷等和時(shí)間相關(guān)的操作。傳統(tǒng)的單線程程序,經(jīng)常用最粗暴的空操作指令循環(huán)來獲得一定的延時(shí),這無可厚非,單線程中一段程序的運(yùn)行時(shí)間能夠被準(zhǔn)確的估算出來。多線程環(huán)境就不同了,一段程序的運(yùn)行時(shí)間長短是不可準(zhǔn)確估算的,取決于就緒線程的數(shù)量和時(shí)間片的長短,這些都是變化的。
需求場景1:線程需要就地等待1分鐘。
需求場景2:線程進(jìn)入一個循環(huán)等待某個信號,當(dāng)時(shí)間超過5秒鐘判斷為超時(shí),退出循環(huán)。
以下是一種設(shè)計(jì)方案:
設(shè)計(jì)一個系統(tǒng)掛鐘,讓他一直以單位時(shí)間往前走。好比在墻上掛一個鐘頭,讓所有線程都能看時(shí)間。為每個線程提供一個鬧鈴指針和喚醒服務(wù)標(biāo)志,如果某個線程需要喚醒服務(wù),就把鬧鈴指針撥到目標(biāo)時(shí)間,然后開啟喚醒服務(wù)。系統(tǒng)掛鐘每走一個刻度都要判斷當(dāng)前時(shí)間和需要喚醒服務(wù)線程的鬧鈴時(shí)間是否一致,一致的話就喚醒這個線程。
當(dāng)某個線程需要就地等待1分鐘就可以這樣做:線程先看一下當(dāng)前的時(shí)間,然后把時(shí)間往前推1分鐘設(shè)置一個鬧鈴,然后告訴系統(tǒng)時(shí)鐘:鬧鈴到了記得叫醒我!接著線程就自我休眠了,等著1分鐘后系統(tǒng)時(shí)鐘的喚醒并繼續(xù)運(yùn)行。
實(shí)踐的栗子:啟用硬件另一個計(jì)時(shí)器,每10ms發(fā)生一次中斷。系統(tǒng)中設(shè)置一個16位的字,每過10ms將這個字加一,這個16位字相當(dāng)于一個量程為65536的掛鐘。設(shè)置一個數(shù)組用于存儲每個線程的鬧鈴時(shí)間,設(shè)置一個狀態(tài)字,用于標(biāo)志線程的喚醒服務(wù)。當(dāng)時(shí)鐘往前走一個刻度時(shí),同時(shí)進(jìn)行一個喚醒的判斷,將當(dāng)前時(shí)間和鬧鈴時(shí)間一致的線程喚醒。
時(shí)鐘的最小刻度設(shè)定為10ms,也就意味著延時(shí)設(shè)定的最小單位是10ms,那么問題又來了,若需要延時(shí)1ms或更小的時(shí)間刻度該怎么解決?
如果把時(shí)鐘刻度直接調(diào)至1ms,會導(dǎo)致時(shí)鐘中斷次數(shù)增加了10倍,時(shí)鐘和鬧鈴的程序段的執(zhí)行次數(shù)也就增加了10倍,這嚴(yán)重消耗了CPU時(shí)間,降低了整個系統(tǒng)的性能。如何解決這個問題留給讀者去思考。
第十三部分: 公用的函數(shù)(或子程序)
如果一個函數(shù)僅僅使用堆棧和局部變量,那么這個函數(shù)就能天然的被多個線程同時(shí)調(diào)用。因?yàn)槎褩:途植孔兞浚ɑ駽PU寄存器)都是受保護(hù)的,假使2個線程同時(shí)調(diào)用一個函數(shù),在線程切換的情況下,雖然都是運(yùn)行在相同的函數(shù)代碼段,但各自線程里的局部變量和堆棧內(nèi)容不同,所以運(yùn)行結(jié)果也是各自分開的。這個函數(shù)就像使了分身之術(shù)一樣,在不同的線程里同時(shí)運(yùn)行著。這種函數(shù)也叫作可重入函數(shù)。
如果一個函數(shù)使用了全局變量,那么假使2個線程同時(shí)調(diào)用了它,那么在線程A里面所定義的全局變量和線程B里面所定義的全局變量指向同一個位置,就會發(fā)生問題。這種不能被多個線程同時(shí)調(diào)用的函數(shù),叫不可重入函數(shù)。
第十四部分: 后記
事實(shí)上,分時(shí)系統(tǒng)是現(xiàn)代操作系統(tǒng)的核心技術(shù)內(nèi)容,它并不過時(shí)。本文所描述的內(nèi)容涉及到操作系統(tǒng)的部分原理知識,當(dāng)然描述不夠?qū)I(yè),沒有準(zhǔn)確引用教材中的各種名詞和術(shù)語,不能完整的討論操作系統(tǒng)。但筆者希望本文能夠成為讀者對操作系統(tǒng)知識的一種指引,如果覺得意猶未盡,可以去翻一翻《操作系統(tǒng)》相關(guān)教材,看看專業(yè)的角度這些概念是怎么被描述的。很多程序員總是以為軟件能夠解決一切問題,其實(shí)不然,有些想法或算法不得不依賴硬件的支持。事實(shí)上,操作系統(tǒng)的各種思想也深刻影響了硬件的設(shè)計(jì),CPU設(shè)計(jì)者在設(shè)計(jì)新的CPU時(shí),充分考慮并納入了好多為操作系統(tǒng)考慮的事情。
現(xiàn)代操作系統(tǒng)往往鑒于UNIX這樣的通用操作系統(tǒng)來描述的,其內(nèi)容除了講分時(shí)調(diào)度以外,還包括內(nèi)存管理,外存管理(也就是文件系統(tǒng)),系統(tǒng)調(diào)用,設(shè)備驅(qū)動等內(nèi)容。硬件往往基于個人計(jì)算機(jī)(intel的架構(gòu))來描述,頭緒非常多,讓學(xué)習(xí)者覺得課程就像是空中閣樓,可遠(yuǎn)觀而不可觸碰。
用單片機(jī)做實(shí)踐是最能摸到計(jì)算機(jī)本質(zhì)的路徑,試想,在一臺裸機(jī)上讓你的程序最接近硬件的跑動起來,是多么踏實(shí)的趕腳(好吧,牽強(qiáng)了)。通用計(jì)算機(jī)和單片機(jī)原理是一樣的,只不過通用計(jì)算機(jī)規(guī)模更大,主頻更高,為了達(dá)到更大的規(guī)模,架構(gòu)起來就要用更多的手段和技巧。
比如在通用計(jì)算機(jī)內(nèi),程序指令并不是固化在ROM或閃存內(nèi)的,而是存放在低速容量更大的外存(硬盤、軟盤、光驅(qū)、U盤)中,需要執(zhí)行程序時(shí),把程序文件成塊的復(fù)制到內(nèi)存中,CPU從內(nèi)存中取指令執(zhí)行。那么問題來了,程序文件必須被裝載在指定內(nèi)存地址段,(跳轉(zhuǎn)指令中包含了具體的地址),如果地址和指令錯位,那么程序跳轉(zhuǎn)將找不到北。要解決這個問題,CPU的設(shè)計(jì)者從早期的內(nèi)存分段管理,發(fā)展到現(xiàn)在的虛擬地址空間轉(zhuǎn)換,可謂費(fèi)盡心思。隨著程序規(guī)模越做越大,當(dāng)單個程序的長度超出內(nèi)存的總?cè)萘繒r(shí),問題又來了。為了解決這個問題,又引入了分頁和虛擬內(nèi)存的方法,分頁允許程序不用一次性全部調(diào)入內(nèi)存,而是將要被執(zhí)行部分的頁面調(diào)入內(nèi)存,剩余的還是存在硬盤里,當(dāng)程序執(zhí)行的指令已經(jīng)不在內(nèi)存時(shí),CPU將會發(fā)生一個缺頁中斷,將硬盤中的目標(biāo)頁調(diào)入內(nèi)存,將不用的頁調(diào)回到硬盤。這些都是內(nèi)存管理單元MMU做的事情,而MMU基本是硬件實(shí)現(xiàn)(做在CPU內(nèi)部),但它需要軟件配合才能用起來。所有的這些機(jī)制都是為了解決過程中碰到的問題而提出的解決方案,要搞清楚源頭。
討論操作系統(tǒng)離不開信息安全的話題,黑客和病毒總是存在。理論上,用戶程序如果要想搞破壞是非常容易做到的,單靠軟件防止黑客或病毒的攻擊理論上是做不到的,因?yàn)槔硐霠顟B(tài)下所有指令都是公平對等的,你不能用程序指令去阻止用于惡意破壞的程序指令,比如故意改寫數(shù)據(jù)(破壞正確的數(shù)據(jù))。所以,計(jì)算機(jī)安全必須依靠硬件提供的機(jī)制,例如CPU設(shè)計(jì)者將處理器的運(yùn)行分成多個狀態(tài),低權(quán)級狀態(tài)下只能執(zhí)行部分安全的指令,高權(quán)級狀態(tài)下才可以執(zhí)行全部指令,用戶程序只能運(yùn)行在低權(quán)級,需要進(jìn)行讀寫操作時(shí)須通過調(diào)用系統(tǒng)提供的功能調(diào)用來實(shí)現(xiàn),系統(tǒng)是工作在特權(quán)級下的,這樣就為計(jì)算機(jī)安全提供了可控手段。
性能是計(jì)算機(jī)永恒的話題。分時(shí)系統(tǒng)上就緒的線程數(shù)量越多,單個線程的性能就越低(可視為CPU的性能被線程瓜分了,線程越多,單個線程瓜分到的性能就少)。為了提升計(jì)算機(jī)性能,除了設(shè)置流水線、多級緩存等方式外,單個CPU主要靠提升主頻來實(shí)現(xiàn)性能的數(shù)量級提升。除此之外,引入多個CPU并行工作,通過增加系統(tǒng)的并行度也是提高性能的主要手段。多個CPU使得線程從物理上真正的并行了,一個多核的CPU在同一時(shí)刻可以同時(shí)運(yùn)行多個線程,多CPU在硬件組織上又是一件費(fèi)盡心機(jī)的活(不要簡單的理解成多插幾個CPU就完事那么簡單)。超級計(jì)算機(jī)(用于氣象、原子能、航天等領(lǐng)域的測算)往往以集群方式組織起來的高度并行的結(jié)構(gòu),這也要求軟件設(shè)計(jì)必須要被分割為高度的并行化才能發(fā)揮超級計(jì)算機(jī)的性能。如果一個問題的算法必須至始至終前后串行,無法被分割成多個同步運(yùn)行的部分,那么這種程序放在超級計(jì)算機(jī)上運(yùn)行,縱使這臺超算有千萬個CPU,程序也只能在其中一個CPU上跑完,剩下的CPU等于閑著沒事干。同樣的道理,多核CPU體現(xiàn)的也是并行性能,只有在線程數(shù)量多的分時(shí)系統(tǒng)內(nèi)才能體現(xiàn)其性能。如果用一個8核的CPU僅僅只運(yùn)行一個線程,那么剩下7個核心就閑著沒事做。
備注:本文一直采用線程來描述并發(fā)執(zhí)行的程序,其實(shí)線程是專有名詞,有更清晰的概念。操作系統(tǒng)中還有進(jìn)程的概念,進(jìn)程也是并發(fā)的程序,一個進(jìn)程還能再分多個線程。在實(shí)時(shí)操作系統(tǒng)中,線程又被稱為任務(wù)。
寫在最后
通過以上敘述,我想讀者一定對分時(shí)、多線程、并發(fā)執(zhí)行有了概念,同時(shí)也一定留下了一堆的疑問和不解,這可是好現(xiàn)象,進(jìn)步都是在疑問和不解中開始的。
還有2個應(yīng)用例程可以從這里下載:http://www.zg4o1577.cn/bbs/dpj-59966-1.html
豆豆的打開關(guān)上
需求:假設(shè)有一個指針,可以作圓周運(yùn)動,通過在鍵盤上輸入具體的角度,使指針指向該角度。
輸入:數(shù)字鍵盤,按3位數(shù)字后回車生效。范圍0-999,表示圓周角度,對360度取模。
顯示:3位段式液晶數(shù)碼顯示。
執(zhí)行:減速步進(jìn)電機(jī),按輸入的角度作圓周運(yùn)動。
結(jié)構(gòu):木結(jié)構(gòu)為主,在電機(jī)上安裝臂桿,做成類停車桿的結(jié)構(gòu)。可360度自由轉(zhuǎn)動。
技術(shù)上要考慮的問題:
1、液晶驅(qū)動的問題,擬采用HT1621芯片驅(qū)動;
2、歸零問題,擬采用12V電池+5V模塊的供電方式,設(shè)置斷電繼電器和斷電歸零程序;
3、轉(zhuǎn)動角度問題,單步判斷逼近方法,or距離計(jì)算一次到位方法;擬采用前者;
操作模型:
1、打開開關(guān),液晶顯示---;臂桿角度為0;(開關(guān)處并聯(lián)的繼電器吸合,開關(guān)的觸點(diǎn)留一組去cpu)
2、輸入0-360之間的一個整數(shù)表示圓周的絕對角度,每輸入一個數(shù)字,顯示到液晶屏;其他輸入作無效處理;
3、回車后,液晶屏內(nèi)的數(shù)字被作為目標(biāo)值,執(zhí)行電機(jī)轉(zhuǎn)到該角度;
4、轉(zhuǎn)動過程中,可以按暫停鍵,暫停或繼續(xù);
5、轉(zhuǎn)動過程中,可以繼續(xù)輸入新的角度值,按回車后變更新的目標(biāo)值執(zhí)行。
6、執(zhí)行完成后,電機(jī)去電待命。
7、開關(guān)關(guān)閉,cpu檢測到關(guān)機(jī)指令,執(zhí)行歸零程序,完成后,繼電器斷開。(歸零程序不可被中斷,歸零后須再確認(rèn)關(guān)機(jī)指令,若還是關(guān)機(jī),則繼電器斷開。)
實(shí)施步驟:
1、整體方案構(gòu)思;資料消化;
2、硬件草圖;
3、電機(jī)驅(qū)動試驗(yàn),正反向,速度和角度;
4、液晶顯示試驗(yàn);
5、按鍵掃描模擬和試驗(yàn);
6、軟件構(gòu)思;
7、編碼和模塊測試;
8、最終調(diào)試。
全局變量:當(dāng)前值,目標(biāo)值,暫停標(biāo)志,鍵值,顯示數(shù)字,ram區(qū),8拍數(shù)組;
按鍵掃描獲取鍵值(任務(wù)2)
鍵值改變數(shù)字or啟動執(zhí)行(轉(zhuǎn)換為目標(biāo)值)or暫停標(biāo)志變更,無效值;新的數(shù)字來了,要標(biāo)志,用于判斷回車鍵的作用;數(shù)字 轉(zhuǎn)換 成ram區(qū);ram區(qū)寫入HT1621的ram;(任務(wù)3)
暫停標(biāo)志沒有時(shí)and當(dāng)前值!=目標(biāo)值時(shí)執(zhí)行:目標(biāo)值在左側(cè)還是在右側(cè):前進(jìn)一步或后退一步,延時(shí),返回;(任務(wù)4)
[一個8拍數(shù)組;前進(jìn)一步,當(dāng)前值(0-4096變更);后退一步,當(dāng)前值(0-4096變更);]
不停的查開關(guān)標(biāo)志,若關(guān)機(jī),改變目標(biāo)為0,執(zhí)行,等待執(zhí)行完成,再判斷是否關(guān)機(jī),關(guān)斷電源繼電器。否則返回。(任務(wù)0)
- ;kernel: sys51 r0.99
- ;project name: doudou's up & down
- ;designer: ut
- ;version: 1.0
- ;date: 2016/11/1
- ;=============================================================================================
- ; TIME-SHARING SYSTEM FOR MCS51 RELEASE 0.99
- ; UT.ZUZU
- ; COPYRIGHT(2012/5/10-2016/11/--)
- ;=============================================================================================
- ;詳細(xì)請查看手冊
- ;硬件要求
- ;1、52系列兼容的51單片機(jī),內(nèi)存256字節(jié)或以上。本程序在AT89S52運(yùn)行,24.576MHZ晶振,改變晶振需調(diào)整計(jì)數(shù)器值。晶振頻率越高,控制器性能越好。
- ;2、256字節(jié)內(nèi)存中,系統(tǒng)使用了大部分高地址部分,0-47由用戶支配,具體請看內(nèi)存分配說明。
- ;3、一共8個線程:TASK0到TASK2為3個主線程,其余為次線程;主線程對9個寄存器和PSW、AB、DPTR進(jìn)行保護(hù),并預(yù)留堆棧最大嵌套調(diào)用為7級;次線程僅保護(hù)PSW,AB,R0-R3,最大嵌套調(diào)用為2級。
- ;4、系統(tǒng)可以在調(diào)度程序中喂看門狗,時(shí)間片不可過大,超過4MS不喂狗看門狗發(fā)出系統(tǒng)復(fù)位信號。看門狗功能可以在配置定義中取消。
- ;5、分時(shí)過程通過定時(shí)器0進(jìn)行,其初值定義在T0_VALUE中,目前設(shè)置的是5MS時(shí)間片。
- ;6、系統(tǒng)時(shí)間記錄在SYS_TIME變量中,通過定時(shí)器2進(jìn)行,目前設(shè)置是10MS加1.
- ;任務(wù)操作說明
- ;0、任務(wù)的邊界應(yīng)該是循環(huán)。不建議跳出邊界。盡可能的使用系統(tǒng)提供的調(diào)用。
- ;1、不同任務(wù)可以調(diào)用同一個子程序,注意子程序內(nèi)受保護(hù)的范圍。
- ;2、主任務(wù)擁有獨(dú)立的R0-R7、ACC、B、PSW寄存器、DPTR指針。次任務(wù)僅保護(hù)7個寄存器。
- ;3、任務(wù)之間可以通過內(nèi)存變量來傳遞信息,注意在寫內(nèi)存時(shí)必須占用系統(tǒng),寫完后再釋放系統(tǒng),建議使用加鎖和解鎖調(diào)用。
- ;4、系統(tǒng)初始化后所有任務(wù)都是睡眠的,系統(tǒng)會喚醒任務(wù)0和任務(wù)7,其他任務(wù)的喚醒由用戶操作。任務(wù)7為伺服任務(wù),不建議休眠它或在該任務(wù)中使用系統(tǒng)延時(shí)調(diào)用。(有一種風(fēng)險(xiǎn):所有任務(wù)處于休眠態(tài),會進(jìn)入待機(jī))
- ;5、系統(tǒng)的性能與晶振頻率、喚醒的任務(wù)數(shù)量、任務(wù)占用的時(shí)間片有關(guān)系。
- ;6、任務(wù)有權(quán)殺死或休眠任何任務(wù),如果系統(tǒng)所有任務(wù)都被殺死或休眠,系統(tǒng)會進(jìn)入節(jié)電POWER_DOWN模式,等待復(fù)位激活。
- ;7、系統(tǒng)提供10MS刻度的16位系統(tǒng)時(shí)間,由TIMER2來完成。任務(wù)可以根據(jù)自己需要來完成延時(shí)功能,其性能優(yōu)于普通的空等待DELAY子程序。
- ;8、任務(wù)不可以操作TIMER0和TIMER2這兩個定時(shí)器,需要時(shí),可以使用TIMER1. 建議不要設(shè)置為高優(yōu)先級,可能導(dǎo)致系統(tǒng)時(shí)間停走。
- ;注:殺死和休眠的區(qū)別:任務(wù)被殺死后再次喚醒從頭開始運(yùn)行,任務(wù)被休眠后再次喚醒是從原來休眠的地方繼續(xù)運(yùn)行(就像暫停)。
- ;用戶使用注意:
- ;1.總計(jì)8個任務(wù),單個線程是死循環(huán),所有線程并發(fā)執(zhí)行,可以有限調(diào)整每個線程的時(shí)間片,默認(rèn)5MS時(shí)間片,合理使用可以滿足實(shí)時(shí)要求。
- ;2.任務(wù)0到2是主線程,線程內(nèi)寄存器A和B,R0-R7,DPTR都受保護(hù),子程序嵌套調(diào)用最大達(dá)8級。
- ;3.任務(wù)3到7是次線程,線程內(nèi)寄存器A和B,R0-R3受保護(hù),子程序嵌套調(diào)用最大2級。注意這個限制條件。嵌套調(diào)用超限將導(dǎo)致堆棧過界破壞,使系統(tǒng)崩潰。
- ;4.用戶只能使用0-59之間的內(nèi)存空間。
- ;5.用戶無需考慮堆棧的分配,禁止任務(wù)程序修改堆棧指針SP。
- ;6.中斷響應(yīng)程序中要注意保護(hù)現(xiàn)場和恢復(fù)現(xiàn)場。
- ;2012-5-22 R0.91 占用系統(tǒng)和釋放系統(tǒng)改用停止和開啟計(jì)時(shí)器的方式實(shí)現(xiàn)。UNDEBUG
- ;2012-5-23 R0.92 喂狗簡化到CPL指令 UNDEBUG
- ;使用時(shí)注意:所有中斷程序內(nèi)要用到PSW,A,B,R0~R7,DPTR,必須事先暫存,返回前恢復(fù),注意它們不受保護(hù)
- ;2016-11-08 確認(rèn)BUG和注釋。可以作為穩(wěn)定版。
- ;2016-11-08 R0.99
- ;為了增強(qiáng)實(shí)用性,擬重新布局內(nèi)存,改用堆棧方式保護(hù)現(xiàn)場,保證3個主線程,每個線程分配29字節(jié),增加對DPTR的保護(hù),;增加對PSW的保護(hù)
- ;可以最大嵌套29-2(PC)-2(DPTR)-10(AB,RG)-1(PSW)=7個CALL;閹割剩余5個次線程,每個線程分配13字節(jié):AB,R0-R3,PC,PSW,最大嵌套2個CALL。
- ;總體256字節(jié)的內(nèi)存:3個主線程:29*3=87;5個次線程:13*5=65;8個線程狀態(tài)(優(yōu)先級、SP、鬧鈴H、鬧鈴L)=32;
- ;系統(tǒng)變量:10;系統(tǒng)堆棧:14;R0-R7:8個除去。 剩余用戶可用的內(nèi)存區(qū):40字節(jié)
- ;真可謂:螺螄殼里做道場。
- ;20161110
- ;備忘:調(diào)度程序要增加加鎖功能(不切換,好處是總是能進(jìn)系統(tǒng)區(qū)做一些系統(tǒng)要做的事,比如喂狗)
- ;延時(shí)誤差太大,最大誤差是一個單位(不累計(jì)),考慮系統(tǒng)(放在時(shí)鐘程序內(nèi))來負(fù)責(zé)高精度大跨度的計(jì)時(shí),和喚醒服務(wù),需要額外16字節(jié)用于8個線程的鬧鐘記錄。
- ;看門狗使用指南:時(shí)間片調(diào)節(jié)的太大就會觸發(fā)看門狗,應(yīng)能根據(jù)需要關(guān)閉看門狗。13位,每一個機(jī)器周期+1
- ;時(shí)鐘要方便配置
- ;注意中斷嵌套的影響 ;注意測量 系統(tǒng)服務(wù)的時(shí)間,及其與中斷時(shí)間的比重,比重和效率成正比 ;中斷響應(yīng)前后次序的關(guān)系分析,用戶怎么用中斷
- ;喚醒服務(wù)要注意的是:必須留一個伺服線程,該線程始終保持就緒(不能使用系統(tǒng)延時(shí))。否則有一種風(fēng)險(xiǎn):所有任務(wù)同時(shí)調(diào)用系統(tǒng)延時(shí)而休眠,調(diào)度程序?qū)⑥D(zhuǎn)入節(jié)電模式。要復(fù)位或外部中斷才能恢復(fù)。
- ;20161111 r0.99基本調(diào)試完成
- ;0.99版本比較0.92版本特點(diǎn)如下:
- ;1、充分利用堆棧的特點(diǎn)布局內(nèi)存,使得保護(hù)內(nèi)容的調(diào)整變的靈活。
- ;2、不改變8個任務(wù)的總數(shù),但集中資源到3個主任務(wù)上,增加對psw、dptr寄存器的保護(hù)(原來沒考慮周全,如psw是必須保護(hù)的)。使主任務(wù)不再有束縛。
- ;3、取消原有的延時(shí)服務(wù),增加系統(tǒng)時(shí)鐘的定時(shí)喚醒服務(wù)功能,每個任務(wù)可以設(shè)置自己的延時(shí)時(shí)間,然后進(jìn)入休眠態(tài)等待,時(shí)間到了系統(tǒng)時(shí)鐘會喚醒你。
- ;4、改變了殺、休眠、喚醒的方式,采用位表示殺死信號、就緒態(tài)、喚醒服務(wù),可以用邏輯的方法快速操作。
- ;5、增加了調(diào)度程序的加鎖功能,加鎖狀態(tài)下,調(diào)度程序不進(jìn)行任務(wù)切換,但繼續(xù)執(zhí)行其他系統(tǒng)功能。
- ;6、看門狗、初始時(shí)間片可配置。
- ;7、任務(wù)7作為伺服線程,可以做一些簡單的脈搏動作。伺服線程必須始終就緒,否則有任務(wù)全部休眠的風(fēng)險(xiǎn)。
- ;0.99的篇幅反而比0.92下降了7%,除了更加實(shí)用以外,顯得更加優(yōu)美。
- ;實(shí)際應(yīng)用達(dá)到3個以上時(shí),修復(fù)一些潛在的bug之后,可以升為r1.0版本,并出一份《51多任務(wù)內(nèi)核的應(yīng)用手冊》
- ;內(nèi)存地圖規(guī)劃
- ;0-47 用戶
- ;48-63 鬧鐘數(shù)組-每個任務(wù)2個字節(jié),用于指示鬧鐘時(shí)間 /30H
- ;64-73 系統(tǒng)變量 /40H
- ;74-87 系統(tǒng)堆棧 7個CALL 包括中斷 SP_SYS:73 /49H 再壓縮至6個call
- ;88-95 任務(wù)優(yōu)先狀態(tài)字節(jié) /58H
- ;96-103 任務(wù)SP指針 /60H
- ;104-132 任務(wù)0堆棧 SP0:103 /67H
- ;133-161 任務(wù)1堆棧 SP1:132 /84H
- ;162-190 任務(wù)2堆棧 SP2:161 /A1H
- ;191-203 任務(wù)3堆棧 SP3:190 /BEH
- ;204-216 任務(wù)4堆棧 SP4:203 /CBH
- ;217-229 任務(wù)5堆棧 SP5:216 /D8H
- ;230-242 任務(wù)6堆棧 SP6:229 /E5H
- ;243-255 任務(wù)7堆棧 SP7:242 /F2H
- ;NOTE:OPRATING SFR OR RAM WHERE HAVE THE SAME ADDRESS WITH EACH OTHER WILL BE ATTENTED! CARE <DATASHEET OF AT89S52>
- ;-------------------------------------------------------------------------------
- ;標(biāo)號定義
- PRI_BYTE EQU 0D8H ;INIT PRIORITY OF EVERY TASK ;時(shí)間片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H 這里是參考值,初始化時(shí)間片請定義在PRI_BYTE
- SYS_SP EQU 4bH ;SYSTEM STACK HEAD
- START_TASK_SP EQU 67H
- TAB_PRI EQU 58H ;基址 見內(nèi)存分配規(guī)劃
- TAB_SP EQU 60H ;基址
- TAB_CLK EQU 30H ;BASE
- WDT_PIE EQU 00H ;設(shè)置為1E,看門狗開啟,其他值則關(guān)閉看門狗 NO TEST 13位計(jì)時(shí)器1FFF復(fù)位,合計(jì)4MS :意味著啟用看門狗時(shí),時(shí)間片必須小于4MS,占用系統(tǒng)時(shí)也要注意這個問題,建議用加鎖功能代替占用系統(tǒng)
- ;系統(tǒng)全局變量定義
- sys_bit_byte equ 2fh ;留給系統(tǒng)的8個標(biāo)志位 位地址78-7fh
- TMP_A EQU 40H
- TASK_CURT_P EQU 41H ;當(dāng)前的任務(wù)指針
- task_sch_p equ 4ah ;調(diào)度任務(wù)指針
- CLK_ALARM EQU 42H ;鬧鐘字節(jié) 從左到右每一位依次標(biāo)志任務(wù)0到7的鬧鈴請求,1為有鬧鈴請求
- DEAD_SIG EQU 43H ;從左到右每一位依次標(biāo)志任務(wù)0到7的殺死請求,1為有殺死請求
- READY_BYTE EQU 44H ;從左到右每一位依次標(biāo)志任務(wù)0到7的就緒狀態(tài),1為就緒
- LOCK_BYTE EQU 45H ;5A表示加鎖,其他值表示解鎖
- TMP_SP EQU 46H
- WDT_BYTE EQU 47H ;狗盆子
- SYS_TIME_H EQU 48H ;系統(tǒng)時(shí)鐘高8位
- SYS_TIME_L EQU 49H ;系統(tǒng)時(shí)鐘低8位
- nouse equ 4bh ;預(yù)留
- preempt_bit bit 78h ;是否搶占
- delay_sv_bit bit 79h ;定時(shí)器1中斷服務(wù) 標(biāo)志 用于小刻度的延時(shí)需求
- preempt_task EQU 2eh ;搶占任務(wù)號 僅0-7有效,搶占后作廢,用于調(diào)度程序切換到指定的任務(wù)去。
- delay_times equ 2dh ;用于timer1計(jì)時(shí)刻度的次數(shù)
- ;系統(tǒng)晶振:24.576MHZ
- T0_VALUE_H EQU 0D8H ;時(shí)間片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H 這里是初始賦值,初始化時(shí)間片請定義在PRI_BYTE
- T0_VALUE_L EQU 00H
- T2_VALUE_H EQU 0B0H ;時(shí)鐘刻度 參考上面
- T2_VALUE_L EQU 00H
- T1_VALUE_H EQU 0fcH ;時(shí)鐘刻度 參考上面 500us
- T1_VALUE_L EQU 00h ;
- ;------------------------------規(guī)劃程序入口
- ORG 00H
- JMP SYS_START
- ORG 03H
- ;LJMP INT_INT0 ;(INT0)
- RETI
- ORG 0BH
- ;LJMP INT_T0 ;(IF0)
- LJMP SHARE_SYS
- RETI
- ORG 13H
- ;LJMP INT_INT1 ;(INT1)
- RETI
- ORG 1BH
- ;LJMP INT_T1 ;(IF1)
- JMP sys_ms_svrs
- RETI
- ORG 23H
- ;LJMP INT_RTX ;(RI,TI)
- RETI
- ORG 2BH
- ;LJMP INT_T2 ;(IF2)
- JMP SYS_TIME_RUN
- RETI
- ;標(biāo)記中斷返回:如果意外中斷,直接返回,不至于跳飛;-)
- ;以下是任務(wù)的入口,應(yīng)和表格中定義一致
- ORG 30H
- LJMP TASK_0
- ORG 38H
- LJMP TASK_1
- ORG 40H
- LJMP TASK_2
- ORG 48H
- LJMP TASK_3
- ORG 50H
- LJMP TASK_4
- ORG 58H
- LJMP TASK_5
- ORG 60H
- LJMP TASK_6
- ORG 68H
- LJMP TASK_7
- ;;開機(jī),從00H跳過來*******************************************
- SYS_START:
- MOV SP,#SYS_SP ;SYSTEM STACK
- MOV WDT_BYTE,#WDT_PIE ;準(zhǔn)備好狗糧
- clr preempt_bit
- clr delay_sv_bit
- mov preempt_task,#0
- CALL INIT_RAM ;初始化系統(tǒng)內(nèi)存
- CALL INIT_TIMER ;初始化定時(shí)器
- CALL USER_INIT ;用戶初始化程序
- CALL SYS_TIMER_START ;啟動系統(tǒng)定時(shí)器
-
- MOV DEAD_SIG,#0 ;清空殺手信號
- MOV R1,#0F8H;
- MOV R0,#7;
- CALL SET_PRIBYTE ;任務(wù)7時(shí)間片設(shè)置為1MS
- MOV READY_BYTE,#10000001B ;任務(wù)0就緒,任務(wù)7當(dāng)作伺服線程,如果沒有一個線程就緒,會進(jìn)待機(jī)
- MOV TASK_CURT_P,#0
- MOV TASK_sch_P,#0
- MOV TAB_PRI,#PRI_BYTE ;任務(wù)正常運(yùn)行的要素:不被殺,就緒,優(yōu)先級(時(shí)間片)不要太長(看門狗會叫),SP狀態(tài)
- MOV SP,#START_TASK_SP
- LJMP TASK_0 ;進(jìn)入任務(wù)0,啟動分時(shí),START SHARE
- ;;上面用到的子程序:任務(wù)1到7依次初始化各自內(nèi)存空間-----------------------------
- INIT_RAM:
- MOV R0,#7 ;以此對各任務(wù)進(jìn)行內(nèi)存初始化賦值
- ITR0:
- CALL TASKRAM_INIT
- DJNZ R0,ITR0 ;TASK0任務(wù)作為系統(tǒng)啟動的入口,可以不用初始,其內(nèi)容會在第一個時(shí)間片中斷后調(diào)度程序會給予。
- ;CALL TASKRAM_INIT ;JUST FOR TEST TASK0 RAM INIT
- RET
- ;以下表格用于初始化內(nèi)存用
- TAB_1:
- DB 067H,084H,0A1H,0BEH,0CBH,0D8H,0E5H,0F2H,00H ;任務(wù)棧頂?shù)刂?br />
- TAB_2:
- DB 030H,038H,040H,048H,050H,058H,060H,068H,00H ;任務(wù)入口地址 和ORG 30H.. 對應(yīng)
- ;上面用到的子程序:開機(jī)初始化任務(wù)內(nèi)存操作:1、根據(jù)任務(wù)號查表得棧頂位置、入口位置;2、在棧頂壓入:入口、現(xiàn)場;3、將SP存到SP_I; 4、清就緒態(tài)
- ;初始化任務(wù)內(nèi)存分主次 ;任務(wù)號先存R0
- TASKRAM_INIT:
- MOV A,R0
- MOV DPTR,#TAB_1
- MOVC A,@A+DPTR ;查表得初始SP
- MOV TMP_SP,SP
- MOV SP,A ;開始壓棧
- MOV A,R0
- MOV DPTR,#TAB_2
- MOVC A,@A+DPTR ;查表得初始PC
-
- MOV 02H,A
- PUSH 02H ;PUSH PC_L
- MOV 02H,#0
- PUSH 02H ;PUSH PC_H PC是16位的
- PUSH 02H ;PSW,AB,R0-R7,DPTR
- PUSH 02H
- PUSH 02H
- PUSH 02H
- PUSH 02H
- PUSH 02H
- PUSH 02H
- ;區(qū)分主次任務(wù)
- ;任務(wù)號大于2則跳過以下步驟
- CLR C
- MOV A,#2
- SUBB A,R0
- JC TKI00
- PUSH 02H ;R4 R5 R6 R7 DPL DPH
- PUSH 02H
- PUSH 02H
- PUSH 02H
- PUSH 02H
- PUSH 02H
- TKI00:
- ;保存SP到數(shù)組,SP--> SP_I
- MOV A,#TAB_SP
- ADD A,R0
- MOV R1,A ;這個是指針變量,指向當(dāng)前SP的存放地址
- MOV @R1,SP ;記錄SP
- MOV SP,TMP_SP ;壓棧完成,恢復(fù)SP
- ;優(yōu)先級字節(jié)賦值初始值
- MOV A,#TAB_PRI
- ADD A,R0
- MOV R1,A
- MOV @R1,#PRI_BYTE
- ;清就緒態(tài)
- CALL CLR_READY_BIT
- RET
- ;子程序:以下初始化系統(tǒng)定時(shí)器 TIMER2 DEBUGED 120516 --------------------------
- INIT_TIMER:
- ;TIMER2 SETUP
- MOV 0C8H,#00H ;MOV T2CON,#00H
- MOV 0C9H,#00H ;MOV T2MOD,#00H
- MOV 0CCH,#T2_VALUE_L ;MOV TL2,#T2_VALUE_L
- MOV 0CDH,#T2_VALUE_H ;MOV TH2,#T2_VALUE_H
- MOV 0CAH,0CCH ;MOV RCAP2L,TL2
- MOV 0CBH,0CDH ;MOV RCAP2H,TH2
- ;TIMER0 SETUP
- ANL 88H,#11101111B;TCON CLR TR0 : STOP TIMER0
- ANL 89H,#11110000B ;TMOD(SET TIMER0)
- ORL 89H,#00000001B ;TMOD(SET TIMER0) MODE:01 16BIT COUNT UP
- MOV 8AH,#T0_VALUE_L ;TL0
- MOV 8CH,#T0_VALUE_H ;TH0
- ;TIMER1 SETUP
- ANL 88H,#10111111B;TCON CLR TR0 : STOP TIMER1
- ANL 89H,#00001111B ;TMOD(SET TIMER0)
- ORL 89H,#00010000B ;TMOD(SET TIMER0)MODE:01 16BIT COUNT UP MODE:02 8BIT autoCOUNT UP
- MOV 8bH,#T1_VALUE_L ;TL0
- MOV 8dH,#T1_VALUE_H ;TH0
- RET
- ;子程序:啟動TIMER0和TIMER2 ;DEBUGED 120516---------------------------------
- SYS_TIMER_START:
- MOV SYS_TIME_H,#00
- MOV SYS_TIME_L,#00
- MOV IP,#00000000B ;SET PRIORITY
- MOV IE,#10101010B ;SETB EA ;SETB ET2 ;SETB ET0 ET1 TO ENABLE INTERUPT OF TIMER2 AND TIMER0 AND TIMER1
- ORL 88H,#01010000B ;TCON SETB TR0,TR1 START TIMER0 TIMER1
- ORL 0C8H,#00000100B ;ORL T2CON,#00000100B ;SETB TR2 TO START TIMER2
- RET
- ;;系統(tǒng)時(shí)間處理,在TIMER2中斷后跳進(jìn)來
- ;系統(tǒng)時(shí)間處理有2大內(nèi)容:1、比較各鬧鐘的目標(biāo)時(shí)間是否到達(dá),到達(dá)并且該任務(wù)有喚醒服務(wù),就執(zhí)行喚醒;2、時(shí)鐘刻度加一。
- SYS_TIME_RUN:
- CLR EA
- MOV TMP_SP,SP ;保存A 保護(hù)現(xiàn)場
- MOV SP,#SYS_SP ;--------------------------------界面,以下系統(tǒng)區(qū)
- MOV TMP_A,PSW
- PUSH TMP_A
- MOV TMP_A,A ;PUSH A
- PUSH TMP_A
- MOV TMP_A,B ;PUSH B
- PUSH TMP_A
- PUSH 00H ;PUSH R0
- PUSH 01H ;PUSH R1
- PUSH 02H ;PUSH R2
- PUSH 03H ;PUSH R3
- ;處理CLK_ALARM字節(jié)、TAB_CLK數(shù)組
- MOV A,CLK_ALARM
- JZ STR00 ;沒有服務(wù)時(shí)跳過
- MOV R0,#TAB_CLK
- MOV R3,SYS_TIME_L
- CALL PROC_CMP_BYTE ;低8位比較
- MOV R1,A
- MOV R0,#TAB_CLK
- DEC R0
- MOV R3,SYS_TIME_H
- CALL PROC_CMP_BYTE ;高8位比較,對比結(jié)果保存到A 1表示相等 0表示不等
- ANL A,R1 ;H和L的比較結(jié)果合并
- MOV R1,A
- MOV A,CLK_ALARM
- ANL A,R1 ;與喚醒服務(wù)合并
- ORL READY_BYTE,A ;執(zhí)行喚醒
- MOV A,R1
- CPL A
- ANL CLK_ALARM,A ;清喚醒標(biāo)志,表示完成喚醒
- STR00:
- ;16位系統(tǒng)時(shí)鐘+1 放在后面處理,延時(shí)00時(shí)可立即生效
- MOV A,SYS_TIME_L
- INC SYS_TIME_L
- INC A
- JNZ $+4
- INC SYS_TIME_H
- ANL 0C8H,#01111111B ;ANL T2CON,#01111111B ;CLEAR TF2 清TIMER2中斷標(biāo)志
- POP 03H ;POP R3
- POP 02H ;POP R2
- POP 01H ;POP R1
- POP 00H ;POP R0
- POP TMP_A
- MOV B,TMP_A ;POP B
- POP TMP_A
- MOV A,TMP_A ;POP A ;恢復(fù)現(xiàn)場
- POP TMP_A
- MOV PSW,TMP_A
- MOV SP,TMP_SP ;---------------------------------------------界面,以上系統(tǒng)區(qū)
- SETB EA
- RETI
- ;;;;;;;中斷返回
- ;用于刻度為500us,次數(shù)255的等待服務(wù)。只提供一個線程使用,出于系統(tǒng)消耗的考慮,500us中斷必須篇幅足夠小。
- ;定時(shí)器1中斷服務(wù):500us中斷一次,無服務(wù)直接返回。有服務(wù):次數(shù)(time_us字節(jié))為0則讓waiting_task_p任務(wù)搶占(標(biāo)志完成)。不為0時(shí),減一。
- ;系統(tǒng)需要用一個字節(jié)的標(biāo)志位2fh,用戶要避開。
- ;preempt_bit bit 78h ;是否搶占
- ;delay_sv_bit bit 79h ;定時(shí)器1中斷服務(wù) 標(biāo)志 用于小刻度的延時(shí)需求
- ;preempt_task EQU 3fh ;搶占任務(wù)號 僅0-7有效,搶占后作廢,用于調(diào)度程序切換到指定的任務(wù)去。
- ;delay_times equ 3eh ;用于timer1計(jì)時(shí)刻度的次數(shù)
- sys_ms_svrs:
- jb delay_sv_bit,smsv0 ;無服務(wù)直接返回
- MOV 8bH,#T1_VALUE_L ;TL1
- MOV 8dH,#T1_VALUE_H ;TH1
- reti
- smsv0:
- ;保護(hù)現(xiàn)場
- mov tmp_a,a
-
- ;查delay_times次數(shù):等于0時(shí),置搶占任務(wù)preempt_bit
- mov a,delay_times
- jnz smsv1
- setb preempt_bit ;置搶占位
- clr delay_sv_bit ;清服務(wù)位
- ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
- ;中斷不嵌套,本次中斷返回后進(jìn)入系統(tǒng)中斷
- MOV 8bH,#T1_VALUE_L ;TL1
- MOV 8dH,#T1_VALUE_H ;TH1
- mov tmp_a,a
- reti
- ;次數(shù)減一
- smsv1: dec delay_times
- ;恢復(fù)現(xiàn)場
- mov a,tmp_a
- MOV 8bH,#T1_VALUE_L ;TL1
- MOV 8dH,#T1_VALUE_H ;TH1
- reti
- ;;上面要用到
- ;;子程序:基地址存R0(間隔1個字節(jié)的8個數(shù)組),與系統(tǒng)時(shí)鐘(H或L字節(jié))R3進(jìn)行比較,8次,結(jié)果存放在ACC對應(yīng)的位里面,1表示相等
- PROC_CMP_BYTE:
- MOV B,#0
- MOV R2,#8
- MOV A,#15
- ADD A,R0
- MOV R0,A
- PCB00:
- MOV A,@R0
- CJNE A,03H,PCB01
- MOV A,B
- SETB C
- RRC A
- JMP PCB02
- PCB01:
- MOV A,B
- CLR C
- RRC A
- PCB02: MOV B,A
- DEC R0
- DEC R0 ;間隔1字節(jié)的指針,從右到左
-
- DJNZ R2,PCB00
- MOV A,B
- RET
- ;;調(diào)度程序,在timer0中斷后跳過來。
- ;;調(diào)度程序的內(nèi)容:1、保護(hù)現(xiàn)場;2、存sp;3、喂狗、執(zhí)行任務(wù)死刑、判斷加鎖;4、切換下一個就緒的任務(wù)指針;5、調(diào)取新任務(wù)的時(shí)間片設(shè)置到定時(shí)器;6、調(diào)取新sp;7、恢復(fù)現(xiàn)場;8、返回到新任務(wù)。
- SHARE_SYS: ;保護(hù)現(xiàn)場先
- MOV TMP_A,PSW ;PUSH PSW
- PUSH TMP_A
- MOV TMP_A,A ;PUSH A
- PUSH TMP_A
- MOV TMP_A,B ;PUSH B
- PUSH TMP_A
- PUSH 00H ;PUSH R0
- PUSH 01H ;PUSH R1
- PUSH 02H ;PUSH R2
- PUSH 03H ;PUSH R3
- ;區(qū)分主次任務(wù)
- ;TASK_CURT_P 大于2則跳過以下步驟
- CLR C
- MOV A,#2
- SUBB A,TASK_CURT_P
- JC SS00
- PUSH 04H ;PUSH R4
- PUSH 05H ;PUSH R5
- PUSH 06H ;PUSH R6
- PUSH 07H ;PUSH R7
- PUSH DPL
- PUSH DPH
- SS00: ;存SP到數(shù)組SP
- MOV A,#TAB_SP
- ADD A,TASK_CURT_P
- MOV R0,A ;這個是指針變量,指向當(dāng)前SP的存放地址
- MOV @R0,SP ;記錄SP
-
- ;切換SP,以下進(jìn)入系統(tǒng)區(qū)----------------------------------------------------------------INTERFACE
- MOV SP,#SYS_SP ;SP指向系統(tǒng)SP
- CALL WDT ;喂狗
- CALL KILL_TASK ;根據(jù)DEAD_SIG字節(jié),執(zhí)行任務(wù)的死刑 ;-*
- ;是否上鎖,如果上鎖 LOCK_BYTE= 5AH 則不執(zhí)行任務(wù)切換
- MOV A,LOCK_BYTE
- CJNE A,#5AH,SS04
- JMP SS05
- SS04:
- mov r1,task_sch_p ;暫存
- MOV R6,#10
- SELECT_P: ;選擇下一個任務(wù)
- DJNZ R6,SS01 ;選擇次數(shù)計(jì)時(shí),如果連續(xù)選擇超10次就得進(jìn)節(jié)電模式了
- MOV P1,#0FFH
- ORL 87H,#02H ;INTO POWER-DOWN MODE
- LJMP SYS_START ;醒來的話就重新開機(jī)咯
- ;切換任務(wù)指針(0-7) 全局變量TASK_sch_P 任務(wù)指針,僅此進(jìn)行寫操作
- SS01:
- INC TASK_sch_P
- MOV R0,TASK_sch_P
- CJNE R0,#8,SS02 ;超限
- MOV TASK_sch_P,#0
- ;判就緒位,不在就緒態(tài)就跳回 SELECT_P,重復(fù)以上步驟
- SS02:
- MOV R0,TASK_sch_P
- CALL GET_READY_BIT
- JNC SELECT_P
- ;調(diào)度結(jié)束,新的指針在task_sch_p
- ;是否有搶占信號
- jnb preempt_bit,ss06
- mov a,preempt_task
- clr c
- subb a,#8
- jnc ss06 ;搶占任務(wù)號無效(大于7)
- mov a,preempt_task
- cjne a,task_sch_p,ss07 ;如果搶占任務(wù)和本次應(yīng)該調(diào)度的任務(wù)相同,則下一次不要再調(diào)這個任務(wù)了。(本次調(diào)度生效,否則退回上一次調(diào)度指針)。
- jmp ss08
- ss07:
- mov task_sch_p,r1 ;恢復(fù)調(diào)度指針
- ss08:
- mov r0,preempt_task
- call set_ready_bit ;搶占任務(wù)就緒位
- mov task_curt_p,preempt_task ;直接指定任務(wù)號,切換
- clr preempt_bit
- jmp ss05
- ss06: mov task_curt_p,task_sch_p ;調(diào)度盤指針 確定調(diào)度指針和實(shí)際任務(wù)指針分離,解決搶占后調(diào)度不公平問題
- SS05:
- ;取優(yōu)先字節(jié)地址
- MOV A,#TAB_PRI
- ADD A,TASK_CURT_P
- MOV R0,A
- ;時(shí)間片賦值 ;RESET THE TIMER0
- MOV 8AH,#T0_VALUE_L ;TL0
- MOV 8CH,@R0 ;TH0 ;MOV TH0,@R0;選中后,優(yōu)先級設(shè)置到時(shí)間片
- ;取SP_I --> SP
- MOV A,#TAB_SP
- ADD A,TASK_CURT_P
- MOV R0,A
- MOV SP,@R0
- ;以下退出系統(tǒng)態(tài),回到新的任務(wù)態(tài),恢復(fù)現(xiàn)場-------------------------------------------INTERFACE
- ;區(qū)分主次任務(wù)
- ;TASK_CURT_P 大于2則跳過以下步驟
- CLR C
- MOV A,#2
- SUBB A,TASK_CURT_P
- JC SS03
- POP DPH
- POP DPL
- POP 07H ;POP R7
- POP 06H ;POP R6
- POP 05H ;POP R5
- POP 04H ;POP R4
- SS03:
- POP 03H ;POP R3
- POP 02H ;POP R2
- POP 01H ;POP R1
- POP 00H ;POP R0
- POP TMP_A
- MOV B,TMP_A ;POP B
- POP TMP_A
- MOV A,TMP_A ;POP A
- POP TMP_A
- MOV PSW,TMP_A
- ;此時(shí)堆棧內(nèi)當(dāng)前應(yīng)是中斷返回時(shí)的PC值,RETI可以返回。
- ;ANL 88H,#11011111B ;CLR TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
- RETI
- ;;子程序:根據(jù)被殺任務(wù)信號字節(jié)8位,從左到右每一位代表任務(wù)0-7是否要?dú)⒌簦?為殺死,0為不殺 來執(zhí)行死刑
- ;執(zhí)行內(nèi)容:將該任務(wù)的內(nèi)存區(qū)重新初始化(初始化后為休眠態(tài)),下次再輪到時(shí),從頭開始。
- KILL_TASK:
- MOV R3,#8
- KTA00:
- MOV A,DEAD_SIG
- RRC A
- MOV DEAD_SIG,A
- JNC KTA01
- MOV a,R3
- DEC a
- mov r0,a
- CALL TASKRAM_INIT
- KTA01:
- DJNZ R3,KTA00
- MOV DEAD_SIG,#0 ;清掉所有DEAD信息
- RET
- ;;喂狗子程序
- WDT:
- MOV 0A6H,WDT_BYTE ;MOV WDTRST,WDT_BYTE WDT_BYTE= 1EH OR E1H
- MOV A,WDT_BYTE
- CPL A ;取反
- MOV WDT_BYTE,A
- RET
- ;;獲取就緒位:在調(diào)度程序中用到
- GET_READY_BIT: ;任務(wù)號R0, 執(zhí)行結(jié)束后,結(jié)果的位在C
- MOV B,R0
- INC B
- MOV A,READY_BYTE
- GRB00: RLC A
- DJNZ B,GRB00
- RET
- ;提供的系統(tǒng)調(diào)用
- ;-----------------------------------------------------------------------------------------------
- ;子程序:修改任務(wù)的時(shí)間片,任務(wù)號在R0,優(yōu)先字節(jié)(時(shí)間片)在R1,將優(yōu)先字節(jié)寫入到數(shù)組
- SET_PRIBYTE:
- MOV A,#TAB_PRI
- ADD A,R0
- MOV R0,A
- MOV A,R1
- MOV @R0,A
- RET
- ;子程序:回到調(diào)度程序 DEBUGED 120516
- WAITING:
- NOP ;留給中斷響應(yīng)的間隙
- ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
- RET
- ;子程序:占用系統(tǒng),任務(wù)在讀寫的時(shí)候不允許系統(tǒng)中斷,和frees配套使用
- OCCUPY:
- ;ORL IE,#00000010B ;ENABLE INTERRUPT OF TIMER0 方法1:關(guān)閉timer0的中斷
- ANL 88H,#11101111B ;TCON CLR TR0, STOP TIMER0 方法2:關(guān)閉timer0的計(jì)時(shí)
- RET
- ;子程序:釋放系統(tǒng) 和occupy配套使用,任務(wù)占用系統(tǒng)后應(yīng)及時(shí)釋放
- FREES:
- ;ANL IE,#11111101B ;DISABLE INTERUPT OF TIMER0
- ORL 88H,#00010000B ;TCON SETB TR0, START TIMER0
- RET
- ;注意:occupy和free要配套用,他們之間就是臨界區(qū),然而occupy會導(dǎo)致不進(jìn)調(diào)度程序,不建議使用。建議用加鎖和解鎖來實(shí)現(xiàn)臨界區(qū)的操作。
- LOCK_SYS:
- MOV LOCK_BYTE,#5AH
- RET
- UNLOCK_SYS:
- MOV LOCK_BYTE,#0EEH
- RET
- ;精確的系統(tǒng)延時(shí)-將16位的延時(shí)數(shù),每一位為一個時(shí)刻,存放在DPTR,計(jì)算目標(biāo)時(shí)間,設(shè)置喚醒任務(wù),休眠自己。等待系統(tǒng)時(shí)鐘在時(shí)間到了再喚醒你,誤差為一個調(diào)度周期。
- ;任務(wù)號為全局變量指針TASK_CURT_P
- ;延時(shí)步驟:1、將dptr個刻度和當(dāng)前時(shí)間相加得到目標(biāo)時(shí)間,存入到鬧鈴數(shù)組當(dāng)前任務(wù)位置;2、設(shè)置本任務(wù)的喚醒服務(wù)位,當(dāng)目標(biāo)時(shí)間到達(dá),系統(tǒng)時(shí)鐘會喚醒你;3、進(jìn)入休眠態(tài);
- DELAY_SYS:
- ;計(jì)算目標(biāo)16位目標(biāo)值,存放在TAB_CLK對應(yīng)的位置
- MOV A,SYS_TIME_L
- ADD A,DPL
- MOV DPL,A
- MOV A,SYS_TIME_H
- ADDC A,DPH ;帶進(jìn)位
- MOV DPH,A
- MOV A,#TAB_CLK
- MOV R0,TASK_CURT_P
-
- ADD A,R0
- ADD A,R0
- ;雙字節(jié)指針
- MOV R0,A
- MOV @R0,DPH
- INC R0
- MOV @R0,DPL
- ;設(shè)置喚醒位,在CLK_ALARM字節(jié),8個位標(biāo)志8個任務(wù)的喚醒服務(wù),1為有服務(wù)。
- MOV R0,TASK_CURT_P
- CALL SET_ALARM_BIT
- ;清就緒位,在READY_BYTE
- MOV R0,TASK_CURT_P
- CALL CLR_READY_BIT
- ;回調(diào)度
- ORL 88H,#00100000B
- RET
- ;上面用到的子程序:設(shè)置喚醒服務(wù)的位,任務(wù)號預(yù)先放在R0
- SET_ALARM_BIT:
- MOV B,R0
- MOV A,#10000000B
- INC B ;最小任務(wù)號為1
- SAB00: DJNZ B,SAB01 ;循環(huán)左移
- ORL CLK_ALARM,A
- JMP SAB02
- SAB01: RR A
- JMP SAB00
- SAB02:
- RET
- ;子程序:任務(wù)自殺
- KILL_SELF:
- MOV B,TASK_CURT_P
- MOV A,#10000000B
- INC B ;最小任務(wù)號為1
- KSF00: DJNZ B,KSF01 ;循環(huán)左移
- ORL DEAD_SIG,A
- JMP KSF02
- KSF01: RR A
- JMP KSF00
- KSF02:
- RET
- ;子程序:殺死,任務(wù)號存R0
- KILL_TASK_CALL:
- MOV B,R0
- MOV A,#10000000B
- INC B ;最小任務(wù)號為1
- KTSK00: DJNZ B,KTSK01 ;循環(huán)左移
- ORL DEAD_SIG,A
- JMP KTSK02
- KTSK01: RR A
- JMP KTSK00
- KTSK02:
- RET
- ;子程序:清就緒位,就緒態(tài)字節(jié) 8位 從左到右每一位分別代表任務(wù)0-7是否就緒,1為就緒,0為休眠
- ;任務(wù)號存在R0
- CLR_READY_BIT:
- MOV B,R0
- MOV A,#01111111B
- INC B ;最小任務(wù)號為1
- CRB00: DJNZ B,CRB01 ;循環(huán)左移
- ANL READY_BYTE,A
- JMP CRB02
- CRB01: RR A
- JMP CRB00
- CRB02:
- RET
- ;子程序:置就緒位,上面的相反操作 ;任務(wù)號存在R0
- SET_READY_BIT:
- MOV B,R0
- MOV A,#10000000B
- INC B ;最小任務(wù)號為1
- SRB00: DJNZ B,SRB01 ;循環(huán)左移
- ORL READY_BYTE,A
- JMP SRB02
- SRB01: RR A
- JMP SRB00
- SRB02:
- RET
- ;子程序:小刻度的延時(shí)功能(通過定時(shí)器1和搶占機(jī)制完成),次數(shù)放在r0
- delay_sys_us:
- mov delay_times,r0
- mov preempt_task,task_curt_p ;占用的任務(wù)號預(yù)存
- mov r0,task_curt_p
- call clr_ready_bit ;延時(shí)期間要休眠
- setb delay_sv_bit ;開啟延時(shí)服務(wù)
- ORL 88H,#00100000B ;SETB TF0 ;SOFT INTERUPT TIMER0 TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
- nop
- nop ;中斷響應(yīng)
- ret
- ;;;;;;;;;;;;;;;;;;伺服線程:任務(wù)7
- task_7:
- mov r2,#77h
- mov r3,#77h
- tk700:
- mov r0,#01h
- mov r1,#0eh
- call delay16b
- setb p1.7 ;led 熄滅100ms
- mov r0,#0dh
- mov r1,#0bdh
- call delay16b
- clr p1.7 ;led 點(diǎn)亮900ms
- jmp tk700
- jmp task_7
- ;r0:h r1:l 16位數(shù)的nop延時(shí) 一個周期為10.25us(全速) ,高8位放在r0,低8位放在r1
- delay16b:
- dll00:
- mov a,r1
- clr c
- subb a,#1
- mov r1,a
- mov a,r0
- subb a,#0 ;進(jìn)位 16位數(shù)減一
- mov r0,a
- div ab ;純粹為了延時(shí)
- div ab
- nop
- nop
- mov a,r0
- orl a,r1
- jnz dll00
- ret
- ;注意:以上僅做了關(guān)于殺死、休眠、喚醒任務(wù)的調(diào)用,僅為了使用方便,實(shí)際使用時(shí)推薦使用更高效的邏輯方法:
- ;比如:要?dú)⑷蝿?wù)3和6,可以將dead_sig ORL 00010010 即可
- ;要休眠任務(wù)2和4,可以將ready_byte ANL 11010111 即可
- ;要喚醒任務(wù)1和7,可以將ready_byte ORL 10000001 即可
- ;SYSTEM END==============================================================line number of r0.92 is 750
- ;User's code
- ;*********************************************************************
- ;project name: 360度指示器 豆豆的打開關(guān)上
- ;designer: ut
- ;version: 1.0
- ;date: 16-11-16
- ;*********************************************************************
- ;用戶在此定義自己的變量地址 及 標(biāo)號 0-46d 0-2cH :一共45個字節(jié),除去0-7,可以用8-2cH這37個字節(jié)
- ;需求:
- ;1\ 16位數(shù)字鍵,0-9 abcd * #
- ; 2\ 3位段碼LCD顯示
- ; 3\ 按下數(shù)字,插入到LCD的左側(cè)。
- ; 4\ 按下D(回車),LCD的數(shù)字作為角度值,電機(jī)轉(zhuǎn)動到指定角度,三位數(shù)字范圍0-999,對360取模,執(zhí)行完畢后再輸入數(shù)字時(shí)LCD清零再插入。
- ; 5\ 按下A電機(jī)向上微調(diào),按下B電機(jī)向下微調(diào)
- ; 6\ 按下C,LCD清零。
- ; 7\ 關(guān)機(jī)時(shí),壁板歸位至270度位置,再切斷電源
- ; 8\ 開機(jī)時(shí),壁板開啟到0度位置。
- ;task0: 主線程,開機(jī)初始化,啟動其他任務(wù),主循環(huán)是不停的取鍵、取鍵值成功后處理鍵值。
- ;task1: 電機(jī)移動,始終試圖將當(dāng)前位置靠近目標(biāo)位置,直到達(dá)到為止。
- ;task2: 按鍵掃描,轉(zhuǎn)換為鍵值存入到緩沖區(qū)。
- ;處理鍵值:0-9,執(zhí)行循環(huán)插入 BCD 數(shù)組,如果有清屏標(biāo)志,則先清屏再插入。
- ;處理鍵值:A-B, 執(zhí)行電機(jī)走12拍,約1度,A為正方向,B為反方向。
- ;處理鍵值:C, 將BCD全部設(shè)置為0
- ;處理鍵值:D,將BCD轉(zhuǎn)成一個16位數(shù),再mod360運(yùn)算,將結(jié)果寫到電機(jī)目標(biāo)值。設(shè)置清屏標(biāo)志。
- ;關(guān)于顯示:在主線程的循環(huán)中,涉及到BCD變化時(shí),才會觸發(fā)顯示,顯示過程:將BCD碼轉(zhuǎn)換成段碼,將段碼輸出到HT1621驅(qū)動器。
- WR_1621 BIT P3.6
- ;RD_1621 BIT P3.7
- DATA_1621 BIT P3.5
- CS_1621 BIT P3.7
- BIAS EQU 52H; //0B1000 0101 0010 1/3DUTY 4COM
- SYSDIS EQU 0; //0B1000 0000 0000 關(guān)振系統(tǒng)蕩器和LCD偏壓發(fā)生器
- SYSEN EQU 02H; //0B1000 0000 0010 打開系統(tǒng)振蕩器
- LCDOFF EQU 04H; //0B1000 0000 0100 關(guān)LCD偏壓
- LCDON EQU 06H; //0B1000 0000 0110 打開LCD偏壓
- XTAL EQU 28H; //0B1000 0010 1000 外部接時(shí)鐘
- RC256 EQU 30H; //0B1000 0011 0000 內(nèi)部時(shí)鐘
- TONEON EQU 12H; //0B1000 0001 0010 打開聲音輸出
- TONEOFF EQU 10H; //0B1000 0001 0000 關(guān)閉聲音輸出
- WDTDIS EQU 0AH; //0B1000 0000 1010 禁止看門狗
- ;;;內(nèi)存變量,范圍(8-2cH)
- nouse1 equ 20h ;預(yù)留給可尋址的位
- nouse2 equ 21h
- pool_key equ 21h ;22,23,24;類似堆棧指針,前推一位
- p_key equ 25h
- pool_bcd equ 26h ;26,27,28;存放bcd碼 循環(huán)覆蓋
- p_bcd equ 29h ;存放bcd指針,0-2 循環(huán)
- pool_print equ 8h ;8 9 10 存放3位數(shù)碼管的段碼
- key_value equ 0bh
- p_moto_H equ 0ch
- p_moto_L equ 0dh
- targ_moto_H equ 0eh
- targ_moto_L equ 0fh
- p_step equ 10h ;表的指針
- p_deg equ 11h
- ;定義位
- key_catched bit 00h ;獲取到一個按鍵后置位
- bcd_ready bit 01h ;bcd插入新值時(shí)置位
- moto_dir bit 02h ;電機(jī)方向
- tmp_dir bit 03h
- reset_bcd bit 04h ;重置bcd
- ;;用戶在這里寫初始化程序,在系統(tǒng)開機(jī)初始化時(shí),被調(diào)用,注意:此處不可進(jìn)行系統(tǒng)功能的調(diào)用
- user_init:
- mov p1,#0f0h ;關(guān)電機(jī)
- mov 08h,#0 ;段碼區(qū)
- mov 09h,#0
- mov 0ah,#0
- mov 26h,#0 ;bcd區(qū)
- mov 27h,#0
- mov 28h,#0
-
- mov p_key,#0 ;表示無按鍵
- mov p_bcd,#0
- mov p_step,#0
- mov p_deg,#0
- mov p_moto_h,#0
- mov p_moto_l,#0
- mov targ_moto_h,#0
- mov targ_moto_l,#0
- clr reset_BCD
- clr p2.2 ;開啟關(guān)機(jī)繼電器
-
- ret
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;;;;;;任務(wù)0 主線程 ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;主線程,1、從鍵盤緩沖池取一個按鍵; 2、處理該按鍵(數(shù)字鍵插入bcd區(qū))其他鍵(步數(shù)增減、清零、回車)3、bcd轉(zhuǎn)換為段碼,隱去尾部0
- ;4、段碼輸出
- ;;;其他按鍵處理:步數(shù)增加一個幅度,不改變參數(shù),步數(shù)減少一個幅度,不改變參數(shù),bcd歸零,bcd轉(zhuǎn)換為目標(biāo)值;;;;;;;;;;
- ;;-----------------------------------任務(wù)0
- task_0:
- CALL Ht1621_Init;() ; 上電初始化LCD驅(qū)動芯片
- mov dptr,#tab_ht1621
- mov r3,#0
- mov r5,#16
- call Ht1621WrAllData;(0,Ht1621Tab,16) ;清除1621寄存器數(shù)據(jù),清屏
- mov dptr,#tab_ht1621_dou
- mov r3,#0
- mov r5,#3
- call Ht1621WrAllData;(0,Ht1621Tab,3);顯示 ;logo
- ;LCD的掃描是不需要延時(shí)的
- orl ready_byte,#01100000b ;開啟任務(wù)2:鍵盤掃描程序 ;開啟任務(wù)1:電機(jī)驅(qū)動:實(shí)際值逼近目標(biāo)值
-
- ;臂板垂直向下270度為初始態(tài)(壓縮狀態(tài),方便包裝和移動)在電氣驅(qū)動里面初始化。
- tsk0tv0:
- jb p2.1,tsk04 ;關(guān)機(jī)信號判斷
- clr key_catched
- call catch_a_key
- jnb key_catched,tsk0tv0 ;沒有獲取到鍵值
- clr bcd_ready
- call key_proc
- jnb bcd_ready,tsk0tv0 ;不涉及到bcd變化
-
- call bcd2print ;bcd 轉(zhuǎn)換為段碼
- call hide_zero ;消隱尾部的0
- mov r3,#0
- mov r4,#pool_print
- mov r5,#3
- ; mov lock_byte,#5ah ;加鎖
- call print ;輸出到LCD
- ; mov lock_byte,#11h
-
- jmp tsk0tv0
- ;;關(guān)機(jī)流程,回到270度位置
- tsk04:
- mov dptr,#tab_ht1621_off
- mov r3,#0
- mov r5,#3
- call Ht1621WrAllData;(0,Ht1621Tab,3);顯示off
- mov targ_moto_l,#0eh
- mov targ_moto_h,#01h ;電機(jī)目標(biāo)為270
- ;等待電機(jī)到點(diǎn)
- tsk040: mov a,P_moto_l
- cjne a,targ_moto_l,tsk040
- mov a,p_moto_h
- cjne a,targ_moto_h,tsk040
- jnb p2.1,tsk0tv0 ;最后確認(rèn)是否關(guān)機(jī)
- setb p2.2 ;關(guān)機(jī)
- ORL 87H,#02H ;INTO POWER-DOWN MODE
- LJMP SYS_START ;醒來的話就重新開機(jī)咯
- ;步驟:1、指針為0則無按鍵值,2、取鍵值,指針-1,(臨界區(qū));3處理鍵值;
- ;print_LCD: 函數(shù) 將3個字節(jié)的內(nèi)容顯示到LCD 0-f 都能顯示 步奏:1、字節(jié)數(shù)轉(zhuǎn)換到 段碼字節(jié) 2發(fā)送給ht1621
- ;如何循環(huán)顯示,三個字節(jié)要構(gòu)成單向環(huán),abcabcabc,始終顯示指針后3位數(shù)字,加入新的字節(jié)時(shí)指針往前推。
- ;將上面的三個字節(jié),轉(zhuǎn)為一個整數(shù)<=999,占2個字節(jié)。
- ;設(shè)為目標(biāo)值
- ;當(dāng)前值與目標(biāo)值比較,不等于則靠近,等于則關(guān)閉。
- ;關(guān)機(jī)線程
- ;正或反,步數(shù)n個。函數(shù)
- jmp task_0
- ;--------task0 end----------
- ;任務(wù)0子程序:從pool_key,p_key取一個鍵值,存放在key_value,并置位key_catched
- catch_a_key:
- ;clr key_catched
- mov a,p_key
- jnz cak00
- ret ;p_key 為0表示鍵值池空
- cak00:
- ;臨界區(qū):取一個鍵值
- mov lock_byte,#5ah
- mov a,#pool_key
- add a,p_key
- mov r0,a
- mov a,@r0
- dec p_key
- mov lock_byte,#11;臨界區(qū)
- mov key_value,a ;鍵值
- setb key_catched
- ret
- ;任務(wù)0子程序:處理當(dāng)前鍵值,小于10放到循環(huán)的bcd池里(pool_bcd,p_bcd),大于10則調(diào)用相關(guān)功能
- key_proc:
- mov a,key_value
- clr c
- subb a,#10
- jc kpc00
- mov a,key_value
- cjne a,#0ah,kpc01
- ;0a鍵功能
- call up_a_bit
- kpc01: cjne a,#0bh,kpc02
- ;0b鍵功能
- call down_a_bit
- kpc02: cjne a,#0ch,kpc03
- ;0c鍵功能
- call clr_bcd
- kpc03: cjne a,#0dh,kpc04
- ;0d鍵功能
- setb reset_BCD ;回車后前面的數(shù)據(jù)在下次按鍵輸入后,清掉
- call set_target
- ret
- kpc00: call keyv2bcd
- kpc04:
- ret
- ;;;;;;第二層子程序
- clr_bcd:
- mov r0,#pool_bcd
- mov @r0,#0
- inc r0
- mov @r0,#0
- inc r0
- mov @r0,#0
- setb bcd_ready
- ret
- set_target: ;將bcd里的3位數(shù)字轉(zhuǎn)換16位數(shù)字,并存入到targ_moto_L, targ_moto_H 中
-
- mov r1,p_bcd ;0-2范圍
- mov r3,#0
- mov r4,#0
- ;;個位數(shù)
- mov a,#pool_bcd
- add a,r1
-
- mov r0,a
- mov a,@r0 ; 取到bcd值 從個位數(shù)查起
- mov r4,a ;r3存H,r4存L 100* + 10* + L
- ;;;重復(fù)
- dec r1
- mov a,r1
- cjne a,#0ffh,ste00;
- mov r1,#2 ;過界處理
- ste00:
- mov a,#pool_bcd
- add a,r1
- mov r0,a
- mov a,@r0
- mov b,#10
- mul ab ;十位數(shù)
- clr c ;16位加法
- addc a,r4
- mov r4,a
- mov a,b
- addc a,r3
- mov r3,a
- ;;;重復(fù)以上
- dec r1
- mov a,r1
- cjne a,#0ffh,ste01;
- mov r1,#2 ;過界處理
- ste01:
- mov a,#pool_bcd
- add a,r1
- mov r0,a
- mov a,@r0
- mov b,#100
- mul ab ;百位數(shù)
- clr c ;16位加法
- addc a,r4
- mov r4,a
- mov a,b
- addc a,r3
- mov r3,a
- call targ_mod360
- mov lock_byte,#5ah
- mov targ_moto_L,r4
- mov targ_moto_H,r3
- mov lock_byte,#11h
- ret
- ;目標(biāo)值在r3,r4(HL),結(jié)果調(diào)整后還是在R3 R4
- targ_mod360: ;輸入的bcd值(0-999)轉(zhuǎn)換為0-360度范圍,與360取模:求余
- mov dph,r3 ;暫存
- mov dpl,r4
- mov a,r4 ;360d=0168h
- clr c
- subb a,#68h
- mov r4,a
- mov a,r3
- subb a,#01h
- mov r3,a
- jc tmd00;表示過頭了
- jmp targ_mod360
- tmd00:
- mov r3,dph
- mov r4,dpl
- ret
- up_a_bit: ;電機(jī)向上微調(diào)一個距離 ;臨界區(qū)處理,禁止其他控制電機(jī)的操作
- anl ready_byte,#10111111b ;休眠電機(jī)任務(wù)(task1)
- mov r3,#12 ;走12拍
-
- uab00:
- dec p_step
- mov a,p_step
- cpl a
- jnz uab03 ;過0則回到7
- mov p_step,#7
- uab03:
- mov a,p_step
- mov dptr,#tab_step
- MOVC A,@a+dptr
- anl P1,#11110000b ;驅(qū)動電機(jī)
- orl P1,a
- call waiting
-
- djnz r3,uab00
- anl p1,#11110000b ;關(guān)電機(jī)
- orl ready_byte,#01000000b ;喚醒電機(jī)任務(wù)
- ret
- down_a_bit: ;電機(jī)向下微調(diào)一個距離 ;臨界區(qū)處理,禁止其他控制電機(jī)的操作
- anl ready_byte,#10111111b ;休眠電機(jī)任務(wù)(task1)
- mov r3,#12 ;走12拍
- dab00:
- inc p_step
- mov a,p_step
- cjne a,#8,dab03 ;過7則回到0
- mov p_step,#0
- dab03:
- mov a,p_step
- mov dptr,#tab_step
- MOVC A,@a+dptr
- anl P1,#11110000b ;驅(qū)動電機(jī)
- orl P1,a
-
- call waiting
- djnz r3,dab00
- anl p1,#11110000b ;關(guān)電機(jī)
- orl ready_byte,#01000000b ;喚醒電機(jī)任務(wù)
- ret
- ;任務(wù)0子程序:鍵值key_value放到循環(huán)的bcd池里(pool_bcd,p_bcd),置位bcd_ready *****orig
- keyv2bcd:
- jnb reset_bcd,k2b10
- call clr_bcd
- clr reset_bcd
- k2b10: mov b,key_value ;鍵值
- ;存放在pool_bcd
- mov a,p_bcd ;容錯處理:p_bcd只能0-2,超范圍就置0
- clr c
- subb a,#3
- jnc k2b01
- ;先推指針
- inc p_bcd
- mov a,p_bcd
- cjne a,#3,k2b02 ;0-2循環(huán)處理
- k2b01: mov p_bcd,#0
- k2b02:
- mov a,#pool_bcd
- add a,p_bcd
- mov r0,a
- mov @r0,b ;再存鍵值
- setb bcd_ready
- ret
- ;任務(wù)0子程序:從循環(huán)的bcd池里取最近3個值(pool_bcd,p_bcd),查表轉(zhuǎn)換成段碼,存放到段碼數(shù)組(pool_print);
- bcd2print:
- mov r1,p_bcd ;0-2范圍
- mov r2,#3 ;依次取3個數(shù)
- b2p00: mov a,#pool_bcd
- add a,r1
- mov r0,a
- mov a,@r0 ; 取到bcd值然后 查表獲取段碼
- mov dptr,#tab_ht1621_seg
- movc a,@a+dptr
- mov r3,a
- mov a,r2
- dec a
- add a,#pool_print
- mov r0,a
- mov a,r3
- mov @r0,a
- dec r1
- mov a,r1
- cpl a
- jnz b2p01
- mov r1,#2
- b2p01:
- djnz r2,b2p00
- ret
- ;任務(wù)0子程序:將段碼數(shù)組pool_print的3個字節(jié)前面的0隱去
- hide_zero:
- mov r3,#2 ;2次,個位數(shù)不管
- mov r0,#pool_print
- hzo00:
- mov a,@r0
- cjne a,#5fh,hzo01 ; 5f 為0的段碼
- mov @r0,#0
- inc r0
- djnz r3,hzo00
- hzo01:
- ret
- ;任務(wù)0子程序:將段碼數(shù)組pool_print的3個字節(jié)輸出到ht1621
- print: ;(uchar Addr,uchar *p,uchar cnt) R3:ADDR r4:P R5:CNT
- CLR CS_1621;
- MOV A,#0A0H
- MOV R0,#3
- CALL Ht1621Wr_Data ;(0xa0,3); // - - 寫入數(shù)據(jù)標(biāo)志101
- MOV A,R3
- RLC A
- RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數(shù)據(jù)
- MOV R0,#6
- CALL Ht1621Wr_Data
-
- prt00:
- mov a,r4
- mov r0,a
- MOV A,@r0 ;取段碼
- MOV R0,#8
- CALL Ht1621Wr_Data ;Ht1621Wr_Data(*p,8); // - - 寫入數(shù)據(jù)
- INC r4
- DJNZ R5,prt00
- SETB CS_1621
- CALL delay_a_while
- RET
- ;任務(wù)0清零表
- TAB_HT1621:
- DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;
- TAB_HT1621_off:
- DB 033h,078h,078h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;
- TAB_HT1621_dou:
- DB 0b7h,0b3h,093h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;
- ;任務(wù)0段碼表,依據(jù)硬件線序確定
- tab_ht1621_seg:
- db 5fh; 0
- db 06h; 1
- db 3dh; 2
- db 2fh; 3
- db 66h; 4
- db 6bh; 5
- db 7bh; 6
- db 0eh; 7
- db 7fh; 8
- db 6fh; 9
- db 7eh; A
- db 73h; b
- db 31h; c
- db 37h; d
- db 79h; E
- db 78h; F
- db 33h; o
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;;;任務(wù)1:電機(jī)驅(qū)動,實(shí)際值逼近目標(biāo)值 ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;實(shí)際值:p_moto_L、p_moto_H、目標(biāo)值targ_moto_L、targ_moto_H 最大360度
- ;步驟:目標(biāo)值-實(shí)際值,記錄符號(正負(fù))到moto_dir, 結(jié)果等于0時(shí),關(guān)閉電機(jī),返回;結(jié)果小于180時(shí),moto_dir反置(尋找最短路徑)
- ;讓電機(jī)逼近一步,實(shí)際值+1或-1。返回。
- ;;------------------------------------------任務(wù)1
- task_1:
- mov p_moto_L,#0eh
- mov p_moto_H,#01h ; 0-359范圍
- mov targ_moto_L,#0
- mov targ_moto_H,#0 ; 0-999范圍 ;要mod360處理,變?yōu)?-359范圍
-
- ;臂板垂直向下270度為初始態(tài)(壓縮狀態(tài),方便包裝和移動)在電氣驅(qū)動里面初始化。
- tsk100:
- ;16位減法 目標(biāo)值-當(dāng)前值,默認(rèn)為+
- clr moto_dir ;默認(rèn)電機(jī)方向
- clr c
- mov a,targ_moto_L
- subb a,P_moto_L
- mov b,a
- mov a,targ_moto_H
- subb a,P_moto_H ;結(jié)果高位在a,低位在b
- jnc tsk105 ;結(jié)果為負(fù)數(shù)的話 被減數(shù)+360,再減
- clr c
- mov a,targ_moto_L
- addc a,#68h
- mov r0,a
- mov a,#01h
- addc a,targ_moto_h
- mov r1,a
- clr c ;重新算一次
- mov a,r0
- subb a,P_moto_L
- mov b,a
- mov a,r1
- subb a,P_moto_H ;結(jié)果高位在a,低位在b
- tsk105:
- mov r1,a ;H
- mov r0,b ;L 暫存結(jié)果(正偏差:0-359)
- jnz tsk104
- mov a,b
- jnz tsk104
- ;結(jié)果為0 關(guān)閉電機(jī) 并返回
- anl p1,#11110000b ;驅(qū)動電機(jī)
- orl ready_byte,#11100001b ;開啟其他任務(wù)
- jmp tsk100
- tsk104: ;偏差如果大于180,電機(jī)方向反向
- anl ready_byte,#11011111b ;暫停鍵盤線程
- clr c
- mov a,r0
- subb a,#180
- mov b,a
- mov a,r1
- subb a,#0 ;16位減去180
- jc tsk101 ;
- cpl moto_dir
- tsk101:
- call moto_move
- ;實(shí)際位置指針調(diào)整一位
- jb moto_dir,tsk102
- clr c
- mov a,p_moto_L
- addc a,#1
- mov p_moto_L,a
- clr a
- addc a,p_moto_H
- mov p_moto_H,a
- cjne a,#01h,tsk100 ;如果等于360則歸零
- mov a,p_moto_L
- cjne a,#68h,tsk100
- mov p_moto_L,#0
- mov p_moto_H,#0
- jmp tsk100
- tsk102:
- clr c
- mov a,p_moto_L
- subb a,#1
- mov p_moto_L,a
- mov a,p_moto_H
- subb a,#0
- mov p_moto_H,a
- cpl a ;如果等于ffff,則改為359
- jnz tsk100
- mov a,p_moto_L
- cpl a
- jnz tsk100
- mov p_moto_L,#67h
- mov p_moto_H,#01h
- jmp tsk100
- jmp task_1
- ;----------task1 end-------}}}}--
- ;任務(wù)1子程序:電機(jī)走一度,方向在moto_dir,
- ;涉及2張表:表1,45度折合512拍表,tab_deg,p_deg(0-44), 表2,8拍表tab_step,p_step(0-7)
- ;步驟:1根據(jù)方向調(diào)整度數(shù)指針,取一個度數(shù)拍數(shù) 2根據(jù)方向走N拍并更新拍數(shù)指針;
- moto_move:
- mov c,moto_dir ;防止過程中改變方向
- mov tmp_dir,c
- mmv00:
- jb tmp_dir,mmv01 ;正方向
- dec p_deg
- mov a,p_deg
- cpl a
- jnz mmv03 ;過0則回到44
- mov p_deg,#44
- mmv03:
- mov a,p_deg
- mov dptr,#tab_deg
- movc a,@a+dptr
- jmp mmv02
- mmv01:
- inc p_deg ;反方向
- mov a,p_deg
- cjne a,#45,mmv04 ;過44則回到0
- mov p_deg,#0
- mmv04:
- mov a,p_deg
- mov dptr,#tab_deg
- movc a,@a+dptr
- mmv02:
- mov r3,a
- call move_n_step
- ret
- move_n_step:;方向在tmp_dir,步數(shù)在r3, 表2,8拍表tab_step,p_step(0-7)
- mns00:
- jb tmp_dir,msn01 ;正方向
- dec p_step
- mov a,p_step
- cpl a
- jnz msn03 ;過0則回到7
- mov p_step,#7
- msn03:
- mov a,p_step
- mov dptr,#tab_step
- MOVC A,@a+dptr
- jmp msn02
- msn01:
- inc p_step ;反方向
- mov a,p_step
- cjne a,#8,msn04 ;過7則回到0
- mov p_step,#0
- msn04:
- mov a,p_step
- mov dptr,#tab_step
- MOVC A,@a+dptr
-
- msn02:
- anl p1,#11110000b ;驅(qū)動電機(jī)
- orl p1,a
- ;至此,電機(jī)走動了一拍,下面是延時(shí):需要2ms,采用不可重入的delay_sys_us完成
- ;CALL delay_a_step
- ;call waiting
- ;mov dptr,#10
- ;call delay_sys
-
- mov r0,#3
- call delay_sys_us
-
- djnz r3,mns00 ;走r3步數(shù)
- ret
- tab_step: ;步進(jìn)電機(jī)8拍表,循環(huán)使用
- DB 1001B,0001B,0011B,0010B,0110B,0100B,1100B,1000B
- tab_deg: ;45度折合512拍表,循環(huán)使用
- db 11,11,12,11,11,12,11,11,12,11,12,12,11,11,12
- db 11,11,12,11,11,12,11,11,12,11,11,12,11,11,12
- db 11,11,12,12,11,12,11,11,12,11,11,12,11,11,12
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; ;
- ;;;;;任務(wù)2:按鍵掃描 ;
- ;;標(biāo)準(zhǔn)的4*4按鍵掃描程序,鍵值為0-fh,設(shè)置3個字節(jié)的緩沖pool_key,設(shè)置一個緩沖指針p_key(0-3),當(dāng)緩沖區(qū)滿,丟棄新的按鍵
- task_2:
- ;初始化
- mov p_key,#0 ;0表示緩沖區(qū)空,3表示滿了,類似堆棧指針,注意定義時(shí)往前推一格
- scan_key:
- mov a,p_key
- cjne a,#3,tk301 ;緩沖區(qū)滿了
- jmp scan_key
- tk301:
- anl p0,#00001111b ;p0.7 p0.6 p0.5 p0.4 為豎線 從左到右
- mov a,p2 ;p2.4 p2.5 p2.6 p2.7 為橫線 從上到下
- orl a,#00001111b
- cpl a
- jz scan_key ;快速判斷,無任何按鍵時(shí)不要去挨個掃了,這樣響應(yīng)更快
- mov r3,#4 ;豎線循環(huán)4次
- mov a,#01111111b
- tk300:
- orl p0,#11110000b
- anl p0,a
- mov r2,a ;暫存
- jb p2.4,tk303
- mov r0,#0
- call take_keyv
- tk303:
- jb p2.5,tk304
- mov r0,#1
- call take_keyv
- tk304:
- jb p2.6,tk305
- mov r0,#2
- call take_keyv
- tk305:
- jb p2.7,tk302
- mov r0,#3
- call take_keyv
- tk302:
- mov a,r2
- rr a ;下一個豎線
- djnz r3,tk300
- jmp scan_key
-
- jmp task_2
- ;----------------------------task2 end---}}}}}-----
- ;
- tab_key16: ;二位數(shù)組的4*4鍵值表
- db 0ah,0bh,0ch,0dh
- db 03,06,09,0fh
- db 02,05,08,0
- db 01,04,07,0eh
- ;子程序:查表取鍵值,r3:行號,r0:列號
- take_keyv:
- mov a,r3 ;1-4 轉(zhuǎn)為 0-3
- dec a
-
- mov dptr,#tab_key16
- mov b,#4
- mul ab ;調(diào)整基地址
- add a,dpl
- mov dpl,a
- clr a
- addc a,dph ;進(jìn)位考慮
- mov dph,a
- mov a,r0
- movc a,@a+dptr ;查表取到對應(yīng)的鍵值 在b
- mov b,a
- ;存到緩沖池
- mov a,p_key
- cjne a,#3,tkv00 ;緩沖區(qū)滿了
- ret
- tkv00:
-
- mov lock_byte,#5ah ;;臨界區(qū),加鎖
- inc p_key
- mov a,p_key
- add a,#pool_key
- mov r1,a
- mov @r1,b ;存緩沖
- mov lock_byte,#11h ;;;;退臨界區(qū),解鎖
- mov a,#30
- add a,sys_time_l ;設(shè)置300ms時(shí)限
- mov r1,a
- tkv01: ;按鍵釋放時(shí)立即返回,連續(xù)按住時(shí)要間隔延時(shí)
- ;超時(shí)退出
- mov a,sys_time_l
- clr c
- subb a,r1
- clr c
- subb a,#5 ;時(shí)間模糊處理,只要接近目標(biāo)時(shí)間50ms以內(nèi),就算超時(shí),擔(dān)心有錯過時(shí)鐘刻度的考慮
- jc tkv02
- ;anl p0,#00001111b ;p0.7 p0.6 p0.5 p0.4 為豎線 改變p0可能會擾亂豎線掃描
- mov a,p2 ;p2.4 p2.5 p2.6 p2.7 為橫線 從上到下
- orl a,#00001111b
- cpl a
- jnz tkv01 ;判斷是否釋放(范圍為本行)
- tkv02:
- ret
- ;;--------------任務(wù)3-----次任務(wù),注意保護(hù)范圍:psw\a\b\r0-r3 以及最大嵌套2個call
- ;;-----------------------------------任務(wù)3
- ;;步進(jìn)電機(jī)每走一步的延時(shí)喚醒線程
- task_3:
- jmp task_3
- ;-----------------------------------------------task3 end--------------
- ;
- ;;-----------------------------------任務(wù)4
- task_4:
- jmp task_4
- ;-----------------------------------------------task5 end--------------
- ;
- ;;-----------------------------------任務(wù)5
- task_5:
- jmp task_5
- ;-----------------------------------------------task5 end--------------
- ;
- ;;-----------------------------------任務(wù)6
- task_6:
- jmp task_6
- ;-----------------------------------------------task6 end--------------
- ;
- ;
- ;--------------------------------------------------------------------------------------
- ;以下用戶子程序區(qū)
- ;;ht1621b driver RD WR DATA CS
- ;/********************************************************
- ;函數(shù)名稱:void Ht1621_Init(void)
- ;功能描述: HT1621初始化
- Ht1621_Init:
- SETB CS_1621;
- SETB WR_1621;
- SETB DATA_1621;
- MOV dptr,#5;
- CALL delay_sys; // - - 延時(shí)使LCD工作電壓穩(wěn)定
- MOV R1,#BIAS
- CALL Ht1621WrCmd;
- MOV R1,#RC256
- CALL Ht1621WrCmd; // - - 使用內(nèi)部振蕩器
- MOV R1,#SYSDIS
- CALL Ht1621WrCmd; // - - 關(guān)振系統(tǒng)蕩器和LCD偏壓發(fā)生器
- MOV R1,#WDTDIS
- CALL Ht1621WrCmd;; // - - 禁止看門狗
- MOV R1,#SYSEN; // - - 打開系統(tǒng)振蕩器
- CALL Ht1621WrCmd;
- MOV R1,#LCDON; // - - 打開聲音輸出
- CALL Ht1621WrCmd;
- ret
- ;**寫數(shù)據(jù)到ht1621,數(shù)據(jù)存A,發(fā)送位數(shù)存R0*****************************************************/
- Ht1621Wr_Data:;(uchar Data,uchar cnt) A:DATA R0:number of send-bit
- CLR WR_1621;
- CALL delay_a_while
- RLC A;
- MOV DATA_1621,C;
- CALL delay_a_while
- SETB WR_1621;
- CALL delay_a_while
- DJNZ R0,Ht1621Wr_Data
- ret
- ;****寫命令給HT1621****************************************************
- Ht1621WrCmd: ;(uchar Cmd) cmd byte store in R1
- CLR CS_1621
- CALL delay_a_while
- MOV A,#80H
- MOV R0,#4
- CALL Ht1621Wr_Data; // - - 寫入命令標(biāo)志1000
- MOV A,R1
- MOV R0,#8
- CALL Ht1621Wr_Data; // - - 寫入命令數(shù)據(jù)
- SETB CS_1621
- CALL delay_a_while
- RET
- ;*******************************************************
- ;函數(shù)名稱:void Ht1621WrOneData(uchar Addr,uchar Data)
- ;功能描述: HT1621在指定地址寫入數(shù)據(jù)函數(shù)
- ;全局變量:無
- ;參數(shù)說明:Addr為寫入初始地址,Data為寫入數(shù)據(jù)
- ;返回說明:無
- ;說 明:因?yàn)镠T1621的數(shù)據(jù)位4位,所以實(shí)際寫入數(shù)據(jù)為參數(shù)的后4位
- ;********************************************************/
- Ht1621WrOneData:;(uchar Addr,uchar Data) R2,R3
- CLR CS_1621;
- MOV A,#0A0H
- MOV R0,#3
- CALL Ht1621Wr_Data;(0xa0,3); // - - 寫入數(shù)據(jù)標(biāo)志101
- MOV A,R2
- RLC A
- RLC A
- MOV R0,#6
- CALL Ht1621Wr_Data;(Addr<<2,6); // - - 寫入地址數(shù)據(jù)
- MOV A,R3
- RLC A
- RLC A
- RLC A
- RLC A
- MOV R0,#4
- CALL Ht1621Wr_Data;(Data<<4,4); // - - 寫入數(shù)據(jù)
- SETB CS_1621
- CALL delay_a_while
- RET
- ;*********函數(shù)名稱:void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt)
- ;功能描述: HT1621連續(xù)寫入方式函數(shù)
- ;參數(shù)說明:Addr為寫入初始地址,*p為連續(xù)寫入數(shù)據(jù)指針,
- ;cnt為寫入數(shù)據(jù)總數(shù)
- ;返回說明:無
- ;說 明:HT1621的數(shù)據(jù)位4位,此處每次數(shù)據(jù)為8位,寫入數(shù)據(jù)
- ;總數(shù)按8位計(jì)算
- ;********************************************************/
- Ht1621WrAllData:;(uchar Addr,uchar *p,uchar cnt) R3:ADDR DPTR:P R5:CNT
- CLR CS_1621;
- MOV A,#0A0H
- MOV R0,#3
- CALL Ht1621Wr_Data ;(0xa0,3); // - - 寫入數(shù)據(jù)標(biāo)志101
- MOV A,R3
- RLC A
- RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數(shù)據(jù)
- MOV R0,#6
- CALL Ht1621Wr_Data
-
- hwd00:
- CLR A
- MOVC A,@A+DPTR
- MOV R0,#8
- CALL Ht1621Wr_Data ;Ht1621Wr_Data(*p,8); // - - 寫入數(shù)據(jù)
- INC DPTR
- DJNZ R5,hwd00
- SETB CS_1621
- CALL delay_a_while
- RET
- ;;;----------------
- delay_a_while:
- mov r3,#50
- daw00:
- nop
- djnz r3,daw00
- ret
- delay_a_step:
- mov r6,#0ffh
- dast00:
- nop
- nop
- nop
- djnz r6,dast00
- ret
- ;---------------------------------------以下用戶數(shù)據(jù)表區(qū)------------------
- ;用戶可以在此定義所需要的數(shù)據(jù)表
- end
- ;*******************************************the end**********************
復(fù)制代碼
|
評分
-
查看全部評分
|