我們從一開始接觸Arduino編程就知道,Arduino程序結構由setup()和loop()兩部分組成,我們需要反覆執行的代碼要放在loop()中,並且這些代碼一般都是順序執行的。
隨着我們需要實現的功能越來越複雜,這種順序執行的方式很難達到實時性,這個時候就需要使用操作系統了,就類似於我們的PC機,可以同時運行多個軟件,你可以一邊聊QQ一邊看電影,或者你用手機一邊聽歌一邊看這篇文章。當然PC機和手機的處理器要強大的太多太多了,而我們的Arduino UNO開發板上使用的是一顆8位的AVR單片機。
接觸過嵌入式的朋友都知道,我們會在ARM處理器上使用Linux系統,而在STM32這種較ARM低端而又比單片機強大的MCU上一般會使用更輕量級的實時操作系統,類似的如UCOS、FreeRTOS、RTThread等。習慣了STM32上運行FreeRTOS,真的沒有想過在Arduino上來運行,最近發現了被移植到Arduino上運行的FreeRTOS實時操作系統,趕緊來嘗試下。
1. 安裝Arduino FreeRTOS庫
在Arduino IDE中,點擊「項目」—「加載庫」—「管理庫」,在搜索欄輸入"FreeRTOS",查找並安裝庫。
2. Arduino FreeRTOS的使用
Arduino FreeRTOS庫可運行於Arduino AVR設備,如Uno、Leonardo、Mega等。本篇使用Uno開發板。
首先要包含Arduino FreeRTOS庫的頭文件。
#include <Arduino_FreeRTOS.h>
我們使用xTaskCreate()函數來創建任務,函數原型爲:
xTaskCreate(TaskFunction_t pvTaskCode,const char * const pcName,uint16_t usStackDepth,void * pvParameters,UBaseType_t uxPriority,TaskHandle_t * pxCreatedTask)
創建任務時需要傳入6個參數:
- pvTaskCode:任務函數。
- pcName:任務名稱,一般用於調試和追蹤。
- usStackDepth:任務堆棧,內核在創建任務時將其分配給任務。該值指定堆棧可以容納的字數,而不是字節數。例如,如果堆棧爲32位寬,並且usStackDepth作爲100傳入,那麼將在RAM中分配400字節的堆棧空間(100 * 4字節)。合理使用此項,因爲Arduino Uno只有2KB的RAM。
- pvParameters:任務輸入參數(可以爲NULL)。
- uxPriority:任務優先級(0是最低優先級)。
- pxCreatedTask:可用於向正在創建的任務傳遞句柄。然後,可以使用此句柄在API調用中引用任務,例如,更改任務優先級或刪除任務(可以爲NULL)。
本次實驗創建兩個串口打印任務:
xTaskCreate(TaskPrint1, "Print1", 128, NULL, 1, NULL);
xTaskCreate(TaskPrint2, "Print2", 128, NULL, 2, NULL);
其中任務2有更高的優先級,會首先執行。
創建任務後,使用**vTaskStartScheduler()**函數啓動任務調度。
創建任務實現函數。一般結構如下:
void task(void *param)
{
while(1)
{
....//需要執行的代碼
}
}
大多數代碼都需要延遲函數來停止正在運行的任務,但是在RTOS中,不建議使用**Delay()**函數,因爲它會停止CPU,因此RTOS也將停止工作。因此,FreeRTOS具有內核API,可以在特定時間內阻止任務:
vTaskDelay(const TickType_t xTicksToDelay)
例如延時1秒:
vTaskDelay(1000 / portTICK_PERIOD_MS)
其中portTICK_PERIOD_MS與實際MCU的時鐘頻率相關。
3. 本實驗代碼如下,拷貝編譯下載。
#include <Arduino_FreeRTOS.h>
void TaskPrint1(void *param); //聲明打印任務1
void TaskPrint2(void *param); //聲明打印任務2
void setup() {
Serial.begin(9600);
while (!Serial);//等待串口連接後執行
xTaskCreate(TaskPrint1, "Print1", 128, NULL, 1, NULL); //創建任務1
xTaskCreate(TaskPrint2, "Print2", 128, NULL, 2, NULL); //創建任務2
vTaskStartScheduler(); //啓動任務調度
}
void TaskPrint1(void *param)
{
while (1)
{
Serial.println("TaskPrint1...");
vTaskDelay(1000 / portTICK_PERIOD_MS ); // 等待1秒
}
}
void TaskPrint2(void *param)
{
while (1)
{
Serial.println("TaskPrint2...");
vTaskDelay(2000 / portTICK_PERIOD_MS ); // 等待2秒
}
}
void loop() {
}
4. 實驗現象
打開串口監視器,波特蘭設置與程序中一致的9600,會看到任務2先運行打印,由於任務1等待1秒,任務2等待2秒,所以每次打印任務1兩次,打印任務2一次。
關注公衆號「TonyCode」,回覆「1024」獲取1000G學習資料。
個人博客