【FreeRTOS】內核實現:task.c

之前寫博客說過:

一直跟着野火的教程學習,從STM32基礎、初級、高級,這部分學完就是下一部分的FreeRTOS。FreeRTOS的學習分兩個階段:1.從0到1寫出FreeRTOS的內核,2.移植FreeRTOS到開發板上並逐步添加外設功能。

 

這章就是手把手寫task.c的內容:

FreeRTOS學習記錄:

-------01.07----------------------
今日完成:(1)將書第七章《任務的定義》,任務切換,所有代碼手敲一遍,儘量作註釋,理解FreeRTOS中任務調度
		  (2)調試代碼  ——————


------------------------------------
以下筆記:
------------------------------------
第一部分:幾個頭文件的作用,功能,內容
	//頭文件portmacro.h :關於數據類型、函數返回值、宏定義的一些說明
		#ifndef PORTMACRO_H
		#define PORTMACRO_H

		#include "stdint.h"
		#include "stddef.h"


		//數據類型的重定義 :FreeRTOS中
		#define portCHAR	  	char    //
		#define portFLOAT		  float   //
		#define portDOUBLE		double  //
		#define portLONG		  long    //
		#define portSHORT		  short   //
		#define portSTACK_TYPE	uint32_t  //棧類型:
		#define portBASE_TYPE	long   // 基類型:根據處理器的結構決定多少位,用於函數返回值 / bool類型

		//自定義的數據類型 ,很多都是 xxx_t
		typedef portSTACK_TYPE StackType_t;
		typedef long           BaseType_t;        
		typedef unsigned long  UBaseType_t;

		#if( configUSE_16_BIT_TICKS == 1 )
			typedef uint16_t TickType_t;   //定義時基計數器16位
			#define portMAX_DELAY ( TickType_t ) 0xffff
		#else
			typedef uint32_t TickType_t; //定義時基計數器32位,根據configUSE_16_BIT_TICKS宏來定義計數器的類型
			#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
		#endif  //configUSE_16_BIT_TICKS==1


		//以下是FreeRTOS中的編程風格,弄明白這個看代碼會清晰點
		/*
		變量名前綴(箭頭後面的內容):
			char -> c
			short -> s
			long -> l
			portBaseType -> x  (portXxxxType -> x)
			指針 -> p  
			char*  -> pc
			long*  -> pl
		*/

		/*
		函數名:包含函數返回值、文件名、功能
			vTaskPrioritySet():返回值爲 void 型,在 task.c 這個文件中定義
			xQueueReceive():返回值爲 portBASE_TYPE 型,在 queue.c 這個文件中定義
			vSemaphoreCreateBinary():返回值爲 void 型,在 semphr.h 這個文件中定義
		*/

		/*
		宏:大寫字母爲宏,前綴小寫字母表示那個文件定義
		前綴:                                 宏定義的文件:
			port (舉例, portMAX_DELAY)                    portable.h
			task (舉例, taskENTER_CRITICAL())             task.h
			pd (舉例, pdTRUE)                             projdefs.h
			config(舉例, configUSE_PREEMPTION)            FreeRTOSConfig.h
			err (舉例, errQUEUE_FULL)                     projdefs.h
			
		幾個通用宏:
			pdTRUE            1
			pdFALSE           0
			pdPASS            1
			pdFAIL            0
		*/

		#endif /* PORTMACRO_H */
			
	//頭文件portable.h : 一個作用:include portmacro.h

	//頭文件FreeRTOS.h:在這裏定義任務控制塊
		#ifndef INC_FREERTOS_H
		#define INC_FREERTOS_H

		#include "FreeRTOSConfig.h"
		#include "portable.h"
		#include "projdefs.h"
		#include "list.h"

		//該聲明放在FreeRTOS.h中爲了tskTCB在其他文件使用
		//任務控制塊(結構體tskTCB) :任務的身份證(概念上有點類似操作系統中進程控制塊)
		typedef struct tskTaskControlBlock
		{
			volatile StackType_t*           pxTopOfStack; //棧頂指針 ,StackType_t在portmacro.h中定義
			ListItem_t                      xStateListItem;//任務結點:鏈表結點;可將任務控制塊掛在鏈表中
			StackType_t*                    pxStack;   //任務棧的起始地址
			char                            pcTaskName[configMAX_TASK_NAME_LEN];//任務名稱:字符串 (configMAX_TASK_NAME_LEN在FreeRTOSConfig中)
		}tskTCB;

		typedef tskTCB TCB_t; //將任務控制塊重定義爲:TCB_t

		#endif /* INC_FREERTOS_H */	
			
	//頭文件FreeRTOSConfig.h:宏定義的一些系統配置:FreeRTOS細節上的配置
		#ifndef FREERTOS_CONFIG_H
		#define FREERTOS_CONFIG_H

		//配置TickType_t :1-> TickType_t爲16位  ,0->32位
		#define configUSE_16_BIT_TICKS	               	0

		//配置任務控制塊:任務名稱長度爲16
		#define configMAX_TASK_NAME_LEN		            ( 16 )

		//配置靜態任務創建方式:1->靜態創建
		#define configSUPPORT_STATIC_ALLOCATION          1
		
		//最大任務優先級的宏
		#define configMAX_PRIORITIES                    5

		#endif //FREERTOS_CONFIG_H		
			
	//頭文件projdefs.h:這裏定義了任務的函數名稱
		#ifndef PROJDEFS_H
	#define PROJDEFS_H

	//任務入口:任務的函數名稱
	typedef void (*TaskFunction_t)( void * );

	#define pdFALSE			( ( BaseType_t ) 0 )
	#define pdTRUE			( ( BaseType_t ) 1 )

	#define pdPASS			( pdTRUE )
	#define pdFAIL			( pdFALSE )

	#endif /* PROJDEFS_H */		
		
		
