久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 4524|回復: 3
打印 上一主題 下一主題
收起左側

分時操作系統的設計-51單片機實踐

[復制鏈接]
跳轉到指定樓層
樓主
ID:160871 發表于 2017-1-9 02:59 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
轉自:http://www.zg4o1577.cn/bbs/dpj-59966-1.html
作者:  朱有田


本文僅討論筆者在實踐分時系統過程中的方法和思想,不拘泥于編程語言工具及其技巧,不用程序語言描述,全文采用敘事的方法,引出工程實踐過程中解決一些問題的過程和思路。
第一部分:  單CPU計算機運行的模型
計算機最基本模型是圖靈模型(相關知識自行腦補),計算機將所有要處理的復雜任務分解到有限的基本的操作(指令),這個操作的集合就是指令集,指令集被設計固化到硬件(CPU或處理器)中。程序是為了解決特定問題而編制的一個指令序列,計算機的運行就是一個在時間上串行的一個指令流。
如果有2個程序需要在一臺計算機上運行,常見的場景是先運行其中一個程序,運行結束后,再運行第二個程序。
第二部分:  單CPU計算機中多個程序并發執行
多個程序并行的概念出現的很早,早期為了共享昂貴的計算機資源,人們試圖使一個計算機為多個用戶同時提供服務。多個程序并發執行,采取分時的方法來實現,稱分時系統。
分時即將時間視為資源進行分配,將時間切分為人們感知上較小的一個單位,比如20毫秒(0.02秒),稱為一個時間片。若程序1和程序2都要被執行,每個程序輪流被執行一個時間片。從宏觀(感官)上來看,程序1和程序2是同時一起執行的,就像兩臺計算機在同步工作一樣。
若程序1和程序2同步運行,計算機是這樣進行的:先運行程序1,20毫秒后,切換到程序2,20毫秒后又切換到程序1,20毫秒后再次切換到程序2,如此反復… 一秒鐘切換了50次,程序1和程序2都在運行、暫停、運行、暫停這樣的狀態中進行,由于切換時間夠快,人的感官認為程序1和程序2是同步運行的。
第三部分:  中斷和定時器的作用
要實現分時的機制,離不開定時器和中斷機制。定時器就是定時發出中斷信號讓計算機能夠進入切換程序;中斷機制是指計算機的硬件要能夠支持在執行過程中被中斷,跳轉到指定的中斷程序中去運行,并在運行結束后能夠返回到被中斷的點繼續運行原來的程序。如果計算機硬件沒有中斷機制,則無法實現分時。
對于信號的輸入處理,程序總是以順序的方式進行的。比如按下一個按鍵,程序并不是立即就知道有按鍵被按下,而是要等到程序指針運行到按鍵處理程序時才會被發現。按鍵響應的速度取決于程序指針本身的運行速度和按鍵處理程序的長短及間隔距離,前者依賴于硬件主頻,后者依賴于程序設計的水平。這種依賴程序主動去發現的信號輸入方式一般稱查詢方式,與查詢方式相對應的是中斷方式。
中斷方式是設計在硬件級別的,假如按下一個按鍵采用中斷的方式處理,那么當按鍵按下時觸發中斷,系統執行完當前指令后保存當前的程序指針位置(返回時候用),然后將程序指針跳轉到指定的點,程序設計時在這個點編寫好按鍵處理程序,程序結束后返回到被中斷的點繼續運行。很顯然,中斷方式的響應時間很小且容易估算,程序也不需要“定時的去查詢”。中斷機制依賴于硬件提供的中斷資源(可接受的中斷源數量),往往比較有限;其次當多個中斷信號發生時,要考慮優先次序和中斷嵌套(中斷過程中再響應中斷)的問題。
事實上,中斷的實現也是一種查詢方式,當中斷信號發生時,計算機并不是真正的立即響應,而是要等待當前的指令被執行完,硬件才會去查詢是否有中斷信號存在。中斷也可以理解為在微指令級別(一個指令由一段微指令組成)下進行查詢處理的,其顆粒度是一條指令。
定時器是一種二進制計數器,其硬件上原理非常簡單:用一些邊沿觸發器串接起來就能構成一個二進制計數器,只要輸入一個方波,就能使計數器的數值加一。當計數器加到溢出時,將溢出信號引出到中斷信號,就能起到定時的作用。我們通過給定時器設置一個初始值來設置定時器時間的長短。
比如在程序中要等待一秒鐘的延時,如不用定時器,則必須讓程序指針在一個循環體中空轉N次,達到延時的作用。這種方式有兩個弊端:一是程序空轉浪費計算機資源,在這個延時過程中,程序啥也干不了,無法響應按鍵,無法刷新顯示,如同死機一樣;二是延時精度難控制,由于程序指令本身執行時間有長短之分,要達到精確的延時,程序設計要反復調教循環體空轉的次數。
如采用定時器進行一秒延時,只要在定時器內設置好初始值(使初始值到溢出的時間剛好為1秒),開啟定時器后,程序可以繼續執行其他工作,如按鍵掃描等。當1秒時間到達,定時器溢出發出中斷信號,使程序指針跳轉到中斷響應程序,程序在此進行延時后的處理。由此可以發現,定時器相當于硬件級別并行的設備,程序指針的跑動和定時器內計數值的增加是并行的。
言歸正傳,定時器和中斷是實現分時系統的基礎,其中中斷機制尤為重要,70年代流行的PDP系列小型機上,分時系統的定時中斷是由一個從工頻電源上獲取的50-60Hz的定時中斷外設產生,稱之為電源時鐘,令人腦洞大開。現代操作系統的祖宗UNIX就是在這種機型上產生的一種分時操作系統,UNIX最初的用戶是貝爾實驗室的文員們,他們用它來處理專利文書,由于多個終端上可以同步進行操作,又能方便進行共享和調用,得到當時女職員們的好評。
摘要:UNIX是用于DEC PDP-11及Interdata 8/32計算機的一個通用的交互作用的分時操作系統。從1971年開始以來,使用日趨廣泛。UNIX所論及的內容有以下領域: 1.文件結構:一個統一的、可隨機尋址的字節序列。取消“記錄”的概念。文件尋址的效率。2.文件系統設備的結構:目錄與文件。3.I/O設備與文件系統的一體化。4.用戶接口:外殼程序原理,I/O重新定向以及管道。5.進程環境:系統調用、信號、以及地址空間。6.可靠性:癱瘓,文件的丟失。7.安全性:損壞與檢查時數據保護;阻塞情況下的系統保護。8.一個高級語言的使用—收益與代價。9.UNIX系統沒有一般意義上的“實時”、進程間通訊、異步的I/O等功能。
UNIX內核由10,000行左右的C語言代碼和1,000行左右的匯編語言代碼所組成。匯編語言代碼又可進一步分成兩部份:一部份為200行,包括為提高系統效率而設計的那些代碼(可以用C語言來寫);另一部份為800行,包括不能用C語言寫的、執行硬件功能的那些代碼。
第四部分:  分時的意義
從程序設計角度,程序是一個單線順序的指令流(語句流),在程序里,指令(語句)一個接一個被執行,從開始到結束,或條件轉移,或反復循環。程序員安排好這一切前后處理的次序。在應用角度,用戶打開程序,運行程序,直到結束退出,再打開另一個程序。在DOS時代,人們就是這么做的,那真是一個單純的時代。
在分時系統下,多個程序并行,程序員設計程序時可以設計多個線程,比如一個線程負責顯示,一個線程負責鍵盤輸入,一個線程負責數據處理。每一個線程都是單獨的程序,最終他們將同步運行,程序員除了要考慮單個線程的運行,還要考慮線程之間的同步、通信和互斥。這改變了傳統的程序設計思維,使軟件的表現和可能性更加豐富多彩,程序設計手段也更加多樣。
在應用角度,當你在編輯文檔時,同時又在下載電影,耳機里又能播放著音樂。這些當下看起來稀松平常的事情,在計算機還是單任務時代真是難以想象的。


第五部分:  在80c52單片機上實踐分時系統
分時系統并不高不可攀,了解以上的概念后,任何有經驗的程序員都能對它進行實踐,筆者就是在最便宜和古老的51單片機上實踐了分時系統。
80c52是intel的一款增強型mcs-51單片機,51系列8位單片機自80年代開始至今仍被廣泛使用在各種場景,這種芯片很便宜,也很容易就能搞到。后來Intel將mcs-51授權給了多個芯片制造商。以ATMEL生產的AT89S52為例,它支持總計111條的51指令集,256字節RAM,8k字節程序閃存(可反復刷寫),3個16位計時器,5個中斷源,1個串口,4個8位IO口。要使她運行起來非常的方便:只需在XTAL管腳之間加一個晶振和2個27pF電容(晶振的頻率決定運行速度),在VCC腳加5V電源,然后把RESET管腳對地短路一下(復位),計算機便開始從程序閃存的0地址開始取指令執行..
沒玩過51單片機的讀者一定好奇程序是怎么編制到芯片的閃存里面去的。單片機程序的開發主要依賴個人電腦,首先通過文本編輯器編寫源程序,然后通過51的編譯器程序編譯成目標文件,最后通過芯片燒錄器將目標文件復制(燒入)到51芯片內。然后你的程序就可以在單片機跑動了。
事實上,無論是偉福,還是uvsion,都提供了包括編輯器、編譯器、仿真和調試為一體的開發環境,程序在編制完成后,直接在開發環境里面仿真調試,經過排錯后,將問題降到最低再燒入到單片機運行,這比反復燒寫芯片驗證程序效率要高的多。
最要緊的事情是:AT89S52具備實現分時系統的必要條件:1支持中斷,2具備計時器,3勉強夠用的RAM空間。
第六部分:  時間片的分配
實現分時系統的第一要考慮怎么進行時間片的分配,也就是程序之間的切換問題。這個很簡單,可利用一個定時器產生一個固定的延時,當延時到達,進入定時器中斷,將這個中斷的入口跳轉到切換程序。注意:程序的切換不是簡單的跳轉,而是要先保存當前程序的執行狀態、被中斷的地址,不至于下次回來繼續當前程序時變量、狀態都被改變,那就會亂套了。然后再選擇下一個要運行的程序,將被選中程序的狀態恢復到當前的各狀態寄存器當中,恢復到這個程序上次被打斷的點繼續運行,等待下一個時間片用完,進中斷,重復以上過程。
既然每個程序都要保存和恢復它的運行狀態,那么問題來了。一是要保存哪些信息,稱之為保護的范圍;二是需要多少內存,能夠支撐多少個程序進行分時。
第一個問題,保護范圍取決于用戶程序的斷點地址、狀態字、寄存器組,以及堆棧空間。第二個問題,支撐多少個程序進行分時取決于內存的大小,89C52的256字節支撐8個線程后,留給應用程序的內存只有36個字節,因此80c51的128個字節RAM是不夠的。
在此,為了區別于系統程序,改稱以上所謂被分時的程序為任務或線程。

第七部分:  內存組織的方法
我們能設想到最原始的方法是通過數組(數據表)的方式,將每一個線程的保護內容固定的保存起來。這種方法的缺點是不靈活,要增補被保護的內容就要重新定義數組,改動操作數據的代碼。當然還有更值得推薦的方法:用堆棧來組織內存。
堆棧是一種數據結構,數據如被壓入沖鋒步槍AK47彈匣的子彈,最先被壓進去的子彈,最后才被彈出到槍膛。在計算機內一般設有一個SP寄存器,存放堆棧指針,如果執行PUSH指令,SP先向前推一格,數據被寫入SP所指向的內存;如果執行POP指令,數據從SP所指向的內存讀出,SP再向后退一格。這種先入后出的結構能夠很好的支撐子程序嵌套調用時的數據存儲。舉一個栗子:
主程序運行時調用子程序A,當前的程序指針PC被壓入堆棧(必須留下跳轉點的腳印,否則子程序完成后返回不到原來被跳過去的點),接著PC跳轉到子程序A中開始運行,在子程序A里面又有調用子程序B,此刻的程序指針PC也被壓入堆棧,接著跳到子程序B中,子程序B完成后返回,將堆棧中最近保存的PC彈出到PC寄存器,程序返回到子程序A,子程序A完成后返回,從堆棧彈出最早壓入的PC,程序返回到主程序。
系統在線程之間切換時,將當前線程的所有需要保護的內容全部壓入堆棧,最后僅需要保存好它的堆棧指針(就像把東西全扔箱子里,只需保管好鑰匙)。將下一個線程的堆棧指針取出來,從堆棧彈出所有要恢復的數據,然后返回到新線程運行下一個時間片。
第八部分:  關于線程調度
當一個時間片用完,定時器發生中斷后,程序指針跳轉到線程切換程序,首先是保護現場。然后是選擇下一個線程。那么問題又來了,我們打算怎么來選擇下一個線程?這就是所謂的線程調度,操作系統的教課書里叫進程調度。
最原始粗暴的方法就是順序循環調度,所有線程先來后到排個隊,挨個輪著,實行平均主義的制度。在一些實時系統里面,比如μCOS,是按優先級來確定調度權的:永遠只選擇優先級最高的線程,也就是說,如果線程一直處于最高優先級,那么它就一直占著CPU不放,就不會被搶占。如果要讓線程立即得到執行,就必須設置最高優先級。這種方法能夠提供手段讓用戶設計的任務滿足實時的要求(所謂實時,就是對響應時間可評估,可控制,不是字面理解上的實時。)
除了優先級方式,還可以設定一種搶占的機制,比如在順序調度的過程中來一個半道插隊方式的搶占,也是一種可以滿足實時要求的方法。半路殺出個陳咬金,需要提前設置一個信號通知給調度程序,表示當前有某個線程需要插隊搶占。這里要考慮到原有的順序調度不能被打亂,或者被搶占的線程正好又是被輪到的線程,那么下次調度應繼續往后調度。
調度算法有很多,應按工程實際需求來設計,讀者不妨也可以設想更有創新意義的方法。


