FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码

Posted 果果小师弟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码相关的知识,希望对你有一定的参考价值。

现在开始使用FreeRTOS,对于代码的编写很简单,FreeRTOS是可裁剪的,想要什么不想要什么直接在FreeRTOSConfig.h文件中取消和打开相关的宏定义就可以了。

下面是小编的FreeRTOSConfig.h文件配置,你可以作为参考。

FreeRTOSConfig.h

/*
 * FreeRTOS V202107.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved. *
 * http://www.FreeRTOS.org
 * http://aws.amazon.com/freertos
 * See http://www.freertos.org/a00110.html
 * 1 tab == 4 spaces!
 */

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H


/* 确保stdint仅由编译器使用,而不是由汇编程序使用.针对不同的编译器调用不同的stdint.h文件 */
#if defined (__ICCARM__)|| defined(__CC_ARM)|| defined(__GNUC__)
	#include <stdint.h>
	extern uint32_t SystemCoreClock;
#endif

/***************************************************************************************************************/
/*                                        FreeRTOS基础配置配置选项                                              */
/***************************************************************************************************************/
#define configUSE_TIME_SLICING					1	//1使能时间片调度(默认式使能的)
/*
某些运行 FreeRTos的硬件有两种方法选择下一个要执行的任务:
*通用方法和特定于硬件的方法(以下简称“特殊方法〃)。

*通用方法
	1.configUSE_PORT_OPTIMISED_TASK_SELECTION0或者硬件不支持这种特殊方法
	2.可以用于所有FreeRTOS支持的硬件
	3.完全用C实现,效率略低于特殊方法
	4.不强制要求限制最大可用优先级数目
	
*特殊方法

	1.必须将 configUSE PORT OPTIMISED TASK SELECTIoN设置为1。
	2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CL2]指令)
	3.比通用方法更高效
	4.一般强制限定最大可用优先级数目为32般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0!
*/
#define configUSE_PORT_OPTIMISED_TASK_SELECTION	1    /*1启用特殊方法来选择下一个要运行的任务
                                                     一般是硬件计算前导零指令,如果所使用的
                                                     MCU没有这些硬件指令的话此宏应该设置为0!*/

#define configUSE_TICKLESS_IDLE			0   //置 1:使能低功耗 tickless 模式;置 0:保持系统节拍(tick)中断一直运行
#define configUSE_QUEUE_SETS			1   //为1时启用队列


#define configUSE_PREEMPTION			1   //1使用抢占式内核,0使用协程
/*

* 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fc1k Fc1k
* 为供给CPU内核的时钟信号,我们所说的CPU主频为 XX MHZ,
* 就是指的这个时钟信号,相应的,1/Fc1k即为cpu时钟周期;

*/
#define configCPU_CLOCK_HZ				( SystemCoreClock )  //设置CPU 时钟频率
//RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断 RTOS 都会进行任务调度,这里设置为1000,周期就是1ms
#define configTICK_RATE_HZ				( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES			( 32 ) //最大任务优先级默认为5,最大支持256个优先级
#define configMINIMAL_STACK_SIZE		( ( unsigned short ) 130 )  //空闲任务使用的堆栈大小
#define configMAX_TASK_NAME_LEN			( 16 )  //任务名字字符串长度,这里定义的长度包括字符串结束符’\\0’
#define configUSE_TRACE_FACILITY		1

#define configUSE_16_BIT_TICKS			0 //系统节拍计数器变量数据类型,1 表示为 16 位无符号整形,0 表示为 32 位无符号整形
#define configIDLE_SHOULD_YIELD			1 //为1时 空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configUSE_MUTEXES				1 //为1时使用互斥信号量
#define configQUEUE_REGISTRY_SIZE		8 //不为0时表示启用队列记录,具体的值是可以
#define configCHECK_FOR_STACK_OVERFLOW	0 /*大于0时启用堆栈溢出检测功能,如果使用此功能
                                            用户必须提供一个栈溢出钩子函数,如果使用的话
                                            此值可以为1或者2,因为有两种栈溢出检测方法。*/
