找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 10206|回復(fù): 6
收起左側(cè)

分時(shí)操作系統(tǒng)的設(shè)計(jì)-51單片機(jī)實(shí)踐

  [復(fù)制鏈接]
ID:153041 發(fā)表于 2016-12-16 11:15 | 顯示全部樓層 |閱讀模式
本文僅討論筆者在實(shí)踐分時(shí)系統(tǒng)過程中的方法和思想,不拘泥于編程語言工具及其技巧,不用程序語言描述,全文采用敘事的方法,引出工程實(shí)踐過程中解決一些問題的過程和思路。
第一部分:  單CPU計(jì)算機(jī)運(yùn)行的模型
計(jì)算機(jī)最基本模型是圖靈模型(相關(guān)知識自行腦補(bǔ)),計(jì)算機(jī)將所有要處理的復(fù)雜任務(wù)分解到有限的基本的操作(指令),這個(gè)操作的集合就是指令集,指令集被設(shè)計(jì)固化到硬件(CPU或處理器)中。程序是為了解決特定問題而編制的一個(gè)指令序列,計(jì)算機(jī)的運(yùn)行就是一個(gè)在時(shí)間上串行的一個(gè)指令流。
如果有2個(gè)程序需要在一臺計(jì)算機(jī)上運(yùn)行,常見的場景是先運(yùn)行其中一個(gè)程序,運(yùn)行結(jié)束后,再運(yùn)行第二個(gè)程序。
第二部分:  單CPU計(jì)算機(jī)中多個(gè)程序并發(fā)執(zhí)行
多個(gè)程序并行的概念出現(xiàn)的很早,早期為了共享昂貴的計(jì)算機(jī)資源,人們試圖使一個(gè)計(jì)算機(jī)為多個(gè)用戶同時(shí)提供服務(wù)。多個(gè)程序并發(fā)執(zhí)行,采取分時(shí)的方法來實(shí)現(xiàn),稱分時(shí)系統(tǒng)。
分時(shí)即將時(shí)間視為資源進(jìn)行分配,將時(shí)間切分為人們感知上較小的一個(gè)單位,比如20毫秒(0.02秒),稱為一個(gè)時(shí)間片。若程序1和程序2都要被執(zhí)行,每個(gè)程序輪流被執(zhí)行一個(gè)時(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)行的。比如按下一個(gè)按鍵,程序并不是立即就知道有按鍵被按下,而是要等到程序指針運(yùn)行到按鍵處理程序時(shí)才會(huì)被發(fā)現(xiàn)。按鍵響應(yīng)的速度取決于程序指針本身的運(yùn)行速度和按鍵處理程序的長短及間隔距離,前者依賴于硬件主頻,后者依賴于程序設(shè)計(jì)的水平。這種依賴程序主動(dòng)去發(fā)現(xiàn)的信號輸入方式一般稱查詢方式,與查詢方式相對應(yīng)的是中斷方式。
中斷方式是設(shè)計(jì)在硬件級別的,假如按下一個(gè)按鍵采用中斷的方式處理,那么當(dāng)按鍵按下時(shí)觸發(fā)中斷,系統(tǒng)執(zhí)行完當(dāng)前指令后保存當(dāng)前的程序指針位置(返回時(shí)候用),然后將程序指針跳轉(zhuǎn)到指定的點(diǎn),程序設(shè)計(jì)時(shí)在這個(gè)點(diǎn)編寫好按鍵處理程序,程序結(jié)束后返回到被中斷的點(diǎn)繼續(xù)運(yùn)行。很顯然,中斷方式的響應(yīng)時(shí)間很小且容易估算,程序也不需要“定時(shí)的去查詢”。中斷機(jī)制依賴于硬件提供的中斷資源(可接受的中斷源數(shù)量),往往比較有限;其次當(dāng)多個(gè)中斷信號發(fā)生時(shí),要考慮優(yōu)先次序和中斷嵌套(中斷過程中再響應(yīng)中斷)的問題。
事實(shí)上,中斷的實(shí)現(xiàn)也是一種查詢方式,當(dāng)中斷信號發(fā)生時(shí),計(jì)算機(jī)并不是真正的立即響應(yīng),而是要等待當(dāng)前的指令被執(zhí)行完,硬件才會(huì)去查詢是否有中斷信號存在。中斷也可以理解為在微指令級別(一個(gè)指令由一段微指令組成)下進(jìn)行查詢處理的,其顆粒度是一條指令。
定時(shí)器是一種二進(jìn)制計(jì)數(shù)器,其硬件上原理非常簡單:用一些邊沿觸發(fā)器串接起來就能構(gòu)成一個(gè)二進(jìn)制計(jì)數(shù)器,只要輸入一個(gè)方波,就能使計(jì)數(shù)器的數(shù)值加一。當(dāng)計(jì)數(shù)器加到溢出時(shí),將溢出信號引出到中斷信號,就能起到定時(shí)的作用。我們通過給定時(shí)器設(shè)置一個(gè)初始值來設(shè)置定時(shí)器時(shí)間的長短。
比如在程序中要等待一秒鐘的延時(shí),如不用定時(shí)器,則必須讓程序指針在一個(gè)循環(huán)體中空轉(zhuǎn)N次,達(dá)到延時(shí)的作用。這種方式有兩個(gè)弊端:一是程序空轉(zhuǎn)浪費(fèi)計(jì)算機(jī)資源,在這個(gè)延時(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è)備,程序指針的跑動(dòng)和定時(shí)器內(nèi)計(jì)數(shù)值的增加是并行的。
言歸正傳,定時(shí)器和中斷是實(shí)現(xiàn)分時(shí)系統(tǒng)的基礎(chǔ),其中中斷機(jī)制尤為重要,70年代流行的PDP系列小型機(jī)上,分時(shí)系統(tǒng)的定時(shí)中斷是由一個(gè)從工頻電源上獲取的50-60Hz的定時(shí)中斷外設(shè)產(chǎn)生,稱之為電源時(shí)鐘,令人腦洞大開。現(xiàn)代操作系統(tǒng)的祖宗UNIX就是在這種機(jī)型上產(chǎn)生的一種分時(shí)操作系統(tǒng),UNIX最初的用戶是貝爾實(shí)驗(yàn)室的文員們,他們用它來處理專利文書,由于多個(gè)終端上可以同步進(jìn)行操作,又能方便進(jìn)行共享和調(diào)用,得到當(dāng)時(shí)女職員們的好評。
摘要:UNIX是用于DEC PDP-11及Interdata 8/32計(jì)算機(jī)的一個(gè)通用的交互作用的分時(shí)操作系統(tǒng)。從1971年開始以來,使用日趨廣泛。UNIX所論及的內(nèi)容有以下領(lǐng)域: 1.文件結(jié)構(gòu):一個(gè)統(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.一個(gè)高級語言的使用—收益與代價(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ì)角度,程序是一個(gè)單線順序的指令流(語句流),在程序里,指令(語句)一個(gè)接一個(gè)被執(zhí)行,從開始到結(jié)束,或條件轉(zhuǎn)移,或反復(fù)循環(huán)。程序員安排好這一切前后處理的次序。在應(yīng)用角度,用戶打開程序,運(yùn)行程序,直到結(jié)束退出,再打開另一個(gè)程序。在DOS時(shí)代,人們就是這么做的,那真是一個(gè)單純的時(shí)代。
在分時(shí)系統(tǒng)下,多個(gè)程序并行,程序員設(shè)計(jì)程序時(shí)可以設(shè)計(jì)多個(gè)線程,比如一個(gè)線程負(fù)責(zé)顯示,一個(gè)線程負(fù)責(zé)鍵盤輸入,一個(gè)線程負(fù)責(zé)數(shù)據(jù)處理。每一個(gè)線程都是單獨(dú)的程序,最終他們將同步運(yùn)行,程序員除了要考慮單個(gè)線程的運(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)給了多個(gè)芯片制造商。以ATMEL生產(chǎn)的AT89S52為例,它支持總計(jì)111條的51指令集,256字節(jié)RAM,8k字節(jié)程序閃存(可反復(fù)刷寫),3個(gè)16位計(jì)時(shí)器,5個(gè)中斷源,1個(gè)串口,4個(gè)8位IO口。要使她運(yùn)行起來非常的方便:只需在XTAL管腳之間加一個(gè)晶振和2個(gè)27pF電容(晶振的頻率決定運(yùn)行速度),在VCC腳加5V電源,然后把RESET管腳對地短路一下(復(fù)位),計(jì)算機(jī)便開始從程序閃存的0地址開始取指令執(zhí)行..
沒玩過51單片機(jī)的讀者一定好奇程序是怎么編制到芯片的閃存里面去的。單片機(jī)程序的開發(fā)主要依賴個(gè)人電腦,首先通過文本編輯器編寫源程序,然后通過51的編譯器程序編譯成目標(biāo)文件,最后通過芯片燒錄器將目標(biāo)文件復(fù)制(燒入)到51芯片內(nèi)。然后你的程序就可以在單片機(jī)跑動(dòng)了。
事實(shí)上,無論是偉福,還是uvsion,都提供了包括編輯器、編譯器、仿真和調(diào)試為一體的開發(fā)環(huán)境,程序在編制完成后,直接在開發(fā)環(huán)境里面仿真調(diào)試,經(jīng)過排錯(cuò)后,將問題降到最低再燒入到單片機(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í)間片的分配,也就是程序之間的切換問題。這個(gè)很簡單,可利用一個(gè)定時(shí)器產(chǎn)生一個(gè)固定的延時(shí),當(dāng)延時(shí)到達(dá),進(jìn)入定時(shí)器中斷,將這個(gè)中斷的入口跳轉(zhuǎn)到切換程序。注意:程序的切換不是簡單的跳轉(zhuǎn),而是要先保存當(dāng)前程序的執(zhí)行狀態(tài)、被中斷的地址,不至于下次回來繼續(xù)當(dāng)前程序時(shí)變量、狀態(tài)都被改變,那就會(huì)亂套了。然后再選擇下一個(gè)要運(yùn)行的程序,將被選中程序的狀態(tài)恢復(fù)到當(dāng)前的各狀態(tài)寄存器當(dāng)中,恢復(fù)到這個(gè)程序上次被打斷的點(diǎn)繼續(xù)運(yùn)行,等待下一個(gè)時(shí)間片用完,進(jìn)中斷,重復(fù)以上過程。
既然每個(gè)程序都要保存和恢復(fù)它的運(yùn)行狀態(tài),那么問題來了。一是要保存哪些信息,稱之為保護(hù)的范圍;二是需要多少內(nèi)存,能夠支撐多少個(gè)程序進(jìn)行分時(shí)。
第一個(gè)問題,保護(hù)范圍取決于用戶程序的斷點(diǎn)地址、狀態(tài)字、寄存器組,以及堆?臻g。第二個(gè)問題,支撐多少個(gè)程序進(jìn)行分時(shí)取決于內(nèi)存的大小,89C52的256字節(jié)支撐8個(gè)線程后,留給應(yīng)用程序的內(nèi)存只有36個(gè)字節(jié),因此80c51的128個(gè)字節(jié)RAM是不夠的。
在此,為了區(qū)別于系統(tǒng)程序,改稱以上所謂被分時(shí)的程序?yàn)槿蝿?wù)或線程。