第九部分:  系統效率和延遲
分時總是要付出一些代價的,假如一顆CPU全力運行一個單程序,那么CPU利用率是100%。如果這顆CPU要分時運行兩個程序,那么每個程序對CPU的利用率都不足50%,因為切換要耗費CPU時間,切換次數越多越耗費,“切換可是要上稅的”。切換次數和時間片的設置相關,一般來說時間片設置范圍在1ms到20ms之間,時間片太小,浪費了大量CPU時間在上下文切換上,時間片太大,線程被輪到的時間就長,響應變差。一個線程的運行延遲在分時系統里很難被準確的估算,它與已就緒的線程數量、CPU主頻、時間片長短、中斷響應等各種因素相關,這些因素動態影響著單個線程的運行延遲。
分時系統中單個線程性能的一種簡單的估算方法:假如一個CPU工作在30MHz的主頻下,有3個已就緒的線程被平均調度,時間片相等,那么單個線程的運行性能相當于它獨占了一個10MHz不到的CPU的效果,相當于這個CPU被拆分成了3個10MHz不到的CPU在同步運行。由于現場就緒與否是動態變化的,因此很難完全準確估算。
總而言之,分時盡管耗費了一些CPU時間,但相對它所帶來驚奇效果和并行思維給程序設計帶來更多的可能性而言,微不足道。分時就像變魔術一樣,能把一顆CPU拆分成N個弱小的CPU同步運行不同的程序。從宏觀上,分時能夠充分挖掘CPU資源,當一個線程需要等待或有目的的延時,完全可以把CPU時間讓給其他線程,而不是白白的空等。

第十部分:  線程的休眠和就緒
當一個線程需要等待或延時,或者索性暫停掉,可以通過狀態標記,使調度程序跳過對它的調度,那么這個線程就是被休眠了,或稱該線程處于休眠態。反過來,如果線程等著被調度,稱該線程處于就緒態。這個功能在調度程序內做一個判斷不難實現。
如果要使一個線程休眠,系統可以提供一個函數(子程序)來修改線程的休眠標志(變量)。任何線程,包括線程自己都可以進行休眠操作。所謂想睡就睡,但不是想醒就醒,喚醒一定是其他線程或時鐘服務來幫助喚醒你。
除了休眠,還可以設置一個殺死的操作,所謂殺死線程,就是讓線程的狀態信息恢復初始化,下次再喚醒該線程,相當于要從頭開始執行。殺死線程,或線程自殺,可以理解為線程的復位并休眠。
可以設想一種簡單的設計場景:用線程1來處理用戶輸入,線程2、3、4為不同功能的獨立任務,線程1可通過用戶輸入選擇喚醒或休眠線程2、3、4來調取不同的功能。對于任務的開發來說,確實提供了與往常不同的手段,用的好,絕對順手。
第十一部分:    線程之間通信和互斥
感官上,線程是獨立運行的,微觀上,線程跑著跑著不知道什么時候就咔的被喊停,CPU被強行切換到別的線程去運行。也就是說,線程的運行是隨時隨刻隨地都可能被中斷的。
線程和線程之間要通信,一般通過全局變量、全局的數據結構(內存的一塊空間)來共享信息。比如線程A往全局變量內寫信息,線程B從該變量讀信息。以達到讓線程A的信息傳達給線程B的目的。那么問題又來了,如果線程A對全局變量寫了一半,咔,時間片到了,輪到線程B運行,這時候線程B從該變量讀到的信息就是一個錯誤的信息。
先來分析這個問題:
中斷是在指令和指令之間響應的,也就是其最小顆粒度是單條指令,如果寫變量是一條指令搞定的,由于指令在執行過程中不會被中斷,所以不會發生以上情況。單條指令的操作也稱為原子操作。
可惜的是,一條指令至多可寫一個字。對于沒有接觸過匯編的程序員來說,一條高級語言的語句很容易被認為是一個原子操作,其實不然,經過編譯后,一條高級語言的語句往往都由若干條指令組成。
思考解決問題:
要解決寫變量時候不被破壞的問題,就要排斥其他線程對這塊變量的操作,或防止在寫的過程中被中斷。方法有幾種:
最簡單粗暴的方法是開始寫共享變量之前直接把中斷給關了,寫完以后再開。還有更流氓的做法,就是在寫共享變量之前直接讓計時器停擺,定住,結束后再繼續往前計數。方法雖然簡單,但是這么做會讓整個系統短暫停滯,明顯有副作用。
推薦的方法是讓調度程序帶上鎖功能,如果處于上鎖狀態,調度程序不執行切換。線程在寫共享變量之前先加鎖,結束后再解鎖。在加鎖期間,雖然中斷和計時都不受影響,但是其他所有線程都被禁止了,還是有些缺憾。
更好的辦法是采用信號量,相當于給共享變量加一個紅綠燈。設置紅綠燈的操作必須是原子操作,線程在操作共享變量時先查一下是不是綠燈,如果是綠燈就把它變為紅燈,然后操作變量,結束后再設為綠燈;如果是紅燈就排隊等待,直到綠燈來了再進行操作。
名詞摘要:在教材中,把對需要互斥的共享變量和設備進行獨占操作,叫臨界區操作,結束后要退出臨界區。
第十二部分:    掛鐘和鬧鈴叫醒服務
線程在運行過程中經常會用到等待相對精確的延時、以及超時判斷等和時間相關的操作。傳統的單線程程序,經常用最粗暴的空操作指令循環來獲得一定的延時,這無可厚非,單線程中一段程序的運行時間能夠被準確的估算出來。多線程環境就不同了,一段程序的運行時間長短是不可準確估算的,取決于就緒線程的數量和時間片的長短,這些都是變化的。
需求場景1:線程需要就地等待1分鐘。
需求場景2:線程進入一個循環等待某個信號,當時間超過5秒鐘判斷為超時,退出循環。
以下是一種設計方案:
設計一個系統掛鐘,讓他一直以單位時間往前走。好比在墻上掛一個鐘頭,讓所有線程都能看時間。為每個線程提供一個鬧鈴指針和喚醒服務標志,如果某個線程需要喚醒服務,就把鬧鈴指針撥到目標時間,然后開啟喚醒服務。系統掛鐘每走一個刻度都要判斷當前時間和需要喚醒服務線程的鬧鈴時間是否一致,一致的話就喚醒這個線程。
當某個線程需要就地等待1分鐘就可以這樣做:線程先看一下當前的時間,然后把時間往前推1分鐘設置一個鬧鈴,然后告訴系統時鐘:鬧鈴到了記得叫醒我!接著線程就自我休眠了,等著1分鐘后系統時鐘的喚醒并繼續運行。
實踐的栗子:啟用硬件另一個計時器,每10ms發生一次中斷。系統中設置一個16位的字,每過10ms將這個字加一,這個16位字相當于一個量程為65536的掛鐘。設置一個數組用于存儲每個線程的鬧鈴時間,設置一個狀態字,用于標志線程的喚醒服務。當時鐘往前走一個刻度時,同時進行一個喚醒的判斷,將當前時間和鬧鈴時間一致的線程喚醒。
時鐘的最小刻度設定為10ms,也就意味著延時設定的最小單位是10ms,那么問題又來了,若需要延時1ms或更小的時間刻度該怎么解決?
如果把時鐘刻度直接調至1ms,會導致時鐘中斷次數增加了10倍,時鐘和鬧鈴的程序段的執行次數也就增加了10倍,這嚴重消耗了CPU時間,降低了整個系統的性能。如何解決這個問題留給讀者去思考。
第十三部分:    公用的函數(或子程序)
如果一個函數僅僅使用堆棧和局部變量,那么這個函數就能天然的被多個線程同時調用。因為堆棧和局部變量(或CPU寄存器)都是受保護的,假使2個線程同時調用一個函數,在線程切換的情況下,雖然都是運行在相同的函數代碼段,但各自線程里的局部變量和堆棧內容不同,所以運行結果也是各自分開的。這個函數就像使了分身之術一樣,在不同的線程里同時運行著。這種函數也叫作可重入函數。
如果一個函數使用了全局變量,那么假使2個線程同時調用了它,那么在線程A里面所定義的全局變量和線程B里面所定義的全局變量指向同一個位置,就會發生問題。這種不能被多個線程同時調用的函數,叫不可重入函數。
第十四部分:    后記
事實上,分時系統是現代操作系統的核心技術內容,它并不過時。本文所描述的內容涉及到操作系統的部分原理知識,當然描述不夠專業,沒有準確引用教材中的各種名詞和術語,不能完整的討論操作系統。但筆者希望本文能夠成為讀者對操作系統知識的一種指引,如果覺得意猶未盡,可以去翻一翻《操作系統》相關教材,看看專業的角度這些概念是怎么被描述的。很多程序員總是以為軟件能夠解決一切問題,其實不然,有些想法或算法不得不依賴硬件的支持。事實上,操作系統的各種思想也深刻影響了硬件的設計,CPU設計者在設計新的CPU時,充分考慮并納入了好多為操作系統考慮的事情。
現代操作系統往往鑒于UNIX這樣的通用操作系統來描述的,其內容除了講分時調度以外,還包括內存管理,外存管理(也就是文件系統),系統調用,設備驅動等內容。硬件往往基于個人計算機(intel的架構)來描述,頭緒非常多,讓學習者覺得課程就像是空中閣樓,可遠觀而不可觸碰。
用單片機做實踐是最能摸到計算機本質的路徑,試想,在一臺裸機上讓你的程序最接近硬件的跑動起來,是多么踏實的趕腳(好吧,牽強了)。通用計算機和單片機原理是一樣的,只不過通用計算機規模更大,主頻更高,為了達到更大的規模,架構起來就要用更多的手段和技巧。
比如在通用計算機內,程序指令并不是固化在ROM或閃存內的,而是存放在低速容量更大的外存(硬盤、軟盤、光驅、U盤)中,需要執行程序時,把程序文件成塊的復制到內存中,CPU從內存中取指令執行。那么問題來了,程序文件必須被裝載在指定內存地址段,(跳轉指令中包含了具體的地址),如果地址和指令錯位,那么程序跳轉將找不到北。要解決這個問題,CPU的設計者從早期的內存分段管理,發展到現在的虛擬地址空間轉換,可謂費盡心思。隨著程序規模越做越大,當單個程序的長度超出內存的總容量時,問題又來了。為了解決這個問題,又引入了分頁和虛擬內存的方法,分頁允許程序不用一次性全部調入內存,而是將要被執行部分的頁面調入內存,剩余的還是存在硬盤里,當程序執行的指令已經不在內存時,CPU將會發生一個缺頁中斷,將硬盤中的目標頁調入內存,將不用的頁調回到硬盤。這些都是內存管理單元MMU做的事情,而MMU基本是硬件實現(做在CPU內部),但它需要軟件配合才能用起來。所有的這些機制都是為了解決過程中碰到的問題而提出的解決方案,要搞清楚源頭。  
討論操作系統離不開信息安全的話題,黑客和病毒總是存在。理論上,用戶程序如果要想搞破壞是非常容易做到的,單靠軟件防止黑客或病毒的攻擊理論上是做不到的,因為理想狀態下所有指令都是公平對等的,你不能用程序指令去阻止用于惡意破壞的程序指令,比如故意改寫數據(破壞正確的數據)。所以,計算機安全必須依靠硬件提供的機制,例如CPU設計者將處理器的運行分成多個狀態,低權級狀態下只能執行部分安全的指令,高權級狀態下才可以執行全部指令,用戶程序只能運行在低權級,需要進行讀寫操作時須通過調用系統提供的功能調用來實現,系統是工作在特權級下的,這樣就為計算機安全提供了可控手段。
性能是計算機永恒的話題。分時系統上就緒的線程數量越多,單個線程的性能就越低(可視為CPU的性能被線程瓜分了,線程越多,單個線程瓜分到的性能就少)。為了提升計算機性能,除了設置流水線、多級緩存等方式外,單個CPU主要靠提升主頻來實現性能的數量級提升。除此之外,引入多個CPU并行工作,通過增加系統的并行度也是提高性能的主要手段。多個CPU使得線程從物理上真正的并行了,一個多核的CPU在同一時刻可以同時運行多個線程,多CPU在硬件組織上又是一件費盡心機的活(不要簡單的理解成多插幾個CPU就完事那么簡單)。超級計算機(用于氣象、原子能、航天等領域的測算)往往以集群方式組織起來的高度并行的結構,這也要求軟件設計必須要被分割為高度的并行化才能發揮超級計算機的性能。如果一個問題的算法必須至始至終前后串行,無法被分割成多個同步運行的部分,那么這種程序放在超級計算機上運行,縱使這臺超算有千萬個CPU,程序也只能在其中一個CPU上跑完,剩下的CPU等于閑著沒事干。同樣的道理,多核CPU體現的也是并行性能,只有在線程數量多的分時系統內才能體現其性能。如果用一個8核的CPU僅僅只運行一個線程,那么剩下7個核心就閑著沒事做。
備注:本文一直采用線程來描述并發執行的程序,其實線程是專有名詞,有更清晰的概念。操作系統中還有進程的概念,進程也是并發的程序,一個進程還能再分多個線程。在實時操作系統中,線程又被稱為任務。
寫在最后
通過以上敘述,我想讀者一定對分時、多線程、并發執行有了概念,同時也一定留下了一堆的疑問和不解,這可是好現象,進步都是在疑問和不解中開始的。



還有2個應用例程可以從這里下載:http://www.zg4o1577.cn/bbs/dpj-59966-1.html


 豆豆的打開關上
需求:假設有一個指針,可以作圓周運動,通過在鍵盤上輸入具體的角度,使指針指向該角度。
輸入:數字鍵盤,按3位數字后回車生效。范圍0-999,表示圓周角度,對360度取模。
顯示:3位段式液晶數碼顯示。
執行:減速步進電機,按輸入的角度作圓周運動。
結構:木結構為主,在電機上安裝臂桿,做成類停車桿的結構。可360度自由轉動。

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