#define configUSE_RECURSIVE_MUTEXES		1 //为1时使用递归互斥信号量
#define configUSE_MALLOC_FAILED_HOOK	0 //1使用内存申请失败钩子函数
#define configUSE_APPLICATION_TASK_TAG	0
#define configUSE_COUNTING_SEMAPHORES	1 //为1时使用计数信号量
#define configGENERATE_RUN_TIME_STATS	0


/***************************************************************************************************************/
/*                                FreeRTOS与内存申请有关配置选项                                                */
/**************************************************************************************************************/
#define configSUPPORT_DYNAMIC_ALLOCATION        1   /*支持动态内存申请,一般在系统中采用的内存分配都是动态内存分配。
													   FreeRTOS同时也支持静态分配内存,但是常用的就是动态分配了。*/
#define configSUPPORT_STATIC_ALLOCATION 		0	/*  1支持静态内存
														如果使用静态方法的话需要自己实现两个函数vApplicationGetIdleTaskMemory()和
														vApplicationGetTimerTaskMemory()。通过这两个函数来给空闲任务和定时器服务任务的任务堆
														栈和任务控制块分配内存,*/

#define configTOTAL_HEAP_SIZE					((size_t)(20*1024))     //系统所有总的堆大小


/***************************************************************************************************************/
/*                                FreeRTOS与钩子函数有关的配置选项                                              */
/***************************************************************************************************************/

/*
* 置1:使用空闲钩子(Idle hook类似于回调函数);置0:忽略空闲钩子
* 空闲任务钩子是一个函数,这个函数由用户来实现,
* FreeRTOS规定了函数的名字和参数:void vApplicationIdleHook(void),
* 这个函数在每个空闲任务周期都会被调用
* 对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。
* 因此必须保证空闲任务可以被CPU执行
* 使用空闲钩子函数设置CPU进入省电模式是很常见的
* 不可以调用会引起空闲任务阻塞的API函数
*/
#define configUSE_IDLE_HOOK				0  //空闲钩子
/*

* 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子
* 
* 时间片钩子是一个函数,这个函数由用户来实现,
* FreerToS规定了函数的名字和参数:void vApplicationTickHook(void)
* 时间片中断可以周期性的调用
* 函数必须非常短小,不能大量使用堆栈,
* 不能调用以〃FromIsR"或"FROM ISR"结尾的API函数

*/
#define configUSE_TICK_HOOK				0  //滴答定时器钩子

/***************************************************************************************************************/
/*                                FreeRTOS与运行时间和任务状态收集有关的配置选项                                 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS	        0     //为1时 启用运行时间统计功能
#define configUSE_TRACE_FACILITY				1     //为1时 启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS	1    /* 宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
                                                      * prvWriteNameToBuffer(),
													  * vTaskList(),
                                                      * vTaskGetRunTimeStats()
													  */


/***************************************************************************************************************/
/*                                FreeRTOS与协程有关的配置选项                                                  */
/***************************************************************************************************************/

#define configUSE_CO_ROUTINES 		0          //为1时启用协程,启用协程以后必须添加文件croutine.c
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )  //协程的有效优先级数目

/***************************************************************************************************************/
/*                                FreeRTOS与软件定时器有关的配置选项                                            */
/***************************************************************************************************************/
#define configUSE_TIMERS				1								 //为1时启用软件定时器
#define configTIMER_TASK_PRIORITY		( 2 )							 //软件定时器优先级
#define configTIMER_QUEUE_LENGTH		10								 //软件定时器队列长度
#define configTIMER_TASK_STACK_DEPTH	( configMINIMAL_STACK_SIZE * 2 ) //软件定时器任务堆栈大小