第二部分:重點:任務的定義和函數實現 task.h task.c		
		
	//頭文件task.h中函數聲明:創建任務、列表初始化、調度器...
	#ifndef __TASK_H
	#define __TASK_H

	#include "FreeRTOS.h"

	//任務句柄
	typedef void* TaskHandle_t; //空指針

	//任務創建函數(靜態創建):該函數需要調用prvInitialiseNewTask()
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )                //如果靜態創建宏定義命中
	TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode, //任務入口:任務的函數名稱
									const char * const pcName,           //任務名稱:字符串
									const uint32_t ulStackDepth,         //任務棧大小:單位Byte
									void * const pvParameters,           //任務形參
									StackType_t * const puxStackBuffer,  //任務棧起始地址
									TCB_t * const pxTaskBuffer );        //任務控制塊指針
	#endif /* configSUPPORT_STATIC_ALLOCATION */

												
	//初始化任務就緒列表:表示任務已經就緒,系統隨時可以調度										
	void prvInitialiseTaskLists( void );  

	//開啓調度器										
	void vTaskStartScheduler( void );
												
	//(批註補充)
	void vTaskSwitchContext( void );

	#endif //__TASK_H	
		
	//task.c文件
		#include "task.h"
		#include "FreeRTOS.h"

		//用於指向當前正在運行或者即將要運行的任務的任務控制塊
		TCB_t * volatile pxCurrentTCB = NULL;


		//創建新任務,該函數調用pxPortInitialiseStack 定義在port.c中
		static void prvInitialiseNewTask(TaskFunction_t        pxTaskCode,   //任務入口:任務的函數名稱
																			const char * const   pcName,       //任務名稱:字符串
																			const uint32_t       ulStackDepth, //任務棧大小:單位Byte
																			void * const          pvParameters,//任務形參
																			TaskHandle_t * const pxHandle,        //任務句柄
																			TCB_t*               pxNewTCB)             //任務控制塊
		{
			StackType_t * pxTopOfStack;
			UBaseType_t   x;
			
			//獲取棧頂地址
			pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);	
			//向下做8字節對齊
			pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );
			
			//將任務名稱存起TCB中:字符串的拷貝
			for(x = ( UBaseType_t )0;x < (UBaseType_t)configMAX_TASK_NAME_LEN;x++)
			{
				pxNewTCB->pcTaskName[x] = pcName[x];
				if(pcName[x] == 0x00) break;
			}
			
			//任務名稱的長度不超過configMAX_TASK_NAME_LEN
			pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN-1] = '\0';
			
			// 初始化 TCB 中的 xStateListItem 節點 */
		  vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
			
		  // 設置 xStateListItem 節點的擁有者 
		  listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
			
			
			//初始化任務棧
			pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
			
			//讓任務句柄指向任務控制塊
			if ( ( void * ) pxHandle != NULL )
		  {
				*pxHandle = ( TaskHandle_t ) pxNewTCB;
		  }
		}


		//任務創建函數(靜態創建):該函數需要調用prvInitialiseNewTask()
		TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode, //任務入口:任務的函數名稱
										const char * const pcName,           //任務名稱:字符串
										const uint32_t ulStackDepth,         //任務棧大小:單位Byte
										void * const pvParameters,           //任務形參
										StackType_t * const puxStackBuffer,            //任務棧起始地址
										TCB_t * const pxTaskBuffer )                   //任務控制塊指針
		{
			TCB_t* pxNewTCB;
			TaskHandle_t xReturn;//任務句柄,用於指向任務的TCB
			
			if((pxTaskBuffer != NULL) && (puxStackBuffer != NULL) )
			{
				pxNewTCB = (TCB_t*) pxTaskBuffer;
				pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;

				//創建新任務
				prvInitialiseNewTask(pxTaskCode,pcName,ulStackDepth,pvParameters,xReturn,pxNewTCB);
			}
			else
			{
				xReturn = NULL;
			}
			//返回任務句柄,如果任務創建成功,此時 xReturn 應該指向任務控制塊
			return xReturn;
		}

		//定義就緒列表 configMAX_PRIORITIES宏表示最大優先級 ,結構是結點的數組
		 List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

		//初始化任務就緒列表:表示任務已經就緒,系統隨時可以調度										
		void prvInitialiseTaskLists( void )
		{
			UBaseType_t uxPriority;
			
			for(uxPriority = (UBaseType_t)0u;uxPriority < ( UBaseType_t ) configMAX_PRIORITIES;uxPriority++)
			{
				//調用list.c中的根節點初始化函數
				vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
			}
		}

		extern TCB_t Task1TCB;
		extern TCB_t Task2TCB;
		//開啓調度器:實現任務切換,從就緒列表中找優先級最高的然後執行									
		void vTaskStartScheduler( void )
		{
			//手動指定第一個任務運行
			pxCurrentTCB = &Task1TCB;
			
			//啓動調度器  xPortStartScheduler() != pdFALSE定義在port.c
			if( xPortStartScheduler() != pdFALSE)
			{
				
			}
		}


		void vTaskSwitchContext( void )
		{    
			/*兩任務切換 */
			if( pxCurrentTCB == &Task1TCB )
			{
				pxCurrentTCB = &Task2TCB;
			}
			else
			{
				pxCurrentTCB = &Task1TCB;
			}
		}
		
		
第三部分:port.c 和 main函數	
	//還有個port.c文件 裏面最後實現的功能用匯編完成	
		
--------------------------------------------------------------		

		

任務和任務切換: 任務怎麼創建?任務怎麼切換?  這是FreeRTOS的基礎中的基礎,非常非常重要!

    每個任務獨立不干擾,所以每個任務需要獨立棧空間(函數調用、中斷、局部變量)
    
    程序思路:
        首先,定義一個任務棧,就是個全局數組
        然後搞一個任務控制塊的結構體(包含棧頂,任務結點,任務名稱)
        然後,編寫創建任務函數(分動態靜態,能不能自動管理內存)
        再然後,搞個任務棧的初始化函數

        有任務了,系統需要調度了。所以搞個就緒列表,
            就序列表的初始化函數,任務插入列表函數
            
        實現一個調度器,實現任務的切換(從列表中找到優先級最高的)
            啓動調度器函數、任務切換函數(利用中斷)

思路就是大概這樣一個思路,實現的代碼還有很多細節需要深究的地方...感覺不是100%看懂,但也理解個大概了。

發佈了90 篇原創文章 · 獲贊 70 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章