第七部分:  內(nèi)存組織的方法
我們能設(shè)想到最原始的方法是通過數(shù)組(數(shù)據(jù)表)的方式,將每一個(gè)線程的保護(hù)內(nèi)容固定的保存起來。這種方法的缺點(diǎn)是不靈活,要增補(bǔ)被保護(hù)的內(nèi)容就要重新定義數(shù)組,改動(dòng)操作數(shù)據(jù)的代碼。當(dāng)然還有更值得推薦的方法:用堆棧來組織內(nèi)存。
堆棧是一種數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)如被壓入沖鋒步槍AK47彈匣的子彈,最先被壓進(jìn)去的子彈,最后才被彈出到槍膛。在計(jì)算機(jī)內(nèi)一般設(shè)有一個(gè)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ù)存儲。舉一個(gè)栗子:
主程序運(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)容全部壓入堆棧,最后僅需要保存好它的堆棧指針(就像把東西全扔箱子里,只需保管好鑰匙)。將下一個(gè)線程的堆棧指針取出來,從堆棧彈出所有要恢復(fù)的數(shù)據(jù),然后返回到新線程運(yùn)行下一個(gè)時(shí)間片。
第八部分:  關(guān)于線程調(diào)度
當(dāng)一個(gè)時(shí)間片用完,定時(shí)器發(fā)生中斷后,程序指針跳轉(zhuǎn)到線程切換程序,首先是保護(hù)現(xiàn)場。然后是選擇下一個(gè)線程。那么問題又來了,我們打算怎么來選擇下一個(gè)線程?這就是所謂的線程調(diào)度,操作系統(tǒng)的教課書里叫進(jìn)程調(diào)度。
最原始粗暴的方法就是順序循環(huán)調(diào)度,所有線程先來后到排個(gè)隊(duì),挨個(gè)輪著,實(shí)行平均主義的制度。在一些實(shí)時(shí)系統(tǒng)里面,比如μCOS,是按優(yōu)先級來確定調(diào)度權(quán)的:永遠(yuǎn)只選擇優(yōu)先級最高的線程,也就是說,如果線程一直處于最高優(yōu)先級,那么它就一直占著CPU不放,就不會(huì)被搶占。如果要讓線程立即得到執(zhí)行,就必須設(shè)置最高優(yōu)先級。這種方法能夠提供手段讓用戶設(shè)計(jì)的任務(wù)滿足實(shí)時(shí)的要求(所謂實(shí)時(shí),就是對響應(yīng)時(shí)間可評估,可控制,不是字面理解上的實(shí)時(shí)。
除了優(yōu)先級方式,還可以設(shè)定一種搶占的機(jī)制,比如在順序調(diào)度的過程中來一個(gè)半道插隊(duì)方式的搶占,也是一種可以滿足實(shí)時(shí)要求的方法。半路殺出個(gè)陳咬金,需要提前設(shè)置一個(gè)信號通知給調(diào)度程序,表示當(dāng)前有某個(gè)線程需要插隊(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)行一個(gè)單程序,那么CPU利用率是100%。如果這顆CPU要分時(shí)運(yùn)行兩個(gè)程序,那么每個(gè)程序?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)變差。一個(gè)線程的運(yùn)行延遲在分時(shí)系統(tǒng)里很難被準(zhǔn)確的估算,它與已就緒的線程數(shù)量、CPU主頻、時(shí)間片長短、中斷響應(yīng)等各種因素相關(guān),這些因素動(dòng)態(tài)影響著單個(gè)線程的運(yùn)行延遲。
分時(shí)系統(tǒng)中單個(gè)線程性能的一種簡單的估算方法:假如一個(gè)CPU工作在30MHz的主頻下,有3個(gè)已就緒的線程被平均調(diào)度,時(shí)間片相等,那么單個(gè)線程的運(yùn)行性能相當(dāng)于它獨(dú)占了一個(gè)10MHz不到的CPU的效果,相當(dāng)于這個(gè)CPU被拆分成了3個(gè)10MHz不到的CPU在同步運(yùn)行。由于現(xiàn)場就緒與否是動(dòng)態(tài)變化的,因此很難完全準(zhǔn)確估算。
總而言之,分時(shí)盡管耗費(fèi)了一些CPU時(shí)間,但相對它所帶來驚奇效果和并行思維給程序設(shè)計(jì)帶來更多的可能性而言,微不足道。分時(shí)就像變魔術(shù)一樣,能把一顆CPU拆分成N個(gè)弱小的CPU同步運(yùn)行不同的程序。從宏觀上,分時(shí)能夠充分挖掘CPU資源,當(dāng)一個(gè)線程需要等待或有目的的延時(shí),完全可以把CPU時(shí)間讓給其他線程,而不是白白的空等。