/***************************************************************************************************************/
/*                                FreeRTOS可选函数配置选项                                                      */
/***************************************************************************************************************/
#define INCLUDE_vTaskPrioritySet		1 //如果要使用函数 vTaskPrioritySet() 的话需要将宏 vTaskPrioritySet 定义为1。
#define INCLUDE_uxTaskPriorityGet		1 //如果要使用函数 uxTaskPriorityGet() 的话需要将宏 uxTaskPriorityGet 定义为1。
#define INCLUDE_vTaskDelete				1 //如果要使用函数 vTaskDelete() 的话需要将宏 vTaskDelete 定义为1。
#define INCLUDE_vTaskCleanUpResources	1 //如果要使用函数 vTaskCleanUpResources() 的话需要将宏 vTaskCleanUpResources 定义为1。
#define INCLUDE_vTaskSuspend			1 //如果要使用函数 vTaskSuspend() 的话需要将宏 vTaskSuspend 定义为1。
#define INCLUDE_vTaskDelayUntil			1 //如果要使用函数 vTaskDelayUntil() 的话需要将宏 vTaskDelayUntil 定义为1
#define INCLUDE_vTaskDelay				1 //如果要使用函数 vTaskDelay() 的话需要将宏 INCLUDE_vTaskDelay 定义为1
#define INCLUDE_xTaskGetSchedulerState  1 //如果要使用函数 xTaskGetSchedulerState() 的话需要将宏 xTaskGetSchedulerState 定义为1

/***************************************************************************************************************/
/*                                FreeRTOS与中断有关的配置选项                                                  */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
	/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
	#define configPRIO_BITS       		__NVIC_PRIO_BITS
#else
	#define configPRIO_BITS       		4        /* 15 priority levels */
#endif

/* 中断最低优先级.而 FreeRTOS 的任务优先级是,任务优先级数值越小,任务优先级越低。*/
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			0xf

/* 系统可管理的最高中断优先级。优先级越高,数值越低。 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5

/* 内核端口层本身使用的中断优先级。这些是所有Cortex-M端口的通用端口,不依赖于任何特定的库函数。 */
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
	
/* 断言 */
#define vAssertCalled(char,int) printf("Error:%s,%d\\r\\n",char,int)
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }	
	
/****************************************************************
		FreeRTOS 与中断服务函数有关的配置选项
****************************************************************/
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler

#endif 

main()函数中,我们直接可以对FreeRTOS进行创建任务操作,因为FreeRTOS会自动帮我们做初始化的事情,比如初始化堆内存。FreeRTOS的简单方便是在别的实时操作系统上都没有的,像RT-Thread,需要做很多事情,具体可以看野火出版的一本书《RT_Thread内核实现与应用开发实战一基于STM32》;华为 LiteS也需要我们用户进行初始化内核,具体可以看野火出版的另一本书籍华为 LiteOS《华为 LiteOS内核实现与应用开发实战一基于STM32》。

这种简单的特点使得FreeRTOS在初学的时候变得很简单,我们自己在main();函数中直接初始化我们的板级外设,然后进行任务的创建即可—xTaskCreate(),在任务创建中,FreeRTOS会帮我们进行一系列的系统初始化,在创建任务的时候,会帮我们初始化堆内存。

main.c

1、使用动态任务创建函数xTaskCreate();创建一个开始任务start_task();然后再开始任务中创建任务一task2_task();和任务二task2_task();

2、开始任务start_task();在执行完创建两个后就马上删除自己这个开始任务,自己删自己,铁锅炖自己

3、之后系统就开始不断地执行task2_task();和任务二task2_task();

4、在任务一中我们写了一个按键扫描函数key_Scan(),不断检测按下的键值赋值给kValue,因为kValue定义的是一个全局变量,所以可以在任务二函数中使用这个Value值。

5、在任务二中通过判断kValue值来执行点灯关灯的操作。

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "SEGGER_RTT.h"
#include "math.h"
#include "FreeRTOS.h"
#include "task.h"

#define START_TASK_PRIO		1				//任务优先级
#define TASK1_TASK_PRIO		2				//任务优先级
#define TASK2_TASK_PRIO		3 				//任务优先级

#define START_STK_SIZE 		128  			//任务堆栈大小
#define TASK1_STK_SIZE 		128  			//任务堆栈大小	
#define TASK2_STK_SIZE 		128  			//任务堆栈大小

TaskHandle_t StartTask_Handler;				//任务句柄
TaskHandle_t Task1Task_Handler;				//任务句柄
TaskHandle_t Task2Task_Handler;				//任务句柄

