Linux和,FreeRTOS 任务调度原理,r0-r15寄存器,以及移植freertos(一)

目录、
1、r0-r15寄存器,保护现场,任务切换的原理
2、freertos移植
3、freertos的任务管理。

一、前言

写这篇文章的目的,是之前面试官,刚好问到我,移植FreeRTOS 到mcu,需要做哪些步骤,当时回答的时候,我愣了一下。想了想
基本上说出来了。移植freertos,是五年前学习过,需要修改哪些文件,但是现在一直做的是,linux方向,早就不记得太底层的细节了
但是根据我对arm架构的了解,我还是大概说了步骤。面试过也比较满意。

操作系统是允许多个任务“同时运行”的,操作系统的这个特性被称为多任务。
然而实际上,一个 CPU 核心在某一时刻只能运行一个任务,而操作系统中任务调度器的责任,
就是决定在某一时刻 CPU 究竟要运行哪一个任务,任务调度器使得 CPU 在各个任务之间来回切换并处理任务

首先,我们回想一下,freertos是不是,实时抢占系统,创建的任务,一般有这些状态。多个任务,按时间片轮询,高优先级优先运行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

有了上面这两张图,是不是,比较清晰了,当发生高优先级事件,中断时,就会发生任务切换,那rtos,要和linux系统一样,可以实现进程,任务的管理,是不是要能获得cpu的管理权,能操控底层的寄存器。

玩过arm架构的mcu还是soc,都知道,对进程管理,任务切换,是需要保存现场的(哪个函数被调用函数被执行到哪句代码,执行过程中,计算的局部变量的值),都是需要保存,才能返回的。

# 发生中断,或者线程上下文,进程上下文,我们需要,保存寄存器状态
pushq %rbx        # 将rbx寄存器的值压入堆栈
pushq %r12        # 将r12寄存器的值压入堆栈
pushq %r13        # 将r13寄存器的值压入堆栈
 
# 执行一些操作,(进程切换,或线程切换,或中断处理)...
 

# 返回,恢复寄存器状态
popq %r13         # 从堆栈中弹出值到r13寄存器
popq %r12         # 从堆栈中弹出值到r12寄存器
popq %rbx         # 从堆栈中弹出值到rbx寄存器
 
# 继续执行,前面未完成的操作。

再来回想下这种图,是不是很熟悉,在学linux还是stm32,应该都见过这种图,cpu的寄存器图。
在这里插入图片描述

R15 PC程序计数器(Program Counter),存储下一条要执行的指令的地址。
R14 LR连接寄存器(Link Register ),保存函数返回地址,当通过BL或BLX指令调用函数时,硬件自动将函数返回地址保存在R14寄存器中。当函数完成时,将LR值传到PC,即可返回到被调用位置。
R13 SP 堆栈指针(Process Stack Pointer),保护现场和恢复现场要用,当发生异常的时候,硬件会把当前状态(使用到寄存器数值)保存在堆栈中,SP保存这个堆栈指针,异常处理完成,通过SP出栈,恢复到异常前的状态,有两种堆栈指针MSP、PSP。(Main_Stack_Pointer 和Process_Stack_Pointer)CPSR 程序状态寄存器(current program status register),CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义。
函数形参被放在R0-R3中,超过4个参数值传递则放栈里双堆栈指针双堆栈指针对于任务现场保护、恢复现场至关重要。
在这里插入图片描述

可以看下,汇编是如何实现的
在这里插入图片描述

在这里插入图片描述

看下freertos源码,是如何实现的。进入任务切换中断函数,挑选下一个最高优先级的任务(vTaskSwitchContext())去执行

void xPortPendSVHandler( void )
{
       /* This is a naked function. */
       __asm volatile
       (

       "      .syntax unified                               \n"
       "      mrs r0, psp                                    \n"
       "      ldr    r3, pxCurrentTCBConst                   \n" /* Get the location of the current TCB. */
       "      ldr    r2, [r3]                                \n"
       "      subs r0, r0, #32                              \n" /* Make space for the remaining low registers. */
       "      str r0, [r2]                                  \n" /* Save the new top of stack. */
       "      stmia r0!, {r4-r7}                            \n" /* Store the low registers that are not saved automatically. */
       "      mov r4, r8                                     \n" /* Store the high registers. */
       "      mov r5, r9                                     \n"
       "      mov r6, r10                                   \n"
       "      mov r7, r11                                    \n"
       "      stmia r0!, {r4-r7}                            \n"
       "      push {r3, r14}                                 \n"
       "      cpsid i                                       \n"
       "      bl vTaskSwitchContext                         \n"
       "      cpsie i                                        \n"
       "      pop {r2, r3}                                  \n" /* lr goes in r3. r2 now holds tcb pointer. */
       "      ldr r1, [r2]                                  \n"
       "      ldr r0, [r1]                                  \n" /* The first item in pxCurrentTCB is the task top of stack. */
       "      adds r0, r0, #16                              \n" /* Move to the high registers. */
       "      ldmia r0!, {r4-r7}                            \n" /* Pop the high registers. */
       "      mov r8, r4                                    \n"
       "      mov r9, r5                                    \n"
       "      mov r10, r6                                   \n"
       "      mov r11, r7                                  \n"
       "      msr psp, r0                                   \n" /* Remember the new top of stack for the task. */
       "      subs r0, r0, #32                              \n" 
       "      ldmia r0!, {r4-r7}                            \n" /* Pop low registers.  */
       "      bx r3                                          \n"
       "      .align 4                                       \n"
       "pxCurrentTCBConst: .word pxCurrentTCB   "
       );
}

