原創(當然借鑒了官網資料^_^):
在之前的移植工作准備好之后,我們需要調用freertos提供給我們的API函數實現操作系統地運行。首先,第一個函數:
任務函數
任務是由 C 語言函數實現的。唯一特別的只是任務的函數原型,其必須返回 void,
而且帶有一個 void 指針參數。
void ATaskFunction( void *pvParameters );任務函數原型
每個任務都是在自己權限范圍內的一個小程序。其具有程序入口,通常會運行在一個死循環中,也不會退出。
/********************************************************************************* * @ 函數名 : vTaskLed1 * @ 功能說明: LED1 任務,實現一個周期性的閃爍 * @ 參數 : pvParameters,當任務創建的時候傳進來,可以沒有 * @ 返回值 : 無 ********************************************************************************/ void vTaskLed1(void *pvParameters) { /* 任務都是一個無限,不能返回 */ while(1) { LED2( ON ); /* 阻塞延時,單位ms */ vTaskDelay( 500 ); LED2( OFF ); vTaskDelay( 500 ); } }
這就是本次實驗的任務函數,控制LED燈的亮滅,注意任務函數中的while(1),以及返回void。
FreeRTOS 任務不允許以任何方式從實現函數中返回——它們絕不能有一
條”return”語句,也不能執行到函數末尾。如果一個任務不再需要,可以顯式地將其刪
除。這也在程序清單 2 展現。
一個任務函數可以用來創建若干個任務——創建出的任務均是獨立的執行實例,擁
有屬於自己的棧空間,以及屬於自己的自動變量(棧變量),即任務函數本身定義的變量。
應用程序可以包含多個任務。如果運行應用程序的微控制器只有一個核(core),那
么在任意給定時間,實際上只會有一個任務被執行。這就意味着一個任務可以有一個或
兩個狀態,即運行狀態和非運行狀態。我們先考慮這種最簡單的模型——但請牢記這其
實是過於簡單,我們稍后將會看到非運行狀態實際上又可划分為若干個子狀態。
當某個任務處於運行態時,處理器就正在執行它的代碼。當一個任務處於非運行態
時,該任務進行休眠,它的所有狀態都被妥善保存,以便在下一次調試器決定讓它進入
運行態時可以恢復執行。當任務恢復執行時,其將精確地從離開運行態時正准備執行的
那一條指令開始執行。
任務從非運行態轉移到運行態被稱為”切換入或切入(switched in)”或”交換入
(swapped in)”。相反,任務從運行態轉移到非運行態被稱為”切換出或切出(switched
out)”或”交換出(swapped out)”。 FreeRTOS 的調度器是能讓任務切入切出的唯一實體。
創建任務使用 FreeRTOS 的 API 函數 xTaskCreate()。
usStackDepth :
當任務創建時,內核會分為每個任務分配屬於任務自己的唯一狀態。
usStackDepth 值用於告訴內核為它分配多大的棧空間。
這個值指定的是棧空間可以保存多少個字(word),而不是多少個字
節(byte)。比如說,如果是 32 位寬的棧空間,傳入的 usStackDepth
值為 100,則將會分配 400 字節的棧空間(100 * 4bytes)。棧深度乘
以棧寬度的結果千萬不能超過一個 size_t 類型變量所能表達的最大
值。
應用程序通過定義常量 configMINIMAL_STACK_SIZE 來決定空閑
任務任用的棧空間大小。在 FreeRTOS 為微控制器架構提供的
Demo 應用程序中,賦予此常量的值是對所有任務的最小建議值。
如果你的任務會使用大量棧空間,那么你應當賦予一個更大的值。
沒有任何簡單的方法可以決定一個任務到底需要多大的棧空間。計
算出來雖然是可能的,但大多數用戶會先簡單地賦予一個自認為合
理的值,然后利用 FreeRTOS 提供的特性來確證分配的空間既不欠
缺也不浪費。第六章包括了一些信息,可以知道如何去查詢任務使
用了多少棧空間。
在我的stm32上:
/********************************************************************************* * @ 函數名 : AppTaskCreate * @ 功能說明: 任務創建,為了方便管理,所有的任務創建函數都可以放在這個函數里面 * @ 參數 : 無 * @ 返回值 : 無 ********************************************************************************/ static void AppTaskCreate(void) { xTaskCreate(vTaskLed1, /* 任務函數名 */ "Task Led1", /* 任務名,字符串形式,方便調試 */ 512, /* 棧大小,單位為字,即4個字節 */ NULL, /* 任務形參 */ 1, /* 優先級,數值越大,優先級越高 */ &xHandleTaskLED1); /* 任務句柄 */ }
int main(void) { NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /* LED 端口初始化 */ LED_GPIO_Config(); /*創建任務*/ AppTaskCreate(); /*啟動任務調度器,任務開始執行*/ vTaskStartScheduler(); while (1) { } }
這樣編譯下載之后,可以看到LED燈閃爍,即移植成功,其中省略了基礎硬件初始化部分,建立在你可以不使用操作系統點亮LED燈的基礎上,完成系統移植。
其中關於任務參數做一點說明:
在我的LED實驗中沒有使用任務參數,但是如果有相似功能的函數,我們可以不用使用多個任務函數實體,而在一個任務函數中多次創建任務,具體例子如下所示:
例 1 中創建的兩個任務幾乎完全相同,唯一的區別就是打印輸出的字符串。這種重
復性可以通過創建同一個任務代碼的兩個實例來去除。這時任務參數就可以用來傳遞各
自打印輸出的字符串。
程序清單 8 包含了例 2 中用到的唯一一個任務函數代碼(vTaskFunction)。這一個
任務函數代替了例 1 中的兩個任務函數(vTask1 與 vTask2)。這個函數的任務參數被強
制轉化為 char*以得到任務需要打印輸出的字符串。
void vTaskFunction( void *pvParameters ) { char *pcTaskName; volatile unsigned long ul; /* 需要打印輸出的字符串從入口參數傳入。強制轉換為字符指針。 */ pcTaskName = ( char * ) pvParameters; /* As per most tasks, this task is implemented in an infinite loop. */ for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskName ); /* Delay for a period. */ for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { /* This loop is just a very crude delay implementation. There is nothing to do in here. Later exercises will replace this crude loop with a proper delay/sleep function. */ } } } 程序清單 8 例 2 中用於創建兩個任務實例的任務函
盡管現在只有一個任務實現代碼(vTaskFunction),但是可以創建多個任務實例。每
個任務實例都可以在 FreeRTOS 調度器的控制下獨運行。
傳遞給 API 函數 xTaskCreate()的參數 pvPrameters 用於傳入字符串文本。如程序
清單 9 所示。
/* 定義將要通過任務參數傳遞的字符串。定義為const,且不是在棧空間上,以保證任務執行時也有效。 */ static const char *pcTextForTask1 = “Task 1 is running\r\n”; static const char *pcTextForTask2 = “Task 2 is running\t\n”; int main( void ) { /* Create one of the two tasks. */ xTaskCreate( vTaskFunction, /* 指向任務函數的指針. */ "Task 1", /* 任務名. */ 1000, /* 棧深度. */ (void*)pcTextForTask1, /* 通過任務參數傳入需要打印輸出的文本. */ 1, /* 此任務運行在優先級1上. */ NULL ); /* 不會用到此任務的句柄. */ /* 同樣的方法創建另一個任務。至此,由相同的任務代碼(vTaskFunction)創建了多個任務,僅僅是傳入 的參數不同。同一個任務創建了兩個實例。 */ xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL ); /* Start the scheduler so our tasks start executing. */ vTaskStartScheduler(); /* If all is well then main() will never reach here as the scheduler will now be running the tasks. If main() does reach here then it is likely that there was insufficient heap memory available for the idle task to be created. CHAPTER 5 provides more information on memory management. */ for( ;; ); } 程序清單 9 例 2 中的 main()函數實現代碼
既然官方手冊都給出了參考了,那么作為學習,我們也該操練一下,使用任務參數,完成三個LED燈切換:
更改任務函數如下:
void vTaskLed1(void *pvParameters) { /* 任務都是一個無限,不能返回 */ int *piParameters; piParameters=(int *)pvParameters; while(1) { if(*piParameters==1) { LED1( ON ); /* 阻塞延時,單位ms */ vTaskDelay( 500 ); LED1( OFF ); vTaskDelay( 500 ); } else if(*piParameters==2) { LED2( ON ); /* 阻塞延時,單位ms */ vTaskDelay( 500 ); LED2( OFF ); vTaskDelay( 500 ); } else if(*piParameters==3) { LED3( ON ); /* 阻塞延時,單位ms */ vTaskDelay( 500 ); LED3( OFF ); vTaskDelay( 500 ); } } }
全局區增加標志定義:
static const int task_led1=1;
static const int task_led2=2;
static const int task_led3=3;
更改任務創建函數,注意任務參數處不再是NULL;
static void AppTaskCreate(void) { xTaskCreate(vTaskLed1, /* 任務函數名 */ "Task Led1", /* 任務名,字符串形式,方便調試 */ 512, /* 棧大小,單位為字,即4個字節 */ (void *)&task_led3, // task_led1-task_led3可以切換 /* 任務形參 */ 1, /* 優先級,數值越大,優先級越高 */ &xHandleTaskLED1); /* 任務句柄 */ }
經過測試,程序正常運行,更改任務參數1-3,可以看到三個不同顏色的燈在閃爍,達到訓練目的。
其中需要注意,任務參數的原型:void * const pvParameters;如果我們不強制轉化成void *將會報錯。是不是覺得奇怪,void類型的指針不應該兼容嗎?當然,你只用把我定義變量的標志的const去掉,就不用強制轉化成void *了,那么是什么原因呢?const int a;對a取地址是const int *類型,是底層const了,不容忽略。這里和keil編譯器有關系,在gcc中,雖然有警告,但是可以通過編譯並運行。但是這樣的類型不兼容情況我們應該避免。