第十部分:  線程的休眠和就緒
當(dāng)一個(gè)線程需要等待或延時(shí),或者索性暫停掉,可以通過狀態(tài)標(biāo)記,使調(diào)度程序跳過對它的調(diào)度,那么這個(gè)線程就是被休眠了,或稱該線程處于休眠態(tài)。反過來,如果線程等著被調(diào)度,稱該線程處于就緒態(tài)。這個(gè)功能在調(diào)度程序內(nèi)做一個(gè)判斷不難實(shí)現(xiàn)。
如果要使一個(gè)線程休眠,系統(tǒng)可以提供一個(gè)函數(shù)(子程序)來修改線程的休眠標(biāo)志(變量)。任何線程,包括線程自己都可以進(jìn)行休眠操作。所謂想睡就睡,但不是想醒就醒,喚醒一定是其他線程或時(shí)鐘服務(wù)來幫助喚醒你。
除了休眠,還可以設(shè)置一個(gè)殺死的操作,所謂殺死線程,就是讓線程的狀態(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從該變量讀到的信息就是一個(gè)錯(cuò)誤的信息。
先來分析這個(gè)問題:
中斷是在指令和指令之間響應(yīng)的,也就是其最小顆粒度是單條指令,如果寫變量是一條指令搞定的,由于指令在執(zhí)行過程中不會(huì)被中斷,所以不會(huì)發(fā)生以上情況。單條指令的操作也稱為原子操作。
可惜的是,一條指令至多可寫一個(gè)字。對于沒有接觸過匯編的程序員來說,一條高級語言的語句很容易被認(rèn)為是一個(gè)原子操作,其實(shí)不然,經(jīng)過編譯后,一條高級語言的語句往往都由若干條指令組成。
思考解決問題:
要解決寫變量時(shí)候不被破壞的問題,就要排斥其他線程對這塊變量的操作,或防止在寫的過程中被中斷。方法有幾種:
最簡單粗暴的方法是開始寫共享變量之前直接把中斷給關(guān)了,寫完以后再開。還有更流氓的做法,就是在寫共享變量之前直接讓計(jì)時(shí)器停擺,定住,結(jié)束后再繼續(xù)往前計(jì)數(shù)。方法雖然簡單,但是這么做會(huì)讓整個(gè)系統(tǒng)短暫停滯,明顯有副作用。
推薦的方法是讓調(diào)度程序帶上鎖功能,如果處于上鎖狀態(tài),調(diào)度程序不執(zhí)行切換。線程在寫共享變量之前先加鎖,結(jié)束后再解鎖。在加鎖期間,雖然中斷和計(jì)時(shí)都不受影響,但是其他所有線程都被禁止了,還是有些缺憾。
更好的辦法是采用信號量,相當(dāng)于給共享變量加一個(gè)紅綠燈。設(shè)置紅綠燈的操作必須是原子操作,線程在操作共享變量時(shí)先查一下是不是綠燈,如果是綠燈就把它變?yōu)榧t燈,然后操作變量,結(jié)束后再設(shè)為綠燈;如果是紅燈就排隊(duì)等待,直到綠燈來了再進(jìn)行操作。
名詞摘要:在教材中,把對需要互斥的共享變量和設(shè)備進(jìn)行獨(dú)占操作,叫臨界區(qū)操作,結(jié)束后要退出臨界區(qū)。
第十二部分:    掛鐘和鬧鈴叫醒服務(wù)
線程在運(yùn)行過程中經(jīng)常會(huì)用到等待相對精確的延時(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)入一個(gè)循環(huán)等待某個(gè)信號,當(dāng)時(shí)間超過5秒鐘判斷為超時(shí),退出循環(huán)。
以下是一種設(shè)計(jì)方案:
設(shè)計(jì)一個(gè)系統(tǒng)掛鐘,讓他一直以單位時(shí)間往前走。好比在墻上掛一個(gè)鐘頭,讓所有線程都能看時(shí)間。為每個(gè)線程提供一個(gè)鬧鈴指針和喚醒服務(wù)標(biāo)志,如果某個(gè)線程需要喚醒服務(wù),就把鬧鈴指針撥到目標(biāo)時(shí)間,然后開啟喚醒服務(wù)。系統(tǒng)掛鐘每走一個(gè)刻度都要判斷當(dāng)前時(shí)間和需要喚醒服務(wù)線程的鬧鈴時(shí)間是否一致,一致的話就喚醒這個(gè)線程。
當(dāng)某個(gè)線程需要就地等待1分鐘就可以這樣做:線程先看一下當(dāng)前的時(shí)間,然后把時(shí)間往前推1分鐘設(shè)置一個(gè)鬧鈴,然后告訴系統(tǒng)時(shí)鐘:鬧鈴到了記得叫醒我!接著線程就自我休眠了,等著1分鐘后系統(tǒng)時(shí)鐘的喚醒并繼續(xù)運(yùn)行。
實(shí)踐的栗子:啟用硬件另一個(gè)計(jì)時(shí)器,每10ms發(fā)生一次中斷。系統(tǒng)中設(shè)置一個(gè)16位的字,每過10ms將這個(gè)字加一,這個(gè)16位字相當(dāng)于一個(gè)量程為65536的掛鐘。設(shè)置一個(gè)數(shù)組用于存儲每個(gè)線程的鬧鈴時(shí)間,設(shè)置一個(gè)狀態(tài)字,用于標(biāo)志線程的喚醒服務(wù)。當(dāng)時(shí)鐘往前走一個(gè)刻度時(shí),同時(shí)進(jìn)行一個(gè)喚醒的判斷,將當(dāng)前時(shí)間和鬧鈴時(shí)間一致的線程喚醒。
時(shí)鐘的最小刻度設(shè)定為10ms,也就意味著延時(shí)設(shè)定的最小單位是10ms,那么問題又來了,若需要延時(shí)1ms或更小的時(shí)間刻度該怎么解決?
如果把時(shí)鐘刻度直接調(diào)至1ms,會(huì)導(dǎo)致時(shí)鐘中斷次數(shù)增加了10倍,時(shí)鐘和鬧鈴的程序段的執(zhí)行次數(shù)也就增加了10倍,這嚴(yán)重消耗了CPU時(shí)間,降低了整個(gè)系統(tǒng)的性能。如何解決這個(gè)問題留給讀者去思考。
第十三部分:    公用的函數(shù)(或子程序)
如果一個(gè)函數(shù)僅僅使用堆棧和局部變量,那么這個(gè)函數(shù)就能天然的被多個(gè)線程同時(shí)調(diào)用。因?yàn)槎褩:途植孔兞浚ɑ駽PU寄存器)都是受保護(hù)的,假使2個(gè)線程同時(shí)調(diào)用一個(gè)函數(shù),在線程切換的情況下,雖然都是運(yùn)行在相同的函數(shù)代碼段,但各自線程里的局部變量和堆棧內(nèi)容不同,所以運(yùn)行結(jié)果也是各自分開的。這個(gè)函數(shù)就像使了分身之術(shù)一樣,在不同的線程里同時(shí)運(yùn)行著。這種函數(shù)也叫作可重入函數(shù)。
如果一個(gè)函數(shù)使用了全局變量,那么假使2個(gè)線程同時(shí)調(diào)用了它,那么在線程A里面所定義的全局變量和線程B里面所定義的全局變量指向同一個(gè)位置,就會(huì)發(fā)生問題。這種不能被多個(gè)線程同時(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ū)動(dòng)等內(nèi)容。硬件往往基于個(gè)人計(jì)算機(jī)(intel的架構(gòu))來描述,頭緒非常多,讓學(xué)習(xí)者覺得課程就像是空中閣樓,可遠(yuǎn)觀而不可觸碰。
用單片機(jī)做實(shí)踐是最能摸到計(jì)算機(jī)本質(zhì)的路徑,試想,在一臺裸機(jī)上讓你的程序最接近硬件的跑動(dòng)起來,是多么踏實(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)指令中包含了具體的地址),如果地址和指令錯(cuò)位,那么程序跳轉(zhuǎn)將找不到北。要解決這個(gè)問題,CPU的設(shè)計(jì)者從早期的內(nèi)存分段管理,發(fā)展到現(xiàn)在的虛擬地址空間轉(zhuǎn)換,可謂費(fèi)盡心思。隨著程序規(guī)模越做越大,當(dāng)單個(gè)程序的長度超出內(nèi)存的總?cè)萘繒r(shí),問題又來了。為了解決這個(gè)問題,又引入了分頁和虛擬內(nèi)存的方法,分頁允許程序不用一次性全部調(diào)入內(nèi)存,而是將要被執(zhí)行部分的頁面調(diào)入內(nèi)存,剩余的還是存在硬盤里,當(dāng)程序執(zhí)行的指令已經(jīng)不在內(nèi)存時(shí),CPU將會(huì)發(fā)生一個(gè)缺頁中斷,將硬盤中的目標(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)行分成多個(gè)狀態(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ù)量越多,單個(gè)線程的性能就越低(可視為CPU的性能被線程瓜分了,線程越多,單個(gè)線程瓜分到的性能就少)。為了提升計(jì)算機(jī)性能,除了設(shè)置流水線、多級緩存等方式外,單個(gè)CPU主要靠提升主頻來實(shí)現(xiàn)性能的數(shù)量級提升。除此之外,引入多個(gè)CPU并行工作,通過增加系統(tǒng)的并行度也是提高性能的主要手段。多個(gè)CPU使得線程從物理上真正的并行了,一個(gè)多核的CPU在同一時(shí)刻可以同時(shí)運(yùn)行多個(gè)線程,多CPU在硬件組織上又是一件費(fèi)盡心機(jī)的活(不要簡單的理解成多插幾個(gè)CPU就完事那么簡單)。超級計(jì)算機(jī)(用于氣象、原子能、航天等領(lǐng)域的測算)往往以集群方式組織起來的高度并行的結(jié)構(gòu),這也要求軟件設(shè)計(jì)必須要被分割為高度的并行化才能發(fā)揮超級計(jì)算機(jī)的性能。如果一個(gè)問題的算法必須至始至終前后串行,無法被分割成多個(gè)同步運(yùn)行的部分,那么這種程序放在超級計(jì)算機(jī)上運(yùn)行,縱使這臺超算有千萬個(gè)CPU,程序也只能在其中一個(gè)CPU上跑完,剩下的CPU等于閑著沒事干。同樣的道理,多核CPU體現(xiàn)的也是并行性能,只有在線程數(shù)量多的分時(shí)系統(tǒng)內(nèi)才能體現(xiàn)其性能。如果用一個(gè)8核的CPU僅僅只運(yùn)行一個(gè)線程,那么剩下7個(gè)核心就閑著沒事做。
備注:本文一直采用線程來描述并發(fā)執(zhí)行的程序,其實(shí)線程是專有名詞,有更清晰的概念。操作系統(tǒng)中還有進(jìn)程的概念,進(jìn)程也是并發(fā)的程序,一個(gè)進(jìn)程還能再分多個(gè)線程。在實(shí)時(shí)操作系統(tǒng)中,線程又被稱為任務(wù)。
寫在最后
通過以上敘述,我想讀者一定對分時(shí)、多線程、并發(fā)執(zhí)行有了概念,同時(shí)也一定留下了一堆的疑問和不解,這可是好現(xiàn)象,進(jìn)步都是在疑問和不解中開始的。