有了上面这些知识,就很容易了,知道移植free时,需要干些什么事情了。

所以我需要,将这些寄存器,给到freertos系统,让它能够管理mcu,除了这些,还需要设置栈空间大小,时钟等等。

二、移植freertos
在这里插入图片描述
重要的文件如下
在这里插入图片描述

找一个工程文件,开始移植。
在这里插入图片描述
修改完如下
在这里插入图片描述

在这里插入图片描述

移植完这些还不够,还需要如下,
在这里插入图片描述

添加完,需要设置一下kell的环境
在这里插入图片描述

接下来,就可以开始编译了
在这里插入图片描述

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
    #include <stdint.h>
    extern uint32_t SystemCoreClock;
#endif

2、中断文件修改

此时在编译发现还是有错,分别是SysTick 中断、SVC 中断、PendSV 中断报错,这是因为 FreeRTOS里的port.c文件里已经实现同样功能的函数
如下,freertos实现的。
在这里插入图片描述

前面我们讲过,arm架构,有这些异常模式,7种模式,stm32就这这几种,所以,我们找到stm32f4xx_it.c中断文件,先将SVC_Handler、PendSV_Handler屏蔽掉
用freertos的。
在这里插入图片描述
继续往下找,找到SysTick_Handler,SysTick就是FreeRTOS的一个心跳时钟,FreeRTOS 帮我们实现了 SysTick 的启动的配置:

在 port.c文件中已经实现 vPortSetupTimerInterrupt()函数,并且 FreeRTOS 通用的 SysTick 中断服务函数也实现了:
在 port.c 文件中已经实现xPortSysTickHandler()函数,
所以移植的时候只需要我们在 stm32f10x_it.c文件中,实现我们对应(STM32)平台上的 SysTick_Handler()函数即可。按照以下修改,需要添加头文件FreeRTOS.h和task.h:

#include "FreeRTOS.h"
#include "task.h"

extern void xPortSysTickHandler(void);
//systick中断服务函数
void SysTick_Handler(void)
{	
	if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
	{
	  xPortSysTickHandler();
	}
}

最后到FreeRTOSConfig.h,最下面找到 #define xPortSysTickHandler SysTick_Handler将其注释掉,这样就解决了3个重复定义的报错
在这里插入图片描述

所以上面报错,就是重定义而已。

3、钩子函数修改

修改好之后继续编译,发现以下与Hook有关的报错,这是因为FreeRTOS定义了,但是没用到
在这里插入图片描述
我们找到FreeRTOSConfig.h将上面4个定义为0
在这里插入图片描述
此时再编译发现没有错误了。

这是我以前企业级项目,移植后的目录。
在这里插入图片描述

三、任务管理

3.1.1 多任务系统
  
  首先对比不使用RTOS,即裸机编程方式,这种编程方式的框架一般都是在 main()函数中使用一个大循环,在循环中顺序地调用相应的函数以处理相应的事务,这个大循环的部分可以视为应用程序的后台,而应用程序的前台,则是各种中断的中断服务函数。因此单任务系统也叫做前后台系统,前后台系统的运行示意图,如下图所示:
在这里插入图片描述

而加上RTOS之后就变成多任务系统,多任务系统的任务调度器会根据相关的任务调度算法,将 CPU 的使用权分配给任务,在任务获取 CPU 使用权之后的极短时间(宏观角度)后,任务调度器又会将 CPU 的使用权分配给其他任务,如此往复,在宏观的角度看来,就像是多个任务同时运行了一样。
在这里插入图片描述
如上图,发现freertos是可以,支持,中断,高优先级抢占的,linux有多种模式,如果要设置为实时,抢占时,需要设置编译内核。
从上图可以看出,相较于单任务系统而言,多任务系统的任务也是具有优先级的,高优先级的任务可以像中断的抢占一样,抢占低优先级任务的 CPU 使用权;优先级相同的任务则各自轮流运行一段极短的时间(宏观角度),从而产生“同时”运行的错觉。以上就是抢占式调度和时间片调度的基本原理。在任务有了优先级的多任务系统中,用户就可以将紧急的事务放在优先级高的任务中进行处理,那么整个系统的实时性就会大大地提高。

我们来看下freertos系统,设置task的结构体,需要比较关注的是,taskname,栈大小,优先级,以及中断函数。

