【FreeRTOS】【STM32】06 FreeRTOS的使用-动态创建单任务

前言

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,//任务函数
						const char * const pcName,//任务函数名称,字符串
						const uint16_t usStackDepth,//堆栈深度
						void * const pvParameters,//可能的其他参数
						UBaseType_t uxPriority,//优先级
						TaskHandle_t * const pxCreatedTask )//任务句柄
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,//任务函数
								const char * const pcName,//任务名称
								const uint32_t ulStackDepth,//任务中栈大小
								void * const pvParameters,//参数
								UBaseType_t uxPriority,//优先级
								StackType_t * const puxStackBuffer,//栈
								StaticTask_t * const pxTaskBuffer )//控制块

两种方式创建任务的函数中,可以明显看出参数的不同。

使用静态方法创建任务时需要传递进的参数中有任务堆栈和任务控制块,他们按照如下的形式定义成全局变量,并且把数组名也就是数组的地址传递给xTaskCreateStatic函数。

 /* AppTaskCreate 任务任务堆栈 */
 static StackType_t AppTaskCreate_Stack[128];
 
 /* AppTaskCreate 任务控制块 */
 static StaticTask_t AppTaskCreate_TCB;

任务控制块和任务栈的内存空间都是从内部的 SRAM 里面分配的,具体分配到哪个地址由编译器决定。

那么动态创建任务的方式是?

FreeRTOS 做法是在 SRAM 里面定义一个大数组,也就是堆内存,供 FreeRTOS 的动态内存分配函数使用,在第一次使用的时候,系统会将定义的堆内存进行初始化,这些代码在 FreeRTOS 提供的内存管理方案中实现如heap4.c.

//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024)) (1)
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; (2)
/* 如果这是第一次调用 malloc 那么堆将需要初始化,以设置空闲块列表。*/
if ( pxEnd == NULL )
{
	prvHeapInit(); (3)
} else
 {
	 mtCOVERAGE_TEST_MARKER();
 }

堆内存的大小为configTOTAL_HEAP_SIZE , 在FreeRTOSConfig.h 中由我们自己定义,configSUPPORT_DYNAMIC_ALLOCATION 这个宏定义在使用 FreeRTOS 操作系统的时候必须开启。

动态创建任务

动态创建任务API函数

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,//任务函数
						const char * const pcName,//任务函数名称,字符串
						const uint16_t usStackDepth,//堆栈深度
						void * const pvParameters,//可能的其他参数
						UBaseType_t uxPriority,//优先级
						TaskHandle_t * const pxCreatedTask )//任务句柄

可以看到需要我们提供的参数。

1.定义任务函数

FreeRTOS 官方给出的任务函数模板如下:

void vATaskFunction(void *pvParameters) (1)
{
	for( ; ; ) (2)
	{
		
		/*--任务应用程序-- 
		  比如点亮一个LED,
		   启动一个马达
		*/(3)
		
		vTaskDelay(); (4)
	}
	
	/* 不能从任务函数中返回或者退出, 从任务函数中返回或退出的话就会调用
	configASSERT(),前提是你定义了 configASSERT()。如果一定要从任务函数中退出的话那一定 
	要调用函数 vTaskDelete(NULL)来删除此任务。*/
	vTaskDelete(NULL); (5)
}

(1)、任务函数本质也是函数,返回类型一定要为void 类型 ,任务的参数也是 void 指针类型
(2)、任务的具体执行过程是一个大循环,for(; ; )就代表一个循环,作用和 while(1)一样
(3)、循环里面就是真正的任务代码
(4)、FreeRTOS 的延时函数,此处不一定要用延时函数,其他只要能让 FreeRTOS 发生任务切换的 API 函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用的就是 FreeRTOS 的延时函数。
(5)、任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用函数 vTaskDelete(NULL)删除此任务!

2.定义任务栈(FreeRTOS自动)

使用动态内存的时候,任务栈在任务创建的时候创建,不用跟使用静态内存那样要预
先定义好一个全局的静态的栈空间,动态内存就是按需分配内存,随用随取。

3.定义任务控制块指针

使用动态内存时候,不用跟使用静态内存那样要预先定义好一个全局的静态的任务控制块空间。由FreeRTOS创建,FreeRTOS会自动返回一个TaskHandle_t *类型指针,这个指针指向任务控制块(任务控制块就是描述任务各种属性的一个结构体),这个指针也叫任务句柄,操作任务都通过这个任务句柄来实现。

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为 NULL。
*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED 任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;

启动任务

当任务创建好后,是处于任务就绪(Ready),在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个任务就是在启动任务调度器中实现,每个操作系统,任务调度器只启动一次,之后就不会再次执行了,FreeRTOS 中启动任务调度器的函数是 vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管理都由FreeRTOS管理。