操作模型:
1、打開開關,液晶顯示---;臂桿角度為0;(開關處并聯的繼電器吸合,開關的觸點留一組去cpu)
2、輸入0-360之間的一個整數表示圓周的絕對角度,每輸入一個數字,顯示到液晶屏;其他輸入作無效處理;
3、回車后,液晶屏內的數字被作為目標值,執行電機轉到該角度;
4、轉動過程中,可以按暫停鍵,暫停或繼續;
5、轉動過程中,可以繼續輸入新的角度值,按回車后變更新的目標值執行。
6、執行完成后,電機去電待命。
7、開關關閉,cpu檢測到關機指令,執行歸零程序,完成后,繼電器斷開。(歸零程序不可被中斷,歸零后須再確認關機指令,若還是關機,則繼電器斷開。)


實施步驟:

1、整體方案構思;資料消化;
2、硬件草圖;
3、電機驅動試驗,正反向,速度和角度;
4、液晶顯示試驗;
5、按鍵掃描模擬和試驗;
6、軟件構思;
7、編碼和模塊測試;
8、最終調試。

全局變量:當前值,目標值,暫停標志,鍵值,顯示數字,ram區,8拍數組;

按鍵掃描獲取鍵值(任務2)
鍵值改變數字or啟動執行(轉換為目標值)or暫停標志變更,無效值;新的數字來了,要標志,用于判斷回車鍵的作用;數字 轉換 成ram區;ram區寫入HT1621的ram;(任務3)
暫停標志沒有時and當前值!=目標值時執行:目標值在左側還是在右側:前進一步或后退一步,延時,返回;(任務4)
[一個8拍數組;前進一步,當前值(0-4096變更);后退一步,當前值(0-4096變更);]
不停的查開關標志,若關機,改變目標為0,執行,等待執行完成,再判斷是否關機,關斷電源繼電器。否則返回。(任務0)

  1. ;kernel: sys51 r0.99
  2. ;project name:    doudou's up & down   
  3. ;designer: ut
  4. ;version: 1.0
  5. ;date: 2016/11/1


  6. ;=============================================================================================
  7. ;                            TIME-SHARING SYSTEM FOR MCS51 RELEASE 0.99
  8. ;                                         UT.ZUZU
  9. ;                               COPYRIGHT(2012/5/10-2016/11/--)
  10. ;=============================================================================================
  11. ;詳細請查看手冊

  12. ;硬件要求
  13. ;1、52系列兼容的51單片機,內存256字節或以上。本程序在AT89S52運行,24.576MHZ晶振,改變晶振需調整計數器值。晶振頻率越高,控制器性能越好。
  14. ;2、256字節內存中,系統使用了大部分高地址部分,0-47由用戶支配,具體請看內存分配說明。
  15. ;3、一共8個線程:TASK0到TASK2為3個主線程,其余為次線程;主線程對9個寄存器和PSW、AB、DPTR進行保護,并預留堆棧最大嵌套調用為7級;次線程僅保護PSW,AB,R0-R3,最大嵌套調用為2級。
  16. ;4、系統可以在調度程序中喂看門狗,時間片不可過大,超過4MS不喂狗看門狗發出系統復位信號。看門狗功能可以在配置定義中取消。
  17. ;5、分時過程通過定時器0進行,其初值定義在T0_VALUE中,目前設置的是5MS時間片。
  18. ;6、系統時間記錄在SYS_TIME變量中,通過定時器2進行,目前設置是10MS加1.

  19. ;任務操作說明
  20. ;0、任務的邊界應該是循環。不建議跳出邊界。盡可能的使用系統提供的調用。
  21. ;1、不同任務可以調用同一個子程序,注意子程序內受保護的范圍。
  22. ;2、主任務擁有獨立的R0-R7、ACC、B、PSW寄存器、DPTR指針。次任務僅保護7個寄存器。
  23. ;3、任務之間可以通過內存變量來傳遞信息,注意在寫內存時必須占用系統,寫完后再釋放系統,建議使用加鎖和解鎖調用。
  24. ;4、系統初始化后所有任務都是睡眠的,系統會喚醒任務0和任務7,其他任務的喚醒由用戶操作。任務7為伺服任務,不建議休眠它或在該任務中使用系統延時調用。(有一種風險:所有任務處于休眠態,會進入待機)
  25. ;5、系統的性能與晶振頻率、喚醒的任務數量、任務占用的時間片有關系。
  26. ;6、任務有權殺死或休眠任何任務,如果系統所有任務都被殺死或休眠,系統會進入節電POWER_DOWN模式,等待復位激活。
  27. ;7、系統提供10MS刻度的16位系統時間,由TIMER2來完成。任務可以根據自己需要來完成延時功能,其性能優于普通的空等待DELAY子程序。
  28. ;8、任務不可以操作TIMER0和TIMER2這兩個定時器,需要時,可以使用TIMER1. 建議不要設置為高優先級,可能導致系統時間停走。

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

  30. ;用戶使用注意:
  31. ;1.總計8個任務,單個線程是死循環,所有線程并發執行,可以有限調整每個線程的時間片,默認5MS時間片,合理使用可以滿足實時要求。
  32. ;2.任務0到2是主線程,線程內寄存器A和B,R0-R7,DPTR都受保護,子程序嵌套調用最大達8級。
  33. ;3.任務3到7是次線程,線程內寄存器A和B,R0-R3受保護,子程序嵌套調用最大2級。注意這個限制條件。嵌套調用超限將導致堆棧過界破壞,使系統崩潰。
  34. ;4.用戶只能使用0-59之間的內存空間。
  35. ;5.用戶無需考慮堆棧的分配,禁止任務程序修改堆棧指針SP。
  36. ;6.中斷響應程序中要注意保護現場和恢復現場。                  

  37. ;2012-5-22 R0.91 占用系統和釋放系統改用停止和開啟計時器的方式實現。UNDEBUG
  38. ;2012-5-23 R0.92 喂狗簡化到CPL指令 UNDEBUG
  39. ;使用時注意:所有中斷程序內要用到PSW,A,B,R0~R7,DPTR,必須事先暫存,返回前恢復,注意它們不受保護
  40. ;2016-11-08 確認BUG和注釋。可以作為穩定版。

  41. ;2016-11-08 R0.99
  42. ;為了增強實用性,擬重新布局內存,改用堆棧方式保護現場,保證3個主線程,每個線程分配29字節,增加對DPTR的保護,;增加對PSW的保護
  43. ;可以最大嵌套29-2(PC)-2(DPTR)-10(AB,RG)-1(PSW)=7個CALL;閹割剩余5個次線程,每個線程分配13字節:AB,R0-R3,PC,PSW,最大嵌套2個CALL。
  44. ;總體256字節的內存:3個主線程:29*3=87;5個次線程:13*5=65;8個線程狀態(優先級、SP、鬧鈴H、鬧鈴L)=32;
  45. ;系統變量:10;系統堆棧:14;R0-R7:8個除去。 剩余用戶可用的內存區:40字節
  46. ;真可謂:螺螄殼里做道場。  

  47. ;20161110
  48. ;備忘:調度程序要增加加鎖功能(不切換,好處是總是能進系統區做一些系統要做的事,比如喂狗)
  49. ;延時誤差太大,最大誤差是一個單位(不累計),考慮系統(放在時鐘程序內)來負責高精度大跨度的計時,和喚醒服務,需要額外16字節用于8個線程的鬧鐘記錄。
  50. ;看門狗使用指南:時間片調節的太大就會觸發看門狗,應能根據需要關閉看門狗。13位,每一個機器周期+1
  51. ;時鐘要方便配置
  52. ;注意中斷嵌套的影響 ;注意測量 系統服務的時間,及其與中斷時間的比重,比重和效率成正比 ;中斷響應前后次序的關系分析,用戶怎么用中斷
  53. ;喚醒服務要注意的是:必須留一個伺服線程,該線程始終保持就緒(不能使用系統延時)。否則有一種風險:所有任務同時調用系統延時而休眠,調度程序將轉入節電模式。要復位或外部中斷才能恢復。

  54. ;20161111 r0.99基本調試完成
  55. ;0.99版本比較0.92版本特點如下:
  56. ;1、充分利用堆棧的特點布局內存,使得保護內容的調整變的靈活。
  57. ;2、不改變8個任務的總數,但集中資源到3個主任務上,增加對psw、dptr寄存器的保護(原來沒考慮周全,如psw是必須保護的)。使主任務不再有束縛。
  58. ;3、取消原有的延時服務,增加系統時鐘的定時喚醒服務功能,每個任務可以設置自己的延時時間,然后進入休眠態等待,時間到了系統時鐘會喚醒你。
  59. ;4、改變了殺、休眠、喚醒的方式,采用位表示殺死信號、就緒態、喚醒服務,可以用邏輯的方法快速操作。
  60. ;5、增加了調度程序的加鎖功能,加鎖狀態下,調度程序不進行任務切換,但繼續執行其他系統功能。
  61. ;6、看門狗、初始時間片可配置。
  62. ;7、任務7作為伺服線程,可以做一些簡單的脈搏動作。伺服線程必須始終就緒,否則有任務全部休眠的風險。

  63. ;0.99的篇幅反而比0.92下降了7%,除了更加實用以外,顯得更加優美。
  64. ;實際應用達到3個以上時,修復一些潛在的bug之后,可以升為r1.0版本,并出一份《51多任務內核的應用手冊》

  65. ;內存地圖規劃
  66. ;0-47 用戶
  67. ;48-63 鬧鐘數組-每個任務2個字節,用于指示鬧鐘時間 /30H
  68. ;64-73 系統變量 /40H
  69. ;74-87 系統堆棧 7個CALL 包括中斷 SP_SYS:73 /49H                   再壓縮至6個call
  70. ;88-95 任務優先狀態字節 /58H
  71. ;96-103 任務SP指針 /60H
  72. ;104-132 任務0堆棧 SP0:103 /67H
  73. ;133-161 任務1堆棧 SP1:132 /84H
  74. ;162-190 任務2堆棧 SP2:161 /A1H
  75. ;191-203 任務3堆棧 SP3:190 /BEH
  76. ;204-216 任務4堆棧 SP4:203 /CBH
  77. ;217-229 任務5堆棧 SP5:216 /D8H
  78. ;230-242 任務6堆棧 SP6:229 /E5H
  79. ;243-255 任務7堆棧 SP7:242 /F2H
  80. ;NOTE:OPRATING SFR OR RAM WHERE HAVE THE SAME ADDRESS WITH EACH OTHER WILL BE ATTENTED!  CARE <DATASHEET OF AT89S52>
  81. ;-------------------------------------------------------------------------------
  82. ;標號定義
  83. PRI_BYTE                EQU        0D8H        ;INIT PRIORITY OF EVERY TASK ;時間片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H           這里是參考值,初始化時間片請定義在PRI_BYTE
  84. SYS_SP                        EQU        4bH        ;SYSTEM STACK HEAD
  85. START_TASK_SP                EQU        67H
  86. TAB_PRI                        EQU        58H        ;基址 見內存分配規劃
  87. TAB_SP                        EQU        60H        ;基址
  88. TAB_CLK                        EQU        30H        ;BASE
  89. WDT_PIE                        EQU        00H        ;設置為1E,看門狗開啟,其他值則關閉看門狗 NO TEST  13位計時器1FFF復位,合計4MS :意味著啟用看門狗時,時間片必須小于4MS,占用系統時也要注意這個問題,建議用加鎖功能代替占用系統

  90. ;系統全局變量定義
  91. sys_bit_byte                equ        2fh        ;留給系統的8個標志位 位地址78-7fh
  92. TMP_A                        EQU        40H
  93. TASK_CURT_P                EQU        41H        ;當前的任務指針
  94. task_sch_p                equ 4ah        ;調度任務指針
  95. CLK_ALARM                EQU        42H        ;鬧鐘字節 從左到右每一位依次標志任務0到7的鬧鈴請求,1為有鬧鈴請求
  96. DEAD_SIG                EQU        43H        ;從左到右每一位依次標志任務0到7的殺死請求,1為有殺死請求
  97. READY_BYTE                EQU        44H        ;從左到右每一位依次標志任務0到7的就緒狀態,1為就緒
  98. LOCK_BYTE                EQU        45H        ;5A表示加鎖,其他值表示解鎖                               
  99. TMP_SP                        EQU        46H       
  100. WDT_BYTE                 EQU        47H        ;狗盆子
  101. SYS_TIME_H                EQU        48H        ;系統時鐘高8位
  102. SYS_TIME_L                EQU        49H        ;系統時鐘低8位
  103. nouse                        equ 4bh ;預留

  104. preempt_bit                 bit        78h        ;是否搶占
  105. delay_sv_bit                bit        79h        ;定時器1中斷服務 標志 用于小刻度的延時需求
  106. preempt_task                EQU        2eh        ;搶占任務號 僅0-7有效,搶占后作廢,用于調度程序切換到指定的任務去。
  107. delay_times                equ        2dh        ;用于timer1計時刻度的次數

  108. ;系統晶振:24.576MHZ
  109. T0_VALUE_H                EQU        0D8H        ;時間片;0.5MS:0FCH,1MS:0F8H,2MS:0F0H,5MS:D8H,10MS:B0H,20MS:60H           這里是初始賦值,初始化時間片請定義在PRI_BYTE
  110. T0_VALUE_L                EQU        00H                                                  
  111. T2_VALUE_H                EQU        0B0H        ;時鐘刻度 參考上面
  112. T2_VALUE_L                EQU        00H
  113. T1_VALUE_H                EQU        0fcH        ;時鐘刻度 參考上面 500us
  114. T1_VALUE_L                EQU        00h        ;       

  115. ;------------------------------規劃程序入口
  116. ORG 00H
  117.         JMP SYS_START
  118. ORG 03H
  119.         ;LJMP INT_INT0  ;(INT0)
  120.         RETI
  121. ORG 0BH
  122.         ;LJMP INT_T0        ;(IF0)
  123.         LJMP SHARE_SYS
  124.         RETI
  125. ORG 13H
  126.         ;LJMP INT_INT1        ;(INT1)
  127.         RETI
  128. ORG 1BH
  129.         ;LJMP INT_T1        ;(IF1)
  130.         JMP sys_ms_svrs
  131.         RETI
  132. ORG 23H
  133.         ;LJMP INT_RTX        ;(RI,TI)
  134.         RETI
  135. ORG 2BH
  136.         ;LJMP INT_T2        ;(IF2)
  137.         JMP SYS_TIME_RUN
  138.         RETI

  139. ;標記中斷返回:如果意外中斷,直接返回,不至于跳飛;-)

  140. ;以下是任務的入口,應和表格中定義一致
  141. ORG 30H
  142.         LJMP TASK_0
  143. ORG 38H
  144.         LJMP TASK_1
  145. ORG 40H
  146.         LJMP TASK_2
  147. ORG 48H
  148.         LJMP TASK_3
  149. ORG 50H
  150.         LJMP TASK_4
  151. ORG 58H
  152.         LJMP TASK_5
  153. ORG 60H
  154.         LJMP TASK_6
  155. ORG 68H
  156.         LJMP TASK_7

  157. ;;開機,從00H跳過來*******************************************
  158. SYS_START:
  159.         MOV SP,#SYS_SP                ;SYSTEM STACK
  160.         MOV WDT_BYTE,#WDT_PIE        ;準備好狗糧
  161.         clr preempt_bit       
  162.         clr delay_sv_bit
  163.         mov preempt_task,#0

  164.         CALL INIT_RAM                ;初始化系統內存
  165.         CALL INIT_TIMER                ;初始化定時器
  166.         CALL USER_INIT                ;用戶初始化程序
  167.         CALL SYS_TIMER_START        ;啟動系統定時器
  168.        
  169.         MOV DEAD_SIG,#0                                ;清空殺手信號
  170.         MOV R1,#0F8H;
  171.         MOV R0,#7;
  172.         CALL SET_PRIBYTE        ;任務7時間片設置為1MS
  173.         MOV READY_BYTE,#10000001B        ;任務0就緒,任務7當作伺服線程,如果沒有一個線程就緒,會進待機
  174.         MOV TASK_CURT_P,#0       
  175.         MOV TASK_sch_P,#0       
  176.         MOV TAB_PRI,#PRI_BYTE                ;任務正常運行的要素:不被殺,就緒,優先級(時間片)不要太長(看門狗會叫),SP狀態
  177.         MOV SP,#START_TASK_SP         
  178.         LJMP TASK_0                        ;進入任務0,啟動分時,START SHARE

  179. ;;上面用到的子程序:任務1到7依次初始化各自內存空間-----------------------------
  180. INIT_RAM:
  181.         MOV R0,#7 ;以此對各任務進行內存初始化賦值
  182. ITR0:
  183.         CALL TASKRAM_INIT
  184.         DJNZ R0,ITR0     ;TASK0任務作為系統啟動的入口,可以不用初始,其內容會在第一個時間片中斷后調度程序會給予。
  185.         ;CALL TASKRAM_INIT          ;JUST FOR TEST        TASK0 RAM INIT
  186. RET

  187. ;以下表格用于初始化內存用
  188. TAB_1:
  189.         DB 067H,084H,0A1H,0BEH,0CBH,0D8H,0E5H,0F2H,00H        ;任務棧頂地址
  190. TAB_2:
  191.         DB 030H,038H,040H,048H,050H,058H,060H,068H,00H        ;任務入口地址 和ORG 30H.. 對應         

  192. ;上面用到的子程序:開機初始化任務內存操作:1、根據任務號查表得棧頂位置、入口位置;2、在棧頂壓入:入口、現場;3、將SP存到SP_I; 4、清就緒態
  193. ;初始化任務內存分主次 ;任務號先存R0       
  194. TASKRAM_INIT:  
  195.         MOV A,R0
  196.         MOV DPTR,#TAB_1       
  197.         MOVC A,@A+DPTR        ;查表得初始SP

  198.         MOV TMP_SP,SP
  199.         MOV SP,A        ;開始壓棧

  200.         MOV A,R0
  201.         MOV DPTR,#TAB_2       
  202.         MOVC A,@A+DPTR        ;查表得初始PC
  203.        
  204.         MOV 02H,A
  205.         PUSH 02H                ;PUSH PC_L
  206.         MOV 02H,#0
  207.         PUSH 02H                ;PUSH PC_H  PC是16位的

  208.         PUSH 02H ;PSW,AB,R0-R7,DPTR
  209.         PUSH 02H
  210.         PUSH 02H
  211.         PUSH 02H
  212.         PUSH 02H
  213.         PUSH 02H
  214.         PUSH 02H

  215. ;區分主次任務
  216. ;任務號大于2則跳過以下步驟
  217.         CLR C
  218.         MOV A,#2
  219.         SUBB A,R0
  220.         JC TKI00

  221.         PUSH 02H ;R4 R5 R6 R7 DPL DPH
  222.         PUSH 02H
  223.         PUSH 02H
  224.         PUSH 02H
  225.         PUSH 02H
  226.         PUSH 02H
  227. TKI00:
  228. ;保存SP到數組,SP--> SP_I
  229.         MOV A,#TAB_SP
  230.         ADD A,R0
  231.         MOV R1,A        ;這個是指針變量,指向當前SP的存放地址
  232.         MOV @R1,SP        ;記錄SP

  233.         MOV SP,TMP_SP ;壓棧完成,恢復SP

  234. ;優先級字節賦值初始值
  235.         MOV A,#TAB_PRI
  236.         ADD A,R0
  237.         MOV R1,A       
  238.         MOV @R1,#PRI_BYTE

  239.         ;清就緒態
  240.         CALL CLR_READY_BIT         
  241. RET                 

  242. ;子程序:以下初始化系統定時器 TIMER2 DEBUGED 120516 --------------------------
  243. INIT_TIMER:       
  244.                 ;TIMER2 SETUP       
  245.         MOV 0C8H,#00H        ;MOV T2CON,#00H
  246.         MOV 0C9H,#00H        ;MOV T2MOD,#00H
  247.         MOV 0CCH,#T2_VALUE_L        ;MOV TL2,#T2_VALUE_L
  248.         MOV 0CDH,#T2_VALUE_H        ;MOV TH2,#T2_VALUE_H
  249.         MOV 0CAH,0CCH         ;MOV RCAP2L,TL2
  250.         MOV 0CBH,0CDH        ;MOV RCAP2H,TH2
  251.                 ;TIMER0 SETUP
  252.         ANL 88H,#11101111B;TCON CLR TR0 : STOP TIMER0
  253.         ANL 89H,#11110000B ;TMOD(SET TIMER0)
  254.         ORL 89H,#00000001B ;TMOD(SET TIMER0) MODE:01 16BIT COUNT UP
  255.         MOV 8AH,#T0_VALUE_L        ;TL0
  256.         MOV 8CH,#T0_VALUE_H        ;TH0

  257.         ;TIMER1 SETUP
  258.         ANL 88H,#10111111B;TCON CLR TR0 : STOP TIMER1
  259.         ANL 89H,#00001111B ;TMOD(SET TIMER0)
  260.         ORL 89H,#00010000B ;TMOD(SET TIMER0)MODE:01 16BIT COUNT UP MODE:02 8BIT autoCOUNT UP
  261.         MOV 8bH,#T1_VALUE_L        ;TL0
  262.         MOV 8dH,#T1_VALUE_H        ;TH0
  263. RET          

  264. ;子程序:啟動TIMER0和TIMER2   ;DEBUGED 120516---------------------------------
  265. SYS_TIMER_START:
  266.         MOV SYS_TIME_H,#00
  267.         MOV SYS_TIME_L,#00
  268.         MOV IP,#00000000B        ;SET PRIORITY
  269.         MOV IE,#10101010B        ;SETB EA ;SETB ET2 ;SETB ET0 ET1  TO ENABLE INTERUPT OF TIMER2 AND TIMER0 AND TIMER1
  270.         ORL 88H,#01010000B           ;TCON SETB TR0,TR1 START TIMER0 TIMER1
  271.         ORL 0C8H,#00000100B           ;ORL T2CON,#00000100B        ;SETB TR2 TO START TIMER2
  272. RET

  273. ;;系統時間處理,在TIMER2中斷后跳進來
  274. ;系統時間處理有2大內容:1、比較各鬧鐘的目標時間是否到達,到達并且該任務有喚醒服務,就執行喚醒;2、時鐘刻度加一。
  275. SYS_TIME_RUN:
  276.         CLR EA
  277.         MOV TMP_SP,SP                ;保存A        保護現場
  278.         MOV SP,#SYS_SP  ;--------------------------------界面,以下系統區
  279.         MOV TMP_A,PSW
  280.         PUSH TMP_A
  281.         MOV TMP_A,A ;PUSH A
  282.         PUSH TMP_A
  283.         MOV TMP_A,B        ;PUSH B
  284.         PUSH TMP_A
  285.         PUSH 00H        ;PUSH R0
  286.         PUSH 01H        ;PUSH R1
  287.         PUSH 02H        ;PUSH R2
  288.         PUSH 03H        ;PUSH R3  

  289. ;處理CLK_ALARM字節、TAB_CLK數組
  290.         MOV A,CLK_ALARM
  291.         JZ STR00              ;沒有服務時跳過
  292.         MOV R0,#TAB_CLK
  293.         MOV R3,SYS_TIME_L
  294.         CALL PROC_CMP_BYTE ;低8位比較
  295.         MOV R1,A

  296.         MOV R0,#TAB_CLK
  297.         DEC R0
  298.         MOV R3,SYS_TIME_H
  299.         CALL PROC_CMP_BYTE ;高8位比較,對比結果保存到A 1表示相等 0表示不等

  300.         ANL A,R1 ;H和L的比較結果合并

  301.         MOV R1,A
  302.         MOV A,CLK_ALARM       
  303.         ANL A,R1 ;與喚醒服務合并

  304.         ORL READY_BYTE,A ;執行喚醒

  305.         MOV A,R1
  306.         CPL A
  307.         ANL CLK_ALARM,A ;清喚醒標志,表示完成喚醒

  308. STR00:
  309. ;16位系統時鐘+1                   放在后面處理,延時00時可立即生效
  310.         MOV A,SYS_TIME_L
  311.         INC SYS_TIME_L
  312.         INC A
  313.         JNZ $+4
  314.         INC SYS_TIME_H
  315.         ANL 0C8H,#01111111B   ;ANL T2CON,#01111111B        ;CLEAR TF2 清TIMER2中斷標志       

  316.         POP 03H        ;POP R3
  317.         POP 02H        ;POP R2
  318.         POP 01H        ;POP R1
  319.         POP 00H        ;POP R0
  320.         POP TMP_A
  321.         MOV B,TMP_A         ;POP B
  322.         POP TMP_A
  323.         MOV A,TMP_A        ;POP A                ;恢復現場
  324.         POP TMP_A
  325.         MOV PSW,TMP_A

  326.         MOV SP,TMP_SP ;---------------------------------------------界面,以上系統區
  327.         SETB EA
  328. RETI
  329. ;;;;;;;中斷返回

  330. ;用于刻度為500us,次數255的等待服務。只提供一個線程使用,出于系統消耗的考慮,500us中斷必須篇幅足夠小。
  331. ;定時器1中斷服務:500us中斷一次,無服務直接返回。有服務:次數(time_us字節)為0則讓waiting_task_p任務搶占(標志完成)。不為0時,減一。
  332. ;系統需要用一個字節的標志位2fh,用戶要避開。
  333. ;preempt_bit                bit        78h        ;是否搶占
  334. ;delay_sv_bit                bit        79h        ;定時器1中斷服務 標志 用于小刻度的延時需求
  335. ;preempt_task                EQU        3fh        ;搶占任務號 僅0-7有效,搶占后作廢,用于調度程序切換到指定的任務去。
  336. ;delay_times                equ        3eh        ;用于timer1計時刻度的次數

  337. sys_ms_svrs:
  338.         jb delay_sv_bit,smsv0 ;無服務直接返回
  339.         MOV 8bH,#T1_VALUE_L        ;TL1
  340.         MOV 8dH,#T1_VALUE_H        ;TH1
  341.         reti
  342. smsv0:
  343.         ;保護現場
  344.         mov tmp_a,a
  345.        
  346.         ;查delay_times次數:等于0時,置搶占任務preempt_bit
  347.         mov a,delay_times
  348.         jnz smsv1
  349.         setb preempt_bit ;置搶占位
  350.         clr delay_sv_bit        ;清服務位
  351.         ORL 88H,#00100000B        ;SETB TF0 ;SOFT INTERUPT TIMER0  TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
  352.         ;中斷不嵌套,本次中斷返回后進入系統中斷
  353.         MOV 8bH,#T1_VALUE_L        ;TL1
  354.         MOV 8dH,#T1_VALUE_H        ;TH1
  355.         mov tmp_a,a
  356.         reti
  357.         ;次數減一
  358. smsv1:        dec delay_times

  359.         ;恢復現場
  360.         mov a,tmp_a
  361.         MOV 8bH,#T1_VALUE_L        ;TL1
  362.         MOV 8dH,#T1_VALUE_H        ;TH1
  363. reti

  364. ;;上面要用到
  365. ;;子程序:基地址存R0(間隔1個字節的8個數組),與系統時鐘(H或L字節)R3進行比較,8次,結果存放在ACC對應的位里面,1表示相等
  366. PROC_CMP_BYTE:
  367.         MOV B,#0
  368.         MOV R2,#8
  369.         MOV A,#15
  370.         ADD A,R0
  371.         MOV R0,A
  372. PCB00:
  373.         MOV A,@R0
  374.         CJNE A,03H,PCB01
  375.         MOV A,B
  376.         SETB C
  377.         RRC A
  378.         JMP PCB02
  379. PCB01:
  380.         MOV A,B
  381.         CLR C
  382.         RRC A
  383. PCB02:        MOV B,A

  384.         DEC R0
  385.         DEC R0    ;間隔1字節的指針,從右到左
  386.        
  387.         DJNZ R2,PCB00
  388.         MOV A,B
  389. RET

  390. ;;調度程序,在timer0中斷后跳過來。
  391. ;;調度程序的內容:1、保護現場;2、存sp;3、喂狗、執行任務死刑、判斷加鎖;4、切換下一個就緒的任務指針;5、調取新任務的時間片設置到定時器;6、調取新sp;7、恢復現場;8、返回到新任務。
  392. SHARE_SYS:  ;保護現場先
  393.         MOV TMP_A,PSW ;PUSH PSW
  394.         PUSH TMP_A
  395.         MOV TMP_A,A ;PUSH A
  396.         PUSH TMP_A
  397.         MOV TMP_A,B        ;PUSH B
  398.         PUSH TMP_A
  399.         PUSH 00H        ;PUSH R0
  400.         PUSH 01H        ;PUSH R1
  401.         PUSH 02H        ;PUSH R2
  402.         PUSH 03H        ;PUSH R3
  403. ;區分主次任務
  404. ;TASK_CURT_P 大于2則跳過以下步驟
  405.         CLR C
  406.         MOV A,#2
  407.         SUBB A,TASK_CURT_P
  408.         JC SS00

  409.         PUSH 04H        ;PUSH R4
  410.         PUSH 05H        ;PUSH R5
  411.         PUSH 06H        ;PUSH R6
  412.         PUSH 07H        ;PUSH R7
  413.         PUSH DPL
  414.         PUSH DPH

  415. SS00:                              ;存SP到數組SP
  416.         MOV A,#TAB_SP
  417.         ADD A,TASK_CURT_P
  418.         MOV R0,A        ;這個是指針變量,指向當前SP的存放地址
  419.         MOV @R0,SP        ;記錄SP       
  420.        
  421. ;切換SP,以下進入系統區----------------------------------------------------------------INTERFACE
  422.         MOV SP,#SYS_SP                ;SP指向系統SP
  423.         CALL WDT            ;喂狗
  424.         CALL KILL_TASK ;根據DEAD_SIG字節,執行任務的死刑 ;-*

  425. ;是否上鎖,如果上鎖 LOCK_BYTE= 5AH 則不執行任務切換
  426.         MOV A,LOCK_BYTE
  427.         CJNE A,#5AH,SS04
  428.         JMP SS05
  429. SS04:
  430.         mov r1,task_sch_p        ;暫存
  431.         MOV R6,#10
  432. SELECT_P:                        ;選擇下一個任務
  433.         DJNZ R6,SS01                ;選擇次數計時,如果連續選擇超10次就得進節電模式了
  434.         MOV P1,#0FFH
  435.         ORL 87H,#02H                ;INTO POWER-DOWN MODE
  436.         LJMP SYS_START                ;醒來的話就重新開機咯

  437. ;切換任務指針(0-7) 全局變量TASK_sch_P 任務指針,僅此進行寫操作
  438. SS01:
  439.         INC TASK_sch_P
  440.         MOV R0,TASK_sch_P
  441.         CJNE R0,#8,SS02 ;超限
  442.         MOV TASK_sch_P,#0
  443. ;判就緒位,不在就緒態就跳回 SELECT_P,重復以上步驟
  444. SS02:       
  445.         MOV R0,TASK_sch_P
  446.         CALL GET_READY_BIT
  447.         JNC SELECT_P
  448.                                            ;調度結束,新的指針在task_sch_p
  449.         ;是否有搶占信號
  450.         jnb preempt_bit,ss06
  451.         mov a,preempt_task
  452.         clr c
  453.         subb a,#8
  454.         jnc ss06 ;搶占任務號無效(大于7)

  455.         mov a,preempt_task
  456.         cjne a,task_sch_p,ss07 ;如果搶占任務和本次應該調度的任務相同,則下一次不要再調這個任務了。(本次調度生效,否則退回上一次調度指針)。
  457.         jmp ss08
  458. ss07:       
  459.         mov task_sch_p,r1 ;恢復調度指針
  460. ss08:       
  461.         mov r0,preempt_task
  462.         call set_ready_bit ;搶占任務就緒位
  463.         mov task_curt_p,preempt_task  ;直接指定任務號,切換
  464.         clr preempt_bit
  465.         jmp ss05

  466. ss06:        mov task_curt_p,task_sch_p ;調度盤指針 確定調度指針和實際任務指針分離,解決搶占后調度不公平問題

  467. SS05:
  468. ;取優先字節地址
  469.         MOV A,#TAB_PRI
  470.         ADD A,TASK_CURT_P
  471.         MOV R0,A

  472. ;時間片賦值 ;RESET THE TIMER0       
  473.         MOV 8AH,#T0_VALUE_L        ;TL0
  474.         MOV 8CH,@R0                ;TH0  ;MOV TH0,@R0;選中后,優先級設置到時間片

  475. ;取SP_I --> SP
  476.         MOV A,#TAB_SP
  477.         ADD A,TASK_CURT_P
  478.         MOV R0,A
  479.         MOV SP,@R0

  480. ;以下退出系統態,回到新的任務態,恢復現場-------------------------------------------INTERFACE
  481. ;區分主次任務
  482. ;TASK_CURT_P 大于2則跳過以下步驟
  483.         CLR C
  484.         MOV A,#2
  485.         SUBB A,TASK_CURT_P
  486.         JC SS03

  487.         POP DPH
  488.         POP DPL
  489.         POP 07H                ;POP R7
  490.         POP 06H                ;POP R6
  491.         POP 05H        ;POP R5
  492.         POP 04H        ;POP R4

  493. SS03:
  494.         POP        03H        ;POP R3
  495.         POP 02H        ;POP R2
  496.         POP 01H        ;POP R1
  497.         POP 00H        ;POP R0
  498.         POP TMP_A
  499.         MOV B,TMP_A         ;POP B
  500.         POP TMP_A
  501.         MOV A,TMP_A        ;POP A
  502.         POP TMP_A
  503.         MOV PSW,TMP_A
  504. ;此時堆棧內當前應是中斷返回時的PC值,RETI可以返回。
  505.         ;ANL 88H,#11011111B        ;CLR TF0 ;SOFT INTERUPT TIMER0  TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
  506. RETI

  507. ;;子程序:根據被殺任務信號字節8位,從左到右每一位代表任務0-7是否要殺掉,1為殺死,0為不殺 來執行死刑
  508. ;執行內容:將該任務的內存區重新初始化(初始化后為休眠態),下次再輪到時,從頭開始。
  509. KILL_TASK:
  510.         MOV R3,#8
  511. KTA00:       
  512.         MOV A,DEAD_SIG
  513.         RRC A
  514.         MOV DEAD_SIG,A

  515.            JNC KTA01
  516.         MOV a,R3
  517.         DEC a       
  518.         mov r0,a
  519.         CALL TASKRAM_INIT                          
  520. KTA01:
  521.         DJNZ R3,KTA00
  522.         MOV DEAD_SIG,#0 ;清掉所有DEAD信息
  523. RET         

  524. ;;喂狗子程序
  525. WDT:
  526.         MOV 0A6H,WDT_BYTE        ;MOV WDTRST,WDT_BYTE  WDT_BYTE= 1EH OR E1H
  527.         MOV A,WDT_BYTE
  528.         CPL A                        ;取反
  529.         MOV WDT_BYTE,A
  530. RET

  531. ;;獲取就緒位:在調度程序中用到
  532. GET_READY_BIT:  ;任務號R0, 執行結束后,結果的位在C
  533.         MOV B,R0
  534.         INC B
  535.         MOV A,READY_BYTE
  536. GRB00:        RLC A
  537.         DJNZ B,GRB00       
  538. RET         

  539. ;提供的系統調用
  540. ;-----------------------------------------------------------------------------------------------

  541. ;子程序:修改任務的時間片,任務號在R0,優先字節(時間片)在R1,將優先字節寫入到數組
  542. SET_PRIBYTE:
  543.         MOV A,#TAB_PRI
  544.         ADD A,R0
  545.         MOV R0,A
  546.         MOV A,R1
  547.         MOV @R0,A
  548. RET

  549. ;子程序:回到調度程序        DEBUGED 120516
  550. WAITING:
  551.         NOP                        ;留給中斷響應的間隙
  552.         ORL 88H,#00100000B        ;SETB TF0 ;SOFT INTERUPT TIMER0  TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
  553. RET


  554. ;子程序:占用系統,任務在讀寫的時候不允許系統中斷,和frees配套使用
  555. OCCUPY:
  556.         ;ORL IE,#00000010B        ;ENABLE INTERRUPT OF TIMER0    方法1:關閉timer0的中斷
  557.         ANL 88H,#11101111B           ;TCON CLR TR0, STOP TIMER0  方法2:關閉timer0的計時
  558. RET


  559. ;子程序:釋放系統 和occupy配套使用,任務占用系統后應及時釋放
  560. FREES:
  561.         ;ANL IE,#11111101B        ;DISABLE INTERUPT OF TIMER0
  562.         ORL 88H,#00010000B           ;TCON SETB TR0, START TIMER0
  563. RET

  564. ;注意:occupy和free要配套用,他們之間就是臨界區,然而occupy會導致不進調度程序,不建議使用。建議用加鎖和解鎖來實現臨界區的操作。
  565. LOCK_SYS:
  566.         MOV LOCK_BYTE,#5AH
  567. RET

  568. UNLOCK_SYS:
  569.         MOV LOCK_BYTE,#0EEH
  570. RET


  571. ;精確的系統延時-將16位的延時數,每一位為一個時刻,存放在DPTR,計算目標時間,設置喚醒任務,休眠自己。等待系統時鐘在時間到了再喚醒你,誤差為一個調度周期。
  572. ;任務號為全局變量指針TASK_CURT_P
  573. ;延時步驟:1、將dptr個刻度和當前時間相加得到目標時間,存入到鬧鈴數組當前任務位置;2、設置本任務的喚醒服務位,當目標時間到達,系統時鐘會喚醒你;3、進入休眠態;
  574. DELAY_SYS:
  575. ;計算目標16位目標值,存放在TAB_CLK對應的位置
  576.         MOV A,SYS_TIME_L
  577.         ADD A,DPL
  578.         MOV DPL,A
  579.         MOV A,SYS_TIME_H
  580.         ADDC A,DPH                ;帶進位
  581.         MOV DPH,A

  582.         MOV A,#TAB_CLK
  583.         MOV R0,TASK_CURT_P
  584.        
  585.         ADD A,R0
  586.         ADD A,R0
  587.                         ;雙字節指針
  588.         MOV R0,A
  589.         MOV @R0,DPH
  590.         INC R0
  591.         MOV @R0,DPL

  592. ;設置喚醒位,在CLK_ALARM字節,8個位標志8個任務的喚醒服務,1為有服務。
  593.         MOV R0,TASK_CURT_P
  594.         CALL SET_ALARM_BIT

  595. ;清就緒位,在READY_BYTE
  596.         MOV R0,TASK_CURT_P
  597.         CALL CLR_READY_BIT

  598. ;回調度
  599.         ORL 88H,#00100000B       
  600. RET

  601. ;上面用到的子程序:設置喚醒服務的位,任務號預先放在R0
  602. SET_ALARM_BIT:
  603.         MOV B,R0
  604.         MOV A,#10000000B
  605.         INC B                        ;最小任務號為1

  606. SAB00:        DJNZ B,SAB01 ;循環左移
  607.         ORL CLK_ALARM,A
  608.         JMP SAB02

  609. SAB01:        RR A
  610.         JMP SAB00
  611. SAB02:
  612. RET


  613. ;子程序:任務自殺
  614. KILL_SELF:
  615.         MOV B,TASK_CURT_P
  616.         MOV A,#10000000B
  617.         INC B                        ;最小任務號為1

  618. KSF00:        DJNZ B,KSF01 ;循環左移
  619.         ORL DEAD_SIG,A
  620.         JMP KSF02

  621. KSF01:        RR A
  622.         JMP KSF00
  623. KSF02:          
  624. RET

  625. ;子程序:殺死,任務號存R0
  626. KILL_TASK_CALL:
  627.         MOV B,R0
  628.         MOV A,#10000000B
  629.         INC B                        ;最小任務號為1

  630. KTSK00:        DJNZ B,KTSK01 ;循環左移
  631.         ORL DEAD_SIG,A
  632.         JMP KTSK02

  633. KTSK01:        RR A
  634.         JMP KTSK00
  635. KTSK02:          
  636. RET

  637. ;子程序:清就緒位,就緒態字節 8位  從左到右每一位分別代表任務0-7是否就緒,1為就緒,0為休眠
  638. ;任務號存在R0               
  639. CLR_READY_BIT:
  640.         MOV B,R0
  641.         MOV A,#01111111B
  642.         INC B                        ;最小任務號為1

  643. CRB00:        DJNZ B,CRB01 ;循環左移
  644.         ANL READY_BYTE,A
  645.         JMP CRB02

  646. CRB01:        RR A
  647.         JMP CRB00
  648. CRB02:

  649. RET

  650. ;子程序:置就緒位,上面的相反操作 ;任務號存在R0
  651. SET_READY_BIT:
  652.         MOV B,R0
  653.         MOV A,#10000000B
  654.         INC B                        ;最小任務號為1

  655. SRB00:        DJNZ B,SRB01 ;循環左移
  656.         ORL READY_BYTE,A
  657.         JMP SRB02

  658. SRB01:        RR A
  659.         JMP SRB00
  660. SRB02:
  661. RET

  662. ;子程序:小刻度的延時功能(通過定時器1和搶占機制完成),次數放在r0
  663. delay_sys_us:       
  664.         mov delay_times,r0
  665.         mov preempt_task,task_curt_p ;占用的任務號預存
  666.         mov r0,task_curt_p
  667.         call clr_ready_bit ;延時期間要休眠
  668.         setb delay_sv_bit ;開啟延時服務       
  669.         ORL 88H,#00100000B        ;SETB TF0 ;SOFT INTERUPT TIMER0  TCON-->:TF1:TR1:TF0:TR0:IE1:IT1:IE0:IT0:
  670.         nop
  671.         nop ;中斷響應                  
  672. ret

  673. ;;;;;;;;;;;;;;;;;;伺服線程:任務7
  674. task_7:
  675.         mov r2,#77h
  676.         mov r3,#77h
  677. tk700:
  678.         mov r0,#01h
  679.         mov r1,#0eh
  680.         call delay16b
  681.         setb p1.7                        ;led 熄滅100ms       

  682.         mov r0,#0dh
  683.         mov r1,#0bdh
  684.         call delay16b

  685.         clr p1.7                        ;led 點亮900ms
  686.         jmp tk700
  687. jmp task_7

  688. ;r0:h r1:l  16位數的nop延時 一個周期為10.25us(全速) ,高8位放在r0,低8位放在r1
  689. delay16b:
  690. dll00:
  691.         mov a,r1
  692.         clr c
  693.         subb a,#1
  694.         mov r1,a
  695.         mov a,r0
  696.         subb a,#0 ;進位                16位數減一
  697.         mov r0,a

  698.         div ab ;純粹為了延時
  699.         div ab
  700.         nop
  701.         nop

  702.         mov a,r0
  703.         orl a,r1
  704.         jnz dll00
  705. ret

  706. ;注意:以上僅做了關于殺死、休眠、喚醒任務的調用,僅為了使用方便,實際使用時推薦使用更高效的邏輯方法:
  707. ;比如:要殺任務3和6,可以將dead_sig ORL 00010010 即可
  708. ;要休眠任務2和4,可以將ready_byte ANL 11010111 即可
  709. ;要喚醒任務1和7,可以將ready_byte ORL 10000001 即可
  710. ;SYSTEM END==============================================================line number of r0.92 is 750


  711. ;User's code  
  712. ;*********************************************************************
  713. ;project name: 360度指示器 豆豆的打開關上  
  714. ;designer: ut
  715. ;version: 1.0
  716. ;date: 16-11-16
  717. ;*********************************************************************       
  718. ;用戶在此定義自己的變量地址 及 標號 0-46d 0-2cH :一共45個字節,除去0-7,可以用8-2cH這37個字節
  719. ;需求:
  720. ;1\ 16位數字鍵,0-9 abcd * #
  721. ; 2\ 3位段碼LCD顯示
  722. ;  3\ 按下數字,插入到LCD的左側。
  723. ;   4\ 按下D(回車),LCD的數字作為角度值,電機轉動到指定角度,三位數字范圍0-999,對360取模,執行完畢后再輸入數字時LCD清零再插入。
  724. ;    5\ 按下A電機向上微調,按下B電機向下微調
  725. ;     6\ 按下C,LCD清零。
  726. ;      7\ 關機時,壁板歸位至270度位置,再切斷電源
  727. ;        8\ 開機時,壁板開啟到0度位置。

  728. ;task0: 主線程,開機初始化,啟動其他任務,主循環是不停的取鍵、取鍵值成功后處理鍵值。
  729. ;task1: 電機移動,始終試圖將當前位置靠近目標位置,直到達到為止。
  730. ;task2: 按鍵掃描,轉換為鍵值存入到緩沖區。

  731. ;處理鍵值:0-9,執行循環插入 BCD 數組,如果有清屏標志,則先清屏再插入。
  732. ;處理鍵值:A-B, 執行電機走12拍,約1度,A為正方向,B為反方向。
  733. ;處理鍵值:C, 將BCD全部設置為0
  734. ;處理鍵值:D,將BCD轉成一個16位數,再mod360運算,將結果寫到電機目標值。設置清屏標志。

  735. ;關于顯示:在主線程的循環中,涉及到BCD變化時,才會觸發顯示,顯示過程:將BCD碼轉換成段碼,將段碼輸出到HT1621驅動器。

  736. WR_1621                 BIT P3.6
  737. ;RD_1621                BIT P3.7
  738. DATA_1621                BIT P3.5
  739. CS_1621                 BIT P3.7

  740. BIAS         EQU 52H; //0B1000 0101 0010 1/3DUTY 4COM
  741. SYSDIS         EQU 0; //0B1000 0000 0000 關振系統蕩器和LCD偏壓發生器
  742. SYSEN         EQU 02H; //0B1000 0000 0010 打開系統振蕩器
  743. LCDOFF        EQU 04H; //0B1000 0000 0100 關LCD偏壓
  744. LCDON        EQU 06H; //0B1000 0000 0110 打開LCD偏壓
  745. XTAL         EQU 28H; //0B1000 0010 1000 外部接時鐘
  746. RC256         EQU 30H; //0B1000 0011 0000 內部時鐘
  747. TONEON         EQU 12H; //0B1000 0001 0010 打開聲音輸出
  748. TONEOFF         EQU 10H; //0B1000 0001 0000 關閉聲音輸出
  749. WDTDIS         EQU 0AH; //0B1000 0000 1010 禁止看門狗

  750. ;;;內存變量,范圍(8-2cH)
  751. nouse1                equ 20h ;預留給可尋址的位
  752. nouse2                equ 21h
  753. pool_key        equ 21h ;22,23,24;類似堆棧指針,前推一位
  754. p_key                equ 25h
  755. pool_bcd        equ 26h ;26,27,28;存放bcd碼 循環覆蓋
  756. p_bcd                equ 29h ;存放bcd指針,0-2 循環
  757. pool_print        equ 8h ;8 9 10 存放3位數碼管的段碼
  758. key_value        equ 0bh
  759. p_moto_H        equ 0ch
  760. p_moto_L        equ 0dh
  761. targ_moto_H        equ 0eh
  762. targ_moto_L        equ 0fh       
  763. p_step                equ 10h        ;表的指針
  764. p_deg                equ 11h


  765. ;定義位
  766. key_catched        bit 00h        ;獲取到一個按鍵后置位
  767. bcd_ready        bit 01h        ;bcd插入新值時置位
  768. moto_dir        bit 02h ;電機方向
  769. tmp_dir                bit 03h
  770. reset_bcd         bit 04h           ;重置bcd


  771. ;;用戶在這里寫初始化程序,在系統開機初始化時,被調用,注意:此處不可進行系統功能的調用
  772. user_init:
  773.         mov p1,#0f0h ;關電機
  774.         mov 08h,#0           ;段碼區
  775.         mov 09h,#0
  776.         mov 0ah,#0

  777.         mov 26h,#0        ;bcd區
  778.         mov 27h,#0
  779.         mov 28h,#0
  780.        
  781.         mov p_key,#0          ;表示無按鍵
  782.         mov p_bcd,#0
  783.         mov p_step,#0
  784.         mov p_deg,#0
  785.         mov p_moto_h,#0
  786.         mov p_moto_l,#0
  787.         mov targ_moto_h,#0
  788.         mov targ_moto_l,#0

  789.         clr reset_BCD
  790.         clr p2.2 ;開啟關機繼電器
  791.        
  792. ret



  793. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  794. ;;;;;;;;任務0   主線程                     ;
  795. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  796. ;主線程,1、從鍵盤緩沖池取一個按鍵; 2、處理該按鍵(數字鍵插入bcd區)其他鍵(步數增減、清零、回車)3、bcd轉換為段碼,隱去尾部0
  797. ;4、段碼輸出
  798. ;;;其他按鍵處理:步數增加一個幅度,不改變參數,步數減少一個幅度,不改變參數,bcd歸零,bcd轉換為目標值;;;;;;;;;;
  799. ;;-----------------------------------任務0
  800. task_0:
  801.         CALL Ht1621_Init;()          ; 上電初始化LCD驅動芯片
  802.         mov dptr,#tab_ht1621  
  803.         mov r3,#0
  804.         mov r5,#16
  805.         call Ht1621WrAllData;(0,Ht1621Tab,16)      ;清除1621寄存器數據,清屏
  806.         mov dptr,#tab_ht1621_dou
  807.         mov r3,#0
  808.         mov r5,#3
  809.         call Ht1621WrAllData;(0,Ht1621Tab,3);顯示    ;logo
  810.         ;LCD的掃描是不需要延時的

  811.         orl ready_byte,#01100000b ;開啟任務2:鍵盤掃描程序 ;開啟任務1:電機驅動:實際值逼近目標值
  812.        
  813.         ;臂板垂直向下270度為初始態(壓縮狀態,方便包裝和移動)在電氣驅動里面初始化。

  814. tsk0tv0:
  815.         jb p2.1,tsk04 ;關機信號判斷
  816.         clr key_catched
  817.         call catch_a_key
  818.         jnb key_catched,tsk0tv0 ;沒有獲取到鍵值

  819.         clr bcd_ready
  820.         call key_proc
  821.         jnb bcd_ready,tsk0tv0 ;不涉及到bcd變化
  822.        
  823.         call bcd2print        ;bcd 轉換為段碼
  824.         call hide_zero        ;消隱尾部的0


  825.         mov r3,#0
  826.         mov r4,#pool_print
  827.         mov r5,#3
  828. ;        mov lock_byte,#5ah                         ;加鎖
  829.         call print        ;輸出到LCD
  830. ;        mov lock_byte,#11h

  831.        
  832.         jmp tsk0tv0

  833. ;;關機流程,回到270度位置
  834. tsk04:       
  835.         mov dptr,#tab_ht1621_off
  836.         mov r3,#0
  837.         mov r5,#3
  838.         call Ht1621WrAllData;(0,Ht1621Tab,3);顯示off

  839.         mov targ_moto_l,#0eh
  840.         mov targ_moto_h,#01h ;電機目標為270

  841.         ;等待電機到點
  842. tsk040:        mov a,P_moto_l
  843.         cjne a,targ_moto_l,tsk040
  844.         mov a,p_moto_h
  845.         cjne a,targ_moto_h,tsk040


  846.         jnb p2.1,tsk0tv0 ;最后確認是否關機
  847.         setb p2.2            ;關機
  848.         ORL 87H,#02H                ;INTO POWER-DOWN MODE
  849.         LJMP SYS_START                ;醒來的話就重新開機咯


  850. ;步驟:1、指針為0則無按鍵值,2、取鍵值,指針-1,(臨界區);3處理鍵值;
  851. ;print_LCD: 函數 將3個字節的內容顯示到LCD  0-f 都能顯示 步奏:1、字節數轉換到 段碼字節 2發送給ht1621
  852. ;如何循環顯示,三個字節要構成單向環,abcabcabc,始終顯示指針后3位數字,加入新的字節時指針往前推。
  853. ;將上面的三個字節,轉為一個整數<=999,占2個字節。
  854. ;設為目標值
  855. ;當前值與目標值比較,不等于則靠近,等于則關閉。
  856. ;關機線程
  857. ;正或反,步數n個。函數

  858. jmp task_0       
  859. ;--------task0 end----------



  860. ;任務0子程序:從pool_key,p_key取一個鍵值,存放在key_value,并置位key_catched  
  861. catch_a_key:
  862.         ;clr key_catched
  863.         mov a,p_key
  864.         jnz cak00
  865.         ret                ;p_key 為0表示鍵值池空
  866. cak00:
  867.         ;臨界區:取一個鍵值
  868.         mov lock_byte,#5ah
  869.         mov a,#pool_key
  870.         add a,p_key
  871.         mov r0,a
  872.         mov a,@r0
  873.         dec p_key
  874.         mov lock_byte,#11;臨界區

  875.         mov key_value,a ;鍵值
  876.         setb key_catched
  877. ret

  878. ;任務0子程序:處理當前鍵值,小于10放到循環的bcd池里(pool_bcd,p_bcd),大于10則調用相關功能
  879. key_proc:
  880.         mov a,key_value
  881.         clr c
  882.         subb a,#10
  883.         jc kpc00

  884.         mov a,key_value
  885.         cjne a,#0ah,kpc01
  886.         ;0a鍵功能
  887.         call up_a_bit

  888. kpc01:        cjne a,#0bh,kpc02
  889.         ;0b鍵功能
  890.         call down_a_bit

  891. kpc02:        cjne a,#0ch,kpc03
  892.         ;0c鍵功能
  893.         call clr_bcd

  894. kpc03:        cjne a,#0dh,kpc04
  895.         ;0d鍵功能
  896.         setb reset_BCD ;回車后前面的數據在下次按鍵輸入后,清掉
  897.         call set_target
  898.         ret

  899. kpc00:        call keyv2bcd
  900. kpc04:       
  901. ret

  902. ;;;;;;第二層子程序
  903. clr_bcd:
  904.         mov r0,#pool_bcd
  905.         mov @r0,#0
  906.         inc r0
  907.         mov @r0,#0
  908.         inc r0
  909.         mov @r0,#0

  910.         setb bcd_ready       
  911. ret

  912. set_target: ;將bcd里的3位數字轉換16位數字,并存入到targ_moto_L, targ_moto_H 中
  913.        
  914.         mov r1,p_bcd ;0-2范圍
  915.         mov r3,#0
  916.         mov r4,#0

  917. ;;個位數
  918.         mov a,#pool_bcd
  919.         add a,r1
  920.        
  921.         mov r0,a
  922.         mov a,@r0 ; 取到bcd值 從個位數查起

  923.         mov r4,a  ;r3存H,r4存L  100* + 10*  + L
  924. ;;;重復
  925.         dec r1  
  926.         mov a,r1   
  927.         cjne a,#0ffh,ste00;
  928.         mov r1,#2 ;過界處理
  929. ste00:
  930.         mov a,#pool_bcd
  931.         add a,r1

  932.            mov r0,a
  933.         mov a,@r0
  934.         mov b,#10
  935.         mul ab                        ;十位數

  936.         clr c        ;16位加法
  937.         addc a,r4
  938.         mov r4,a
  939.         mov a,b
  940.         addc a,r3
  941.         mov r3,a
  942. ;;;重復以上
  943.         dec r1   
  944.         mov a,r1  
  945.         cjne a,#0ffh,ste01;
  946.         mov r1,#2 ;過界處理
  947. ste01:
  948.         mov a,#pool_bcd
  949.         add a,r1

  950.         mov r0,a
  951.         mov a,@r0
  952.         mov b,#100
  953.         mul ab                        ;百位數

  954.         clr c        ;16位加法
  955.         addc a,r4
  956.         mov r4,a
  957.         mov a,b
  958.         addc a,r3
  959.         mov r3,a

  960.         call targ_mod360

  961.    mov lock_byte,#5ah
  962.         mov targ_moto_L,r4
  963.         mov targ_moto_H,r3       
  964.    mov lock_byte,#11h
  965. ret

  966. ;目標值在r3,r4(HL),結果調整后還是在R3 R4
  967. targ_mod360:   ;輸入的bcd值(0-999)轉換為0-360度范圍,與360取模:求余
  968.                 mov dph,r3                ;暫存
  969.                 mov dpl,r4

  970.                 mov a,r4                            ;360d=0168h
  971.                 clr c
  972.                 subb a,#68h
  973.                 mov r4,a
  974.                 mov a,r3
  975.                 subb a,#01h
  976.                 mov r3,a

  977.                 jc tmd00;表示過頭了       
  978.                 jmp targ_mod360
  979. tmd00:
  980.                 mov r3,dph
  981.                 mov r4,dpl
  982. ret


  983. up_a_bit: ;電機向上微調一個距離 ;臨界區處理,禁止其他控制電機的操作
  984.         anl ready_byte,#10111111b ;休眠電機任務(task1)

  985.         mov r3,#12 ;走12拍
  986.        
  987. uab00:       
  988.         dec p_step
  989.         mov a,p_step
  990.         cpl a
  991.         jnz uab03 ;過0則回到7
  992.         mov p_step,#7
  993. uab03:
  994.         mov a,p_step
  995.         mov dptr,#tab_step
  996.         MOVC A,@a+dptr
  997.         anl P1,#11110000b        ;驅動電機
  998.         orl P1,a

  999.         call waiting
  1000.        
  1001.         djnz r3,uab00

  1002.         anl p1,#11110000b ;關電機
  1003.         orl ready_byte,#01000000b ;喚醒電機任務
  1004. ret

  1005. down_a_bit: ;電機向下微調一個距離 ;臨界區處理,禁止其他控制電機的操作
  1006.         anl ready_byte,#10111111b ;休眠電機任務(task1)

  1007.         mov r3,#12 ;走12拍
  1008. dab00:       
  1009.         inc p_step
  1010.         mov a,p_step
  1011.         cjne a,#8,dab03 ;過7則回到0
  1012.         mov p_step,#0
  1013. dab03:
  1014.         mov a,p_step
  1015.         mov dptr,#tab_step
  1016.         MOVC A,@a+dptr
  1017.         anl P1,#11110000b        ;驅動電機
  1018.         orl P1,a
  1019.        
  1020.         call waiting

  1021.         djnz r3,dab00

  1022.         anl p1,#11110000b ;關電機
  1023.         orl ready_byte,#01000000b ;喚醒電機任務
  1024. ret

  1025. ;任務0子程序:鍵值key_value放到循環的bcd池里(pool_bcd,p_bcd),置位bcd_ready *****orig
  1026. keyv2bcd:
  1027.         jnb reset_bcd,k2b10
  1028.         call clr_bcd       
  1029.         clr reset_bcd

  1030. k2b10:                mov b,key_value ;鍵值

  1031.         ;存放在pool_bcd
  1032.         mov a,p_bcd  ;容錯處理:p_bcd只能0-2,超范圍就置0
  1033.         clr c
  1034.         subb a,#3
  1035.         jnc k2b01

  1036.         ;先推指針
  1037.         inc p_bcd
  1038.         mov a,p_bcd
  1039.         cjne a,#3,k2b02 ;0-2循環處理
  1040. k2b01:        mov p_bcd,#0
  1041. k2b02:
  1042.         mov a,#pool_bcd
  1043.         add a,p_bcd
  1044.         mov r0,a
  1045.         mov @r0,b  ;再存鍵值       

  1046.         setb bcd_ready
  1047. ret

  1048. ;任務0子程序:從循環的bcd池里取最近3個值(pool_bcd,p_bcd),查表轉換成段碼,存放到段碼數組(pool_print);
  1049. bcd2print:
  1050.         mov r1,p_bcd    ;0-2范圍
  1051.         mov r2,#3        ;依次取3個數
  1052. b2p00:        mov a,#pool_bcd
  1053.         add a,r1
  1054.         mov r0,a
  1055.         mov a,@r0 ; 取到bcd值然后 查表獲取段碼

  1056.         mov dptr,#tab_ht1621_seg
  1057.         movc a,@a+dptr
  1058.         mov r3,a

  1059.         mov a,r2
  1060.         dec a
  1061.         add a,#pool_print
  1062.         mov r0,a
  1063.         mov a,r3
  1064.         mov @r0,a


  1065.         dec r1
  1066.         mov a,r1
  1067.         cpl a
  1068.         jnz b2p01
  1069.         mov r1,#2

  1070. b2p01:
  1071.         djnz r2,b2p00
  1072. ret

  1073. ;任務0子程序:將段碼數組pool_print的3個字節前面的0隱去
  1074. hide_zero:
  1075.         mov r3,#2 ;2次,個位數不管
  1076.         mov r0,#pool_print
  1077. hzo00:
  1078.         mov a,@r0
  1079.         cjne a,#5fh,hzo01  ; 5f 為0的段碼
  1080.         mov @r0,#0
  1081.         inc r0
  1082.         djnz r3,hzo00
  1083. hzo01:
  1084. ret

  1085. ;任務0子程序:將段碼數組pool_print的3個字節輸出到ht1621
  1086. print:   ;(uchar Addr,uchar *p,uchar cnt)  R3:ADDR r4:P R5:CNT

  1087.         CLR CS_1621;
  1088.         MOV A,#0A0H
  1089.         MOV R0,#3
  1090.         CALL Ht1621Wr_Data ;(0xa0,3); // - - 寫入數據標志101
  1091.         MOV A,R3
  1092.         RLC A
  1093.         RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數據
  1094.         MOV R0,#6
  1095.         CALL Ht1621Wr_Data

  1096. prt00:
  1097.         mov a,r4
  1098.         mov r0,a
  1099.         MOV A,@r0 ;取段碼
  1100.         MOV R0,#8
  1101.         CALL Ht1621Wr_Data         ;Ht1621Wr_Data(*p,8); // - - 寫入數據
  1102.         INC r4
  1103.         DJNZ R5,prt00       

  1104.         SETB CS_1621
  1105.         CALL delay_a_while

  1106. RET

  1107. ;任務0清零表
  1108. TAB_HT1621:
  1109.         DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;
  1110. TAB_HT1621_off:
  1111.         DB 033h,078h,078h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;
  1112. TAB_HT1621_dou:
  1113.         DB 0b7h,0b3h,093h,055h,055h,055h,000h,05h,05h,05h,0,0,0,0,0,0;

  1114. ;任務0段碼表,依據硬件線序確定
  1115. tab_ht1621_seg:
  1116. db 5fh; 0
  1117. db 06h; 1
  1118. db 3dh; 2
  1119. db 2fh; 3
  1120. db 66h; 4
  1121. db 6bh; 5
  1122. db 7bh; 6
  1123. db 0eh; 7
  1124. db 7fh; 8
  1125. db 6fh; 9
  1126. db 7eh; A
  1127. db 73h; b
  1128. db 31h; c
  1129. db 37h; d
  1130. db 79h; E
  1131. db 78h; F
  1132. db 33h; o




  1133. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  1134. ;;;;;任務1:電機驅動,實際值逼近目標值      ;
  1135. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  1136. ;實際值:p_moto_L、p_moto_H、目標值targ_moto_L、targ_moto_H 最大360度
  1137. ;步驟:目標值-實際值,記錄符號(正負)到moto_dir, 結果等于0時,關閉電機,返回;結果小于180時,moto_dir反置(尋找最短路徑)
  1138. ;讓電機逼近一步,實際值+1或-1。返回。
  1139. ;;------------------------------------------任務1
  1140. task_1:
  1141.         mov p_moto_L,#0eh
  1142.         mov p_moto_H,#01h ; 0-359范圍
  1143.         mov targ_moto_L,#0
  1144.         mov targ_moto_H,#0 ; 0-999范圍 ;要mod360處理,變為0-359范圍
  1145.                 
  1146. ;臂板垂直向下270度為初始態(壓縮狀態,方便包裝和移動)在電氣驅動里面初始化。

  1147. tsk100:         
  1148.                 ;16位減法 目標值-當前值,默認為+
  1149.         clr moto_dir ;默認電機方向
  1150.         clr c
  1151.         mov a,targ_moto_L
  1152.         subb a,P_moto_L
  1153.         mov b,a
  1154.         mov a,targ_moto_H
  1155.         subb a,P_moto_H ;結果高位在a,低位在b

  1156.         jnc tsk105 ;結果為負數的話  被減數+360,再減
  1157.         clr c
  1158.         mov a,targ_moto_L
  1159.         addc a,#68h
  1160.         mov r0,a
  1161.         mov a,#01h
  1162.         addc a,targ_moto_h
  1163.         mov r1,a

  1164.         clr c                           ;重新算一次
  1165.         mov a,r0
  1166.         subb a,P_moto_L
  1167.         mov b,a
  1168.         mov a,r1
  1169.         subb a,P_moto_H ;結果高位在a,低位在b

  1170. tsk105:
  1171.           mov r1,a  ;H
  1172.         mov r0,b  ;L  暫存結果(正偏差:0-359)

  1173.         jnz tsk104
  1174.         mov a,b
  1175.         jnz tsk104
  1176.         ;結果為0 關閉電機 并返回
  1177.         anl p1,#11110000b         ;驅動電機       
  1178.         orl ready_byte,#11100001b           ;開啟其他任務
  1179.         jmp tsk100       

  1180. tsk104:         ;偏差如果大于180,電機方向反向  
  1181.         anl ready_byte,#11011111b         ;暫停鍵盤線程
  1182.         clr c
  1183.         mov a,r0
  1184.         subb a,#180
  1185.         mov b,a
  1186.         mov a,r1
  1187.         subb a,#0   ;16位減去180

  1188.         jc tsk101 ;  
  1189.         cpl moto_dir

  1190. tsk101:                
  1191.         call moto_move

  1192. ;實際位置指針調整一位
  1193.         jb moto_dir,tsk102
  1194.         clr c
  1195.         mov a,p_moto_L
  1196.         addc a,#1
  1197.         mov p_moto_L,a

  1198.         clr a
  1199.         addc a,p_moto_H
  1200.         mov p_moto_H,a

  1201.         cjne a,#01h,tsk100 ;如果等于360則歸零
  1202.         mov a,p_moto_L
  1203.         cjne a,#68h,tsk100
  1204.         mov p_moto_L,#0
  1205.         mov p_moto_H,#0
  1206.         jmp tsk100

  1207. tsk102:
  1208.         clr c
  1209.         mov a,p_moto_L
  1210.         subb a,#1
  1211.         mov p_moto_L,a
  1212.         mov a,p_moto_H
  1213.         subb a,#0
  1214.         mov p_moto_H,a

  1215.         cpl a  ;如果等于ffff,則改為359
  1216.         jnz tsk100
  1217.         mov a,p_moto_L
  1218.         cpl a
  1219.         jnz tsk100

  1220.         mov p_moto_L,#67h
  1221.         mov p_moto_H,#01h

  1222. jmp tsk100


  1223. jmp task_1
  1224. ;----------task1 end-------}}}}--

  1225. ;任務1子程序:電機走一度,方向在moto_dir,
  1226. ;涉及2張表:表1,45度折合512拍表,tab_deg,p_deg(0-44),  表2,8拍表tab_step,p_step(0-7)
  1227. ;步驟:1根據方向調整度數指針,取一個度數拍數 2根據方向走N拍并更新拍數指針;
  1228. moto_move:
  1229.         mov c,moto_dir ;防止過程中改變方向
  1230.         mov tmp_dir,c

  1231. mmv00:
  1232.         jb tmp_dir,mmv01 ;正方向
  1233.         dec p_deg
  1234.         mov a,p_deg
  1235.         cpl a
  1236.         jnz mmv03 ;過0則回到44
  1237.         mov p_deg,#44
  1238. mmv03:
  1239.         mov a,p_deg
  1240.         mov dptr,#tab_deg
  1241.         movc a,@a+dptr
  1242.         jmp mmv02
  1243. mmv01:
  1244.         inc p_deg        ;反方向
  1245.         mov a,p_deg
  1246.         cjne a,#45,mmv04 ;過44則回到0
  1247.         mov p_deg,#0
  1248. mmv04:
  1249.         mov a,p_deg
  1250.         mov dptr,#tab_deg
  1251.         movc a,@a+dptr
  1252. mmv02:
  1253.         mov r3,a
  1254.         call move_n_step

  1255. ret


  1256. move_n_step:;方向在tmp_dir,步數在r3,  表2,8拍表tab_step,p_step(0-7)

  1257. mns00:
  1258.         jb tmp_dir,msn01 ;正方向
  1259.         dec p_step
  1260.         mov a,p_step
  1261.         cpl a
  1262.         jnz msn03 ;過0則回到7
  1263.         mov p_step,#7
  1264. msn03:
  1265.         mov a,p_step
  1266.         mov dptr,#tab_step
  1267.         MOVC A,@a+dptr
  1268.         jmp msn02
  1269. msn01:
  1270.         inc p_step        ;反方向
  1271.         mov a,p_step
  1272.         cjne a,#8,msn04 ;過7則回到0
  1273.         mov p_step,#0
  1274. msn04:
  1275.         mov a,p_step
  1276.         mov dptr,#tab_step
  1277.         MOVC A,@a+dptr
  1278.        
  1279. msn02:       
  1280.         anl p1,#11110000b         ;驅動電機
  1281.         orl p1,a

  1282.         ;至此,電機走動了一拍,下面是延時:需要2ms,采用不可重入的delay_sys_us完成
  1283.         ;CALL delay_a_step
  1284.         ;call waiting
  1285.         ;mov dptr,#10
  1286.         ;call delay_sys
  1287.        
  1288.         mov r0,#3
  1289.         call delay_sys_us
  1290.        
  1291.         djnz r3,mns00 ;走r3步數
  1292. ret

  1293. tab_step: ;步進電機8拍表,循環使用
  1294.         DB 1001B,0001B,0011B,0010B,0110B,0100B,1100B,1000B

  1295. tab_deg: ;45度折合512拍表,循環使用
  1296.         db 11,11,12,11,11,12,11,11,12,11,12,12,11,11,12
  1297.         db 11,11,12,11,11,12,11,11,12,11,11,12,11,11,12
  1298.         db 11,11,12,12,11,12,11,11,12,11,11,12,11,11,12



  1299. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  1300. ;                            ;
  1301. ;;;;;任務2:按鍵掃描         ;
  1302. ;;標準的4*4按鍵掃描程序,鍵值為0-fh,設置3個字節的緩沖pool_key,設置一個緩沖指針p_key(0-3),當緩沖區滿,丟棄新的按鍵
  1303. task_2:  
  1304. ;初始化
  1305.         mov p_key,#0        ;0表示緩沖區空,3表示滿了,類似堆棧指針,注意定義時往前推一格

  1306. scan_key:
  1307.         mov a,p_key
  1308.         cjne a,#3,tk301  ;緩沖區滿了
  1309.         jmp scan_key
  1310. tk301:
  1311.         anl p0,#00001111b           ;p0.7 p0.6 p0.5 p0.4 為豎線 從左到右
  1312.         mov a,p2                ;p2.4 p2.5 p2.6 p2.7 為橫線 從上到下
  1313.         orl a,#00001111b

  1314.         cpl a
  1315.         jz scan_key  ;快速判斷,無任何按鍵時不要去挨個掃了,這樣響應更快

  1316.         mov r3,#4          ;豎線循環4次
  1317.         mov a,#01111111b
  1318. tk300:
  1319.         orl p0,#11110000b
  1320.         anl p0,a
  1321.         mov r2,a ;暫存

  1322.         jb p2.4,tk303
  1323.         mov r0,#0
  1324.         call take_keyv

  1325. tk303:       
  1326.         jb p2.5,tk304
  1327.         mov r0,#1
  1328.         call take_keyv

  1329. tk304:       
  1330.         jb p2.6,tk305
  1331.         mov r0,#2
  1332.         call take_keyv

  1333. tk305:       
  1334.         jb p2.7,tk302
  1335.         mov r0,#3
  1336.         call take_keyv

  1337. tk302:       
  1338.         mov a,r2
  1339.         rr a         ;下一個豎線
  1340.         djnz r3,tk300

  1341.         jmp scan_key
  1342.        

  1343. jmp task_2
  1344. ;----------------------------task2 end---}}}}}-----
  1345. ;
  1346. tab_key16:          ;二位數組的4*4鍵值表
  1347. db 0ah,0bh,0ch,0dh
  1348. db 03,06,09,0fh
  1349. db 02,05,08,0
  1350. db 01,04,07,0eh

  1351. ;子程序:查表取鍵值,r3:行號,r0:列號
  1352. take_keyv:
  1353.         mov a,r3 ;1-4 轉為 0-3
  1354.         dec a
  1355.        
  1356.         mov dptr,#tab_key16
  1357.         mov b,#4
  1358.         mul ab ;調整基地址
  1359.         add a,dpl
  1360.         mov dpl,a
  1361.         clr a
  1362.         addc a,dph  ;進位考慮
  1363.         mov dph,a

  1364.         mov a,r0
  1365.         movc a,@a+dptr ;查表取到對應的鍵值 在b
  1366.         mov b,a

  1367.         ;存到緩沖池
  1368.         mov a,p_key
  1369.         cjne a,#3,tkv00  ;緩沖區滿了
  1370.         ret
  1371. tkv00:
  1372.        
  1373.         mov lock_byte,#5ah  ;;臨界區,加鎖
  1374.         inc p_key
  1375.         mov a,p_key
  1376.         add a,#pool_key
  1377.         mov r1,a
  1378.         mov @r1,b        ;存緩沖
  1379.         mov lock_byte,#11h        ;;;;退臨界區,解鎖

  1380.         mov a,#30
  1381.         add a,sys_time_l ;設置300ms時限
  1382.         mov r1,a
  1383. tkv01:                          ;按鍵釋放時立即返回,連續按住時要間隔延時

  1384.         ;超時退出
  1385.         mov a,sys_time_l  
  1386.         clr c
  1387.         subb a,r1
  1388.         clr c
  1389.         subb a,#5                ;時間模糊處理,只要接近目標時間50ms以內,就算超時,擔心有錯過時鐘刻度的考慮
  1390.         jc tkv02       

  1391.     ;anl p0,#00001111b           ;p0.7 p0.6 p0.5 p0.4 為豎線 改變p0可能會擾亂豎線掃描
  1392.         mov a,p2                ;p2.4 p2.5 p2.6 p2.7 為橫線 從上到下
  1393.         orl a,#00001111b         
  1394.         cpl a
  1395.         jnz tkv01  ;判斷是否釋放(范圍為本行)


  1396. tkv02:       
  1397. ret





  1398. ;;--------------任務3-----次任務,注意保護范圍:psw\a\b\r0-r3  以及最大嵌套2個call       
  1399. ;;-----------------------------------任務3
  1400. ;;步進電機每走一步的延時喚醒線程
  1401. task_3:       

  1402.         jmp task_3         
  1403. ;-----------------------------------------------task3 end--------------
  1404. ;

  1405. ;;-----------------------------------任務4
  1406. task_4:         
  1407.         jmp task_4         
  1408. ;-----------------------------------------------task5 end--------------
  1409. ;

  1410. ;;-----------------------------------任務5
  1411. task_5:
  1412.         jmp task_5

  1413. ;-----------------------------------------------task5 end--------------
  1414. ;


  1415. ;;-----------------------------------任務6
  1416. task_6:

  1417.         jmp task_6
  1418. ;-----------------------------------------------task6 end--------------
  1419. ;




  1420. ;
  1421. ;--------------------------------------------------------------------------------------
  1422. ;以下用戶子程序區          
  1423. ;;ht1621b driver  RD WR DATA CS
  1424. ;/********************************************************
  1425. ;函數名稱:void Ht1621_Init(void)
  1426. ;功能描述: HT1621初始化

  1427. Ht1621_Init:

  1428. SETB CS_1621;
  1429. SETB WR_1621;
  1430. SETB DATA_1621;
  1431. MOV dptr,#5;
  1432. CALL delay_sys; // - - 延時使LCD工作電壓穩定
  1433. MOV R1,#BIAS
  1434. CALL Ht1621WrCmd;
  1435. MOV R1,#RC256
  1436. CALL Ht1621WrCmd; // - - 使用內部振蕩器
  1437. MOV R1,#SYSDIS
  1438. CALL Ht1621WrCmd; // - - 關振系統蕩器和LCD偏壓發生器
  1439. MOV R1,#WDTDIS
  1440. CALL Ht1621WrCmd;; // - - 禁止看門狗
  1441. MOV R1,#SYSEN; // - - 打開系統振蕩器
  1442. CALL Ht1621WrCmd;
  1443. MOV R1,#LCDON; // - - 打開聲音輸出
  1444. CALL Ht1621WrCmd;

  1445. ret

  1446. ;**寫數據到ht1621,數據存A,發送位數存R0*****************************************************/
  1447. Ht1621Wr_Data:;(uchar Data,uchar cnt) A:DATA R0:number of send-bit

  1448. CLR WR_1621;
  1449. CALL delay_a_while
  1450. RLC A;
  1451. MOV DATA_1621,C;
  1452. CALL delay_a_while
  1453. SETB WR_1621;
  1454. CALL delay_a_while
  1455. DJNZ R0,Ht1621Wr_Data

  1456. ret

  1457. ;****寫命令給HT1621****************************************************
  1458. Ht1621WrCmd: ;(uchar Cmd) cmd byte store in R1
  1459. CLR CS_1621
  1460. CALL delay_a_while
  1461. MOV A,#80H
  1462. MOV R0,#4
  1463. CALL Ht1621Wr_Data; // - - 寫入命令標志1000
  1464. MOV A,R1
  1465. MOV R0,#8
  1466. CALL Ht1621Wr_Data; // - - 寫入命令數據
  1467. SETB CS_1621
  1468. CALL delay_a_while

  1469. RET

  1470. ;*******************************************************
  1471. ;函數名稱:void Ht1621WrOneData(uchar Addr,uchar Data)
  1472. ;功能描述: HT1621在指定地址寫入數據函數
  1473. ;全局變量:無
  1474. ;參數說明:Addr為寫入初始地址,Data為寫入數據
  1475. ;返回說明:無
  1476. ;說 明:因為HT1621的數據位4位,所以實際寫入數據為參數的后4位
  1477. ;********************************************************/
  1478. Ht1621WrOneData:;(uchar Addr,uchar Data)        R2,R3

  1479. CLR CS_1621;
  1480. MOV A,#0A0H
  1481. MOV R0,#3
  1482. CALL Ht1621Wr_Data;(0xa0,3); // - - 寫入數據標志101
  1483. MOV A,R2
  1484. RLC A
  1485. RLC A
  1486. MOV R0,#6
  1487. CALL Ht1621Wr_Data;(Addr<<2,6); // - - 寫入地址數據

  1488. MOV A,R3
  1489. RLC A
  1490. RLC A
  1491. RLC A
  1492. RLC A
  1493. MOV R0,#4
  1494. CALL Ht1621Wr_Data;(Data<<4,4); // - - 寫入數據
  1495. SETB CS_1621
  1496. CALL delay_a_while

  1497. RET

  1498. ;*********函數名稱:void Ht1621WrAllData(uchar Addr,uchar *p,uchar cnt)
  1499. ;功能描述: HT1621連續寫入方式函數
  1500. ;參數說明:Addr為寫入初始地址,*p為連續寫入數據指針,
  1501. ;cnt為寫入數據總數
  1502. ;返回說明:無
  1503. ;說 明:HT1621的數據位4位,此處每次數據為8位,寫入數據
  1504. ;總數按8位計算
  1505. ;********************************************************/
  1506. Ht1621WrAllData:;(uchar Addr,uchar *p,uchar cnt)  R3:ADDR DPTR:P R5:CNT

  1507. CLR CS_1621;
  1508. MOV A,#0A0H
  1509. MOV R0,#3
  1510. CALL Ht1621Wr_Data ;(0xa0,3); // - - 寫入數據標志101
  1511. MOV A,R3
  1512. RLC A
  1513. RLC A ;Ht1621Wr_Data(Addr<<2,6); // - - 寫入地址數據
  1514. MOV R0,#6
  1515. CALL Ht1621Wr_Data

  1516. hwd00:
  1517. CLR A
  1518. MOVC A,@A+DPTR
  1519. MOV R0,#8
  1520. CALL Ht1621Wr_Data         ;Ht1621Wr_Data(*p,8); // - - 寫入數據
  1521. INC DPTR
  1522. DJNZ R5,hwd00       

  1523. SETB CS_1621
  1524. CALL delay_a_while

  1525. RET

  1526. ;;;----------------

  1527. delay_a_while:
  1528.         mov r3,#50
  1529. daw00:
  1530.         nop
  1531.         djnz r3,daw00
  1532. ret

  1533. delay_a_step:
  1534.         mov r6,#0ffh
  1535. dast00:
  1536.         nop
  1537.         nop
  1538.         nop
  1539.         djnz r6,dast00
  1540. ret
  1541. ;---------------------------------------以下用戶數據表區------------------
  1542. ;用戶可以在此定義所需要的數據表



  1543. end
  1544. ;*******************************************the end**********************