typedef struct
{
	void (*TaskSetupFunction)(void);
    TaskFunction_t  task_func;
    const char*	    task_name;
    uint16_t		task_statcksize;
    void *		    pvParameters;
    UBaseType_t     uxPriority;
    TaskHandle_t	taskhandle;
    uint8_t         queue_size;
    QueueHandle_t   queue_handle;
}task_info_st;
static task_info_st usrtask[]={
	{MainTaskSetup, 	DoMainTask,         "man", 	1024*4, NULL, 8, NULL, 160, NULL},
	{NULL,				gps_task,           "gps",  1024*3, NULL, 3, NULL, 80, NULL},
	{NULL, 				com_task,          	"com", 	1024,   NULL, 7, NULL, 10, NULL},
	{NbTaskSetup,		nb_task,          	"nbi", 	1024,   NULL, 4, NULL, 50, NULL},
	{WifiTaskSetup,		wifi_task,          "wifi", 1024,   NULL, 6, NULL, 8, NULL},
	{RadioTaskSetup,    RadioTask,          "bt",   1024,   NULL, 5, NULL, 8, NULL},
	{NULL, 				heart_rate_task,    "hrs", 	512 , 	NULL, 2, NULL, 10, NULL},
	{isrTaskSetup,		isr_task,			"hisr_task",	512 ,	NULL, configMAX_PRIORITIES-1, NULL, 0, NULL},
	{ATTaskSetup,		readerLoop, 		"AT_task",	512 ,	NULL, configMAX_PRIORITIES-2, NULL, 0, NULL},
};

