跟着野火从一开始手搓FreeRTOS(2)创建任务

PS:本人因为一些自己的原因,从本篇开始一些函数名之类的自定义的名字会用自己的方式命名,会和野火的教程有出入,还请自行甄别。

        从本质上来讲,FreeRTOS的运行就是不断执行每个任务。本篇会介绍静态和动态创建任务的方法。

        静态创建需要指定任务栈的大小和起始地址;动态创建在SRAM中申请内存,用完就释放。二者区别在于静态的内存始终存在,无法被释放。

静态单任务

硬件初始化

        首先是静态创建单个任务。在main.c中定义初始化函数并进行声明,将会用到的外设都在这个函数里进行初始化。

#include "FreeRTOS.h"
#include "task.h"
#include "led.h"
#include "usart.h"

static void Device_Init(void);

int main(void)
{	
  
}

/* 静态创建 硬件初始化 */
static void Device_Init(void)
{
    /* 中断优先级分组,配置为4 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

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

        STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15。优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,都统一用这个优先级分组,千万不要再分组。

        在程序执行的过程中,会先运行上述函数。这个函数和裸机程序的硬件初始化是一样的,在初始化完成后才会启动操作系统。

        因此,在实际开发过程中,可以在初始化函数中加入一些命令和死循环来进行测试,确认硬件没有问题后注释掉循环即可。测试命令因为只执行一次,可自行判断是否留下。

创建单个任务——静态

        当我们静态创建任务的时候,FreeRTOSConfig.h的configSUPPORT_STATIC_ALLOCATION 这个宏必 须为 1,否则会报错。 

定义任务函数

        任务就是无限执行的函数,在main.c中定义任务:让LED1以500ms的频率闪烁。

static void LED1_Task(void* pvParameters);

static void LED1_Task(void* parameter)
{	
    while (1)
    {
        LED1_ON;
        vTaskDelay(500);   /* 延时500个tick */
        printf("LED_Task Running,LED1_ON\r\n");
        
        LED1_OFF;     
        vTaskDelay(500);   /* 延时500个tick */		 		
        printf("LED_Task Running,LED1_OFF\r\n");
    }
}

         这里的延时函数必须使用 FreeRTOS 提供的延时函数,不要使用我们裸机编程中的软件延时。vTaskDelay()会让当前任务被阻塞转而去执行其他任务;而裸机的延时会让CPU陷入死循环,一直在执行当前任务无法跳出,无法实现多任务系统。

定义空闲任务与定时器任务堆栈函数

        接下来实现两个函数 : vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配。

/* 空闲任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];

/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;	
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;

/* 获取空闲任务的任务堆栈和任务控制块内存 */ 
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
								   StackType_t **ppxIdleTaskStackBuffer, 
								   uint32_t *pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
	*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}

/* 获取定时器任务的任务堆栈和任务控制块内存 */ 
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize)
{
	*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
	*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
	*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}

定义任务栈和任务控制块

        由于当前只有一个任务,在当前任务进入延时后,系统就会进入运行空闲任务。空闲任务的优先级最低,所以在任务延时结束后就会继续运行这个任务。

        这些任务同样需要定义栈空间。

/* AppTaskCreate任务任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* LED任务堆栈 */
static StackType_t LED1_Task_Stack[128];

/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* AppTaskCreate 任务控制块 */
static StaticTask_t LED_Task_TCB;

        在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中一般都是以 8 字节大小对齐,并且会检查堆栈是否已经对齐。字节对齐通过portmacro.h里的宏定义。此外用户也可以选择按 1、 2、 4、 8、 16、 32 等字节对齐。

静态创建任务

        创建一个任务需要三样东西,分别是任务主体函数,任务栈和任务控制块。有了这些之后,通过静态任务创建函数 xTaskCreateStatic()将这些信息整合成任务。 

        我们把任务主体,任务栈,任务控制块这三部分都在 main.c 中进行编写和声明,并在main.c 文件中创建一个 AppTaskCreate 任务,我们创建的所有的任务都会放在这个函数里面。

/* 创建 AppTaskCreate 任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t)AppTaskCreate,		//任务函数
										(const char* 	)"AppTaskCreate",	//任务名称
										(uint32_t 		)128,	//任务堆栈大小
										(void* 		  	)NULL,	//传递给任务函数的参数
										(UBaseType_t 	)3, 	//任务优先级
										(StackType_t*   )AppTaskCreate_Stack,//任务堆栈
										(StaticTask_t*  )&AppTaskCreate_TCB);	//任务控制块 

空闲任务和定时器任务

        在FreeRTOS中,只有我们自定义的任务是不行的,还需要有空闲任务和定时器任务。这两个任务在调度器中实现,在静态创建时我们只能自己来定义。这里我们定义在main.c中。

/* 获取空闲任务的任务堆栈和任务控制块内存 */ 
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
								   StackType_t **ppxIdleTaskStackBuffer, 
								   uint32_t *pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
	*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}