一個(gè)應(yīng)用案例-360度指示器.rar

19.36 KB, 下載次數(shù): 71, 下載積分: 黑幣 -5

評分

參與人數(shù) 4黑幣 +148 收起 理由
lemaden520 + 30 共享資料的黑幣獎(jiǎng)勵(lì)!
43037384 + 10 贊一個(gè)!
ssfc + 8 贊一個(gè)!
admin + 100 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評分

回復(fù)

使用道具 舉報(bào)

ID:1 發(fā)表于 2016-12-23 03:11 | 顯示全部樓層
豆豆的打開關(guān)上
需求:假設(shè)有一個(gè)指針,可以作圓周運(yùn)動(dòng),通過在鍵盤上輸入具體的角度,使指針指向該角度。
輸入:數(shù)字鍵盤,按3位數(shù)字后回車生效。范圍0-999,表示圓周角度,對360度取模。
顯示:3位段式液晶數(shù)碼顯示。
執(zhí)行:減速步進(jìn)電機(jī),按輸入的角度作圓周運(yùn)動(dòng)。
結(jié)構(gòu):木結(jié)構(gòu)為主,在電機(jī)上安裝臂桿,做成類停車桿的結(jié)構(gòu)?360度自由轉(zhuǎn)動(dòng)。

技術(shù)上要考慮的問題:
1、液晶驅(qū)動(dòng)的問題,擬采用HT1621芯片驅(qū)動(dòng);
2、歸零問題,擬采用12V電池+5V模塊的供電方式,設(shè)置斷電繼電器和斷電歸零程序;
3、轉(zhuǎn)動(dòng)角度問題,單步判斷逼近方法,or距離計(jì)算一次到位方法;擬采用前者;

