“從0開始的FreeRTOS”系列教程第四講
作者:satori
這一次我們來進行基於FreeRTOS的任務管理實驗。
在開講之前,推薦一下Zou Changjun翻譯的FreeRTOS實時內核使用指南(官方網站上的英文原名是Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide),在后面講解API時我們力求精簡易懂,所以不會說太多詳細的內容,具體可以參見該文檔和官方API手冊(文末有本文檔的下載鏈接)。
回顧一下上次我們介紹的有關FreeRTOS的進程的知識:
主要有:進程的概念,進程的調度機制
對於FreeRTOS而言,不同優先級的進程之間采用優先級調度算法,對於同優先級的進程之間采用時間片輪轉調度算法+FCFS算法。
本次實驗我們主要的實驗內容為
任務的創建
同優先級進程之間的時間片輪轉調度算法
不同優先級進程之間的優先級調度算法
任務的刪除
任務的掛起(延時函數)
首先我們先來學習一下和FreeRTOS的任務創建有關的API:
對於初學者而言,比較需要關心的參數有以下幾個:pvTaskCode,pcName和uxPriority
pvTaskCode是任務函數名
pcName是用戶起的函數名
uxPriority是任務優先級
在cude建立的工程中,我們不會直接使用freertos的API,而是會使用CMSIS-RTOS的API:
#define osThreadDef (
name, //進程名
priority, //進程優先級
instances, //進程函數
stacksz //棧大小
)
其中priority一般使用的是CMISIS-RTOS自己定義的七個優先級(詳見上一講介紹的結構體)
osThreadId osThreadCreate (
const osThreadDef_t * thread_def, //傳遞在osThreadDef中輸入的參數
void * argument //參數
)
這里我們通過一個實例來學習進程的創建:
實驗1:
使用CMSIS-RTOS創建一個進程:
仿照第二講中的方式新建一個工程
並啟用串口1(后續的教程中串口會非常常用)
參數設置如下
將默認的任務的名字改成task_1
創建工程,在freertos.c上加入
#include "stm32f1xx_hal.h"
在usart.c下加入以下代碼,就可以在32中使用printf功能了
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); //具體根據自己啟用的串口修改這個函數
return ch;
}
在freertos.c中添加如下代碼
/* USER CODE BEGIN FunctionPrototypes */
void delay(int x);
/* USER CODE END FunctionPrototypes */
/* task1_handler function */
void task1_handler(void const * argument)
{
/* USER CODE BEGIN task1_handler */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
printf("this is task_1 running\r\n");
delay(500);
}
/* USER CODE END task1_handler */
}
/* USER CODE BEGIN Application */
void delay(int x)
{
for(int i=0;i<x;i++)
{
for(int j=0;j<1000;j++);
}
}
/* USER CODE END Application */
編譯后燒錄
可以發現板子上的綠燈閃爍+串口發送數據
然后我們注釋掉所有和task_1有關的代碼,來嘗試不借助cubemx自己生成一個系統任務
(實際上關於cubemx,我的個人意見是,創建工程時這是一個非常方便的工具,但是帶來的后續開發的麻煩也很多,比如要修改外設配置的時候,要經歷cube中修改------>重新生成工程---->繼續寫代碼這樣一個過程,如果你只是要做修改串口波特率這類很簡單的參數修改的話,何苦非要經過上面這個繁瑣的過程而非直接在代碼中修改呢?)
需要添加的內容如下
/聲明任務句柄
osThreadId task_2Handle;
//聲明任務函數
void task2_handler(void const * argument);
//利用osTreadDef和osThreadCreate兩個函數定義任務參數+創建任務
osThreadDef(task_2, //任務名
task2_handler, //任務函數
osPriorityNormal, //任務優先級
0, // 子進程數
128); //任務棧
task_2Handle = osThreadCreate(osThread(task_2), //任務名要和上面的函數中一致
NULL);
/關於兩個函數更具體的講解可以看官方API手冊
http://www.keil.com/pack/doc/CMSIS_Dev/RTOS/html/group__CMSIS__RTOS__ThreadMgmt.html#gac59b5713cb083702dce759c73fd90dff
最后添加任務函數
void task2_handler(void const * argument)
{
/* USER CODE BEGIN task1_handler */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
printf("this is task_2 running\r\n");
delay(500);
}
/* USER CODE END task1_handler */
}
編譯下載,查看實驗效果
紅燈閃爍,串口發送數據如下
實驗2:
時間片輪轉調度實驗
取消task_1有關代碼的注釋,讓task_1和task_2同時開始運行,觀察實驗結果——
1.紅燈和綠燈同時閃爍
2.串口發送數據如下
可以發現串口發送的數據存在錯位的情況,思考一下這是為什么?
實驗3:
優先級調度實驗
在創建task2的函數中修改task2的優先級
osThreadDef(task_2, task2_handler, osPriorityAboveNormal, 0, 128);
task_2Handle = osThreadCreate(osThread(task_2), NULL);
編譯下載后發現只有紅燈閃爍
串口發送數據如下
這個現象的原理是只有在同優先級的任務間才存在時間片輪轉調度,當task2優先級高於task1時,系統會始終執行task2
再修改代碼如下
void task2_handler(void const * argument)
{
/* USER CODE BEGIN task1_handler */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
printf("this is task_2 running\r\n");
osDelay(500);
}
/* USER CODE END task1_handler */
}
會發現任務交替運行
這就體現出了osDelay和普通的延時之間的區別
osDelay的原理是將一個任務修改到最低優先級(IDLE優先級),使任意一個就緒進程都可以搶占cpu
通過以上三個實驗,我們學習了任務創建,時間片輪轉調度和優先級調度的實踐
實際上在rtos中有相當豐富的進程管理函數,包括獲取進程id,設置進程優先級,設置進程優先級等(可以用來實現動態優先級算法
這里就不多加介紹了,有興趣的可以自己去了解
最后附上推薦教材(官方文檔的中文翻譯版)
《FreeRTOS實時內核使用指南》:
鏈接:https://pan.baidu.com/s/1qouYjnNSsa0u6KxI-0jMRQ
提取碼:al12