/* 获取定时器任务的任务堆栈和任务控制块内存 */ 
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize)
{
	*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
	*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
	*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */

启动任务

        当任务创建好后,需要开启任务调度器,才能执行任务。任务调度器只会在整个系统中启动一次,之后就不会再次执行了。 当启动任务调度器后,便不会有返回,之后任务都由FreeRTOS进行管理。

if(NULL != AppTaskCreate_Handle)/* 创建成功 */
    vTaskStartScheduler();   /* 启动任务,开启调度 */

PS:启动任务的代码请参考野火例程,本文只进行流程讲解。 

动态单任务

        动态任务使用的的任务控制块和任务栈是SRAM分配的,具体位置由编译器决定。

        堆,也就是动态内存,也是在SRAM里的一段内存。操作系统在SRAM里定义一个大数组作为堆,提供给动态内存分配函数使用。在第一次使用的时候,系统会将定义的堆内存进行初始化,至于初始化的方式在官方提供的内存管理方案,即heap源文件中实现。

        堆的大小在FreeRTOSConfig.h中,由宏定义#define configTOTAL_HEAP_SIZE确定,目前设置大小为36KB。系统关于内存空间的开辟在heap源文件中进行配置,不需要我们去管理。同时我们需要将支持动态内存申请的宏#define configSUPPORT_DYNAMIC_ALLOCATION置为1。

//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION        1    
				
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE					((size_t)(36*1024))    

定义任务函数

        和静态创建一样,动态创建也需要定义任务函数。

static void LED_Task(void* parameter)
{	
    while (1)
    {
        LED1_ON;
        vTaskDelay(500);   /* 延时500个tick */
        printf("LED_Task Running,LED1_ON\r\n");
        
        LED1_OFF;     
        vTaskDelay(500);   /* 延时500个tick */		 		
        printf("LED_Task Running,LED1_OFF\r\n");
    }
}

        任务必须是一个无限循环,如果出现异常,则必须通过 LR 返回。这时操作系统会运行任务退出函数prvTaskExitError(),使系统在这个死循环任务里一直循环下去,这种情况是很危险的。为了避免这种情况,只需要让AppTaskCreate 执行一次之后就删除掉即可。

        任务里面的延时函数必须使用 FreeRTOS 里面提供的阻塞延时函数。关于阻塞延时在之前的文章提过,不了解的可以去翻一翻。

        此外,动态创建任务时,任务栈会在任务创建的时候创建,不需要提前定义栈空间。动态内存就是按需分配内存,需要就分配,用完就释放。

        因为这种随取随用的风格,一般我们都使用用动态创建任务。

定义任务控制块指针

        其实任务控制块也是在任务创建的时候创建,任务创建函数会返回一个指针,用于指向任务控制块。因此要提前为任务栈定义一个任务控制块指针,也就是任务句柄。这部分在main.c中定义。

 /* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;

动态创建任务

        动态创建任务时,使用 xTaskCreate()函数来创建任务,和静态创建有很多相似之处,也有些区别。

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

 这里的是一个创建信息返回值,默认值为pdPASS。

BaseType_t xReturn = pdPASS;

        这里再次把静态任务的定义放在这:

/* 创建 AppTaskCreate 任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t)AppTaskCreate,		//任务函数
										(const char* 	)"AppTaskCreate",	//任务名称
										(uint32_t 		)128,	//任务堆栈大小
										(void* 		  	)NULL,	//传递给任务函数的参数
										(UBaseType_t 	)3, 	//任务优先级
										(StackType_t*   )AppTaskCreate_Stack,//任务堆栈
										(StaticTask_t*  )&AppTaskCreate_TCB);	//任务控制块 

         对比静态任务,动态任务少了堆栈的定义。

启动任务

同静态一样,动态任务也需要调度器才能启动任务。

  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;

创建多任务 ——动态

        创建多任务和单任务很相似,只需要把任务创建有关的东西写两遍,注意一下函数名和优先级即可。接下来创建两个任务,任务 1 让一个 LED 灯闪烁,任务 2 让另外一个 LED 闪烁,两个 LED 闪烁的频率不一样。

        接下来将不一样的地方展示出来,剩下的和动态单任务基本一致。

/* LED1任务句柄 */
static TaskHandle_t LED1_Task_Handle = NULL;
/* LED2任务句柄 */
static TaskHandle_t LED2_Task_Handle = NULL;


/* 函数声明 */
static void LED1_Task(void* pvParameters);/* LED1_Task任务实现 */
static void LED2_Task(void* pvParameters);/* LED2_Task任务实现 */


static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED1_Task, /* 任务入口函数 */
                        (const char*    )"LED1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED1_Task任务成功!\r\n");
  
	/* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED2_Task, /* 任务入口函数 */
                        (const char*    )"LED2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )3,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED2_Task任务成功!\r\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}


/* LED1任务函数主体 */
static void LED1_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");
    }
}

/* LED2任务函数主体 */
static void LED2_Task(void* parameter)
{	
    while (1)
    {
        LED2_ON;
        vTaskDelay(500);   /* 延时500个tick */
        printf("LED2_Task Running,LED2_ON\r\n");
        
        LED2_OFF;     
        vTaskDelay(500);   /* 延时500个tick */		 		
        printf("LED2_Task Running,LED2_OFF\r\n");
    }
}

        在实际使用中,我们肯定不会只创建一两个任务。在这基础上,我们进行多任务的动态创建基本就能满足大部分需求。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pQAQqa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值