xTaskCreate函数原型。

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and */
	{
	TCB_t *pxNewTCB;
	BaseType_t xReturn;

		#if( portSTACK_GROWTH > 0 )     // if stack grows up
		{
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
			if( pxNewTCB != NULL )
			{

				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
				if( pxNewTCB->pxStack == NULL )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */ // Stack grows DOWN on M4F
		{
		StackType_t *pxStack;

			/* Allocate space for the stack used by the task being created. */
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 M */

			if( pxStack != NULL )
			{
				/* Allocate space for the TCB. */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exceptioredundant for some paths. */

				if( pxNewTCB != NULL )
				{
					/* Store the stack location in the TCB. */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created dynamically in case it is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, 
			pxCreatedTask, pxNewTCB, NULL );
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

FreeRTOS 任务状态

FreeRTOS 中任务存在四种任务状态,分别为运行态、就绪态、阻塞态和挂起态。
运行态:如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果
运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。

就绪态:如果一个任务已经能够被执行(不处于阻塞态后挂起态),但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。

阻塞态:如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后,即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。要注意的是,处于阻塞态的任务是无法被运行的。

挂起态:任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态与阻塞态一样,处于挂起态的任务也无法被运行。

在这里插入图片描述

3.1.3 任务调度器

FreeRTOS一共支持三种任务调度方式,分别为
抢占式调度、
时间片调度
协程式调度。
协程式调度
  FreeRTOS 官方对协程式调度的特殊说明,翻译过来就是“协程式调度是用于一些资源非常少的设备上的,但是现在已经很少用到了。虽然协程式调度的相关代码还没有被删除,但是今后也不打算继续开发协程式调度。

抢占式调度
  抢占式调度主要时针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。

时间片调度
  时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。有关系统时钟节拍的相关内容,在下文讲解 FreeRTOS 系统时钟节拍的时候会具体分析。

在这里插入图片描述
config配置项

configUSE_PREEMPTION:此宏用于设置系统的调度方式。当宏 configUSE_PREEMPTION设置为1时,系统使用抢占式调度;当宏configUSE_PREEMPTION设置为0时,系统使用协程式调度。

configUSE_PORT_OPTIMISED_TASK_SELECTION:当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 0 时,使用通用方法。通

用方法是完全使用 C 实现的软件算法,因此支持所用硬件,并且不限制任务优先级的最大值,但效率相较于特殊方法低。当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1 时,使用特殊方法。特殊方法的效率相较于通用方法高,但是特殊方法依赖于一个或多个特定架构的汇编指令,因此特殊方法并不支持所有硬件,并且对任务优先级的最大值一般也有限制,通常为 32。

configUSE_TICKLESS_IDLE:当宏 configUSE_TICKLESS_IDLE 设置为 1 时,使能 tickless 低功耗模式;设置为 0 时,tick 中断则会移植运行。tickless 低功耗模式并不适用于所有硬件。

configCPU_CLOCK_HZ:此宏应设置为 CPU 的内核时钟频率,单位为 Hz。

configSYSTICK_CLOCK_HZ:此宏应设置为 SysTick 的时钟频率,当 SysTick 的时钟源频率与内核时钟频率不同时才可以定义,单位为 Hz。

configTICK_RATE_HZ:此宏用于设置 FreeRTOS 系统节拍的中断频率,单位为Hz

configMAX_PRIORITIES:此宏用于定义系统支持的最大任务优先级 数 量 , 最 大 任 务 优 先 级 数 值 为configMAX_PRIORITIES-1。

configMINIMAL_STACK_SIZE:此宏用于设置空闲任务的栈空间大小,单位为 word

configMAX_TASK_NAME_LEN:此宏用于设置任务名的最大字符数。

configUSE_16_BIT_TICKS:此宏用于定义系统节拍计数器的数据类型,当宏 configUSE_16_BIT_TICKS 设置为 1 时,系统节拍计数器的数据类型为 16 位无符号整形;当宏 configUSE_16_BIT_TICKS 设置为 0 时,系统节拍计数器的数据类型为 32 为无符号整型。

configIDLE_SHOULD_YIELD:当宏 configIDLE_SHOULD_YIELD 设置为 1 时,在抢占调度下,同等优先级的任务可抢占空闲任务,并延用空闲任务剩余的时间片。

configUSE_TASK_NOTIFICATIONS:当宏 configUSE_TASK_NOTIFICATIONS 设置为 1 时,开启任务通知功能。当开启任务通知功能后,每个任务将多占用 8 字节的内存空间。

configTASK_NOTIFICATION_ARRAY_ENTRIES:此宏用于定义任务通知数组的大小。

configUSE_MUTEXES:此宏用于使能互斥信号量,当宏 configUSE_MUTEXS 设置为 1 时,使能互斥信号量;当宏configUSE_MUTEXS 设置为 0 时,则不使能互斥信号量。

configUSE_RECURSIVE_MUTEXES:此宏用于使能递归互斥信号量,当宏 configUSE_RECURSIVE_MUTEXES 设置为 1 时,使能递归互斥信号量;当宏 configUSE_RECURSIVE_MUTEXES 设置为 0 时,则不使能递归互斥信号量。

configUSE_COUNTING_SEMAPHORES:此宏用于使能计数型信号量,当宏 configUSE_COUNTING_SEMAPHORES 设置为 1 时,使能计数型信号量;当宏 configUSE_COUNTING_SEMAPHORES 设置为 0 时,则不使能计数型信号量。

configUSE_ALTERNATIVE_API:此宏在 FreeRTOS V9.0.0 之后已弃用。

configQUEUE_REGISTRY_SIZE:此宏用于定义可以注册的队列和信号量的最大数量。此宏定义仅用于调试使用。

configUSE_QUEUE_SETS:此宏用于使能队列集,当宏 configUSE_QUEUE_SETS 设置为 1 时,使能队列集;当宏configUSE_QUEUE_SETS 设置为 0 时,则不使能队列集。

configUSE_TIME_SLICING:此宏用于使能时间片调度,当宏 configUSE_TIMER_SLICING 设置为 1 且使用抢占式调度时,使能时间片调度;当宏 configUSE_TIMER_SLICING 设置为 0 时,则不使能时间片调度。

configUSE_NEWLIB_REENTRANT:此宏用于为每个任务分配一个 NewLib 重 入 结 构 体 , 当 宏configUSE_NEWLIB_REENTRANT 设置为 1 时,FreeRTOS 将为每个创建的任务的任务控制块中分配一个 NewLib 重入结构体。

configENABLE_BACKWARD_COMPATIBILITY:此宏用于兼容 FreeRTOS 老版本的 API 函数。

configNUM_THREAD_LOCAL_STORAGE_POINTERS:此宏用于在任务控制块中分配一个线程本地存储指着数组,当此宏被定义为大于 0 时,configNUM_THREAD_LOCAL_STORAGE_POINTERS 为线程本地存储指针数组的元素个数;当宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 为 0 时,则禁用线程本地存储指针数组。

configSTACK_DEPTH_TYPE:此宏用于定义任务堆栈深度的数据类型,默认为 uint16_t。

configMESSAGE_BUFFER_LENGTH_TYPE:此宏用于定义消息缓冲区中消息长度的数据类型,默认为 size_t。

configSUPPORT_STATIC_ALLOCATION:当宏 configSUPPORT_STSTIC_ALLOCATION 设置为 1 时,FreeRTOS 支持使用静态方式管理内存,此宏默认设置为 0。如果将configSUPPORT_STATIC_ALLOCATION 设置为 1,用户还 需 要 提 供 两 个 回 调 函 数 : vApplicationGetIdleTaskMemory()vApplicationGetTimerTaskMemory()

configSUPPORT_DYNAMIC_ALLOCATION:当宏 configSUPPORT_DYNAMIC_ALLOCATION 设置为 1 时,FreeRTOS 支持使用动态方式管理内存,此宏默认设置为 1。

configTOTAL_HEAP_SIZE:此宏用于定义用于 FreeRTOS 动态内存管理的内存大小,即 FreeRTOS 的内存堆,单位为Byte。

configAPPLICATION_ALLOCATED_HEAP:此宏用于自定义 FreeRTOS 的内存堆,当宏 configAPPLICATION_ALLOCATED_HEAP 设置为 1 时,用户需要自行创建 FreeRTOS 的内存堆,否则 FreeRTOS 的内存堆将由编译器进行分配。利用此宏定义,可以使用 FreeRTOS 动态管理外扩内存。

configSTACK_ALLOCATION_FROM_SEPARATE_HEAP:此宏用于自定义动态创建和删除任务时,任务栈内存的申请与释放函数pvPortMallocStack()vPortFreeStack(),当宏configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 设置为1是,用户需提供 pvPortMallocStack()vPortFreeStack()函数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、函数xTaskCreate()
  此函数用于使用动态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从FreeRTOS管理的堆中分配,若使用此函数,需要在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                      const char * const pcName, 
                      const configSTACK_DEPTH_TYPE usStackDepth,
                      void * const pvParameters,
                      UBaseType_t uxPriority,
                      TaskHandle_t * const pxCreatedTask)

在这里插入图片描述
在这里插入图片描述

2、函数xTaskCreateStatic()

此函数用于使用静态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,需要由用户分配提供, 若使用此函数,需 要 在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                              const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                              const uint32_t ulStackDepth,
                              void * const pvParameters,
                              UBaseType_t uxPriority,
                              StackType_t * const puxStackBuffer,
                              StaticTask_t * const pxTaskBuffer )

函数xTaskCreateStatic()的形参描述,如下表所示:
在这里插入图片描述

3、函数vTaskDelete()

此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为 1。函数原型如下所示:

void vTaskDelete(TaskHandle_t xTaskToDelete);

4、 挂起和恢复任务相关函数

有时候需要暂停某个任务的运行,如果使用任务删除,那么任务变量的值就丢了。FreeRTOS使用任务挂起和恢复来达到对应效果。FreeRTOS 中用于挂起和恢复任务的 API 函数如下表所示:
在这里插入图片描述
4、函数vTaskSuspend()
  此函数用于 挂 起 任务, 若使用此函数,需 要 在 FreeRTOSConfig.h 文件中将宏INCLUDE_vTaskSuspend配置为 1。无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。此函数并不支持嵌套,不论使用此函数重复挂起任务多少次,只需调用一次恢复任务的函数,那么任务就不再被挂起。函数原型如下所示:

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

形参xTaskToSuspend:待挂起任务的任务句柄

5、函数vTaskResume()
  此函数用于在任务中恢复被挂起的任务,若使用此函数,需要在 FreeRTOSConfig.h 文件中将宏INCLUDE_vTaskSuspend配置为 1。不论一个任务被函数 vTaskSuspend()挂起多少次,只需要使用函数 vTakResume()恢复一次,就可以继续运行。函数原型如下所示:
形参xTaskToResume:待恢复任务的任务句柄

6、函数xTaskResumeFromISR()
  此函数用于在中断中恢复被挂起的任务,若使用此函数,需要在 FreeRTOSConfig.h 文件中将宏INCLUDE_xTaskResumeFromISR配置为 1。不论一个任务被函数 vTaskSuspend()挂起多少次,只需要使用函数 vTakResumeFromISR()恢复一次,就可以继续运行。函数原型如下所示:

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

形参xTaskToResume:待恢复任务的任务句柄

四、内核控制函数
  FreeRTOS 内核的控制函数描述,如下表所示:

在这里插入图片描述在这里插入图片描述

在任务创建好之后就开启任务调度。临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。

4 示例应用程序
  主要对main.c文件修改,加了一些FreeRTOS任务函数

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

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LED0_TASK_PRIO		2
//任务堆栈大小	
#define LED0_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);

//任务优先级
#define LED1_TASK_PRIO		3
//任务堆栈大小	
#define LED1_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);

//任务优先级
#define FLOAT_TASK_PRIO		4
//任务堆栈大小	
#define FLOAT_STK_SIZE 		128
//任务句柄
TaskHandle_t FLOATTask_Handler;
//任务函数
void float_task(void *pvParameters);

int main(void)
{ 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	delay_init(168);		//初始化延时函数
	uart_init(115200);     	//初始化串口
	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);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}
 
//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建LED0任务
    xTaskCreate((TaskFunction_t )led0_task,     	
                (const char*    )"led0_task",   	
                (uint16_t       )LED0_STK_SIZE, 
                (void*          )NULL,				
                (UBaseType_t    )LED0_TASK_PRIO,	
                (TaskHandle_t*  )&LED0Task_Handler);   
    //创建LED1任务
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler);        
    //浮点测试任务
    xTaskCreate((TaskFunction_t )float_task,     
                (const char*    )"float_task",   
                (uint16_t       )FLOAT_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )FLOAT_TASK_PRIO,
                (TaskHandle_t*  )&FLOATTask_Handler);  
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//LED0任务函数 
void led0_task(void *pvParameters)
{
    while(1)
    {
        LED0=~LED0;
        vTaskDelay(500);
    }
}   

