之前写博客说过:
一直跟着野火的教程学习,从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%看懂,但也理解个大概了。