操作模型:
1、打開開關(guān),液晶顯示---;臂桿角度為0;(開關(guān)處并聯(lián)的繼電器吸合,開關(guān)的觸點(diǎn)留一組去cpu)
2、輸入0-360之間的一個(gè)整數(shù)表示圓周的絕對角度,每輸入一個(gè)數(shù)字,顯示到液晶屏;其他輸入作無效處理;
3、回車后,液晶屏內(nèi)的數(shù)字被作為目標(biāo)值,執(zhí)行電機(jī)轉(zhuǎn)到該角度;
4、轉(zhuǎn)動(dòng)過程中,可以按暫停鍵,暫;蚶^續(xù);
5、轉(zhuǎn)動(dòng)過程中,可以繼續(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ū)動(dòng)試驗(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啟動(dòng)執(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)
[一個(gè)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)
  1. // - - * * * * * * * HT1621 段碼屏驅(qū)動(dòng) * * * * * * *
  2. // - - * MCU: AT89C51 單片機(jī) *
  3. // - - * MCU-Crystal: 12M 24M 晶振 *
  4. // - - * Version: 00 版本 *
  5. // - - * Last Updata: *
  6. // - - * Author: www.ndiy.cn *
  7. // - - * Description: HT1621 段碼屏驅(qū)動(dòng) *
  8. // - - * C 語言程序 *
  9. // - - * * * * * * * * * * * * * * * * * * * * * * *


  10. /********************************************************
  11. 功能描述: HT1621芯片測試程序

  12. 說 明:本測試程序能夠測試HT1621的每一個(gè)字段,

  13. 依次點(diǎn)亮每一個(gè)字段

  14. 參數(shù):
  15. ********************************************************/

  16. // - - 引用庫
  17. #include <REGX51.H>

  18. // - - 定義函數(shù)
  19. #define uchar unsigned char
  20. #define uint unsigned int

  21. #define BIAS 0x52 //0b1000 0101 0010 1/3duty 4com
  22. #define SYSDIS 0X00 //0b1000 0000 0000 關(guān)振系統(tǒng)蕩器和LCD偏壓發(fā)生器
  23. #define SYSEN 0X02 //0b1000 0000 0010 打開系統(tǒng)振蕩器
  24. #define LCDOFF 0X04 //0b1000 0000 0100 關(guān)LCD偏壓
  25. #define LCDON 0X06 //0b1000 0000 0110 打開LCD偏壓
  26. #define XTAL 0x28 //0b1000 0010 1000 外部接時(shí)鐘
  27. #define RC256 0X30 //0b1000 0011 0000 內(nèi)部時(shí)鐘
  28. #define TONEON 0X12 //0b1000 0001 0010 打開聲音輸出
  29. #define TONEOFF 0X10 //0b1000 0001 0000 關(guān)閉聲音輸出
  30. #define WDTDIS 0X0A //0b1000 0000 1010 禁止看門狗

  31. // - - 定義接口
  32. // - - HT1621控制位(液晶模塊接口定義,根據(jù)自已的需要更改)
  33. sbit HT1621_CS=P3^2; // - - HT1621使能引腳
  34. sbit HT1621_WR=P3^3; // - - HT1621時(shí)鐘引腳
  35. sbit HT1621_DAT=P3^4; // - - HT1621數(shù)據(jù)引腳

  36. // - - 定義變量,數(shù)組
  37. uchar code
  38. Ht1621Tab[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

  39. 0x00,0x00,0x00,0x00,0x00};

  40. // - - 定義子程序函數(shù)
  41. void Ht1621_Init(void); // - - 初始化 HT1621
  42. void Ht1621Wr_Data(uchar Data,uchar cnt); // - - HT1621 寫入數(shù)據(jù)函數(shù)
  43. void Ht1621WrCmd(uchar Cmd); // - - HT1621 寫入命令函數(shù)
  44. void Ht1621WrOneData(uchar Addr,uchar Data); // - - HT1621 在指定地址寫入數(shù)據(jù)函數(shù)
  45. void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt); // - - HT1621 連續(xù)寫入數(shù)據(jù)函數(shù)

  46. void Ht1621_delay_10us(uint n); // - - 10微秒的延時(shí)子程序
  47. void Ht1621_delayms(uint xms); // - - 1毫秒延時(shí)程序

  48. /********************************************************
  49. 函數(shù)名稱:void Ht1621_Init(void)
  50. 功能描述: HT1621初始化
  51. 全局變量:無
  52. 參數(shù)說明:無
  53. 返回說明:無
  54. 版 本:1.0
  55. 說 明:初始化后,液晶屏所有字段均顯示
  56. ********************************************************/
  57. void Ht1621_Init(void)
  58. {
  59. HT1621_CS=1;
  60. HT1621_WR=1;
  61. HT1621_DAT=1;
  62. Ht1621_delayms(500); // - - 延時(shí)使LCD工作電壓穩(wěn)定
  63. Ht1621WrCmd(BIAS);
  64. Ht1621WrCmd(RC256); // - - 使用內(nèi)部振蕩器
  65. Ht1621WrCmd(SYSDIS); // - - 關(guān)振系統(tǒng)蕩器和LCD偏壓發(fā)生器
  66. Ht1621WrCmd(WDTDIS); // - - 禁止看門狗
  67. Ht1621WrCmd(SYSEN); // - - 打開系統(tǒng)振蕩器
  68. Ht1621WrCmd(LCDON); // - - 打開聲音輸出
  69. }

  70. /******************************************************
  71. 函數(shù)名稱:void Ht1621Wr_Data(uchar Data,uchar cnt)
  72. 功能描述: HT1621數(shù)據(jù)寫入函數(shù)
  73. 全局變量:無
  74. 參數(shù)說明:Data為數(shù)據(jù),cnt為數(shù)據(jù)位數(shù)
  75. 返回說明:無
  76. 說 明:寫數(shù)據(jù)函數(shù),cnt為傳送數(shù)據(jù)位數(shù),數(shù)據(jù)傳送為低位在前
  77. *******************************************************/
  78. void Ht1621Wr_Data(uchar Data,uchar cnt)
  79. {
  80. uchar i;
  81. for (i=0;i<cnt;i++)
  82. {
  83. HT1621_WR=0;
  84. Ht1621_delay_10us(1);
  85. HT1621_DAT=Data&0x80;
  86. Ht1621_delay_10us(1);
  87. HT1621_WR=1;
  88. Ht1621_delay_10us(1);
  89. Data<<=1;
  90. }
  91. }

  92. /********************************************************
  93. 函數(shù)名稱:void Ht1621WrCmd(uchar Cmd)
  94. 功能描述: HT1621命令寫入函數(shù)
  95. 全局變量:無
  96. 參數(shù)說明:Cmd為寫入命令數(shù)據(jù)
  97. 返回說明:無
  98. 說 明:寫入命令標(biāo)識位100
  99. ********************************************************/
  100. void Ht1621WrCmd(uchar Cmd)
  101. {
  102. HT1621_CS=0;
  103. Ht1621_delay_10us(1);
  104. Ht1621Wr_Data(0x80,4); // - - 寫入命令標(biāo)志100
  105. Ht1621Wr_Data(Cmd,8); // - - 寫入命令數(shù)據(jù)
  106. HT1621_CS=1;
  107. Ht1621_delay_10us(1);
  108. }

  109. /********************************************************
  110. 函數(shù)名稱:void Ht1621WrOneData(uchar Addr,uchar Data)
  111. 功能描述: HT1621在指定地址寫入數(shù)據(jù)函數(shù)
  112. 全局變量:無
  113. 參數(shù)說明:Addr為寫入初始地址,Data為寫入數(shù)據(jù)
  114. 返回說明:無
  115. 說 明:因?yàn)镠T1621的數(shù)據(jù)位4位,所以實(shí)際寫入數(shù)據(jù)為參數(shù)的后4位
  116. ********************************************************/
  117. void Ht1621WrOneData(uchar Addr,uchar Data)
  118. {
  119. HT1621_CS=0;
  120. Ht1621Wr_Data(0xa0,3); // - - 寫入數(shù)據(jù)標(biāo)志101
  121. Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數(shù)據(jù)
  122. Ht1621Wr_Data(Data<<4,4); // - - 寫入數(shù)據(jù)
  123. HT1621_CS=1;
  124. Ht1621_delay_10us(1);
  125. }

  126. /********************************************************
  127. 函數(shù)名稱:void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt)
  128. 功能描述: HT1621連續(xù)寫入方式函數(shù)
  129. 全局變量:無
  130. 參數(shù)說明:Addr為寫入初始地址,*p為連續(xù)寫入數(shù)據(jù)指針,
  131. cnt為寫入數(shù)據(jù)總數(shù)
  132. 返回說明:無
  133. 說 明:HT1621的數(shù)據(jù)位4位,此處每次數(shù)據(jù)為8位,寫入數(shù)據(jù)
  134. 總數(shù)按8位計(jì)算
  135. ********************************************************/
  136. void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt)
  137. {
  138. uchar i;
  139. HT1621_CS=0;
  140. Ht1621Wr_Data(0xa0,3); // - - 寫入數(shù)據(jù)標(biāo)志101
  141. Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數(shù)據(jù)
  142. for (i=0;i<cnt;i++)
  143. {
  144. Ht1621Wr_Data(*p,8); // - - 寫入數(shù)據(jù)
  145. p++;
  146. }
  147. HT1621_CS=1;
  148. Ht1621_delay_10us(1);
  149. }

  150. void Ht1621_delay_10us(uint n) // - - 10微秒的延時(shí)子程序
  151. {
  152. uint i,j;
  153. for(i=n;i>0;i--)
  154. for(j=2;j>0;j--);
  155. }

  156. void Ht1621_delayms(uint xms) // - - 1毫秒延時(shí)程序
  157. {
  158. uint i,j;
  159. for(i = xms;i > 0;i --)
  160. for(j = 110;j > 0;j --);
  161. }


  162. /***********************************
  163. HT1621 測試程序,作為庫文件請注釋掉
  164. ***********************************/
  165. void main()
  166. {
  167. uchar i,j,t;
  168. Ht1621_Init(); // - - 上電初始化LCD
  169. Ht1621_delayms(5000); // - - 延時(shí)一段時(shí)間
  170. while(1){
  171. Ht1621_delayms(2000);
  172. Ht1621WrAllData(0,Ht1621Tab,16); // - - 清除1621寄存器數(shù)據(jù),暨清屏
  173. for (i=0;i<32;i++)
  174. {
  175. t=0x01;
  176. for (j=0;j<4;j++)
  177. {
  178. Ht1621WrOneData(i,t);
  179. t<<=1;
  180. t++;
  181. P1_5=~P1_5; // - - 循環(huán)點(diǎn)亮LED
  182. Ht1621_delayms(500);
  183. }
  184. }
  185. }
  186. }
