Coos簡介
最近開始為51寫一個分時任務管理,取名coos,終于也差不多把功能完善了,趁這幾天還有點沖動發布上來吧。Coos最大的特色:完全用C語言實現。讓我不禁原創一句:敲一敲鍵盤,不留下一句匯編。呵呵~~~接下來吐槽一下先。
吐槽 在51上實現任務管理?省省吧,有時間不如去搞arm,Linux?那才是高深技術!如果你是這樣想的,那這篇文章你沒必要再看下去了…….如果你覺得不把51玩個透徹,使盡腦汁就是要把它發揮到極致,就像PC上很多高手,聽說有不少人拿CPU來超頻,玩得可以聞到燒焦味,偶爾主板冒起煙來,甚至時不時地噴出火花(那種感覺是帥啊~~)。就算不是發燒友,但至少溫度稍高一點點吧。如果是這樣的話,那么,兄弟………………..知音吶!嗚嗚嗚,海內難道真的存知己?
還是轉入正題吧。貌似已經有不少高手為51寫系統了,ucos也算是可以移植到51上(我沒測試過),嵌入式實時內核,但是畢竟占用資源太多,而且讓我感覺學術性大于使用性,好像很少人用?(至少我沒用過)。俺的分時任務管理,算不上系統,估計也經不起什么考驗,不過俺還是寫了出來,畢竟自己寫的東西自己用著才舒服嘛。老是用別人提供的玩久了也無聊。慢慢完善俺的coos吧,下次在把它移植到stm32上,嘿嘿~~
為什么寫這個分時任務管理?嗯,這個問題問得好。ǹ跉鈦碜9527…)。這學期上了一門課,微型計算機原理與接口技術,加上看了ucos一點點任務管理代碼,心里那個是不淡定啊。感覺任務管理也不難,何不嘗試自己寫一個任務管理?再加上本人實在無聊,就決定寫出這個無聊的任務管理了。
Coos的命名 先說一下命名,本人網名coolweedman,操作系統簡寫為OS,coolweedman+OS讓我只能想到coos,所以就命名coos了,譯音“褲子”,在51上跑coos,我就想到了給51穿一條褲子呵呵,習慣裸跑,偶爾加條褲子感覺還行。
Coos的任務管理原理 說一下任務管理的原理。學過微機的同學就知道,無非是直接或間接去修改PC指針。沒錯。我這里是間接修改PC指針的。怎么個間接法?利用出棧來修改!如果入棧的時候PC指針入棧,那么出棧的時候就是對PC指針的修改了。只要控制出棧時PC指針的值,就可以實現任務調度了。具體做法是: 1. 定時器產生中斷(分時的依據) 2. 中斷引起入棧,(PC指針入棧,其他寄存器也有) 3. 定時器響應函數判斷需要執行哪個任務 4. 修改SP指針為相應任務的SP,(注意不是PC指針) 5. 定時器按照相應的SP出棧,實現間接修改PC指針(任務調度) 大概思路就是這樣,算是偽代碼吧。然后把相應的偽代碼用C語言實現,就完成了(當然其中還有很多細節部分,編寫代碼時才會發現)。
Coos的任務管理機制 再說一下任務管理機制吧。分時任務管理,一個周期20個時間片(多少個可配置,時間片具體時間也可配置),在這20個時間片上,運行任務。coos最多管理8個任務(多少個可配置),每個任務時間片在創建時候指定。比如有5個任務,時間片分別為2,3,10,11,6。那么coos執行任務是這樣的。
第一次:
任務1執行1個時間片
任務2執行1個時間片
任務3執行1個時間片
任務4執行1個時間片
任務5執行1個時間片
(已占用5個時間片)
第二次:
任務1執行1個時間片
任務2執行1個時間片
任務3執行1個時間片
任務4執行1個時間片
任務5執行1個時間片
(已占用10個時間片)
第三次:(任務1的2個時間片執行完,不執行任務1)
任務2執行1個時間片
任務3執行1個時間片
任務4執行1個時間片
任務5執行1個時間片
(已占用14個時間片)
第四次:(任務2的3個時間片執行完,不執行任務2)
任務3執行1個時間片
任務4執行1個時間片
任務5執行1個時間片
(已占用17個時間片)
第五次:
任務3執行1個時間片
任務4執行1個時間片
任務5執行1個時間片
(已占用20個時間片)
一次系統周期結束(一次任務循環),coos重新刷新時間片。也就是5個任務,時間片分別為2,3,10,11,6。然后按照任務時間片去執行。
這樣就實現了所謂的分時任務管理了。因為是分時,所以如果有多個任務,沒有一個任務可以占用全部CPU,各個任務都是平等的,呵呵。
這樣的效果是,如果有多個任務,那么,每個任務占用CPU必然減小,就像平時Windows開了太多任務,就會覺得有點卡。哈哈~~~
Coos的使用 最后說一次怎么使用吧。coos包含文件:(6個) Coos_typedef.h 類型定義
Coos_conf.h coos配置,相當于裁剪
Coos.c coos實現的源文件
Coos.h coos實現的頭文件
Coos_hook.h coos鉤子函數頭文件
Coos_hook.c coos鉤子函數源文件
包含這些文件之后,主函數只要調用幾個coos函數就可以實現分時任務管理了。舉個例子:
void main(void)
{
OS_Init(); //coos初始化
OS_TaskCreate(Task_One, Stk_TaskOne, 5,0);
OS_TaskCreate(Task_Two Stk_TaskTwo, 2, 1);
OS_TaskCreate(Task_Thr, Stk_TaskThr, 7, 2);
OS_Start(); //coos開始運行, 任務交給coos管理
}
結束語 簡介到此結束,再慢慢為coos寫一份說明書吧,呵呵,源碼開發下載,上傳在我的百度網盤上。 歡迎同學們下載,當然coos會有bug,但是我還沒有發現,還請同學們多多支持多多反饋。!
目錄
第一章前言 第二章coos簡介 第三章任務管理詳解 第四章任務管理的幕后操作 第五章任務管理的幕后操作 第六章coos條件編譯 第七章coos的配置 裁剪 類型定義
前言 (以下所說的51特指STC89C52RC,晶振12M)
經過了七七四十九天的閉關修煉,終于把51的分時任務管理實現了?
有點夸張。從產生為51寫任務管理的念頭,到開始著手寫代碼,應該有3,4天的時間。然后接下來就一直寫個不停了。本來是從開始寫到第二天,功能就實現了,剩下的就是慢慢完善。但是,事情總是沒那么順利,發現了一個又一個的bug,修改完一個又有另一個,再修改一個。這樣持續了好幾天。有時候一整天沒解決一個bug …
修改bug就是每天晚上睡不好,老是想著coos的bug,失眠是必須的。到最后發現了一個致命的bug,發現不得不用匯編來解決,然后就嘗試著C語言嵌入匯編,但卻偏偏一個C語言函數匯編出來的代碼執行有錯…讓我只能懷疑編譯器的問題的,因為本來沒嵌入匯編那個函數是正常的,嵌入匯編后,函數的返回值傳遞出錯了(編譯器把返回值放到R3還是哪個忘記了,然后卻從R4取返回值?問題我至今仍未能理解),我只能崩潰了。最后終于還是上火了,感冒發燒頭痛…到校醫院看病額。
把coos擱一邊一兩個星期了,心里那個還真是不爽。把代碼全部重寫,并用回了最初的設計思想,任務調度完全在中斷里面實現,讓中斷負責任務切換所有工作,參考著自己上次寫的代碼,老樣子,功能是一兩天就實現了,幸運的是這次調試沒發現什么bug。然后,這樣一擱又是好多天。
在電腦上安裝win8來玩玩,挺新鮮的,界面也挺好看。期間曾一度因為在win8上運行不了RVMDK而讓我再度崩潰多次。最終放棄win8,用回win7。
重新在win7上把coos完善。也就是多寫了一些函數。像延時,任務刪除,條件編譯等等,也是搞了好多天,每個函數都需要調試看看正不正確啊…
當然最終功能也差不多,而且發現暫時已經不想再寫下去了,就發布一下新浪博客。雖然我知道沒人看,但是因為以前在網上看到一句話,原話我忘記了。意思就差不多是:經歷了一段時間的學習,最好最后把學到的東西記錄下來,走的彎路也記下來,不然留不下什么東西,最終忘記了也就像一無所獲。學到的東西最好是發到網上分享。這些話我是接受了,因為我也是經常在網上下載別人的資料學習,所以這次我就發了出來…雖然沒用。就當留作紀念吧呵呵。
第二章 coos簡介
coos是一個專門為51寫的分時任務管理,完全由C語言實現,傳統的任務管理總會涉及到匯編,因為有些操作是C語言實現不了的,例如出棧入棧,現場保護等。設計coos是我就試著盡量減少匯編代碼,到最后居然真的實現了零匯編,是最理想的結果。不過我得先說明的是,任務管理的話,我也覺得從匯編的角度去理解效果會好一點。
coos是利用51的定時器0中斷作為分時依據,進入中斷后判斷需要運行哪個任務,中斷退出便切換到相應的任務去執行,實現了任務調度。這種管理機制類似于我們熟悉Windows,例如我們平時在Windows上運行多個任務時,有時會覺得卡。coos也一樣,如果任務多了,每個任務占用的CPU必然就減少,給我們的感覺也是有點卡。
分時籠統的講就是把一秒鐘(比較容易理解的單位)劃分周期,我這里是劃分成10個周期,每個周期100ms,然后每個周期劃分成20個時間片,每個時間片為5ms,也即是每個任務運行時間的最小單位是5ms(一個時間片)。一個周期有20個時間片,當有多個任務時,每個任務輪流運行一個時間片。還需要說明的是一個周期有20個時間片,每個任務一個周期運行多少個時間片需要設置。例如有2個任務,其中一個任務設置運行時間片為4,另一個設置運行時間片為50,那么在coos管理的一個周期中,任務一運行4個時間片,任務二運行16個時間片。如果沒創建新任務或者刪除任務的話,下個周期也是這樣執行。
當然為了讓coos更靈活,我也用了不少的條件編譯。實現可配置,可裁剪。例如一個時間片的時間是5ms還是其他,用戶可自行配置。一個周期有多少個時間片也可配置。當然還提供了不少函數,例如任務刪除,任務主動放棄CPU,系統延時等。因為有些實在不需要用到,所以用條件編譯實現可裁剪。嘗試了裁剪掉除任務管理以外的功能,coos占用RAM :44Byte + 1bit,code:692
還需要說的是coos的包含的文件。 coos.c 任務管理實現源文件 coos.h 任務管理實現頭文件 coos_hook.c 鉤子函數源文件 coos_hook.h 鉤子函數頭文件 coos_conf.h 任務管理配置頭文件 coos_typedef.h 類型定義頭文件
coos的實現由coos.c完成,是coos最核心的文件。 coos_typedef.h是為了coos的移植性需要加入的,定義coos使用的變量類型,如uint8_t coos_conf.h是coos的配置文件,實現的可裁剪功能都在這個文件設置,也就是條件編譯的開關。 coos_hook.c鉤子函數源文件是由用戶實現的,類似于ucos的鉤子函數,例如選擇啟用任務調度的鉤子函數,用戶將自己的鉤子函數寫好后每次任務調度都會調用任務調度的鉤子函數。
第三章 任務管理原理
在這里最重要的是要知道PC指針是什么?翻譯成中文是程序計數器,它保存的是CPU下一個指令的地址。下一個指令是什么?PC指針不知道,但是它知道下一條指令放在哪里,就是PC指針指向的那個地址的內容。修改了PC指針,就修改了下一條指令的位置,PC指針指向哪里,CPU就運行哪個指令。任務調度就是通過修改PC指針實現的,我這里是間接修改PC指針,當然修改前要將它保存,也就是入棧操作。修改它就是通過出棧操作完成的。還有51的PC指針是16位的。入棧占用了兩個字節。下面再詳細介紹:先看任務管理函數的截圖。
功能看截圖就大概可以理解了。我再說明一下大概思路: 606行之前:51產生定時中斷,C語言看不到的入棧操作:包含R0~R7,ACC,B,DP0L,DP0H,PSW,還有最重要的PC指針,也就是現場保護。 606行:一個賦值語句,保存的是當前的SP(棧指針),保存在當前任務的任務控制塊里面 607行:也是賦值語句,修改當前的SP,將當前棧指針修改為定時器函數專用的棧 609~627行:先別管 629行:賦值語句,修改SP指針為下一個任務的SP指針,中斷退出將按下一個任務的任務棧出棧,出棧時修改了PC指針,實現了現場還原和任務調度
至于為什么要這么做我也說明一下。任務調度最重要的就是現場保護和現場還原了。現場保護利用的是51產生中斷時的入棧操作,這是C語言不用管也管不了的。現場還原是利用51退出中斷的出棧操作,也是C語言所管不了的。雖然說不用管,但是大部分時候現場保護并不完全。我們需要將每個任務用到的寄存器都入棧。經調試發現,51入棧機制是,在中斷服務程序有用到的寄存器就一定會入棧,PSW和PC是一定會入棧的。
為了現場保護完全,我在中斷服務函數里面,用C語言把每個通用寄存器都訪問了一遍(因為有一些通用寄存器沒入棧保護),所以每個通用寄存器都入棧。其他像ACC,B寄存器等也入棧了。這些操作在606行之前完成的。要看匯編出來的代碼才知道,所以說任務管理還是從匯編的角度去理解好一點。 然后現場還原就好辦了。只要在中斷服務函數退出前修改SP指針(629行),SP指針在中斷服務函數就保存在相應任務的任務控制塊了(606行),629行一個賦值語句的作用就是,中斷會按照相應的任務棧出棧(在606行保存的SP的值),之后會還原寄存器和PC指針,實現了任務調度。只要修改了PC指針,程序就跳轉到相應的位置執行,而PC指針在中斷服務函數里面已將入棧保存起來了,出棧后就切換到上次該任務執行的斷點位置,實現任務的繼續。
能理解這些操作就好辦了,簡單的說就是定時器產生中斷會引起入棧,退出中斷會出棧,每個任務的任務棧不同,入棧由51中斷的現場保護完成,出棧由51中斷的現場還原完成。任務切換需要做的就僅僅是在退出中斷前修改出棧的SP指針(棧指針),629行就完成這個功能。剩下的就是51的現場還原了。還原后就回去上次被中斷的任務的斷點處所,繼續執行上次被中斷的任務。
詳細的任務管理將在下一章繼續介紹。
第四章 任務管理詳解
有了上一章的任務管理原理的基礎,來理解整個中斷響應函數的功能就容易多了。繼續上一章的截圖。(大概瀏覽一下代碼,不到20行)
609~612行,624~627行。這兩個實現相同的功能,為定時器賦初值,條件編譯的條件為是否使能精確延時,條件編譯結果是選擇其中的一個。 在coos_conf.h文件里有: #define OS_ACRT_TIM_EN 0 也就是默認沒有使能精確延時,如果需要使能精確延時的話只要將0改為1就行了。 說明一下65535 - OS_TICK_CNT + OS_ETR_TICK_TIM OS_TICK_CNT 是定時器計數個數,可配置(coos_conf.h),默認5000,分時時間片 OS_ETR_TICK_TIM 是定時器響應周期,也就是入棧所用時間,默認0x2A
使能精確延時的結果是,coos提供的時鐘對外是比較精確的,例如做一個定時器時鐘,可以比較精確的運行一段時間。但是每個任務執行的時間卻減少了,因為中斷響應函數的執行也是需要時間的,這對于分時任務管理來說是不公平的,因為任務調度的時間是不一定相同的,導致任務執行的時間也不一定相同。 沒有使能精確延時,那么coos提供的延時默認是5ms + 任務調度的時間。分時任務管理的話,每個任務是公平的,執行的時間相同。但是coos提供的延時對外是不精確的。
接下來看一下619~621行。條件編譯,條件是如果使能定時器鉤子函數,在配置文件里是否使能(coos_conf.h)。 函數OS_Hook_Tick()在coos_hook.c文件,默認函數體為空,該函數由用戶實現。
最后就剩下614~617行,三個函數了,一個一個來,都是比較好理解的。 先看最簡單的617行,OS_ResSav();該函數訪問通用寄存器,引起定時器中斷函數執行前,通用寄存器入棧(中斷服務函數訪問到哪些通用寄存器,那些通用寄存器就會入棧),具體代碼是: 540行:讓res_ptr指向0x00,也就是51的工作寄存器組0的R0,這個還是從匯編的角度去看會好理解一點。51的256Byte的RAM,前8Byte * 4是四個工作寄存器組。 542~549行:訪問通用寄存器,用下標的形式訪問,自己給自己賦值,分別訪問R0,R1…R7,實際寄存器值沒改變,目的是要在定時器函數執行前將通用寄存器入棧。完善現場保護工作。
接下來看614行,OS_TickTimDeal(); 代碼也是相當簡單的,看截圖 該函數處理coos的時間,由于是分時任務管理,當然要知道當前運行到哪個時間片,并且每個任務一旦運行了一個時間片,也要將時間片減一。 499行:當前任務時間片減一,也就是當前任務運行了一個時間片 501行:系統時間片加一,分時管理任務,當然要有一個變量(OS_Tick)記錄當前時間片
502行:OS_TICK_PERIOD系統時間片周期,默認為20,判斷是否滿一個周期 504行:滿一個時間片周期,時間片清零 505行:滿一個時間片周期,置位系統周期標志位
508行:判斷是否滿一個系統周期,標志位是在505行置位的 510行:滿一個系統周期,標志位清零 512行~518行:條件編譯,如果使能系統提供參考時間,也就是延時時間才編譯這幾行 513行:系統時間加一 514行:如果時間滿一個時間周期 516行:時間清零 520行:調用任務時間片初始化函數,重新刷新任務時間片,具體代碼如下截圖 477行:一個for循環,循環次數為任務個數,其中OS_TASK_NUM可配置,最大為8 479行:for循環的循環體,OS_TaskTimGrp數組記錄任務時間片,這里是按照任務創建時的時間片去刷新 481~483行:條件編譯,如果使能系統空閑任務。系統空閑任務是指在一個系統周期內(例如20個時間片內),如果沒有可運行任務(只需要執行不到20個時間片的任務),也就是創建的各個任務時間片已經執行完,但是一個周期還沒到,就執行空閑任務。coos默認提供的空閑任務是讓51進入低功耗模式,省電。當然可配置,用戶可以自行創建空閑任務。 482行:刷新空閑任務時間片
任務調度還剩下最后一個函數,繼續任務調度那張截圖: 看615行最后一個函數:目的是獲取下一個需要執行的任務,為切換任務做準備 具體函數代碼如下截圖:
該函數的思路是:在任務狀態表上查找下一個任務。任務狀態表是579行的OS_TaskStatGrp,是一個8bit的變量,每一位為0或者為1代表是否創建了任務,1表示創建了任務,當然最多支持8個任務。 查找到有創建的任務,然后判斷該任務時間片是否為0,如果不為0,那么下一個任務就是被查找到的任務,如果為0,那么繼續查找下一個任務。如果所有被創建的任務時間片都為0,那么返回OS_TASK_NUM,代表著下一個任務執行的是空閑任務。 567行:為id賦初值,為當前執行任務的id 568行:i初值為0,循環控制用 570行:進入循環,循環次數為coos管理任務的個數,由OS_TASK_NUM決定,可配置 572行:進入了循環,循環次數加一 573行:id加一,表示下一個任務 574行:判斷id是否溢出,超出coos管理任務的個數 676行:id溢出則清零 579行:重點!!判斷該id的任務是否需要執行,判斷的條件有兩個,分別是: 1、在該id上創建了任務 2、該任務時間片不為0
前半部分:(OS_TaskStatGrp & SET_BIT(id)) OS_TaskStatGrp是coos任務狀態表,8bit,每一位代表一個任務,為1表示創建了任務 SET_BIT()是一個宏操作,代碼是 #define SET_BIT(i) (1 << i) 表示置位某個位,這里SET_BIT(id)是置位id那一位 操作(OS_TaskStatGrp & SET_BIT(id)表示判斷在任務狀態表上判斷id那一位是否創建了任務 后半部分:OS_TaskTimGrp[id] > 0 判斷任務時間片數組上該任務時間片是否為0 如果滿足前半部分和后半部分都為真,那么就需要切換到的任務就是臨時變量id了。 所以就有: return id;
當然如果不能同時滿足兩個條件,就 return OS_TASK_NUM; 表示下一個任務是空閑任務。
至此,任務調度講解是完了,不過還是再總結一下: 1、 進入任務調度函數前(定時器中斷服務函數),有C語言看不見的入棧操作,保存了寄存器和PC指針等 2、 入棧后就進入任務調度函數,第一步保存當前任務的SP在相應任務的任務控制塊里面 3、 將SP修改為任務調度專用的SP,任務調度函數專用的棧 4、 系統時間處理,分時管理需要處理時間 5、 獲取下一個需要執行的任務 6、 訪問通用寄存器,為了第一步入棧操作所有通用寄存器都入棧 7、 任務調度的鉤子函數(如果使能) 8、 定時器計時賦初值 9、 修改SP為下一個任務的SP,在相應的任務控制塊里面讀。ǖ2步保存) 10、退出任務調度函數,按照下一個任務的SP出棧,還原寄存器,PC指針等,實現了任務調度
到這里其實還遺留一個問題:切換到的任務如果還沒被執行過的話(定時器中斷入棧是入棧被中斷的任務,說明該任務已經執行了),那么相應的PC指針,寄存器值是什么?下一章繼續介紹。
第五章 任務管理的幕后操作
來到這里,可以看到,要實現任務管理功能,還需要很多幕后操作的。這次我們實際來看任務管理從頭到尾怎么做的,要開始來弄清楚來龍去脈了。這一章,需要分幾個小節了。 5、1 coos的文件結構和使用
看看coos的C語言工程吧。截圖:
先看左邊的文件,兩個文件夾coos和user。 coos里面兩個源文件coos.c和coos_hook.c。這兩個文件是任務管理文件,只要包含了這兩個文件和相應的頭文件,再調用幾個函數就可以實現分時任務管理功能了。 user里面兩個源文件是用戶自己編寫的。main.c和task.c分別是主函數文件和被管理任務的文件。任務到底是什么,等一下再看。這里先介紹主函數應該怎么樣寫,也就是前面啰嗦了那么多,那到底coos怎么用?
要先添加了coos的幾個文件到自己的工程,然后大概瀏覽一下那10來行代碼。可以看到主函數的操作分成三部分: 1、初始化coos 2、創建任務 3、開始運行coos
具體再看代碼:
12~14行:定義三個數組,用來做任務棧?梢钥吹皆撝骱瘮祫摻巳齻任務,因為每個任務要有一個任務棧,所以定義了三個數組來當作任務棧。 16行:主函數開始入口。 18行:coos初始化。調用coos的函數,一些coos運行必要的操作,等一下再說。 20~22行:創建任務。也是調用coos的函數,創建任務后準備交給coos管理。 24行:coos開始運行。同樣是coos的函數,coos開始分時管理被創建的三個任務。
可以看到,要使用來管理任務是非常簡單的,主要在主函數里面調用幾個coos提供的函數就行了。沒錯,但是這次我們要看coos是怎樣運行的,了解到具體的每一步。只要理解coos幾個重要的函數,就能很深入地理解coos了。
5、2 coos重要函數說明
1、void OS_Tick_Init(); coos 定時器初始化函數,跟普通的51定時器初始化函數沒什么不同,但也看一下具體代碼。 功能是配置定時器0,讓51能夠產生中斷,相當好理解。 其中457行的TR0 = 1;被我注釋掉是因為我們并不是要在這個時候讓coos開始運行,等到創建好任務在讓它開始運行。
2、void OS_TaskCreate(void Task(void), uint8_t *TaskStk, uint8_t TaskTim, uint8_t TaskID) coos創建任務的函數,非常重要!!
可以說coos得以運行都是這個函數和定時器中斷函數在幕后的操作了,這個函數有這跟定時器中斷函數同樣級別的重要性!!并且coos的很多概念都在這個函數里面使用到。
先介紹一下一些基本概念,再來說明任務創建函數。
①在C語言中,函數名代表函數的地址。什么意思?舉個例子說明一下: 比如有一個函數:void Func(); Func是函數名, Func()是這個函數。 要知道它們是有區別的。 首先我們可以把Func當成一個常量,這個常量是一個地址,也就是Func()在ROM存放的地址,ROM在51上我們稱為code。這些屬于C語言的知識,大家有興趣在繼續深入了解。我們這里要用到的只是要知道Func就是一個常量,這個常量是一個地址,這個地址是函數Func()在ROM上的地址。就行了。
但是它有什么用?如果跟PC指針聯系起來的話就知道了解這個是有作用了。 PC指針存放的是下一條指令的地址,如果把Func賦值給PC,那么CPU下一個執行的指令就是Func()函數的指令了,類似于C語言的函數調用,調用的函數是Func()。其實調用函數的時候就是將PC指針修改成對應函數的地址,CPU就跳轉去執行相應的函數了。但是調用函數的話還涉及到函數參數的傳遞和函數的返回值,與只修改PC指針也是有區別的。
②任務控制塊 先看代碼截圖: 要管理任務,當然要有任務的信息。任務控制塊就是用來保存任務信息的。因為51的RAM只有256Byte,所以任務控制塊我也盡量減小,只有任務棧和任務時間片是必要的。
任務棧地址:每個任務要有自己的棧,任務棧的地址用來記錄任務進入中斷的SP的值(棧指針),方便中斷退出按該任務棧出棧實現現場還原。在第二章講任務管理原理時,進入中斷的第一件事就是保存任務的SP到任務控制塊的棧地址,也就是606行的賦值語句。 任務時間片:每個任務還要有時間片。也就是任務在一個周期內最多執行多少個時間片。因為是分時任務管理,所以不能讓某個任務占用所有CPU,要按照時間片去執行。
接下來看任務創建函數,截圖:
函數帶四個參數,每個參數都相當重要。 第一個參數: void Task(void) --- 這個參數是一個函數,可能有點難以理解。但是函數的參數可以是一個函數,這個參數函數不帶參數,不帶返回值。這個參數函數就是我們用C語言寫的任務函數。也就是任務創建函數創建的任務就放在這個參數。 第二個參數: uint8_t *TaskStk --- 這個參數是任務棧首地址。如果我們定義一個數組,那么數組名就是數組的首地址,一般這個參數傳遞的是數組名(數組的首地址),然后數組就作為該任務的任務棧。(當然動態分配內存也行) 第三個參數: uint8_t TaskTim --- 這個參數是任務的時間片。比較容易理解了,一個任務在一個周期內最多運行多少個時間片就是看創建任務時傳遞進來的這個參數。 第四個參數: uint8_t TaskID --- 任務的id。coos管理任務是根據任務的id管理的,只有任務掛在coos任務狀態表(OS_TaskStatGrp)上coos才管理這個任務,不然任務是不會被運行的,哪怕有再多的時間片。并且coos的任務狀態表是一個8bit的變量,每一位代表一個任務id。任務的id分別是從低位的0到高位的7。
開始看具體的函數代碼,現在應該很好理解了。 099行:目的是判斷該id上是否已經創建了任務。判斷條件是任務狀態表該為是否為1。其中操作SET_BIT()是一個宏操作。在coos.h里面有: #define SET_BIT(i) (1 << i) 目的是置位某個位,操作(SET_BIT(TaskID))是置位id那一位,然后跟OS_TaskStatGrp(任務狀態表)與一下是否為真。如果為真,該id上已經創建了任務;如果不為真,該id上未創建任務。
如果任務id已被占用存在,那么運行101~104行。 101~103行:條件編譯,條件是是否使能系統錯誤統計,如果使能系統錯誤統計則運行OS_ErrCnt(); ,這個函數是當coos出錯時候統計錯誤次數的,這里不是重點。 104行:return,函數返回。因為不能創建任務(任務id被占用了)
如果任務id未被占用,則運行108~118行: 108行:因為要在該id上創建任務,所以置位任務狀態表在該id上的那一位
109行:記錄任務的任務棧,也就是任務的SP。記錄在對應任務的任務控制塊里面。任務控制塊是一個結構,任務控制塊在上面已經說明了。我這里是定義一個結構數組,結構就是任務控制塊結構,每個任務的任務控制塊就是在該結構數組上的一個元素(結構數組的元素是一個結構)。
右邊(uint8_t)TaskStk + OS_TASK_STK_SIZE_MIN有兩部分。 第一部分(uint8_t)TaskStk 是任務創建函數的第二個參數,任務棧首地址,本來是一個指針,強制轉換成uint8_t,無符號八位的變量 第二部分 OS_TASK_STK_SIZE_MIN 是任務棧的大小,是一個常量,默認值為13,這個有點難理解。因為與定時器中斷服務函數相關,并且是從匯編的角度去看才能知道這個常量的大小。詳細說明一下:中斷服務函數執行前有入棧操作,但是具體入棧寄存器多少事先難以預知,最好的辦法是查看編譯后的匯編代碼。我就是查看匯編代碼的,默認總共入棧的寄存器有13個,占用14個字節(PC指針占用兩個字節,其他寄存器占用一個字節)。除了PC指針還有額外的12個字節,分別是R0~R7,ACC,DP0H,DP0L,PSW。還需要了解的是51入棧機制,棧是向高地址增長的,而且是先增長再入棧(貌似專業一點講是一個是滿棧,UB,U是up,B是before,要入棧前先增長棧地址)。 了解了這些之后就可以推斷出 OS_TASK_STK_SIZE_MIN 這個常量是13了,當然我也順便說一下?偣踩霔14個字節,數組名是首地址,數組名加上13就是棧頂了,總共是14個字節,這樣就模仿得跟中斷入棧后的棧頂的值是一樣的。
左邊OS_TaskTcbGrp[TaskID].TaskStk 是該任務的任務棧,接受右邊的棧頂地址。
110行:右邊是任務創建函數的第三個參數,任務時間片,在任務控制塊上記錄任務時間片。 112~113行也是重點和難點! 賦值語句,模擬入棧。要先知道PC指針是16bit的,入棧占用兩個字節。但是究竟是高字節先入棧還是低字節先入棧?有辦法!進入調試模式,然后觀察內存。我是用這個辦法知道51入棧PC指針是低地址先入棧,然后高地址的。大家有興趣可以自行嘗試。 知道了先入棧低地址,下一步是模擬出入棧的是任務剛要運行時的PC指針。上面已經講到函數名就是函數的地址,與PC指針有聯系。 112行: TaskStk[0] = FUNC_ADDR_LOW(Task); 右邊是一個宏操作,在coos.h里面有: #define FUNC_ADDR_LOW(func) (uint8_t)(((uint32_t)func) >> 0) 目的是獲取函數的低地址,宏操作的參數是func,具體應用時候是一個函數名,也就是一個常量(函數的地址),操作先將函數的地址func強制轉換成一個32bit的變量,右移0是為了配合獲取函數高地址操作寫的,實際沒用,然后再強制轉換成8bit的變量。 左邊是任務棧,用下標形式訪問棧底,將函數的低地址賦值給棧底,模擬了PC指針入棧時的低八位地址入棧。 113行:TaskStk[1] = FUNC_ADDR_HIGH(Task) 理解了112行,這一行就好辦了。模擬入棧PC指針的高八位。 右邊一樣是一個宏操作,在coos.h里面有: #define FUNC_ADDR_HIGH(func) (uint8_t)(((uint32_t)func) >> 8) 目的是獲取函數func的高八位地址。 左邊任務棧,因為剛才入棧函數的低八位地址,所以棧要向上增長,用下標訪問的形式就是 TaskStk[1]了,這一行模擬出PC指針入棧是的高八位地址入棧。
115~118行:模擬其它寄存器入棧,默認都是0x00,其他寄存器包含有R0~R7,ACC,DP0H,DP0L,PSW具體要看匯編代碼才知道是這些寄存器的。
至此任務創建函數就完了。這里在總結一下,在coos里要創建一個任務就是要將任務掛在任務狀態表上,供coos管理,而且要填好任務的信息,包括任務棧頂,任務時間片。最后模擬任務被中斷服務函數中斷了的入棧操作。具體再分幾步說明: 1、判斷任務id是否被占用 2、如果任務id被占用就不創建任務,根據條件編譯是否運行系統錯誤統計函數
3、任務id沒有被占用,置位coos任務狀態表對應id那一位,說明要在該id創建任務 4、為任務棧的棧頂初始值 5、任務時間片初始化 6、模擬入棧PC指針和其他寄存器。
3、void OS_TaskTim_Init(void) 因為coos管理任務是按時間片管理的,任務一旦運行一個時間片,時間片就被減一。所以每個周期要刷新一次任務時間片。這個函數就是每個周期調用一次的任務時間片初始化,重新刷新任務時間片共coos管理。 477行:for循環,循環次數是coos管理的任務個數 479行:for的循環體,功能是根據任務時間片初始化任務時間片數組,就是記錄各個任務創建時的時間片,創建一個副本。任務運行后的時間片減一操作在這個副本進行的。 481~483行:條件編譯,條件是如果使能系統提供空閑任務。如果使能了系統提供空閑任務,那么就會刷新空閑任務的時間片。其中常量OS_IDLE_TASK_TIM的值與coos一個周期的時間片個數相等。在coos_conf.h里面有: #define OS_IDLE_TASK_TIM OS_TICK_PERIOD 其中后半部分OS_TICK_PERIOD是coos一個周期時間片的個數。
理解了這三個函數后就可以來看看coos具體的運行過程了,F在開始要具體從main函數開始看coos的任務管理了。
5、3 從main函數的執行理解coos的運行
再來main函數的截圖吧: 從現在開始一步一步分析main函數。
1、OS_Init();
進入這個函數繼續分析?丛摵瘮到貓D: 062行:OS_Tick_Init();代碼上節已經分析過,重溫一下,看截圖: 064行:變量OS_CurTaskID記錄當前是執行那個任務,初始化將它初始化為空閑任務。 065行:變量OS_TaskStatGrp是coos任務狀態表,初始化為0表示沒有創建任務。 067行:for循環,循環次數是coos管理任務的個數,也就是常量OS_TASK_NUM,可配置 069~070行:for循環的循環體,將coos管理的任務的任務控制塊任務棧初始化為0x00,時間片初始化為0 073~075行:條件編譯,兩個條件。分別是 1、如果使能coos空閑任務,可以不使能 2、如果使能coos提供的空閑任務,當然可以自行創建其他任務作為coos空閑任務 coos默認提供的空閑任務是讓51進入低功耗模式,省電。此時51等待中斷喚醒。具體可以查找51數據手冊繼續深入理解,代碼看截圖: 到了這里,主函數的第一個函數就完了,接下來繼續看主函數的其他操作。
2、OS_TaskCreate(Task_One, Stk_TaskOne, 5, 0);
重溫一下任務創建函數,截圖:
這個函數在上一節也已經說明過,再羅嗦一下。 該函數的操作是創建一個名字為“Task_One”的任務,其中Task_One是一個函數名,也就是有一個Task_One()的函數,具體任務做什么等一下再說。 任務 Task_One的任務棧是Stk_TaskOne,也就是主函數文件中012行定義的那個數組,將數組的首地址傳遞進來,這個數組的空間給Task_One做任務棧。 任務Task_One的時間片為5,也就是Task_One一個周期最多執行5個時間片 任務Task_One的id為0,也就是Task_One在任務狀態表OS_TaskStatGrp占用了第0位
Task_One的具體代碼再看一下,相當簡單的一個任務。截圖: 說明一下任務必須在一個死循環里面,即時任務只需執行一次,也必須讓任務在死循環里面執行,不能讓任務函數返回。因為返回的話,有發生不可預知的結果,一般就是程序跑飛了。因為返回時誰也不知PC指針會指向哪里。 如果有只需執行一次的任務的話,coos當然考慮了這個情況。那就是在任務運行完后調用coos的任務刪除函數void OS_TaskDel(uint8_t TaskID);將該任務刪除掉,coos就再也不會執行該任務了。參數TaskID是待刪除任務的id。比如要刪除剛才創建的任務的話就調用: OS_TaskDel(0); 就將任務刪除了。 3、OS_TaskCreate(Task_Two, Stk_TaskTwo, 2, 1);和 OS_TaskCreate(Task_Thr, Stk_TaskThr, 7, 3); 再創建兩個任務,類似與創建任務1。 任務代碼也很簡單,截圖: 功能類似與任務一。任務二中被注釋的內容是我在寫coos時測試函數功能是否正常用的。 創建了任務之后,我們要讓coos開始運行了。繼續看mian函數中的最后一個函數。
3、OS_Start(); 代碼在截圖:
135行:coos任務時間片初始化,該函數上面已經上一節也已經說過,重溫一下代碼: 因為創建任務的時候在任務控制塊里面填寫了任務的時間片。然后我們想讓coos管理這些任務,我就創建了一個數組,作為任務時間片的副本。因為任務運行后我們要記錄已經運行了,直接在任務控制塊里面操作會造成任務信息丟失,所以創建一個副本。在coos.c里面有: static uint8_t idata OS_TaskTimGrp[OS_TASK_NUM + 1]; 用這個數組來記錄任務時間片,也就是運行了任務就來一次任務時間片減一操作,滿一個周期后再重新刷新。任務時間片為0任務就不會被執行。
138行:因為coos運行前默認coos是在運行空閑任務,第一次進入中斷服務函數(任務調度函數),會將相應任務的時間片減一(第一次進入中斷空閑任務時間片會被減一),所以先加一,解決第一次進行任務調度出現的bug 138行:因為第一次進入中斷,OS_Tick會加一(coos時間片記錄函數),表示中斷了一個任務,coos運行了一個時間片,但實際第一次進入中斷并沒有運行一個時間片的任務,所以OS_Tick減一,解決第一次進行任務調度的bug
140行:表示定時器開始計時。 141行:表示定時器產生溢出,實際沒溢出,只是為了立刻進入中斷。 此時CPU的不會繼續執行141行下面的代碼了,它會進入定時器0中斷服務函數,但是下面的代碼還是有可能被執行的。 143~147行:條件編譯,如果使能空閑任務和coos提供的空閑任務 空閑任務放在這里,是因為當沒有可運行任務的時候,coos任務調度返回,141行下面的代碼。為什么,可以自行思考一下。原因是141行是第一次被中斷的斷點,然后進行任務調度。任務調度函數會記錄斷點,入棧保護,并且coos初始化是默認的任務是空閑任務,所以入棧保護入的棧是空閑任務的棧。如果將空閑任務放在141行下面,那么就模仿的非常的像是第一次中斷斷點就是空閑任務了。
至此,coos任務調度講解完畢。當然要看代碼,要調試,要看一下匯編代碼,要看51的說明書,要懂一點微機原理,要有C語言基礎,才能更深刻的了解coos。 簡單總結: 1、初始化定時器,coos使用的變量。為coos分時管理任務做準備 2、創建任務,準備讓coos管理(創建任務是記錄任務信息,并模擬任務被中斷函數打斷) 3、開始運行coos,啟用定時器中斷根據任務信息調度任務
當然為了讓coos更強大,并且作為嵌入式的東西,可裁剪是必不可少的。所以很多條件編譯默認都是不編譯的,下一章主要說一下條件編譯函數。
以下已經不是重點了,可有可無。coos的主要目的是任務管理。并且發現分時任務管理比較簡單實現,所以就是分時任務管理的coos了。
第六章 coos條件編譯
數了一下是11個條件編譯函數,都是相當簡單或者說理所當然會有這些函數,沒有多少技術含量了這些。下面一一說明:
1、void OS_TaskDel(uint8_t TaskID) 任務刪除函數,看截圖: 169行:傳遞進來的參數是任務的id 171行:判斷任務是否創建了,因為創建任務函數默認已經置位了任務狀態表(OS_TaskStatGrp)對應的id那一位,所以如果對應的為1則說明確實創建了任務,準備刪除任務。 173行:目的是將任務狀態表(OS_TaskStatGrp)對應任務id那一位清零。其中RESET_BIT()是一個宏操作,在coos.h文件里面有: #define RESET_BIT(i) (~(1 << i)) 目的是清零對應i的那一位,最重要是這一步就把任務刪除了,任務就再也不會運行了,下面的步驟只能說是善后吧。 174~175行:任務棧置為0x00,時間片置零 176行:任務時間片數組(OS_TaskTimGrp)對應任務id那個元素的時間片清零
如果任務狀態表(OS_TaskStatGrp)對應任務id那一位為零,也就是對應id上沒有創建任務,根據條件編譯是否運行系統錯誤統計函數,這里不是重點。
2、void OS_IdleTask(void) coos提供的空閑任務,老樣子,截圖: 跟普通任務一樣是進入一個while死循環,因為一旦函數返回,會有意想不到的結果反生,難以預測發生什么事,程序跑飛了那是不可避免的。 204行:根據51數據手冊,置位PCON的第0位讓51進入低功耗,具體可參考數據手冊
3、uint8_t OS_GetCurTaskID(void) 獲取當前運行任務id的函數,截圖: 直接將OS_CurTaskID返回,該變量記錄當前被運行的任務 來到這里相信同學們也覺得條件編譯函數是相當的…繼續
4、uint8_t OS_GetCurTickTim(void) 獲取coos提供的參考時間,截圖: 類似與上一個函數,不說了。
5、void OS_SetCurTickTim(uint8_t TickTim) 設置coos當前參考時間,截圖: 對OS_Tim賦值,就是設置coos的參考時間了
6、void OS_TaskExit(void) 例如當前正在運行某個任務,我們已經不想再讓任務運行下去了,就可以調用這個函數,進入任務調度函數,讓任務調度函數判斷下一個任務是什么而去執行下一個任務,當前任務就沒有被執行了。 這個時候任務這個函數的價值就體現出來了。因為我們的任務都必須設置成在一個死循環里面執行,如果沒有這個函數,那么coos必須也讓該任務運行一個時間片,如果在這個時間片內引腳的狀態都為0,那么可想而知,該任務是在浪費CPU。如果我們改進一下: 如果狀態為0,則一樣運行一段代碼,如果引腳狀態為1,調用該函數,讓任務放棄CPU。 那么CPU的利用率就大大提高了,下面看代碼。截圖: 282行:判斷任務是否存在 284行:任務確實存在,置位TF0,定時器0溢出標志,則進入定時器中斷函數進行任務調度。 289行:任務并不存在,根據條件編譯是否運行coos錯誤統計函數(這里不是重點)
7、void OS_TaskTimEdit(uint8_t TaskID, uint8_t TaskTim) 任務時間片修改函數:截圖 因為創建任務的時候我們指定了任務的時間片,coos管理任務后任務的CPU使用率是一定的,如果要增加或減少任務的CPU使用率可以試著用這個函數。 傳遞進來的參數: 1、uint8_t TaskID 任務的id 2、uint8_t TaskTim 任務的新時間片 當然311行先判斷任務是否存在 任務存在則修改任務控制塊數組中該任務的任務控制塊的時間片為新設置的時間片 任務不存在根據條件是否編譯運行coos錯誤統計函數
8、void OS_ErrCntClr(void) coos錯誤次數清零函數。截圖: 條件編譯有兩個條件: 1、使能coos的錯誤統計 2、使能錯誤統計清零 340行:簡單的將OS_Err_Cnt賦值為0就清零了
9、uint8_t OS_GetTick(void) 獲取coos當前時間片,截圖: 360行:直接將OS_Tick返回。
10、void OS_TimDly(uint8_t Tim) coos提供延時函數,截圖: 稍微有一點點復雜,不過說一下基本思路應該就很清晰了。 延時的話是根據coos提供的參考時間作為延時依據的,所以要根據延時的時間和coos提供的參考時間,計算出延時的終點(延時的結束條件),在延時的條件內就等待。 384行:臨時保存coos參考時間變量OS_Tim 385行:Tim是傳遞進來的參數,加上OS_Tim是延時的終點,但要判斷是否溢出 387行:判斷延時終點是否溢出,溢出條件是Tim 〉 OS_TIM_PERIOD(coos參考時間的周期,可配置) 389行:延時終點溢出了,用coos的參考時間周期取余Tim,得到新的延時終點。 其實由這里也可以看出,coos提供的延時范圍實在一個coos參考時間周期內的,因為越界部分取余后已經沒了。 390行:進入while等待延時結束。因為延時終點越界了,可知等待的終點coos提供的OS_Tim是小于當前臨時保存的coos參考時間tim_tmp,所以等待條件第一個為: OS_Tim >= tim_tmp 因為延時終點溢出也可知延時終點coos的參考時間OS_Tim大于計算出的延時終點Tim(Tim是計算出的延時終點,第二個條件是只要coos參考時間大于該值則等待結束) ,所以有 OS_Tim < Tim
394行:延時終點為溢出,那么延時就好理解了,只要coos提供的參考時間大于計算出的延時終點,那么延時就結束了,有: while(OS_Tim <= Tim);
如果對延時函數不理解的話拿起筆和紙寫一下就懂了,我是用筆和紙計算出來的…(——#!)
11、void OS_ErrCnt(void) 最后一個條件編譯函數,coos錯誤統計。雖然是個很簡單的函數,不過因為沒有更復雜的函數了,所以也只能拿它來壓壓軸,看截圖: 415行:判斷錯誤次數是否溢出,未溢出進入417~420行 417行:錯誤次數未溢出,錯誤次數加一 418~420行:條件編譯,條件是是否使能coos錯誤統計鉤子函數。什么是鉤子函數,了解過ucos的同學們就知道,為了增強ucos的功能,ucos作者為ucos引入10個鉤子函數,是在當ucos發生某些情況時候運行的函數,由用戶實現的。我這里也是模仿ucos的,引入鉤子函數OS_Hook_Errr(),在coos_hook.c文件里面,默認函數體為空,該函數又用戶實現,目的是為了增強coos的功能,多一些自定義功能,其實也沒什么大不了的。
422~428行:條件編譯,條件是是否使能錯誤次數溢出鉤子函數,進行錯誤次數溢出處理。 425行:錯誤次數清零 426行:錯誤次數溢出鉤子函數,同樣在coos_hook.c文件里面,默認函數體為空,由用戶實現,目的是增強coos功能。
條件編譯函數就完了,最后還剩下的就是coos的配置,裁剪了,下一章介紹。
第七章 coos的配置、裁剪、類型定義
當然作為嵌入式的東西很將就可裁剪性的,配置和裁剪在coos_conf.h文件里面。 先看看配置部分,截圖: 一個一個說: 19行:coos定時器計數值,也就是coos一個時間片的長度。該值越大,coos任務調度的頻率就越小,CPU的使用率就越高。但是太大的話任務被打算時間太長,所以要折衷配置。默認5000,晶振12M的時候一個任務時間片是5ms
20行:coos時間片周期,也就是多少個時間片為一個coos周期。一個周期后coos會重新刷新任務時間片。同樣該值越大,CPU利用率越高,但這個值的影響是不明顯的。
21行:coos參考時間的周期,也是coos提供延時的最大值。對CPU利用率幾乎沒影響。
22行:coos管理任務的個數。該值越小,CPU利用率越高,RAM占用越少。因為任務調度的時候遍歷了每個任務,需要時間。并且每個任務都要有任務控制塊(2Byte)和任務時間片副本(1Byte),占用RAM。綜合說名一下就是,我們需要創建多少個任務,就定義該值為多少,不用預留余量之類的。
23行:定時器專用棧的棧大小,默認為8其實已經夠用了,如果使能的定時器鉤子函數,并且鉤子函數過于復雜(運行起來占用棧較大),那么該值需要適當加大,因為棧溢出了程序運行是肯定會有問題的。
24行:coos空閑任務默認的時間片,該值與coos時間片周期大小一樣。目的是為了在一個時間片周期內,如果沒有一個可以運行的任務,那么所有時間片周期運行空閑任務(不會因為空閑任務時間片不夠而發生意想不到的結果)
接下來是裁剪,老樣子,截圖: 裁剪也就是上一章的條件編譯函數的使能,后面記錄裁剪掉多少RAM和code 需要使用到上一章的條件編譯函數就使能,1是使能 可以看到coos默認使用很少條件編譯函數,因為都是比較簡單的(就是上一章的內容),所以不說了。
鉤子函數的配置,截圖: 相信大家一看就懂了的,1使能。 coos鉤子函數要用戶自己實現(自定義的功能了),在coos_hook.c文件里面,將自己的代碼寫進去就行了,當然要使能了才有用?匆幌coos_hook.c這幾個函數吧。 截圖:
還有類型定義,看截圖: uint8_t 是stm32的風格,因為我已經習慣了這種風格了,不該了,當然也在coos用了這種風格。
附錄 coos的說明書也應該結束了吧。順便提一下就是,實現coos任務管理功能也就那不到200行代碼(除去條件編譯這些可有可無的東西),但是也寫了我一個月左右的時間了。因為確實也走了一下彎路。例如我想讓任務調度獨立成為一個函數,定時器中斷只是負責篡改返回地址,但是卻有寄存器沒入棧保存完善的bug。這個bug曾讓我一度想放棄,因為發現到不得不用匯編,我是大大的不想用匯編的。機器語言已經跟機器密切相關了,移植性大大減少了,同時新鮮性也沒了。因為用匯編的話實現任務管理難度是不大的。 期間也上火,頭痛,牙痛,在校醫院看了兩次,差點要放棄。 裝上win8,安裝了RVMDK,運行不了,用什么win7兼容啊,XP SP3兼容啊什么的,都不行。百度也找不到win8兼容的RVMDK,我崩潰了好幾次。因為裝一次和卸載一次都是比較花時間的… (RVMDK是什么?接近keil增強版吧,我就是用RVMDK寫coos的,keil是RVMDK的前身,RVMDK是stm32,arm之類用的,當然51也行,支持很多微處理器)
參考文獻: 挺多的,可以說看了不少書吧。能想到的就寫出來吧 1、C和指針(C語言的書) 2、51單片機匯編實例(網上隨便下載看看的) 3、c51中文書名keil c51(網上隨便下載的) 4、微型計算機原理與接口技術(上課的課本…) 5、《嵌入式實時操作系統uCOS-II》(第二版) 6、其它的對coos貢獻不大,有C語言的,數據結構的,算法的,stm32的 |