//LED1任务函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}

//浮点测试任务
void float_task(void *pvParameters)
{
	static float float_num=0.00;
	while(1)
	{
		float_num+=0.01f;
		printf("float_num的值为: %.4f\r\n",float_num);
        vTaskDelay(1000);
	}
}

参考了一些,别人的文章,我自己能分析写出来,但是懒,不想造轮子,以前研究过源码,对进程管理,有比较深刻的认识。
原文链接:https://blog.csdn.net/weixin_44567668/article/details/135419275

这是我的实战项目,我把它简化了,具体任务,没有,只有任务名

int main(void)
{
    // Set the clock frequency
    // Set the default cache configuration
    // Enable the floating point module, and configure the core for lazy
    // stacking.
    // Flash bank power set.
    // Enable printing to the console.
    // Initialize plotting interface.
    //initalize gpio
    // Run the application.

    run_tasks();

    // We shouldn't ever get here.
    while (1)
    {
    }
}

void run_tasks(void)
{
    // Set some interrupt priorities before we create tasks or start the scheduler.

    // Create essential tasks.
    xTaskCreate(setup_task, "Setup", 512, 0, 3, &xSetupTask);

    // Start the scheduler.
    vTaskStartScheduler();
}


void setup_task(void *pvParameters)
{
	uint16_t i;
    // Run setup functions.
    for(i = 0; i < sizeof(usrtask) / sizeof(usrtask[0]); i++)
    {
		if(usrtask[i].TaskSetupFunction != NULL){
			usrtask[i].TaskSetupFunction(); //在启动任务时,如果需要先初始化,比如硬件,比如gpio等等。
		}

        if(usrtask[i].queue_size != 0)
        {
            usrtask[i].queue_handle = xQueueCreate(usrtask[i].queue_size, sizeof(wolf_evt_t));
        }
	}

    // Create the functional tasks
    for(i = 0; i < sizeof(usrtask)/sizeof(usrtask[0]); i ++)
    {
        if(i == Mod_Bt)
        {
            usrtask[i].taskhandle = radio_task_handle;
            continue;
        }
        xTaskCreate(usrtask[i].task_func, usrtask[i].task_name, 
            usrtask[i].task_statcksize, usrtask[i].pvParameters, usrtask[i].uxPriority, &usrtask[i].taskhandle);
    
            usrtask[i].task_func, usrtask[i].task_name);
    
            usrtask[i].task_statcksize, usrtask[i].pvParameters, usrtask[i].uxPriority,
            usrtask[i].taskhandle);
    }

    // The setup operations are complete, so suspend the setup task now.
    vTaskSuspend(NULL);

    while (1);
}

