寫在前面:
由于時間的不足與學習的碎片化,寫博客變得有些奢侈。
但是對于記錄學習(忘了以后能快速復習)的渴望一天天變得強烈。
既然如此
不如以天為單位,以時間為順序,僅僅將博客當做一個知識學習的目錄,記錄筆者認為最通俗、最有幫助的資料,并盡量總結幾句話指明本質,以便于日后搜索起來更加容易。
標題的結構如下:“類型”:“知識點”——“簡短的解釋”
部分內容由于保密協議無法上傳。
點擊此處進入學習日記的總目錄
2024.03.02
- 九、UCOSIII:創建任務
- 1、任務的定義
- 2、定義任務棧
- 3、定義任務函數
- 4、定義任務控制塊TCB
- 5、任務創建函數
- 6、任務棧初始化函數
- 7、將任務添加到就緒列表
- 8、全局變量的聲明方法
九、UCOSIII:創建任務
1、任務的定義
在裸機系統中,系統的主體就是main函數里面順序執行的無限循環,這個無限循環里面CPU按照順序完成各種事情。
在多任務系統中,我們根據功能的不同,把整個系統分割成一個個獨立的且無法返回的函數,這個函數我們稱為任務。
void task_entry (void *parg)
{/* 任務主體,無限循環且不能返回 */for (;;){/* 任務主體代碼 */}
}
2、定義任務棧
/*
************************************************************************************************************************
* TCB & STACK & 任務聲明
************************************************************************************************************************
*/
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20static CPU_STK Task1Stk[TASK1_STK_SIZE];
static CPU_STK Task2Stk[TASK2_STK_SIZE];static OS_TCB Task1TCB;
static OS_TCB Task2TCB;void Task1( void *p_arg );
void Task2( void *p_arg );
在多任務系統中,有多少個任務就需要定義多少個任務棧。
任務棧的大小由宏定義控制,在μC/OS-III中, 空閑任務的棧最小應該大于128,那么我們這里的任務的棧也暫且配置為128。
(但是代碼里任務棧數量為20)
任務棧其實就是一個預先定義好的全局數據,數據類型為CPU_STK。
在μC/OS-III中,凡是涉及數據類型的地方, μC/OS-II都會將標準的C數據類型用typedef重新取一個類型名,命名方式則采用見名之義的方式命名且統統大寫。
3、定義任務函數
任務是一個獨立的函數,函數主體無限循環且不能返回。
任務的代碼示意如下:
/* flag 必須定義成全局變量才能添加到邏輯分析儀里面觀察波形
** 在邏輯分析儀中要設置以 bit 的模式才能看到波形,不能用默認的模擬量
*/
uint32_t flag1;
uint32_t flag2;/* 任務1 */
void Task1( void *p_arg )(2)
{
for ( ;; ) {flag1 = 1;delay( 100 );flag1 = 0;delay( 100 );}//任務是一個獨立的、無限循環且不能返回的函數。
}/* 任務2 */
void Task2( void *p_arg )(3)
{
for ( ;; ) {flag2 = 1;delay( 100 );flag2 = 0;delay( 100 );}
}
4、定義任務控制塊TCB
/*
************************************************************************************************************************
************************************************************************************************************************
* 數據類型
************************************************************************************************************************
************************************************************************************************************************
*/typedef struct os_rdy_list OS_RDY_LIST;
typedef void (*OS_TASK_PTR)(void *p_arg);
typedef struct os_tcb OS_TCB;
/*
------------------------------------------------------------------------------------------------------------------------
* 就緒列表
------------------------------------------------------------------------------------------------------------------------
*/
struct os_rdy_list
{OS_TCB *HeadPtr;OS_TCB *TailPtr;
};
在裸機系統中,程序的主體是CPU按照順序執行的。而在多任務系統中,任務的執行是由系統調度的。
系統為了順利的調度任務, 為每個任務都額外定義了一個任務控制塊TCB(Task ControlBlock),這個任務控制塊就相當于任務的身份證, 里面存有任務的所有信息,比如任務的棧,任務名稱,任務的形參等。
有了這個任務控制塊之后, 以后系統對任務的全部操作都可以通過這個TCB來實現。
TCB是一個新的數據類型, 在os.h這個頭文件中聲明,使用它可以為每個任務都定義一個TCB實體。
目前TCB里面的成員還比較少,只有棧指針和棧大小。其中為了以后操作方便,我們把棧指針作為TCB的第一個成員。
5、任務創建函數
#include "os.h"void OSTaskCreate (OS_TCB *p_tcb, OS_TASK_PTR p_task, void *p_arg,CPU_STK *p_stk_base,CPU_STK_SIZE stk_size,OS_ERR *p_err)
{CPU_STK *p_sp;p_sp = OSTaskStkInit (p_task,p_arg,p_stk_base,stk_size);p_tcb->StkPtr = p_sp;p_tcb->StkSize = stk_size;*p_err = OS_ERR_NONE;
}
任務的棧,任務的函數實體,任務的TCB最終需要聯系起來才能由系統進行統一調度。
那么這個聯系的工作就由任務創建函數 OSTaskCreate來實現,該函數在os_task.c中定義,所有跟任務相關的函數都在這個文件定義。
- OSTaskCreate函數遵循μC/OS-III中的函數命名規則,以大小的OS開頭,表示這是一個外部函數,可以由用戶調用,以OS_開頭的函數表示內部函數,只能由μC/OS-III內部使用。緊接著是文件名,表示該函數放在哪個文件,最后是函數功能名稱。
- p_tcb是任務控制塊指針。
- p_task 是任務函數名,類型為OS_TASK_PTR,原型聲明在os.h中
- p_arg是任務形參,用于傳遞任務參數。 p_stk_base 用于指向任務棧的起始地址。 stk_size 表示任務棧的大小。
- p_err 用于存錯誤碼,μC/OS-III中為函數的返回值預先定義了很多錯誤碼,
錯誤碼是枚舉類型的數據,在os.h中定義 OSTaskStkInit()是任務棧初始化函數。 當任務第一次運行的時候, 加載到CPU寄存器的參數就放在任務棧里面,在任務創建的時候,預先初始化好棧。 - OSTaskStkInit()函數在os_cpu_c.c中定義
- p_tcb->StkPtr = p_sp;
將剩余棧的棧頂指針p_sp保存到任務控制塊TCB的第一個成員StkPtr中。 - p_tcb->StkSize = stk_size;
將任務棧的大小保存到任務控制塊TCB的成員StkSize中。 - *p_err = OS_ERR_NONE;
函數執行到這里表示沒有錯誤,即OS_ERR_NONE。
6、任務棧初始化函數
#include "os.h"/* 任務堆棧初始化 */
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,void *p_arg,CPU_STK *p_stk_base,CPU_STK_SIZE stk_size)
{CPU_STK *p_stk;p_stk = &p_stk_base[stk_size];/* 異常發生時自動保存的寄存器 */*--p_stk = (CPU_STK)0x01000000u; /* xPSR的bit24必須置1 */*--p_stk = (CPU_STK)p_task; /* 任務的入口地址 */*--p_stk = (CPU_STK)0x14141414u; /* R14 (LR) */*--p_stk = (CPU_STK)0x12121212u; /* R12 */*--p_stk = (CPU_STK)0x03030303u; /* R3 */*--p_stk = (CPU_STK)0x02020202u; /* R2 */*--p_stk = (CPU_STK)0x01010101u; /* R1 */*--p_stk = (CPU_STK)p_arg; /* R0 : 任務形參 *//* 異常發生時需手動保存的寄存器 */*--p_stk = (CPU_STK)0x11111111u; /* R11 */*--p_stk = (CPU_STK)0x10101010u; /* R10 */*--p_stk = (CPU_STK)0x09090909u; /* R9 */*--p_stk = (CPU_STK)0x08080808u; /* R8 */*--p_stk = (CPU_STK)0x07070707u; /* R7 */*--p_stk = (CPU_STK)0x06060606u; /* R6 */*--p_stk = (CPU_STK)0x05050505u; /* R5 */*--p_stk = (CPU_STK)0x04040404u; /* R4 */return (p_stk);
}
-
p_task是任務名,指示著任務的入口地址,在任務切換的時候,需要加載到R15, 即PC寄存器,這樣CPU就可以找到要運行的任務。
-
p_arg 是任務的形參,用于傳遞參數,在任務切換的時候,需要加載到寄存器R0。R0寄存器通常用來傳遞參數。
-
p_stk_base 表示任務棧的起始地址。
-
stk_size 表示任務棧的大小, 數據類型為CPU_STK_SIZE,在Cortex-M3內核的處理器中等于4個字節,即一個字。
-
p_stk = &p_stk_base[stk_size];
獲取任務棧的棧頂地址,ARMCM3處理器的棧是由高地址向低地址生長的。所以初始化棧之前, 要獲取到棧頂地址,然后棧地址逐一遞減即可。 -
/* 異常發生時自動保存的寄存器 */ 下面的八行
任務第一次運行的時候,加載到CPU寄存器的環境參數我們要預先初始化好。初始化的順序固定, 首先是異常發生時自動保存的8個寄存器,即xPSR、R15、R14、R12、R3、R2、R1和R0。其中xPSR寄存器的位24必須是1, R15PC指針必須存的是任務的入口地址,R0必須是任務形參,剩下的R14、R12、R3、R2和R1為了調試方便,填入與寄存器號相對應的16進制數。 -
/* 異常發生時需手動保存的寄存器 */ 下面的八行
剩下的是8個需要手動加載到CPU寄存器的參數,為了調試方便填入與寄存器號相對應的16進制數。 -
返回棧指針p_stk,這個時候p_stk指向剩余棧的棧頂。
7、將任務添加到就緒列表
任務創建好之后,我們需要把任務添加到一個叫就緒列表的數組里面,表示任務已經就緒,系統隨時可以調度。
int main(void)
{ OS_ERR err;/* 初始化相關的全局變量 */OSInit(&err);/* 創建任務 */OSTaskCreate ((OS_TCB*) &Task1TCB, (OS_TASK_PTR ) Task1, (void *) 0,(CPU_STK*) &Task1Stk[0],(CPU_STK_SIZE) TASK1_STK_SIZE,(OS_ERR *) &err);OSTaskCreate ((OS_TCB*) &Task2TCB, (OS_TASK_PTR ) Task2, (void *) 0,(CPU_STK*) &Task2Stk[0],(CPU_STK_SIZE) TASK2_STK_SIZE,(OS_ERR *) &err);/* 將任務加入到就緒列表 */OSRdyList[0].HeadPtr = &Task1TCB;OSRdyList[1].HeadPtr = &Task2TCB;/* 啟動OS,將不再返回 */ OSStart(&err);
}
把任務TCB指針放到OSRDYList數組里面。
OSRdyList定義如下
OS_CFG_PRIO_MAX是一個定義,表示這個系統支持多少個優先級(剛開始暫時不支持多個優先級,往后章節會支持), 目前這里僅用 來表示這個就緒列表可以存多少個任務的TCB指針。
具體的宏在os_cfg.h中定義
OSRdyList是一個類型為OS_RDY_LIST的全局變量, 在os.h中定義
μC/OS-III中中會為每個數據類型重新取一個大寫的名字。
OS_RDY_LIST里面目前暫時只有兩個TCB類型的指針,一個是頭指針,一個是尾指針。
需要使用頭尾指針來將TCB串成一個雙向鏈表。
8、全局變量的聲明方法
在μC/OS-III中,需要使用很多全局變量,這些全局變量都在os.h這個頭文件中定義,但是os.h會被包含進很多的文件中, 那么編譯的時候,os.h里面定義的全局變量就會出現重復定義的情況,而我們要的只是os.h里面定義的全局變量只定義一次, 其他包含os.h頭文件的時候只是聲明。
通常我們的做法都是在C文件里面定義全局變量,然后在頭文件里面加extern聲明,哪里需要使用就在哪里加extern聲明。
但是μC/OS-III中,文件非常多,這種方法可行,但不現實。所以就有了現在在os.h頭文件中定義全局變量, 然后在os.h文件的開頭加上 代碼清單:任務-17 的宏定義的方法。
但是到了這里還沒成功, μC/OS-III再另外新建了一個os_var.c的文件,在里面包含os.h, 且只在這個文件里面定義OS_GLOBALS這個宏
如果沒有定義OS_GLOBALS這個宏,那么OS_EXT就為空,否則就為extern。
經過這樣處理之后,在編譯整個工程的時候,只有var.c里面的os.h的OS_EXT才會被替換為空,即變量的定義, 其他包含os.h的文件因為沒有定義OS_GLOBALS這個宏,則OS_EXT會被替換成extern,即變成了變量的聲明。 這樣就實現了在頭文件中定義變量。