復(fù)制代碼




回復(fù)

使用道具 舉報(bào)

ID:1 發(fā)表于 2016-12-23 03:13 | 顯示全部樓層
;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個(gè)線程:TASK0到TASK2為3個(gè)主線程,其余為次線程;主線程對9個(gè)寄存器和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ù)位信號?撮T狗功能可以在配置定義中取消。
;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)用同一個(gè)子程序,注意子程序內(nèi)受保護(hù)的范圍。
;2、主任務(wù)擁有獨(dú)立的R0-R7、ACC、B、PSW寄存器、DPTR指針。次任務(wù)僅保護(hù)7個(gè)寄存器。
;3、任務(wù)之間可以通過內(nèi)存變量來傳遞信息,注意在寫內(nèi)存時(shí)必須占用系統(tǒng),寫完后再釋放系統(tǒng),建議使用加鎖和解鎖調(diào)用。
;4、系統(tǒng)初始化后所有任務(wù)都是睡眠的,系統(tǒng)會(huì)喚醒任務(wù)0和任務(wù)7,其他任務(wù)的喚醒由用戶操作。任務(wù)7為伺服任務(wù),不建議休眠它或在該任務(wù)中使用系統(tǒng)延時(shí)調(diào)用。(有一種風(fēng)險(xiǎn):所有任務(wù)處于休眠態(tài),會(huì)進(jìn)入待機(jī))
;5、系統(tǒng)的性能與晶振頻率、喚醒的任務(wù)數(shù)量、任務(wù)占用的時(shí)間片有關(guān)系。
;6、任務(wù)有權(quán)殺死或休眠任何任務(wù),如果系統(tǒng)所有任務(wù)都被殺死或休眠,系統(tǒng)會(huì)進(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這兩個(gè)定時(shí)器,需要時(shí),可以使用TIMER1. 建議不要設(shè)置為高優(yōu)先級,可能導(dǎo)致系統(tǒng)時(shí)間停走。

;注:殺死和休眠的區(qū)別:任務(wù)被殺死后再次喚醒從頭開始運(yùn)行,任務(wù)被休眠后再次喚醒是從原來休眠的地方繼續(xù)運(yùn)行(就像暫停)。

;用戶使用注意:
;1.總計(jì)8個(gè)任務(wù),單個(gè)線程是死循環(huán),所有線程并發(fā)執(zhí)行,可以有限調(diào)整每個(gè)線程的時(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級。注意這個(gè)限制條件。嵌套調(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個(gè)主線程,每個(gè)線程分配29字節(jié),增加對DPTR的保護(hù),;增加對PSW的保護(hù)
;可以最大嵌套29-2(PC)-2(DPTR)-10(AB,RG)-1(PSW)=7個(gè)CALL;閹割剩余5個(gè)次線程,每個(gè)線程分配13字節(jié):AB,R0-R3,PC,PSW,最大嵌套2個(gè)CALL。
;總體256字節(jié)的內(nèi)存:3個(gè)主線程:29*3=87;5個(gè)次線程:13*5=65;8個(gè)線程狀態(tài)(優(yōu)先級、SP、鬧鈴H、鬧鈴L)=32;
;系統(tǒng)變量:10;系統(tǒng)堆棧:14;R0-R7:8個(gè)除去。 剩余用戶可用的內(nèi)存區(qū):40字節(jié)
;真可謂:螺螄殼里做道場。  

;20161110
;備忘:調(diào)度程序要增加加鎖功能(不切換,好處是總是能進(jìn)系統(tǒng)區(qū)做一些系統(tǒng)要做的事,比如喂狗)
;延時(shí)誤差太大,最大誤差是一個(gè)單位(不累計(jì)),考慮系統(tǒng)(放在時(shí)鐘程序內(nèi))來負(fù)責(zé)高精度大跨度的計(jì)時(shí),和喚醒服務(wù),需要額外16字節(jié)用于8個(gè)線程的鬧鐘記錄。
;看門狗使用指南:時(shí)間片調(diào)節(jié)的太大就會(huì)觸發(fā)看門狗,應(yīng)能根據(jù)需要關(guān)閉看門狗。13位,每一個(gè)機(jī)器周期+1
;時(shí)鐘要方便配置
;注意中斷嵌套的影響 ;注意測量 系統(tǒng)服務(wù)的時(shí)間,及其與中斷時(shí)間的比重,比重和效率成正比 ;中斷響應(yīng)前后次序的關(guān)系分析,用戶怎么用中斷
;喚醒服務(wù)要注意的是:必須留一個(gè)伺服線程,該線程始終保持就緒(不能使用系統(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個(gè)任務(wù)的總數(shù),但集中資源到3個(gè)主任務(wù)上,增加對psw、dptr寄存器的保護(hù)(原來沒考慮周全,如psw是必須保護(hù)的)。使主任務(wù)不再有束縛。
;3、取消原有的延時(shí)服務(wù),增加系統(tǒng)時(shí)鐘的定時(shí)喚醒服務(wù)功能,每個(gè)任務(wù)可以設(shè)置自己的延時(shí)時(shí)間,然后進(jìn)入休眠態(tài)等待,時(shí)間到了系統(tǒng)時(shí)鐘會(huì)喚醒你。
;4、改變了殺、休眠、喚醒的方式,采用位表示殺死信號、就緒態(tài)、喚醒服務(wù),可以用邏輯的方法快速操作。
;5、增加了調(diào)度程序的加鎖功能,加鎖狀態(tài)下,調(diào)度程序不進(jìn)行任務(wù)切換,但繼續(xù)執(zhí)行其他系統(tǒng)功能。
;6、看門狗、初始時(shí)間片可配置。
;7、任務(wù)7作為伺服線程,可以做一些簡單的脈搏動(dòng)作。伺服線程必須始終就緒,否則有任務(wù)全部休眠的風(fēng)險(xiǎn)。

;0.99的篇幅反而比0.92下降了7%,除了更加實(shí)用以外,顯得更加優(yōu)美。
;實(shí)際應(yīng)用達(dá)到3個(gè)以上時(shí),修復(fù)一些潛在的bug之后,可以升為r1.0版本,并出一份《51多任務(wù)內(nèi)核的應(yīng)用手冊》

;內(nèi)存地圖規(guī)劃
;0-47 用戶
;48-63 鬧鐘數(shù)組-每個(gè)任務(wù)2個(gè)字節(jié),用于指示鬧鐘時(shí)間 /30H
;64-73 系統(tǒng)變量 /40H
;74-87 系統(tǒng)堆棧 7個(gè)CALL 包括中斷 SP_SYS:73 /49H           再壓縮至6個(gè)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í)也要注意這個(gè)問題,建議用加鎖功能代替占用系統(tǒng)

;系統(tǒng)全局變量定義
sys_bit_byte        equ    2fh    ;留給系統(tǒng)的8個(gè)標(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    ;啟動(dòng)系統(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)作伺服線程,如果沒有一個(gè)線程就緒,會(huì)進(jìn)待機(jī)
    MOV TASK_CURT_P,#0   
    MOV TASK_sch_P,#0   
    MOV TAB_PRI,#PRI_BYTE        ;任務(wù)正常運(yùn)行的要素:不被殺,就緒,優(yōu)先級(時(shí)間片)不要太長(看門狗會(huì)叫),SP狀態(tài)
    MOV SP,#START_TASK_SP     
    LJMP TASK_0            ;進(jìn)入任務(wù)0,啟動(dòng)分時(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)啟動(dòng)的入口,可以不用初始,其內(nèi)容會(huì)在第一個(gè)時(shí)間片中斷后調(diào)度程序會(huì)給予。
    ;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    ;這個(gè)是指針變量,指向當(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      

;子程序:啟動(dòng)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ù)。只提供一個(gè)線程使用,出于系統(tǒng)消耗的考慮,500us中斷必須篇幅足夠小。
;定時(shí)器1中斷服務(wù):500us中斷一次,無服務(wù)直接返回。有服務(wù):次數(shù)(time_us字節(jié))為0則讓waiting_task_p任務(wù)搶占(標(biāo)志完成)。不為0時(shí),減一。
;系統(tǒng)需要用一個(gè)字節(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個(gè)字節(jié)的8個(gè)數(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、切換下一個(gè)就緒的任務(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    ;這個(gè)是指針變量,指向當(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:            ;選擇下一個(gè)任務(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)這個(gè)任務(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會(huì)導(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ù),每一位為一個(gè)時(shí)刻,存放在DPTR,計(jì)算目標(biāo)時(shí)間,設(shè)置喚醒任務(wù),休眠自己。等待系統(tǒng)時(shí)鐘在時(shí)間到了再喚醒你,誤差為一個(gè)調(diào)度周期。
;任務(wù)號為全局變量指針TASK_CURT_P
;延時(shí)步驟:1、將dptr個(gè)刻度和當(dāng)前時(shí)間相加得到目標(biāo)時(shí)間,存入到鬧鈴數(shù)組當(dāng)前任務(wù)位置;2、設(shè)置本任務(wù)的喚醒服務(wù)位,當(dāng)目標(biāo)時(shí)間到達(dá),系統(tǒng)時(shí)鐘會(huì)喚醒你;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個(gè)位標(biāo)志8個(gè)任務(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í) 一個(gè)周期為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個(gè)字節(jié),除去0-7,可以用8-2cH這37個(gè)字節(jié)
;需求:
;1\ 16位數(shù)字鍵,0-9 abcd * #
; 2\ 3位段碼LCD顯示
;  3\ 按下數(shù)字,插入到LCD的左側(cè)。
;   4\ 按下D(回車),LCD的數(shù)字作為角度值,電機(jī)轉(zhuǎn)動(dòng)到指定角度,三位數(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ī)初始化,啟動(dòng)其他任務(wù),主循環(huán)是不停的取鍵、取鍵值成功后處理鍵值。
;task1: 電機(jī)移動(dòng),始終試圖將當(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)成一個(gè)16位數(shù),再mod360運(yùn)算,將結(jié)果寫到電機(jī)目標(biāo)值。設(shè)置清屏標(biāo)志。

;關(guān)于顯示:在主線程的循環(huán)中,涉及到BCD變化時(shí),才會(huì)觸發(fā)顯示,顯示過程:將BCD碼轉(zhuǎn)換成段碼,將段碼輸出到HT1621驅(qū)動(dòng)器。

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    ;獲取到一個(gè)按鍵后置位
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、從鍵盤緩沖池取一個(gè)按鍵; 2、處理該按鍵(數(shù)字鍵插入bcd區(qū))其他鍵(步數(shù)增減、清零、回車)3、bcd轉(zhuǎn)換為段碼,隱去尾部0
;4、段碼輸出
;;;其他按鍵處理:步數(shù)增加一個(gè)幅度,不改變參數(shù),步數(shù)減少一個(gè)幅度,不改變參數(shù),bcd歸零,bcd轉(zhuǎn)換為目標(biāo)值;;;;;;;;;;
;;-----------------------------------任務(wù)0
task_0:
    CALL Ht1621_Init;()          ; 上電初始化LCD驅(qū)動(dòng)芯片
    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ū)動(dòng):實(shí)際值逼近目標(biāo)值
   
    ;臂板垂直向下270度為初始態(tài)(壓縮狀態(tài),方便包裝和移動(dòng))在電氣驅(qū)動(dòng)里面初始化。

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個(gè)字節(jié)的內(nèi)容顯示到LCD  0-f 都能顯示 步奏:1、字節(jié)數(shù)轉(zhuǎn)換到 段碼字節(jié) 2發(fā)送給ht1621
;如何循環(huán)顯示,三個(gè)字節(jié)要構(gòu)成單向環(huán),abcabcabc,始終顯示指針后3位數(shù)字,加入新的字節(jié)時(shí)指針往前推。
;將上面的三個(gè)字節(jié),轉(zhuǎn)為一個(gè)整數(shù)<=999,占2個(gè)字節(jié)。
;設(shè)為目標(biāo)值
;當(dāng)前值與目標(biāo)值比較,不等于則靠近,等于則關(guān)閉。
;關(guān)機(jī)線程
;正或反,步數(shù)n個(gè)。函數(shù)

jmp task_0   
;--------task0 end----------



;任務(wù)0子程序:從pool_key,p_key取一個(gè)鍵值,存放在key_value,并置位key_catched  
catch_a_key:
    ;clr key_catched
    mov a,p_key
    jnz cak00
    ret        ;p_key 為0表示鍵值池空
cak00:
    ;臨界區(qū):取一個(gè)鍵值
    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

;;個(gè)位數(shù)
    mov a,#pool_bcd
    add a,r1
   
    mov r0,a
    mov a,@r0 ; 取到bcd值 從個(gè)位數(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)一個(gè)距離 ;臨界區(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ū)動(dòng)電機(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)一個(gè)距離 ;臨界區(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ū)動(dòng)電機(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  ;容錯(cuò)處理: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個(gè)值(pool_bcd,p_bcd),查表轉(zhuǎn)換成段碼,存放到段碼數(shù)組(pool_print);
bcd2print:
    mov r1,p_bcd    ;0-2范圍
    mov r2,#3    ;依次取3個(gè)數(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個(gè)字節(jié)前面的0隱去
hide_zero:
    mov r3,#2 ;2次,個(gè)位數(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個(gè)字節(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ū)動(dòng),實(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),方便包裝和移動(dòng))在電氣驅(qū)動(dòng)里面初始化。

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ū)動(dòng)電機(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ù)指針,取一個(gè)度數(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ū)動(dòng)電機(jī)
    orl p1,a

    ;至此,電機(jī)走動(dòng)了一拍,下面是延時(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個(gè)字節(jié)的緩沖pool_key,設(shè)置一個(gè)緩沖指針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í)不要去挨個(gè)掃了,這樣響應(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         ;下一個(gè)豎線
    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)心有錯(cuò)過時(shí)鐘刻度的考慮
    jc tkv02   

    ;anl p0,#00001111b           ;p0.7 p0.6 p0.5 p0.4 為豎線 改變p0可能會(huì)擾亂豎線掃描
    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個(gè)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ù)

使用道具 舉報(bào)

ID:1 發(fā)表于 2017-1-4 01:51 | 顯示全部樓層
好資料,51黑有你更精彩。。
回復(fù)

使用道具 舉報(bào)

ID:134870 發(fā)表于 2017-1-17 07:46 來自手機(jī) | 顯示全部樓層
51黑有你更精彩。!
回復(fù)

使用道具 舉報(bào)

ID:309454 發(fā)表于 2018-4-16 18:07 | 顯示全部樓層
在寫一個(gè)同時(shí)控制按鍵和LED的程序,共用一個(gè)IO口,分時(shí)復(fù)用
回復(fù)

使用道具 舉報(bào)

ID:371527 發(fā)表于 2019-3-31 11:40 | 顯示全部樓層
謝謝你的講解,理解了分線程各自非實(shí)時(shí)多任務(wù)
回復(fù)

使用道具 舉報(bào)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表