//启动创建下面这些任务
static task_info_st usrtask[]={
	{MainTaskSetup, 	DoMainTask,         "man", 	1024*4, NULL, 8, NULL, 160, NULL},
	{NULL,				gps_task,           "gps",  1024*3, NULL, 3, NULL, 80, NULL},
	{NULL, 				com_task,          	"com", 	1024,   NULL, 7, NULL, 10, NULL},
	{NbTaskSetup,		nb_task,          	"nbi", 	1024,   NULL, 4, NULL, 50, NULL},
	{WifiTaskSetup,		wifi_task,          "wifi", 1024,   NULL, 6, NULL, 8, NULL},
	{NULL, 				heart_rate_task,    "hrs", 	512 , 	NULL, 2, NULL, 10, NULL},
	{ATTaskSetup,	readerLoop, 		"AT_task",	512 ,	NULL, configMAX_PRIORITIES-2, NULL, 0, NULL},
};


void MainTaskSetup(void)
{
}

static void main_init(void)
{
    wolf_evt_t ev = {0,};
	
    //start app
    ev.evtId = REVT_ADP_APP_START;
    SendEvtToMod(Mod_App, &ev);
}

void GetEvtForMod (wolf_module_e mod, wolf_evt_t* pev)
{
	if(mod >= Mod_Num) return ;
    while(!xQueueReceive(usrtask[mod].queue_handle, pev, portMAX_DELAY));//轮询队列,是否就绪,就绪,就执行
}

void DoMainTask(void *pvParameters)
{
	wolf_evt_t ev = {0,};
    gsnsr_init();
	vibmotor_start_default();
	InitProtocolEventHandler();
	initKey();
	GUI_Init();
	GUI_UC_SetEncodeUCS2();
	setLanguage(1); // 0 ÖÐÎÄ£¬1 Ó¢ÎÄ
	drv_rtc_enable_alm_1sec();

    main_init();

    while(1)
    {
		GetEvtForMod(Mod_App, &ev);
        app_dbgEvt(1, ev.evtId);
        do
        {
            if(app_dispNbMsg(&ev)) break;//类似状态机
            if(app_dispAppMsg(&ev)) break;
            if(app_dispGpsMsg(&ev)) break;
            if(app_dispBtMsg(&ev)) break;
            if(app_dispWifiMsg(&ev)) break;
			if(app_dispMMIMsg(&ev)) break;
            continue;
        }while(0);
        app_dbgEvt(0, ev.evtId);
    }
}

