<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

        uC/OS II 學習筆記

        作者:佚名   來源:本站原創   點擊數:  更新時間:2014年08月18日   【字體:

         uC/OS II 提供給用戶通用接口函數都在Ucos_ii.h中【uC/GUI 提供給用戶通用接口函數都在INC包含的各個頭文件中,使用時參考官方的手冊用就好了,有中文版的】;

         
        INT8U  const  OSUnMapTbl[256] = {
            0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
            4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u 
        }
        【這個表的通俗用法就是 給出 給任意一個長度不超過8位的二進制值a, 將這個a做為該數組的索引, OSUnMapTb[a]就表示二進制值a中第一個位1出現的位置】
         
        這個數組 索引值  0~255;
        即通過這個數組 可以直接獲取0~255各值的二進制形式從右到左第一次出現1的位置
         
        Ucosii 中任務的優先級管理方法(假設最多64個任務):
        因為OSUnMapTbl256個元素,也就是索引是8位的二進制值,這個索引的每一位的0 1 值代表的是對應的任務是否就緒;
        因此管理任務的最小單位為INT8U(也就是8個任務用8個位來組成一個INT8U);
        依次類推:可用一個INT8U來表示某個最小單元中是否有就緒態的任務(即該INT8U是否為0
        這樣就是 8X8 = 64個任務;
        依次往上類推,8X8X….X8,理論上在空間足夠的情況下可以管理無限個任務;
        具體的優先級值的算法就是:
          y = OSUnMapTbl[OSRdyGrp];
          OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
        其實Ucosii 任務數大于64個時,他是16個任務為一組,組成INT16U來管理的
        同樣往上推,也是16組來管理的
        16X16
        if ((*ptbl & 0xFFu) != 0u) {
             //如果低8位不為0,都不用考慮高8位了,直接在低8位中去尋找第一次出現1的地方
                OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
        } else {
        //如果低8位為0,直接在高8位中去尋找第一次出現1的地方,然后加8 就表示整個16位的INT16U中第一次出現1的地方
                OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
            }

         
        //這里用了一個很小的數組,來處理
        //這里GetOnePos 的算法 對于 很多位的大數值來說,可能效率不高,但是對于 只有4位的值來說,就是很快了
        //其實這里只是一個簡單的例子而已,ucos中用的是8 位  也就是256個值的數組,
        //這個就看你的需求,你來選擇了具體的數組的大小了, 看具體的情況來 用時間 換 空間, 或用空間換時間
        unsigned int g_unHighPrio[16] = 
        {
        0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u,
        };
         
        char GetOnePos(int data)
        {
        if(data & 0x0000000F)
        {
        return(0 + g_unHighPrio[(data & 0x0000000F)>>0]);
        else if(data & 0x000000F0)
        {
        return(4 + g_unHighPrio[(data & 0x000000F0)>>4]);
        }
        else if(data & 0x00000F00)
        {
        return(8 + g_unHighPrio[(data & 0x00000F00)>>8]);
        }
        else if(data & 0x0000F000)
        {
        return(12 + g_unHighPrio[(data & 0x0000F000)>>12]);
        }
        else if(data & 0x000F0000)
        {
        return(16 + g_unHighPrio[(data & 0x000F0000)>>16]);
        }
        else if(data & 0x00F00000)
        {
        return(20 + g_unHighPrio[(data & 0x00F00000)>>20]);
        }
        else if(data & 0x0F000000)
        {
        return(24 + g_unHighPrio[(data & 0x0F000000)>>24]);
        }
        else if(data & 0xF0000000)
        {
        return(28 + g_unHighPrio[(data & 0xF0000000)>>28]);
        }
        else
        {
        return 0;
        }
        }
         
        int main(void)
        {
        unsigned int nTst = 0;
        int npos = 0; 
        while(1)
        {
        nTst = 0;
        printf("Input Tst = ");
        scanf("%u",&nTst);
        printf("The First 1 position is %d\n",GetOnePos(nTst));
        getchar();
        }
        }
         
         
         
        實時操作系統RTOS
        實時操作系統包括軟實時與硬實時,軟實時要求各個任務盡快地運行,而不要求限定某一個任務在多長的時間內完成,而硬實時系統不僅須執行無誤而且要做到準時,需要在規定的時間內完成相關操作;
        大多數實時系統是兩者的綜合;
        非實時操作系統指操作系統無法保證哪怕是最高優先級任務開始執行的最后時限。軟實時操作系統指的是操作系統只能保證在xx時間內執行最高優先級的用戶代碼,但用戶軟件是否能及時完成操作,操作系統不管!
        RTlinux就是linux的硬實時操作系統,它能夠創建精確運行的符合POSIX.1B標準的實時進程;
         
        并非所有的嵌入式系統都需要實時操作系統,只有在一些特定的場合,對時間比較敏感的應用才會使用實時操作系統。實時操作系統必須及時響應所要求的任務,在限定時間內完成任務。非實時的操作系統,多時間不是很敏感,對所要求的任務只是會保證完成,但在什么時候完成,或用多長的時間完成就不一定了。例如:手機它不需要實時性。我們發短信時,系統對它的處理早1秒或者晚1秒都不會影響到我們的使用。而對于導彈這樣的應用必須具有實時性。導彈被發射出去鎖定目標后要不斷修正飛行方向,以保證擊中目標,如果它的實時性不好的話,從傳感器傳來的信號沒有及時響應,即使完了1毫秒的時間,那誤差就會很大。用這樣的導彈攻打敵方目標的話,目標很可能沒有擊中,美國大使館倒是有可能被炸掉。
        另外一般linux不具有實時性,它是分時操作系統一般是面向用戶的,但是因為它的源代碼是公開的,它是可以改造成實時系統的,但即使是這樣它的實時性也不會很好,畢竟它最初的設計并不是為了實時性。我們在Linux上面同時運行好幾個程序,它們會被并發的執行。我們會發現同時多運行幾個程序可能會比只允許一個程序慢,這是因為操作系統把處理器按時間片分給了每一個程序。自然會慢一些。而實時操作系統,一般不同的任務會有不同的優先級,他會把擁有最高的優先級的程序一次性執行完畢。然后再執行次一級的程序。這樣的系統只適用于控制,不適合一般的應用。
         
        任務劃分
        目標:
            滿足實時性要求;
            任務數目合理;-->合理使用系統的軟硬件資源
            簡化軟件系統;-->合理規劃任務,降低對操作系統的服務要求,使操作系統功能得到裁剪,簡化系統
            降低資源需求;
           目前,各功能單獨成立為一個任務,比如顯示功能成為顯示任務,文件系統通過一個任務來管理,這樣文件系統任務要顯示時,就向顯示任務的消息隊列里面發送顯示消息,而不是直接在本任務中調用顯示函數。
        0:設備依賴性任務的劃分
        從系統的結構框圖中,我們可以看到系統的輸入輸出各個設備。并發性是任務的基本特性,而控制輸出、輸出的設備的程序具有先天的并發性,把他們分別封裝為不同的任務是合理的,這樣就可以劃分出第一批任務,鍵盤任務,顯示任務,數據采集任務,控制輸出任務和通信任務。
        1:系統關鍵任務劃分:關鍵是指某種功能在應用系統中的重要性,如果該功能不能正常實現,則將造成重大影響,甚至引發災難性的后果;包含該關鍵功能的任務為關鍵任務,關鍵任務必須得到運行機會即使遺漏一次也不行; 對于關鍵功能,必須盡可能與其他的功能剝離,獨立成為一個任務,通過通信的方式再觸發其他的任務,完成后續操作。例如:火災檢測系統中,煙霧傳感器的檢測功能就是一個關鍵功能,必須將其與其他的報警、滅火等功能剝離。OSMboxPostOpt()消息發送函數,具有廣播功能:  O在uC/OS II的較新版本中,消息發送函數OSMboxPostOpt()具有廣播功能,發送一條消息就可以使所有等待該消息的任務進入就緒狀態。
        2:緊迫任務劃分:某種功能必須在規定的時間內得到運行權(及時運行),并在規定的時刻前執行完畢(按時完成),這類功能有嚴格的實時性要求。大多數的緊迫任務是通過異步事件來觸發,這些異步事件一般能夠引發某種中斷。在這種情況下,將緊迫任務安排在相應的ISR中是最有效的辦法,如果不能安排在中斷任務中,那么可竟盡可能提高優先級來解決“及時性”。對于按時完成,需要對緊迫任務進行瘦身,盡可能剝離不太緊迫的操作,只剩下必須立刻做的操作,被剝離的不太緊迫的操作另外封裝成一個任務。例如:能譜分析儀,緊迫任務放在外部中斷服務程序中,完成對脈沖峰值的采樣,并將采樣結果放入消息隊列中。緊迫任務不一定是關鍵任務,所以遺漏一兩次執行會導致工作品質下降,但是不會造成嚴重后果。
        3:數據處理任務劃分:用戶程序中消耗時間最多的是各種數據處理程序單元,這些單元不止一個,且分別為不同的功能服務;所以需要將他們劃分出來,分別包裝為不同的任務,因為他們的處理較耗時,所以他們的優先級須安排低,這樣讓他們使用其他任務的剩余時間來進行數據處理(如果有時間片輪轉,可以安排他們為同一優先級,利用時間片來輪轉他們)。模擬時間片輪轉:假如有3個數據處理任務A、B、C;我們可以將他們細分為A1、A2、A3,B1、B2、B3,C1、C2、C3,再交叉安排優先級就可以了。A1->1,B1->2,C1->3,A2->4,B2->5...
        4:功能聚合任務劃分:將關系密切的若干功能組合為一個任務,達到功能聚合的效果,關系密切:數據關聯和時序關聯,如果他們分開,有可能會使用大量的通信資源,造成較大的負擔。
        5:觸發條件相同的任務劃分
        如果若干個功能由相同的事件觸發,則可以將這些功能組合成為一個任務,從而免除將事件分發給多個任務的工作量。這樣做的條件是:當以某順序執行這些功能時,各個功能的實時性要求仍然可以得到滿足,且各個功能在執行過程中不會出現問題,例如:火警檢測系統中,撥打電話、啟動噴淋滅火系統、保持火警記錄,這些任務是不能合在一起做為一個任務的,否則其中一些功能有誤其他的任務就會被耽擱。
        符合本類任務的通常是內部事件,例如通過運算處理產生某個結果,根據這個結果需要執行若干功能,這些功能可組合為一個任務。
        6:運行周期相同的任務劃分
        絕大多數功能都需要不停的重復執行,如果重復執行的條件是固定的時間間隔,則這個功能具有周期性。將周期相同的功能組合在一起封裝為一個任務,就可以避免一個時間事件觸發幾個任務,省去事件分發操作和他們的之間的通信。
        7:順序操作任務劃分
        如果若干個功能按固定的順序運行流水作業,相互之間完全沒有并發現,則應該將這個功能組合為一個任務。

        任務設計
        任務函數結構
        所設計的任務函數至少有一次對操作系統服務函數的調用,這樣才能讓低優先級的任務得到執行。
        1:單次執行的任務
        任務創建后,得到執行,執行完畢后自我刪除;任務基本分為三大部分:1:準備工作代碼(定義變量以及初始化工作)2:任務實體代碼(完成具體的功能,一般都可以被中斷)3:調用刪除函數。例如啟動任務(如果采用啟動任務去啟動各個任務,那啟動任務的優先級需要比它創建的任務的優先級高,一般系統中通常將啟動任務所做的事情交給系統的一個實質任務去做,節省資源);
        采用“創建任務”的方式來啟動任務,不僅可以省去通常的通信手段激活任務的麻煩,還可以通過*pdata來傳遞參數,是沒有啟動具有不同的工作狀態(比如串口波特率),但是這樣的話實時性會比較差,每一次任務的啟動都需要創建,發費較多的時間,還有可能在刪除時引起不必要的后遺癥(如共享資源釋放、任務關聯);
        所以通過“創建任務”來啟動的任務一般是孤立的任務,他們不和其他的任務進行通信(ISR除外),只使用共享資源來獲取信息和輸出信息。
        2:周期性執行的任務 
        周期性執行的任務,通常在代碼中調用系統延時函數,OSTimeDly或OSTimeDlyHMSM來調整執行周期。但是這兩個函數有延時誤差,至少有一個或小于一個時鐘節拍的誤差,如需精確的定時需采用獨立的定時器。
        3:事件觸發執行的任務
        任務的實體代碼的執行需要等待某種事件的發生,在相關事件發生之前,任務被掛起,相關事件發生一次,任務執行一次。當觸發條件是“時間間隔”(定時器觸發)時,它既是周期任務。
        如觸發條件是某個信號(信號量等),那么這個觸發條件僅僅是觸發任務的執行。
        如觸發條件是某個信息(郵箱等),那么這個觸發條件除了啟動該任務外,還為任務提供原始數據和資料。
        任務優先級安排
        uC/OS II共有64個優先級:0~63。在OS_CFG.h中設置OS_LOWEST_PRIO來確定系統實際使用的優先級范圍,#define OS_LOWEST_PRIO  18 ->系統裁剪到只有19個優先級,節省資源開銷。
        OS_LOWEST_PRIO-->空閑任務;     OS_LOWEST_PRIO-1-->統計任務;
        OS_LOWEST_PRIO-2-->系統保留;   OS_LOWEST_PRIO-3-->系統保留;
        系統最高的4個優先級(0、1、2、3)保留。
        任務優先級安排原則
        中斷關聯性、緊迫性、關鍵性、頻繁性、快捷性、傳遞性
         
        uC/OS II通信機制
        uC/OS II通信機制包括有信號量(計數)、互斥信號量(可以高低優先級翻轉)、事件標志組、郵箱、消息隊列;
        所有的通信機制都有5種功能函數:創建、刪除、查詢、發送、(掛起式)獲取、(不掛起、不等待式)獲;
        且一旦某個通信制作被使用,其創建、發送、(掛起式)獲取功能是不能被裁剪的;
        中斷函數需要盡可能短,實時性高,在中斷中,可以發送信號,其他的不要用。
        除了事件標志組用OS_FLAG_GRP結構體表示外,其余的通信方式都使用OS_EVENT結構體表示;
        typedef struct os_event {
            INT8U    OSEventType;                    //通信事件的類型
            void    *OSEventPtr;                     //郵箱或消息隊列中指向消息實體的指針
            INT16U   OSEventCnt;                     //計數單元
            INT8U    OSEventGrp;                     //等待該通信事件的任務所在的組
            INT8U    OSEventTbl[OS_EVENT_TBL_SIZE];  //等待該通信事件的任務列表
        } OS_EVENT;
        1:信號量(計數型)sem
        信號量是一個可被多個進程共享的數據結構,主要用于任  
        務間少量的信息通信。信號量通常是在多個任務訪問一個共同的但
        非共享的資源的情況下,用于同步各個任務之間的操作。
        OS_EVENT *pevnt;
        pevnt = OSSemCreate(int cnt);//創建并賦初值
        OSsemPost(pevnt );
        OSEventCnt>0表示該信號有效(且表示事件發生的次數),OSEventCnt==0表示該信號無效;
        任務調用OSsemPost(pevnt),表示計數型信號量事件的OS_EVENT結構體中的OSEventCnt++;
        任務調用OSSemPend(pevnt),如果此時OSEventCnt>0,則OSEventCnt--,且該任務接著繼續往下執行;否則該任務掛起(再進行一次任務切換),直到該事件發生(OSEventCnt>0)且此時系統中該任務的優先級最高方可得到運行;
        任務調用OSSemAccept(pevnt),如果此時OSEventCnt>0,則OSEventCnt--,且不論OSEventCnt的值是多少,該函數直接返回OSEventCnt的值,return(cnt);
        任務調用OSSemQuery(OS_EVENT *, OS_SEM_DATA),OS_SEM_DATA是一個精簡的OS_EVENT結構體,用來記錄被查詢信號量的計數值、任務等待列表等;
        任務調用OSSemDel(pevnt )刪除信號量;起初分配的OS_EVENT結構體被釋放到空閑事件鏈表中;
        2:互斥信號量mutex
        在訪問比較耗時的共享資源時,如果采用關中斷的方法(系統此時不能被中斷、任務不能被切換)來實現訪問沖突,這對中斷的響應是很不好的,所以這時采用互斥型信號量就可以很好的解決問題,同時可以響應中斷。
        互斥鎖用來實現任務之間的簡單同步,一個互斥鎖是一個  
        二元信號量,它的狀態只能是0(允許,開鎖)和1(禁止,上鎖)。
           
        在互斥鎖范圍內,任何一個任務都可以對互斥負上鎖,但只有鎖住
           
        該信號的任務才能開鎖從而實現了任務同步。
        mutex不同于sem,mutex是一個互斥型信號量,它可以通過在應用程序中翻轉任務的優先級來解決資源互鎖的問題;
        舉例:首先給一塊共享資源配備一個互斥型信號量Mutex,系統中有兩個任務A、B,他們的優先級分別為5,6;
        假設B先運行,并通過OSMutexPend申請到了Mutex,那么它就會使用該共享資源繼續運行,在某個時刻,任務A搶占了任務B,同時也通過OSMutexPend申請Mutex,由于資源已經被B占用,那么OSMutexPend會將B任務的優先級調高到4(假設),這時任務B得意繼續運行,資源使用完畢后釋放mutex。OSMutexPost注意到原來占有這個mutex的任務的優先級被調高了,于是將B的優先級調低,同時注意到A在申請,于是將mutex給A,做任務切換后A得以執行。
        由于互斥型信號量的特性,互斥型信號量只能由于任務中(包括發送功能);
        OS_EVENT *ResourceMutex;
        ResourceMutex= OSMutexCreate(INT8U prio,&err);//在上述情況中 任務可以被調高到prio指定的優先級
        OS_EVENT中,pevent->OSEventCnt = (INT16U)((INT16U)prio << 8) | OS_MUTEX_AVAILABLE(0xff);
        高八位:PIP;低八位:占用該互斥信號量的任務的優先級(若為0xff表示無任務占用該信號量);
        OSMutexPend(ResourceMutex,..,..):在訪問共享資源時,先通過該函數獲取互斥型信號量,如果互斥型信號量是有效的(沒有被占用),則該Mutex的OSEventCnt的低八位為0xff,如果已被占用,則低八位為占用該信號量的任務的優先級;如果占用該信號量的任務的優先級比調用該申請函數的任務的信號量的優先級低,此時低優先級的任務占用共享資源,且已被高優先級的任務搶占了CPU,此時高優先級的任務再調用OSMutexPend申請互斥量,OSMutexPend內部會將占用mutex的低優先級的任務的優先級調高到信號量指定的PIP,這樣來讓低優先級的任務變為高優先級,盡快釋放資源;
        3:事件標志組event flag
        typedef struct os_flag_grp {                
            INT8U         OSFlagType;               //事件類型
            void         *OSFlagWaitList;           //等待該事件標志組的任務列表
            OS_FLAGS      OSFlagFlags;              //事件標志組標志位
        } OS_FLAG_GRP;
        事件標志組用于實現多個任務(包括ISR)協同控制一個任務,當各個相關任務(ISR)先后發出自己的信號后(使事件標志組的對應標志位有效),預定的邏輯運算結果有效,這時將觸發被控制的任務(使其進去就緒態)。
        事件標志組可以選擇標志位1有效或0有效,邏輯關系可以為“邏輯與”或“邏輯或”,這樣有效定義與邏輯定義有4種組合,同時也可以設定只選擇使用所有標志位中的其中幾位;
        OS_FLAG_GRP *OSFlagCreate(OS_FLAGS flags,INT8U *err);
        flags為事件標志組中各個標志的初始值(1有效時,初始值為0);
        發送標志到事件標志組:
        OS_FLAGS  OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *perr)
        pgrp:事件標志組指針;flags:指明待發送標志在事件標志組中的位置(0x1表示bit0);opt:選擇操作的方式,OS_FLAG_SET(對標志位置1),OS_FLAG_CLR(對標志位置0);perr:執行結果;
        等待事件標志組:
        OS_FLAGS  OSFlagPend (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *perr)
        pgrp:事件標志組指針;flags:指明哪些標志位用來等待,0x03(bit0、1),0x1d(bit0、2、3、4);opt:指明對這些等待的位采用的邏輯運算;perr:執行結果;
        opt如下:
        OS_FLAG_WAIT_CLR_ALL:所以標志位為0時,將等待任務就緒;
        OS_FLAG_WAIT_CLR_ANY任何一個標志位清0時,將等待任務就緒;
        OS_FLAG_WAIT_SET_ALL:所以標志位為1時,將等待任務就緒;
        OS_FLAG_WAIT_SET_ANY:任何一個標志位為1時,將等待任務就緒;
        OS_FLAG_CONSUME:清除標志位(OS_FLAG_WAIT_SET_ANY + OS_FLAG_CONSUME
        4:消息郵箱box
        郵箱是μC/OS-II中另一種通訊機制,它可以使一個任務或者中斷服務子程序向另一個任務發送一個指針型的變量。該指針指向一個包含了特定“消息”的數據結構。
        用信號量進行行為同步時,只能提供同步的時刻信息,不能提供內容信息;
        當控制方在對被控制方進行控制的同時,還需要向被控制方提供內容信息時,消息郵箱是一個有效的方案;
        由于消息郵箱中只能存儲一條消息,在用消息郵箱進行同步控制時,必須滿足一個前提:任何時候消息的生產速度都比消息的消費速度慢,即被控制的任務總是在等待消息;否則就會有消息丟失;
        消息郵箱中可以放入任何類型的信息,通常用空郵箱(void * 0)表示事件沒有發生;用非空郵箱(void * 1)表示事件已發生;因此郵箱也可以用來做二值信號量(注意與互斥信號量的區別);
        郵箱用OS_EVENT結構體中的OSEventPtr指向消息實體;
        OS_EVENT *pbox;
        pbox= OSMBoxCreate(void *msg);//void * 0表示空郵箱
        INT8U OSMboxPost(pbox, void * msg);//向郵箱中發送一條消息
        INT8U OSMboxPostOpt(pbox,void * msg, INT8U opt)//分發消息,將消息分發給所有正在等待該消息的任務,讓他們都處于就緒態
        void *OSMboxPend(pbox,timeout,INT8U *err)//等待消息 當郵箱中的指針為空時,調用等待消息函數后就會被系統掛起。
        5:消息隊列
        將要通信的信息放置在一個預定義的消息結構中,任務生成的消息指定了消息的類型,并把它放在一個由系統負責維護的消息隊列中,而訪問消息隊列的任務可以根據消息類型,有選擇地從隊列中按FIFO的方式讀取特定類型的消息。消息隊列為用戶提供了從多個生產者中獲得多元信息的一種手段。
        相當于一個郵箱隊列,消息隊列可以存放多個消息,能夠有效解決消息的臨時堆積問題。和計數信號量的情況類似,消息隊列的使用仍然需要滿足:消費速度比生產速度快,否則再大的隊列也會滿,從而溢出;
        void *MyArrayOfMsg[SIZE];
        OS_EVNT *pQ;
        pQ=OSQCreate(MyArrayOfMsg,SIZE); 
        INT8U OSQPost(pQ,void *Msg)//發送一條消息
        void *OSQPend(pQ,0,&err)//當消息隊列為空時 掛起


        【各個通信機制都要無等待式獲取相關的信號,均可用在中斷中,中斷中決不能用等待的方式】



         
         
        相關函數使用說明:
        創建任務:
        INT8U  OSTaskCreateExt (void   (*task)(void *p_arg),//被創建的任務函數指針
                                void    *p_arg,               //傳遞給任務的參數的指針
                                OS_STK  *ptos,                //分配給任務的堆棧的棧頂指針
                                INT8U    prio,                //被創建任務的優先級
                                INT16U   id,                  //為創建的任務創建一個特殊的標識符,暫沒用
                                OS_STK  *pbos,                //指向任務的堆棧棧底的指針(用于堆棧檢驗)
                                INT32U   stk_size,            //堆棧的容量
                                void    *pext,                //指向用戶附件的數據域的指針
                                INT16U   opt)                 //指定是否卞堆棧檢驗,是否將堆棧清0,任務是否需要進行浮點操作
        OSTaskCreate中還調用了OSTaskStkInit函數,調用該函數的目標是初始化任務的堆棧,使其看起來像發生過中斷一樣。OSTaskStkInit是需要移植的函數。
         
        修改任務屬性:
        調用OSTaskChangePrio()函數可以動態地改變某一個任務的優先級,調用OSTaskNameGet()函數可以獲取某一個任務的名稱,調用OSTaskNameSet()函數可以設置一個任務的名稱。
        INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio);
        INT8U OSTaskNameGet INT8U   prio,INT8U  *pname,INT8U  *perr);
        void  OSTaskNameSet (INT8U prio, INT8U *pname, INT8U *perr)
        任務的名稱保存在任務對應的任務控制塊中的成員:OSTCBTaskName[OS_TASK_NAME_SIZE]中;
        直接掛起任務的函數:
        有時將任務掛起是很有用的,掛起任務的函數可以通過INT8U  OSTaskSuspend(INT8U prio)來實現,且被掛起的任務只能通過調用INT8U  OSTaskResume (INT8U prio)來恢復。任務可以掛起自己或者其他的任務。
        舉例:在一些設計中,在main函數中只有簡單的幾行,在其中創建了一個啟動任務:用于啟動其他的任務與初始化系統,啟動任務的優先級比它要創建的任務都要高,初始化成功與創建完相關任務之后,一般自己將自己掛起。
        堆棧檢驗函數:
        如果要使用堆棧檢驗函數,那么在任務的建立需要使用OSTaskCreateExt()來建立任務,且需要指定入口參數:opt(OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR);
        OS_TASK_OPT_STK_CHK:指示任務需要使用堆棧校驗功能
        OS_TASK_OPT_STK_CLR:將任務的堆棧RAM清0(因為堆棧檢驗是從棧底開始計算為0(空閑)的空間大。
        在任務創建時指定棧頂、棧底,在任務運行一段時間后,一般需在堆棧使用最充分后才去檢驗較為準確;任務可以檢查自己或者其他任務的堆棧使用情況,堆棧檢驗函數確定堆棧的實際空間字節數和已被占用的字節數,放在入口參數OS_STK_DATA數據結構中;
        [可以閱讀 嵌入式ARM系統原理與實例開發(楊宗德)的第八章,里面對該系統包含的大部分函數簡要說明以及移植時的相關函數說明]

        任務切換過程分析
        1:任務之間搶占式切換(高優先級的任務就緒后立即搶占正在運行的低優先級的任務)
        系統在任何響應后,都需要進行任務調度,確保系統中優先級最高的任務被執行,那么系統采用的任務調度切換函數就是void  OS_Sched(void);
        void  OS_Sched(void)
            OS_ENTER_CRITICAL();   //進入臨界區 關中斷 
            if(OSIntNesting==0) {  //目前系統不處于中斷態                         
                if(OSLockNesting==0) { //系統調度功能使能                    
                    OS_SchedNew();   //計算出目前系統中最高的優先級,保存在INT8U OSPrioHighRdy中
                    if(OSPrioHighRdy!=OSPrioCur) { //判斷當前正運行的任務是不是最高優先級 否則就不用切換
                         OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//將最高優先級任務的控制塊的指針放                                                             //入任務控制塊指針變量OSTCBHighRdy
                         #if OS_TASK_PROFILE_EN > 0
                            OSTCBHighRdy->OSTCBCtxSwCtr++; //目前優先級最高的任務切換次數統計        
                         #endif
                         OSCtxSwCtr++; //系統任務切換的總次數統計
                         OS_TASK_SW(); //實現任務切換
                    }
                }
             }
             OS_EXIT_CRITICAL(); //恢復全局中斷標志 退出臨界區
         
        在任務調度前,OS_SchedNew()計算出目前系統中最高的優先級,保存在INT8U OSPrioHighRdy中
        #define  OS_TASK_SW()         OSCtxSw()
        OSCtxSw //懸起PSV異常
            LDR     R0, =NVIC_INT_CTRL  //NVIC_INT_CTRL為SCB中的中斷控制寄存器(ICSR)的地址數值 0xE000ED04
            LDR     R1, =NVIC_PENDSVSET //NVIC_PENDSVSET 為設置ICSR的值(0x10000000
            STR     R1, [R0]            //設置ICSR = NVIC_PENDSVSET  即將觸發PendSV中斷
            BX      LR                  //因為在OS_Sched中調用OSCtxSw時,是在臨界區,中斷是關閉的,所以這里通過BX LR返回OS_Sched,等退出臨界區后,觸發PendSV中斷【任務切換時必須關中斷”的原則
        PendSV中斷響應函數OSPendSV
        //Cortex-M3進入異常服務例程時,使用的是MSP指向的堆?臻g,且自動壓入了R0-R3,R12,LR(R14,連接寄存器),PSR(程序狀態寄存器)和PC(R15),這些寄存器是任務被中斷時的現場記錄,此時任務所使用的PSP的值沒有變化。
        //M3處理器的控制寄存器的CONTROL[1] = 1(系統復位后默認是0),表明M3的線程模式的堆棧指針SP選用PSP【handler模式只允許使用MSP】,這樣兩個模式下SP使用不同的堆棧指針,且訪問SP時,訪問到的是當前被使用的堆棧指針,這時另一個堆棧指針要通過MRS、MSR命令來訪問,即R13(PSP)或R13(MSP),R13是PSP還是MSP由系統當前的狀態與CONTROL[1]決定,PSP是某個任務在RAM中的堆棧指針,MSP是異常模式下在RAM中的堆棧指針。
        //一個任務占用CPU運行,它的運行現場是R0-R3,R4-R11,R12,R13(PSP/MSP),R14(LR),PSR和R15(PC),如果該任務被中斷,那么在進中斷過程中,硬件會自動對CPU的部分現場寄存器(R0-R3,R12,R14,PSR和R15)進行壓棧,其他寄存器硬件不壓棧,如果CONTROL[1] = 1,那么硬件就會將相關寄存器壓入PSP(R13)指向的堆棧中(線程模式時SP為PSP),CONTROL[1] = 0,硬件就會將相關寄存器壓入MSP(R13)指向的堆棧中;CONTROL[1] = 1時如下圖所示,線程模式與handler模式的SP不一樣。
         
        OSPendSV   【在進入中斷前 R0-R3,R12,R14,PSR和R15已被壓入任務所使用的堆棧中,下面是將其他的寄            存器再壓入任務的堆棧中,以及保存該任務的堆棧棧頂的值到PSP指向的堆棧中去,任務使用            的是PSP,異常模式下使用的是MSP
            MRS     R0, PSP   //R0<=PSP 異常模式下使用的是MSP,所以只能通過MRS讀取被中斷的任務的堆棧指針,
            CBZ     R0, OSPendSV_nosave //如果PSP==0,則跳轉到 OSPendSV_nosave ,否則往下執行【在系統剛開                                //始啟動時,OSStart()會調用OS_SchedNew()與OSStartHighRdy()來使                                 //系統中最高優先級的任務運行,在OSStartHighRdy中,PSP設置為0,因為                                 //在OSStart前還沒有任務運行,所以進入軟中斷后不用保存此時CPU的值,                                 //只要將最高優先級任務的現場恢復到CPU的各寄存器就好了
            SUBS    R0, R0, #0x20   //R0(PSP)!=0,那么R0的值減去0X20,32個字節(下面保持8個32位的寄存器)
            STM     R0, {R4-R11}    //將R4~R11這8個寄存器存儲到PSP對應的空間中去,這些寄存器是當前任務被中                             //斷時的現場
            LDR     R1, __OS_TCBCur //__OS_TCBCur  DCD  OSTCBCur
                                    //當前任務(被中斷的任務)的任務控制塊OSTCBCur, OSTCBCur是當前任務控制                             //塊的首地址,也代表了它的第一個成員的地址,所以下面其實是對他的第一個                             //成員OS_STK *OSTCBStkPtr(指向任務的堆棧棧頂的指針)進行賦值
            LDR     R1, [R1]        //將OSTCBStkPtr中保持的值賦給R1,該值指向任務的堆棧棧頂的指針
                                    //即R1存儲被中斷的當前任務的堆棧棧頂的指針
            STR     R0, [R1]        //將被中斷的當前任務的堆棧棧頂的指針存到PSP指向的堆棧中去,即保存被中斷                             //任務所使用的堆棧棧頂的指針,上面其他的寄存器是任務被中斷的線場
        OSPendSV_nosave  【上面的程序執行完后,接著繼續執行下面的程序】
            PUSH    {R14}           //保存R14寄存器的值 壓入到MSP指向的堆棧中,該寄存器在中斷返回時大有作用
            LDR     R0, __OS_TaskSwHook //調用回調函數  鉤子函數        
            BLX     R0
            POP     {R14}           
            -------------------------------------------
            //相當于定義指針變量:__OS_PrioCur DCD   OSPrioCur;  __OS_PrioHighRdy  DCD  OSPrioHighRdy
            LDR     R0, __OS_PrioCur     //INT8U OSPrioCur當前任務的優先級   
            LDR     R1, __OS_PrioHighRdy //OSPrioHighRdy系統中最高的優先級  
            LDRB    R2, [R1]             //上面兩句是將變量的地址傳給了R0與R1,[R1]最高優先級的值存入R2
            STRB    R2, [R0]             //將R2中的值存入到OSPrioCur變量中去(即接下來運行最高優先級任務)
                                         //即實現:OSPrioCur = OSPrioHighRdy;
            -------------------------------------------------
            //相當于定義指針變量:__OS_TCBCur DCD   OSTCBCur;  __OS_TCBHighRdy  DCD  OSTCBHighRdy     
            LDR     R0, __OS_TCBCur      //R0=&OSTCBCur;  OS_TCB  *OSTCBCur; OS_TCB *OSTCBHighRdy;
            LDR     R1, __OS_TCBHighRdy  //R1=&OSTCBHighRdy;這四句同理上面,OSTCBCur = OSTCBHighRdy
            LDR     R2, [R1]             //R2 = *R1;  R2=OSTCBHighRdy 指向優先級最高的任務的任務控制塊
            STR     R2, [R0]             //*R0 = R2即實現:OSTCBCur = OSTCBHighRdy;
                                         //系統中用OSPrioCur   OSTCBCur 來表示正在運行的任務
            ----------------------------------------------
           通過R2(OSTCBHighRdy)將OSTCBHighRdy->OSTCBStkPtr的值賦給R0,再通過LDM將最高優先級任務的堆棧中保存好的R4-R11恢復到當前CPU的R4-R11寄存器中(這個過程與上面的保存過程是相逆的),因為R0-R3,R12,R14,PSR和R15是硬件自動壓入任務的堆棧的,在中斷中后來再保存R4-R11的(是先對SP減了32后再保存,相當于SP的值沒有變化),且中斷退出時會自動彈出R0-R3,R12,R14,PSR,所以下面的代碼只要恢復最高優先級任務的R4-R11(OSTCBHighRdy->OSTCBStkPtr通過LDM自減了32,),然后將加上32, OSTCBHighRdy->OSTCBStkPtr 相當于沒有變化,再將OSTCBHighRdy->OSTCBStkPtr賦給PSP,這樣硬件在中斷退出時會自動恢復 R0-R3,R12,R14,PSR ,這樣就完全恢復了,一運行就是最高優先級的任務了
            LDR     R0, [R2]             //[R2]為*OSTCBHighRdy,R2是4字節的寄存器,所以[R2]是OSTCBHighRdy                                  //指向地址的后4個字節的值,即OSTCBHighRdy->OSTCBStkPtr,  
                                         //即 R0 = OSTCBHighRdy->OSTCBStkPtr
            LDM     R0, {R4-R11}         //將 OSTCBHighRdy->OSTCBStkPtr(R0)指向的堆棧(向下生長)棧頂后                                  //的32個字節依次存入R4-R11(R0的值自減,后綴為!表示自增),因為堆                                   //棧保持時是自減的,這里相當于恢復最高優先級任務的現場)               
            ADDS    R0, R0, #0x20        //將R0的值加上0x20后賦給R0
            MSR     PSP, R0              //PSP <= R0                              
            ORR     LR, LR, #0x04    //按位或 將LR的第二位置1,這樣中斷返回時,就將從進程的堆棧中做                              //出棧操作,返回后使用PSP(否則將從主堆棧中做出棧操作,返回后                              //使用MSP)                                
            BX      LR                   //中斷返回
         
        xPSR、PC、LR、R12、R3、R2、R1、R0會被硬件按一定的次序壓入PSP所指向的堆棧中(同時被中斷的任務后面要恢復執行,還需保存R4-R11,保存后一定要保證堆棧指針指向硬件自動壓棧后的原位置,這樣硬件才能在恢復任務時硬件自動正確出棧)
         
         
        2:在中斷中實現任務的切換
        uC/OS II  的中斷服務函數必須遵循一定的架構來實現,
        void SysTickHandler(void)
        {
            OS_CPU_SR  cpu_sr;
            OS_ENTER_CRITICAL();  //保存全局中斷標志,關總中斷
            OSIntNesting++;
            OS_EXIT_CRITICAL();  //恢復全局中斷標志

            OSTimeTick();    
         
            OSIntExit();  //在os_core.c文件里定義,如果有更高優先級的任務就緒了,則執行一次任務切換 
        }

        OSInit函數
        系統初始化函數OSInit()初始化所有的變量和數據結構,同時也會建立空閑任務OS_TaskIdle(),該任務永遠處于就緒態,如果統計任務使能,那么他還要建立統計任務OS_TaskStat(),并使其進入就緒態。
        空閑任務
          OS_TaskIdle
        uC/OS II總要建立一個空閑任務,idle task,這個任務在沒有其他任務進入就緒態時投入運行,它的優先級永遠設為最低優先級,即OS_LOWEST_PRIO,且空閑任務是不能被應用軟件刪除的;
        空閑任務不停的給一個32位的OSIdleCtr的變量加1,統計任務用這個計數器變量確定當前應用軟件實際消耗CPU的時間,計數器加1前后分別關閉打開中斷,OS_TaskIdle可以借助OSTaskIdleHook()做CPU的睡眠等;OS_TaskIdle總是處于就緒態。
        統計任務OS_TaskStat
        只要將OS_TASK_STAT_EN宏使能,那么統計任務就會建立,一旦運行,它將每秒運行一次,計算當前CPU的利用率,將值放在OSCPUsage這個8位的變量中,用百分比表示,精度為1%.如果應用程序打算使用統計任務,那么必須在初始化時建立的第一個也是唯一的任務中調用統計任務初始化函數OSStatInit(),也就是在調用系統啟動函數OSStart前,用戶初始化代碼中必須先建立一任務,在這個任務中調用系統統計初始化函數OSStatInit,然后再建立應用程序中的其他任務。它的優先級是OS_LOWEST_PRIO-1
        OSStart操作系統啟動函數
        uC/OS II啟動之前,至少須建立一個應用程序,因為如果使能統計任務,那么統計任務要求必須先建立一個也是唯一一個用戶任務后,再啟動系統,再創建其他的任務,如果什么任務都沒有建立,只要空閑任務,那系統也就一直空閑,所以需要先創建至少一個任務。

        關閉窗口
        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