之前寫博客說過:
一直跟着野火的教程學習,從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%看懂,但也理解個大概了。