static void app_dbgEvt (uint8_t start, uint32_t evtId)
{
    if (start) {
		wolf_dbgEvt(0x3d3d3d10);
		wolf_dbgEvt(evtId);
	}
	else 
		wolf_dbgEvt(0x3d3d3d11);
}

uint8_t app_dispAppMsg (wolf_evt_t* pev)
{
	uint8_t ret = 1;

	switch (pev->evtId)
	{
		case REVT_ADP_APP_START:
            app_sys_startInd(pev);
            break;
        case REVT_COM_APP_UART_SENDDATA_RSP:
            break;
        case REVT_COM_APP_UART_RECVDATA:
            break;
        case REVT_COM_APP_UART_IND:
            break;
        case REVT_COM_APP_GSENSOR_IND:
            break;
        case REVT_COM_APP_MISC_IND:
            app_sys_miscInd(pev);
            break;
        case REVT_APP_APP_TIMEOUT_IND:
            break;
        case REVT_ADP_APP_GSNSR_IND:
            app_gsnsr_ind(pev);
            break;
		case REVT_ISR_APP_TOUCH_START:
			watchserver_first_socket();
			break;
		case REVT_HRS_APP_HRS_GET_DATA:
			call_Run_Report_CB_HR(pev->p1);
			break;

    default:
      ret = 0;
      break;
    }
    return ret;
}

ok,主线终于领情了,还好几个nb,gps等等的,任务,在别的地方。想要研究透freertos,其实并不难,但是,我的方向,暂时linux方向
,这个系统,要是能研究透,年薪40w,真不难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908789.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现

本篇将带你实现一个滑动选择器应用&#xff0c;用户可以通过滑动条选择不同的数值&#xff0c;并实时查看选定的值和提示。这是一个学习如何使用 Slider 组件、状态管理和动态文本更新的良好实践。 关键词 UI互动应用Slider 组件状态管理动态数值更新用户交互 一、功能说明 在…

云服务器防火墙设置方法

云服务器防火墙设置方法通常包括&#xff1a;第一步&#xff1a;登录控制台&#xff0c;第二步&#xff1a;配置安全组规则&#xff0c;第三步&#xff1a;添加和编辑规则&#xff0c;第四步&#xff1a;启用或停用规则&#xff0c;第五步&#xff1a;保存并应用配置。云服务器…

数据中台一键大解析!

自从互联玩企业掀起了数据中台风&#xff0c;数据中台这个点马上就火起来了&#xff0c;短短几年数据中台就得到了极高的热度&#xff0c;一大堆企业也在跟风做数据中台&#xff0c;都把数据中台作为企业数字化转型的救命稻草&#xff0c;可是如果我告诉你数据中台并不是万能钥…

【第一个qt项目的实现和介绍以及程序分析】【正点原子】嵌入式Qt5 C++开发视频

qt项目的实现和介绍 1.第一个qt项目  &#xff08;1).创建qt工程    [1].创建一个存放qt的目录    [2].新建一个qt工程    [3].编译第一个工程    发生错误时的解决方式 二.QT文件介绍  (1).工程中文件简单介绍  (2).项目文件代码流程介绍    [1].添…

计算机网络:网络层 —— 网络地址转换 NAT

文章目录 网络地址转换 NAT 概述最基本的 NAT 方法NAT 转换表的作用 网络地址与端口号转换 NAPTNAT 和 NAPT 的缺陷 网络地址转换 NAT 概述 尽管因特网采用了无分类编址方法来减缓 IPv4 地址空间耗尽的速度&#xff0c;但由于因特网用户数量的急剧增长&#xff0c;特别是大量小…

【算法】【优选算法】双指针(下)

目录 一、611.有效三⻆形的个数1.1 左右指针解法1.2 暴力解法 二、LCR 179.查找总价格为目标值的两个商品2.1 左右指针解法2.2 暴力解法 三、15.三数之和3.1 左右指针解法3.2 暴力解法 四、18.四数之和4.1 左右指针解法4.2 暴力解法 一、611.有效三⻆形的个数 题目链接&#x…

面试题分享11月1日

1、过滤器和拦截器的区别 过滤器是基于spring的 拦截器是基于Java Web的 2、session 和 cookie 的区别、关系 cookie session 存储位置 保存在浏览器 &#xff08;客户端&#xff09; 保存在服务器 存储数据大小 限制大小&#xff0c;存储数据约为4KB 不限制大小&…

VR 创业之路:从《I Expect You To Die》到未来展望

今年是 Reality Labs 成立 10 周年&#xff0c;Meta 每周都会与不同的 XR 先驱进行交流&#xff0c;探讨他们在行业中的经历、经验教训以及对未来的展望。本次&#xff0c;他们与游戏设计师、作家兼 Schell Games CEO Jesse Schell 进行了深入交谈&#xff0c;了解了他的个人故…

