目录、
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,真不难。