復制代碼



評分

參與人數 1黑幣 +5 收起 理由
zhangli019 + 5 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏1 分享淘帖 頂 踩
回復

使用道具 舉報

沙發
ID:160895 發表于 2017-1-9 10:38 | 只看該作者
巨大的篇幅,看完不容易。
回復

使用道具 舉報

板凳
ID:160895 發表于 2017-1-9 16:21 | 只看該作者
長篇大論,還不錯,
回復

使用道具 舉報

地板
ID:346965 發表于 2018-6-8 09:49 | 只看該作者
匯編果然看不懂
回復

使用道具 舉報

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

本版積分規則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 人人九九精| 日韩a在线 | 日日摸日日爽 | 91精品久久久久久久久久入口 | 欧美三区视频 | 亚洲黄色一级毛片 | 日本精品视频一区二区 | 久久久99国产精品免费 | 综合国产在线 | 特级a欧美做爰片毛片 | 男女网站免费观看 | 91久久| 毛片一区二区三区 | 国产精品高潮呻吟 | 欧美日韩精品中文字幕 | 亚洲精品黄色 | 欧美日韩亚洲视频 | 成人影音 | 一区二区三区四区不卡视频 | 久久久一| 黄色电影在线免费观看 | 亚洲一区二区三区在线免费观看 | 国产区精品| 精品国产青草久久久久福利 | 网站一区二区三区 | 美女天天操 | 男人天堂国产 | 国产精品视频网址 | 九九热在线免费观看 | 日韩欧美在线不卡 | 成人日韩精品 | 欧美一区二区三 | 免费观看一区二区三区毛片 | 在线播放亚洲 | 国产精品一区二区在线 | 一级在线视频 | 91av亚洲 | 欧美日韩精品一区二区三区蜜桃 | 日本在线黄色 | 一区二区三区在线电影 | 成人在线小视频 |