mian函数中的主要流程

1.定义任务句柄
static TaskHandle_t AppTaskCreate_Handle = NULL;
2.硬件初始化

3.创建具体功能任务函数

见定义任务函数

4.使用xTaskCreate创建任务
 xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
 						(const char* )"AppTaskCreate",/* 任务名字 */
						 (uint16_t )512, /* 任务栈大小 */
 						(void* )NULL,/* 任务入口函数参数 */
						 (UBaseType_t )1, /* 任务的优先级 */
					 (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
5.使用vTaskStartScheduler(); /* 启动任务,开启调度 */

以下是整个mian.c文件

 /*
 *************************************************************************
 * 包含的头文件
 *************************************************************************
 */
 /* FreeRTOS 头文件 */
 #include "FreeRTOS.h"
 #include "task.h"
 /* 开发板硬件 bsp 头文件 */
 #include "bsp_led.h"
 #include "bsp_usart.h"
 
 /**************************** 任务句柄 ********************************/
 /*
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为 NULL。
 */
 /* 创建任务句柄 */
 static TaskHandle_t AppTaskCreate_Handle = NULL;
 /* LED 任务句柄 */
 static TaskHandle_t LED_Task_Handle = NULL;
 
 /***************************** 内核对象句柄 *****************************/
 /*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 
 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 *
 */
 
 
 /*************************** 全局变量声明 ********************************/
 /*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */
 
 
 /*
 *************************************************************************
 * 函数声明
 *************************************************************************
 */
 static void AppTaskCreate(void);/* 用于创建任务 */
 
 static void LED_Task(void* pvParameters);/* LED_Task 任务实现 */
 
 static void BSP_Init(void);/* 用于初始化板载相关资源 */
 
 /*****************************************************************
 * @brief 主函数
 * @param 无
 * @retval 无
 * @note 第一步:开发板硬件初始化
 第二步:创建 APP 应用任务
 第三步:启动 FreeRTOS,开始多任务调度
 ****************************************************************/
 int main(void)
 {
	 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
 
	 /* 开发板硬件初始化 */
	 BSP_Init();

 /* 创建 AppTaskCreate 任务 */
 xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
 						(const char* )"AppTaskCreate",/* 任务名字 */
						 (uint16_t )512, /* 任务栈大小 */
 						(void* )NULL,/* 任务入口函数参数 */
						 (UBaseType_t )1, /* 任务的优先级 */
					 (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
					 
 /* 启动任务调度 */
 if (pdPASS == xReturn)
	 vTaskStartScheduler(); /* 启动任务,开启调度 */
 else
 	return -1;
 
	 while (1); /* 正常不会执行到这里 */
 }
 
 
 /***********************************************************************
 * @ 函数名 : AppTaskCreate
 * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
 * @ 参数 : 无
 * @ 返回值 : 无
 
*******************************************************************/
 static void AppTaskCreate(void)
 {
 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
 
 taskENTER_CRITICAL(); //进入临界区
 
 /* 创建 LED_Task 任务 */
 xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
 (const char* )"LED_Task",/* 任务名字 */
 (uint16_t )512, /* 任务栈大小 */
 (void* )NULL, /* 任务入口函数参数 */
 (UBaseType_t )2, /* 任务的优先级 */
 (TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
 if (pdPASS == xReturn)
 printf("创建 LED_Task 任务成功!\r\n");
 
 vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
 
 taskEXIT_CRITICAL(); //退出临界区
 }
 
 
 
 /**********************************************************************
 * @ 函数名 : LED_Task
 * @ 功能说明: LED_Task 任务主体
 * @ 参数 :
 * @ 返回值 : 无
 ********************************************************************/
 static void LED_Task(void* parameter)
 {
 while (1) {
 LED1_ON;
 vTaskDelay(500); /* 延时 500 个 tick */
 printf("led1_task running,LED1_ON\r\n");
 
 LED1_OFF;
 vTaskDelay(500); /* 延时 500 个 tick */
 printf("led1_task running,LED1_OFF\r\n");
 }
 }
 
 /***********************************************************************
 * @ 函数名 : BSP_Init
 * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
 * @ 参数 :
 * @ 返回值 : 无
 *********************************************************************/
 static void BSP_Init(void)
 {
 /*
 * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
 * 都统一用这个优先级分组,千万不要再分组,切忌。
 */
 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

 /* LED 初始化 */
 LED_GPIO_Config();

 /* 串口初始化 */
 USART_Config();

 }

 /*************************END OF FILE****************************/

你可能感兴趣的:(RTOS,stm32,单片机,嵌入式硬件)