【大数据学习 | kafka】简述kafka的消费者consumer

1. 消费者的结构 能够在kafka中拉取数据进行消费的组件或者程序都叫做消费者。 这里面要涉及到一个动作叫做拉取。 首先我们要知道kafka这个消息队列主要的功能就是起到缓冲的作用&#xff0c;比如flume采集数据然后交给spark或者flink进行计算分析&#xff0c;但是flume采用的…

​Controlnet作者新作IC-light V2:基于FLUX训练,支持处理风格化图像,细节远高于SD1.5。

大家好&#xff01;今天我要向大家介绍一个超级有趣的话题——Controlnet作者的新作IC-light V2&#xff01;这个工具基于FLUX训练&#xff0c;能够支持处理风格化图像&#xff0c;并且细节表现远高于SD1.5。 想象一下&#xff0c;你有一个强大的AI助手&#xff0c;它能够根据…

危机来临前---- 力扣: 876

危机即将来临 – 链表的中间节点 描述&#xff1a; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例&#xff1a; 何解&#xff1f; 1、遍历找到中间节点 &#xff1a; 这个之在回文链表中找…

【AI绘画】ComfyUI - AnimateDiff基础教程和使用心得

AnimateDiff是什么&#xff1f; AnimateDiff 是一个能够将个性化的文本转换为图像的扩展模型&#xff0c;它可以在无需特定调整的情况下实现动画效果。通过这个项目&#xff0c;用户可以将他们的想象力以高质量图像的形式展现出来&#xff0c;同时以合理的成本实现这一目标。随…

【docker】docker 环境配置及安装

本文介绍基于 官方存储库 docker 的环境配置、安装、代理配置、卸载等相关内容。 官方安装文档说明&#xff1a;https://docs.docker.com/engine/install/ubuntu/ 主机环境 宿主机环境 Ubuntu 20.04.6 LTS 安装步骤 添加相关依赖 sudo apt-get update sudo apt-get install…

一二三应用开发平台自定义查询设计与实现系列3——通用化重构

通用化重构 前面我们以一个实体为目标对象&#xff0c;完成了功能开发与调试。 在此基础上&#xff0c;我们对功能进行重构&#xff0c;使其成为平台的标准化、通用化的功能。 前端重构 首先&#xff0c;先把自定义组件挪到了平台公共组件目录下&#xff0c;如下&#xff1…

国标GB28181视频平台EasyCVR私有化视频平台工地防盗视频监控系统方案

一、方案背景 在当代建筑施工领域&#xff0c;安全监管和防盗监控是保障工程顺利进行和资产安全的关键措施。随着科技进步&#xff0c;传统的监控系统已不足以应对现代工地的安全挑战。因此&#xff0c;基于国标GB28181视频平台EasyCVR的工地防盗视频监控系统应运而生&#xf…

征程 6 工具链性能分析与优化 2|模型性能优化建议

01 引言 为了应对低、中、高阶智驾场景&#xff0c;以及当前 AI 模型在工业界的应用趋势&#xff0c;地平线推出了征程 6 系列芯片。 在软硬件架构方面&#xff0c;征程 6 不仅保持了对传统 CNN 网络的高效支持能力&#xff0c;还强化了对 Transformer 类型网络的支持&#xf…

【真题笔记】16年系统架构设计师要点总结

【真题笔记】16年系统架构设计师要点总结 存储部件接口嵌入式处理器产品配置配置管理用户文档系统文档CMM&#xff08;能力成熟度模型&#xff09;螺旋模型敏捷软件开发的方法学软件工具面向对象的分析模型设计模型COP&#xff08;面向构件的编程&#xff09;构件原子构件模块S…

GR2——在大规模视频数据集上预训练且机器人数据上微调,随后预测动作轨迹和视频(含GR1详解)

前言 上个月的24年10.9日&#xff0c;我在朋友圈看到字节发了个机器人大模型GR2&#xff0c;立马去看了下其论文(当然了&#xff0c;本质是个技术报告) 那天之后&#xff0c;我就一直想解读这个GR2来着 然&#xff0c;意外来了&#xff0c;如此文《OmniH2O——通用灵巧且可全…

Autocad2018

链接: https://pan.baidu.com/s/1MTd0gc5Q5zoKFnPNwk1VXw 提取码: x15v

把握数字化新趋势,迎接生态架构新时代——The Open Group 2024生态系统架构·可持续发展年度大会参会指南

距离大会还有&#xff1a;22天 在数字化转型的浪潮中&#xff0c;如何抓住机遇&#xff0c;实现可持续发展&#xff0c;已成为各行各业关注的焦点。The Open Group 2024生态系统架构可持续发展年度大会&#xff0c;将于2024年11月22日在北京国贸大酒店隆重举行。本次大会汇聚全…