void start_task(void *pvParameters);		//任务函数声明
void task1_task(void *pvParameters);		//任务函数声明
void task2_task(void *pvParameters);		//任务函数声明


int main(void)
{
    HAL_Init();                 //初始化HAL库
    Stm32_Clock_Init(8, 336, 2, 7); //设置时钟,168Mhz
    delay_init(168);  	        //初始化延时函数
    KEY_Init();
	LED_Init();		        //初始化LED端口
    //动态创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄
    //开启任务调度
    /* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
     * 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
     * 如果没有创建多线程的话,那就只有一个线程。*/
    vTaskStartScheduler();

    /* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
    * 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
    while(1)
    {
        //这里的代码不会被执行,写了也没用
    }
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,
                (const char*    )"task1_task",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_TASK_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,
                (const char*    )"task2_task",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t key_Scan(void)
{
    //没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
    //被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
    //key_up检测到已经返回过一次键值了,不再返回键值
    static uint8_t key_up = 0;

    int key1 = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4);
    int key2 = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3);

    if((key_up == 0) && (key1 == 0 || key2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
    {
        delay_ms(10);                      //去抖动
        key_up = 1;                        //设置标志位,表示按下

        if(key1 == 0)return 1; 				//如果k1按下就返回1
        else if(key2 == 0)return 2; 		//如果k2按下就返回2
    }
    else if(key1 == 1 && key2 == 1) key_up = 0; 	 //按键松开,清标志位

    return 0;                              //无按键按下或松开了时就返回0
}
uint8_t kValue = 0;

//task1任务函数
void task1_task(void *pvParameters)
{
    for(;;)
    {
        kValue = key_Scan();
        vTaskDelay(200);
    }
}

//task2任务函数
void task2_task(void *pvParameters)
{

    static uint8_t flag1 = 0;
    static uint8_t flag2 = 0;

    for(;;)
    {
        if(kValue == 1)
        {
            SEGGER_RTT_printf(0, "k1 被按下了\\r\\n");

            if(flag1 == 0) //灯关着就打开
            {
                HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
                flag1 = 1;
            }
            else if(flag1 == 1) //开着就关闭
            {
                HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
                flag1 = 0;
            }
        }
        else if(kValue == 2)
        {
            SEGGER_RTT_printf(0, "k2 被按下了\\r\\n");

            if(flag2 == 0) //灯关着就打开
            {
                HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET);
                flag2 = 1;
            }
            else if(flag2 == 1) //开着就关闭
            {
                HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET);
                flag2 = 0;
            }
        }
        vTaskDelay(200);
    }
}

试验现象


当我们拿到一个移植好FreeRTOS的例程的时候,不出意外,你首先看到的是main函数,当你认真一看main函数里面只是创建并启动一些任务和硬件初始化。而系统初始化这些工作不需要我们实现,因为FreeRTOS在我们使用创建与开启调度的时候就已经偷偷帮我们做完了,如果只是使用FreeRTOS的话,无需关注FreeRTOS API函数里面的实现过程,但是为了避免出现问题,还是建议需要深入了解FreeRTOS然后再去使用。

总结:

一旦开启任务调度。启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,此时PC(程序计数器)就会指向某线程的指令,开始多线程并发运行。
如果没有创建多线程的话,那就只有一个线程。

vTaskStartScheduler();

由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。

.....................;
.....................;
vTaskStartSche

以上是关于FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码的主要内容,如果未能解决你的问题,请参考以下文章

FreeRTOSFreeRTOS学习笔记(11)— FreeRTOS的线程管理定时器管理(CMSIS_API)

FreeRTOSFreeRTOS学习笔记— 中断+临界区的保护

FreeRTOSFreeRTOS学习笔记— 列表和列表项(链表和节点)

FreeRTOSFreeRTOS学习笔记— 手写FreeRTOS双向链表/源码分析

FreeRTOSFreeRTOS学习笔记— 学习FreeRTOS的编程风格和本质

FreeRTOSFreeRTOS学习笔记— 任务创建删除挂起和恢复