做過軟件的同學都知道,任何一個程序都必須要有初始化的過程,在初始化過程中,我們會對外圍硬件以及CPU的奔跑環境進行初步的設置,以便接下來的使用和調度。
以前在寫單片機邏輯程序之時,系統初始化過程大概分為兩種:
①外圍硬件的初始化(比如MCU寄存器,時鍾,看門狗,串口,IO口,SPI等等)
②代碼內參數的初始化(比如堆棧,變量,結構體等等)
UCOSII操作系統想要跑起來,當然也需要一系列的初始化,比如中斷模式、延時模塊、外圍硬件等等,但本文不講硬件相關,只對操作系統本身的初始化進行一些講解。
首先請看一段熟悉的代碼:
1 #include "sys.h" 2 #include "delay.h" 3 #include "led.h" 4 #include "includes.h" 5 6 7 /////////////////////////UCOSII任務設置/////////////////////////////////// 8 //START 任務 9 //設置任務優先級 10 #define START_TASK_PRIO 10 //開始任務的優先級設置為最低 11 //設置任務堆棧大小 12 #define START_STK_SIZE 64 13 //任務堆棧 14 OS_STK START_TASK_STK[START_STK_SIZE]; 15 //任務函數 16 void start_task(void *pdata); 17 18 //LED0任務 19 //設置任務優先級 20 #define LED0_TASK_PRIO 7 21 //設置任務堆棧大小 22 #define LED0_STK_SIZE 64 23 //任務堆棧 24 OS_STK LED0_TASK_STK[LED0_STK_SIZE]; 25 //任務函數 26 void led0_task(void *pdata); 27 28 29 //LED1任務 30 //設置任務優先級 31 #define LED1_TASK_PRIO 6 32 //設置任務堆棧大小 33 #define LED1_STK_SIZE 64 34 //任務堆棧 35 OS_STK LED1_TASK_STK[LED1_STK_SIZE]; 36 //任務函數 37 void led1_task(void *pdata); 38 39 int main(void) 40 { 41 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置中斷優先級分組為組2:2位搶占優先級,2位響應優先級 42 delay_init(); //延時函數初始化 43 LED_Init(); //初始化與LED連接的硬件接口 44 OSInit(); 45 OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//創建起始任務 46 OSStart(); 47 } 48 49 //開始任務 50 void start_task(void *pdata) 51 { 52 OS_CPU_SR cpu_sr=0; 53 pdata = pdata; 54 OS_ENTER_CRITICAL(); //進入臨界區(無法被中斷打斷) 55 OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO); 56 OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO); 57 OSTaskSuspend(START_TASK_PRIO); //掛起起始任務. 58 OS_EXIT_CRITICAL(); //退出臨界區(可以被中斷打斷) 59 } 60 61 //LED0任務 62 void led0_task(void *pdata) 63 { 64 while(1) 65 { 66 LED0=0; 67 delay_ms(80); 68 LED0=1; 69 delay_ms(920); 70 }; 71 } 72 73 //LED1任務 74 void led1_task(void *pdata) 75 { 76 while(1) 77 { 78 LED1=0; 79 delay_ms(300); 80 LED1=1; 81 delay_ms(300); 82 }; 83 }
以上的代碼大家都不陌生,這幾乎便是UCOSII系統初始化的標准格式,首先是定義任務的基本信息,優先級,堆棧大小,堆棧空間,任務函數等等。
然后由main函數開始執行具體的初始化過程,分別是中斷模式設定,延時功能設定,以及外圍硬件設定,等這些東西都設定完成以后,便進入了操作系統的設定,最后起始任務執行完畢,程序會跳進應用任務中執行。
而main函數中的OSInit()這個函數便是我們本次講解的重點。
UCOSII操作系統在初始化的過程中,到底做了一些什么?或者說函數OSInit()中到底有些什么處理?
廢話不多說,直接進入這個函數的定義:
1 void OSInit (void) 2 { 3 OSInitHookBegin(); /* Call port specific initialization code */ 4 5 OS_InitMisc(); /* Initialize miscellaneous variables */ 6 7 OS_InitRdyList(); /* Initialize the Ready List */ 8 9 OS_InitTCBList(); /* Initialize the free list of OS_TCBs */ 10 11 OS_InitEventList(); /* Initialize the free list of OS_EVENTs */ 12 13 #if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u) 14 OS_FlagInit(); /* Initialize the event flag structures */ 15 #endif 16 17 #if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u) 18 OS_MemInit(); /* Initialize the memory manager */ 19 #endif 20 21 #if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u) 22 OS_QInit(); /* Initialize the message queue structures */ 23 #endif 24 25 OS_InitTaskIdle(); /* Create the Idle Task */ 26 #if OS_TASK_STAT_EN > 0u 27 OS_InitTaskStat(); /* Create the Statistic Task */ 28 #endif 29 30 #if OS_TMR_EN > 0u 31 OSTmr_Init(); /* Initialize the Timer Manager */ 32 #endif 33 34 OSInitHookEnd(); /* Call port specific init. code */ 35 36 #if OS_DEBUG_EN > 0u 37 OSDebugInit(); 38 #endif 39 }
以上便是系統初始化函數中的處理,看得出來,它只是一個接口,一個容器,真正的處理還在它內部調用的那些函數里,接下來我們開始一句一句的理解。(簡單的函數用黑色,復雜的函數用紅色)
一:函數OSInitHookBegin();
其定義如下:
1 #if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203 2 void OSInitHookBegin (void) 3 { 4 #if OS_TMR_EN > 0 5 OSTmrCtr = 0; 6 #endif 7 } 8 #endif
這個函數俗稱鈎子函數,它里面本身不帶任何處理,是專門留給用戶擴展的,當用戶需要在初始化里執行某些處理的時候,可以在這里把自己的代碼添加進去。
比如我想在初始化時點亮一個LED小燈或者讓喇叭叫起來,那么就可以在里面寫個點燈的代碼(注意這個函數不能被意外打斷)。
我們可以看到在它的頭上有兩個宏開關,只有都滿足才會執行,第一個開關OS_CPU_HOOKS_EN是使能位,如果我們不需要在這里執行任何處理,可以直接把OS_CPU_HOOKS_EN宏定位為0(不建議定義為0,因為編譯會出問題,需要修改的地方不少)。
1 #define OS_CPU_HOOKS_EN 0u /* uC/OS-II hooks are found in the processor port files */
第二個宏開關是系統版本,只有系統在203版本以上才能用這個功能。
1 #define OS_VERSION 291u /* Version of uC/OS-II (Vx.yy mult. by 100) */
我現在的版本是291,所以當然沒問題啦!
二:函數OS_InitMisc()
其定義如下:
1 static void OS_InitMisc (void) 2 { 3 #if OS_TIME_GET_SET_EN > 0u 4 OSTime = 0uL; /* Clear the 32-bit system clock */ 5 #endif 6 7 OSIntNesting = 0u; /* Clear the interrupt nesting counter */ 8 OSLockNesting = 0u; /* Clear the scheduling lock counter */ 9 10 OSTaskCtr = 0u; /* Clear the number of tasks */ 11 12 OSRunning = OS_FALSE; /* Indicate that multitasking not started */ 13 14 OSCtxSwCtr = 0u; /* Clear the context switch counter */ 15 OSIdleCtr = 0uL; /* Clear the 32-bit idle counter */ 16 17 #if OS_TASK_STAT_EN > 0u 18 OSIdleCtrRun = 0uL; 19 OSIdleCtrMax = 0uL; 20 OSStatRdy = OS_FALSE; /* Statistic task is not ready */ 21 #endif 22 23 #ifdef OS_SAFETY_CRITICAL_IEC61508 24 OSSafetyCriticalStartFlag = OS_FALSE; /* Still allow creation of objects */ 25 #endif 26 }
這個函數的作用,是對系統中的某些全局變量進行初始化:
OSTime是系統中滴答時鍾的存儲變量,如果想要使用這個變量的話,那么宏開關OS_TIME_GET_SET_EN必須要設置為1。
※這個變量還是挺有用的,比如當我需要判斷系統時間是否經過了50ms,那么就可以調用OSTimeGet()函數讀一下這個變量,然后過一會兒再讀一下,只要兩次讀數相差達到50,那就是經過了50ms,這樣就不用專門再開一個定時器了。
OSIntNesting是中斷嵌套計數變量,進中斷時加1,出中斷時減1,當變量為0的時候表示沒有進入過中斷,當等於1的時候表示進入了1重中斷,當等於2的時候表示進入了2重中斷……以此類推。
OSLockNesting是調度器上鎖次數變量,當調用函數OSSchedLock()便會對這個變量進行加1處理,當調用函數OSSchedUnlock()便會對它減1處理,只有在變量OSLockNesting等於0時,系統才會進行任務調度(當執行某些不能被別的任務打斷的處理時,可以用這個功能)。
OSTaskCtr是記錄系統中一共有多少個任務,比如我上面的那段代碼自己創建了3個任務,那等任務建立完畢以后,這個變量至少是大於3了,由於UCOSII系統還保留了一些系統任務(空閑任務,統計任務等),所以這個變量肯定比3大。
OSRunning是記錄操作系統當前的狀態,操作系統跑起來是TRUE,沒跑起來是FALSE,現在還在初始化,肯定是FALSE。
OSCtxSwCtr是記錄任務切換的次數,當從優先級0的任務切換到優先級1的任務之時,它就會加1,在切換回來,它又會加1,等加到極限以后,重新變成0。
OSIdleCtr是記錄空閑任務執行的次數,每執行一次空閑任務,它就加1,這個變量可以配合統計任務來計算CPU的使用率。
1 #if OS_TASK_STAT_EN > 0u 2 OSIdleCtrRun = 0uL; 3 OSIdleCtrMax = 0uL; 4 OSStatRdy = OS_FALSE; /* Statistic task is not ready */ 5 #endif
這三句代碼有宏開關,和統計任務相關,專門用來計算CPU的使用率,如果不需要這個數據,直接把宏開關設0便可。
1 #ifdef OS_SAFETY_CRITICAL_IEC61508 2 OSSafetyCriticalStartFlag = OS_FALSE; /* Still allow creation of objects */ 3 #endif
這段代碼我沒有找到是用來做什么的,如果有哪位同學了解,還希望不吝賜教。
三:函數OS_InitRdyList()
其定義如下:
1 static void OS_InitRdyList (void) 2 { 3 INT8U i; 4 5 6 OSRdyGrp = 0u; /* Clear the ready list */ 7 for (i = 0u; i < OS_RDY_TBL_SIZE; i++) { 8 OSRdyTbl[i] = 0u; 9 } 10 11 OSPrioCur = 0u; 12 OSPrioHighRdy = 0u; 13 14 OSTCBHighRdy = (OS_TCB *)0; 15 OSTCBCur = (OS_TCB *)0; 16 }
該函數的作用是用來初始化任務、以及任務優先級相關的變量。
OSRdyGrp 是記錄當前所有任務組的就緒狀態,是任務調度算法的重要變量。
OSRdyTbl[]是記錄當前所有任務的就緒狀態,是任務調度算法的重要變量。
OSPrioCur是記錄當前正在執行的任務。
OSPrioHighRdy是記錄當前就緒任務中,優先級最高的那個任務的優先級。
OSTCBHighRdy這是一個結構體,里面記錄優先級最高的那個任務的信息,初始化時沒有任務執行,其值為0。
OSTCBCur也是一個結構體,里面記錄當前正在執行的那個任務的信息,初始化時沒有任務執行,其值為0。
四:函數OS_InitTCBList()
定義如下:
1 static void OS_InitTCBList (void) 2 { 3 INT8U ix; 4 INT8U ix_next; 5 OS_TCB *ptcb1; 6 OS_TCB *ptcb2; 7 8 9 OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */ 10 OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */ 11 for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++) { /* Init. list of free TCBs */ 12 ix_next = ix + 1u; 13 ptcb1 = &OSTCBTbl[ix]; 14 ptcb2 = &OSTCBTbl[ix_next]; 15 ptcb1->OSTCBNext = ptcb2; 16 #if OS_TASK_NAME_EN > 0u 17 ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */ 18 #endif 19 } 20 ptcb1 = &OSTCBTbl[ix]; 21 ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB */ 22 #if OS_TASK_NAME_EN > 0u 23 ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */ 24 #endif 25 OSTCBList = (OS_TCB *)0; /* TCB lists initializations */ 26 OSTCBFreeList = &OSTCBTbl[0]; 27 }
在講解函數之前,首先看一下任務信息結構體的定義:
1 typedef struct os_tcb {
2 OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
3
4 #if OS_TASK_CREATE_EXT_EN > 0u
5 void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
6 OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
7 INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
8 INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
9 INT16U OSTCBId; /* Task ID (0..65535) */
10 #endif
11
12 struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
13 struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
14
15 #if (OS_EVENT_EN)
16 OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
17 #endif
18
19 #if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
20 OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks */
21 #endif
22
23 #if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
24 void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
25 #endif
26
27 #if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
28 #if OS_TASK_DEL_EN > 0u
29 OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
30 #endif
31 OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
32 #endif
33
34 INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
35 INT8U OSTCBStat; /* Task status */
36 INT8U OSTCBStatPend; /* Task PEND status */
37 INT8U OSTCBPrio; /* Task priority (0 == highest) */
38
39 INT8U OSTCBX; /* Bit position in group corresponding to task priority */
40 INT8U OSTCBY; /* Index into ready table corresponding to task priority */
41 OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */
42 OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */
43
44 #if OS_TASK_DEL_EN > 0u
45 INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
46 #endif
47
48 #if OS_TASK_PROFILE_EN > 0u
49 INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
50 INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
51 INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
52 OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
53 INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
54 #endif
55
56 #if OS_TASK_NAME_EN > 0u
57 INT8U *OSTCBTaskName; 58 #endif 59 60 #if OS_TASK_REG_TBL_SIZE > 0u 61 INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE]; 62 #endif 63 } OS_TCB;
這個結構體乍一看還是挺大的,不過我們現在只關注核心數據,去掉那些被宏開關包圍的成員,加上一些注釋,再看一下:
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* 指向任務堆棧的指針 */
struct os_tcb *OSTCBNext; /* 指向下一個節點的指針 */ struct os_tcb *OSTCBPrev; /* 指向上一個節點的指針 */ INT32U OSTCBDly; /* 任務的延時參數 */ INT8U OSTCBStat; /* 任務的狀態 */ INT8U OSTCBStatPend; /* 任務的阻塞狀態 */ INT8U OSTCBPrio; /* 任務的優先級 */ /* 下面4個參數是有關優先級算法的,作用三兩句說不清楚,可以參考我上一篇講任務調度的文章 */ INT8U OSTCBX; /* Bit position in group corresponding to task priority */ INT8U OSTCBY; /* Index into ready table corresponding to task priority */ OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */ OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */ } OS_TCB;
簡化后明了多了,我們由定義的那兩個成員可知,這是一個雙向鏈表(如果對鏈表這種數據結構還不是很清楚,建議先去百度了解一下)。
因此,上面那個函數的作用也就是建立一個鏈表(空閑鏈表),長度根據自己的配置。
#define OS_MAX_TASKS 10u /* Max. number of tasks in your application, MUST be >= 2 */
#define OS_N_SYS_TASKS 2u /* Number of system tasks
我這里把宏定義為10(最大支持10個用戶任務),再加上系統本身的保留任務2個(空閑任何、統計任務),那么這個鏈表的長度就是12。
這個鏈表有什么用?
我認為是為了預留出將來的空間,先建立一個空表在內存中占地方,等今后需要建立任務的時候,就直接從里面拿取空間,以避免內存不夠的尷尬情況。
這個函數的作用,是對任務相關的數據進行初始化,最重要的生成一個名為空閑鏈表的鏈表(這個空閑鏈表鏈接事先定義好的任務數組)。
UCOSI操作系統里面有好幾個結構完全一樣的鏈表,比如任務鏈表,優先級鏈表,空閑鏈表等等,也是由這幾個鏈表組合管理任務的信息。
※至於這幾個鏈表的具體應用,暫時不用糾結,會在后面詳細解釋。
五、函數OS_InitEventList()
定義如下:
static void OS_InitEventList (void) { #if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u) #if (OS_MAX_EVENTS > 1u) INT16U ix; INT16U ix_next; OS_EVENT *pevent1; OS_EVENT *pevent2; OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl)); /* Clear the event table */ for (ix = 0u; ix < (OS_MAX_EVENTS - 1u); ix++) { /* Init. list of free EVENT control blocks */ ix_next = ix + 1u; pevent1 = &OSEventTbl[ix]; pevent2 = &OSEventTbl[ix_next]; pevent1->OSEventType = OS_EVENT_TYPE_UNUSED; pevent1->OSEventPtr = pevent2; #if OS_EVENT_NAME_EN > 0u pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */ #endif } pevent1 = &OSEventTbl[ix]; pevent1->OSEventType = OS_EVENT_TYPE_UNUSED; pevent1->OSEventPtr = (OS_EVENT *)0; #if OS_EVENT_NAME_EN > 0u pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */ #endif OSEventFreeList = &OSEventTbl[0]; #else OSEventFreeList = &OSEventTbl[0]; /* Only have ONE event control block */ OSEventFreeList->OSEventType = OS_EVENT_TYPE_UNUSED; OSEventFreeList->OSEventPtr = (OS_EVENT *)0; #if OS_EVENT_NAME_EN > 0u OSEventFreeList->OSEventName = (INT8U *)"?"; /* Unknown name */ #endif #endif #endif }
此函數主要用來對系統的事件機制(郵箱、隊列、信號量等等)做一些初始化,里面處理和任務初始化差不多,作用也是事先占領一些內存空間,以備今后使用。
管理各種消息的也是鏈表(定義就不貼出來了),事件最大的個數也是通過宏定義來實現,如果沒有使用的話,把宏定義關掉可以節約一部分空間(不建議)。
六、七、八、函數OS_FlagInit(),OS_MemInit(),OS_QInit()
這三個函數就是對具體的消息機制、內存管理功能進行初始化,內部的處理也都大同小異,都是根據系統的配置,然后先占領一些空間,以備今后使用(每個事件的鏈表的結構體不同)。
函數定義這里不貼出來,具體可以查看源代碼,在今后專門會推出講解消息量的文章,到時候再詳細說。
九、函數OS_InitTaskIdle()
定義如下:
1 static void OS_InitTaskIdle (void) 2 { 3 #if OS_TASK_NAME_EN > 0u 4 INT8U err; 5 #endif 6 7 8 #if OS_TASK_CREATE_EXT_EN > 0u 9 #if OS_STK_GROWTH == 1u 10 (void)OSTaskCreateExt(OS_TaskIdle, 11 (void *)0, /* No arguments passed to OS_TaskIdle() */ 12 &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Top-Of-Stack */ 13 OS_TASK_IDLE_PRIO, /* Lowest priority level */ 14 OS_TASK_IDLE_ID, 15 &OSTaskIdleStk[0], /* Set Bottom-Of-Stack */ 16 OS_TASK_IDLE_STK_SIZE, 17 (void *)0, /* No TCB extension */ 18 OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */ 19 #else 20 (void)OSTaskCreateExt(OS_TaskIdle, 21 (void *)0, /* No arguments passed to OS_TaskIdle() */ 22 &OSTaskIdleStk[0], /* Set Top-Of-Stack */ 23 OS_TASK_IDLE_PRIO, /* Lowest priority level */ 24 OS_TASK_IDLE_ID, 25 &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Bottom-Of-Stack */ 26 OS_TASK_IDLE_STK_SIZE, 27 (void *)0, /* No TCB extension */ 28 OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */ 29 #endif 30 #else 31 #if OS_STK_GROWTH == 1u 32 (void)OSTaskCreate(OS_TaskIdle, 33 (void *)0, 34 &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u], 35 OS_TASK_IDLE_PRIO); 36 #else 37 (void)OSTaskCreate(OS_TaskIdle, 38 (void *)0, 39 &OSTaskIdleStk[0], 40 OS_TASK_IDLE_PRIO); 41 #endif 42 #endif 43 44 #if OS_TASK_NAME_EN > 0u 45 OSTaskNameSet(OS_TASK_IDLE_PRIO, (INT8U *)(void *)"uC/OS-II Idle", &err); 46 #endif 47 }
這個函數的作用是建立一個空閑任務,所謂的空閑任務就是啥也不干的任務,為什么必須要有這么一個吃干飯的家伙呢?
試想一下,等UCOSII操作系統跑起來以后,如果我現在所有任務都處在延時狀態中,也就是未就緒的狀態,那么系統應該執行什么代碼呢?
CPU的動力來源於晶振,只要晶振不停,那CPU也是不會停下來的啊。
這就是空閑任務的作用,當所有的任務都沒有執行的時候,系統就會執行它,雖然空閑任務啥也不干,但可以避免CPU一臉懵逼加茫然無措。
這個函數我們需要重點分析,因為所有任務的建立原理和過程都是一樣的,只要完全理解了空閑任務的建立過程后,那么建立別的任務也就明白了。
因為我們創建任務使用的是標准的create函數,並未使用拓展的create函數(宏定義OS_TASK_CREATE_EXT_EN == 0),所以我們只需要關注下面那個部分:
OSTaskCreateExt()為OSTaskCreate()拓展版本,功能大同小異,只不過多了一些設置,這些設置基本上不會用到。
1 #else 2 #if OS_STK_GROWTH == 1u 3 (void)OSTaskCreate(OS_TaskIdle, 4 (void *)0, 5 &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u], 6 OS_TASK_IDLE_PRIO); 7 #else 8 (void)OSTaskCreate(OS_TaskIdle, 9 (void *)0, 10 &OSTaskIdleStk[0], 11 OS_TASK_IDLE_PRIO); 12 #endif 13 #endif
#define OS_STK_GROWTH 1 /* Stack grows from HIGH to LOW memory on ARM */
由於我們使用mcu是m3內核,它堆棧的增長方向是由高到低,所以只需要看第一個宏里面的代碼。
看看函數的定義和形參:
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT8U prio)
看看我們傳遞進去的實參:
(void)OSTaskCreate(OS_TaskIdle, (void *)0, &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u], OS_TASK_IDLE_PRIO);
void (*task)(void *p_arg):任務函數名 賦值=OS_TaskIdle(空閑任務函數名,系統已經實現了這個任務,不需要自己添加)
void *p_arg :給任務傳入的參數 賦值= 0
OS_STK *ptos :任務的堆棧空間 賦值= OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u](空閑任務的堆棧空間和大小也是系統定義的)
INT8U prio :任務的優先級 賦值= OS_TASK_IDLE_PRIO(既然都叫空閑任務,那優先級肯定是最低的,這個宏的值是63,由系統定義)
大家快來看啊,原來創建一個任務這么簡單,只需要給4個參數就可以了。
傳遞了4個參數進去后,那里面具體又是怎么實現處理的呢?
待續……