從0開始的FreeRTOS(4)


“從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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM