文章目录
- 0. 综述
- 1. FreeRTOS最新源码下载
- 2. FreeRTOS代码移植
- 3. 任务
- 3.1 任务调度机制
- 3.2 任务状态
- 3.3 任务优先级
- 3.4 任务实现
- 3.5 任务控制块
- 3.6 任务堆栈
- 3.7 任务创建与删除(动态方法)
- 3.8 任务挂起与恢复
- 3.9 与任务相关的API函数
- 3.9.1 函数 `uxTaskPriorityGet()`
- 3.9.2 函数 `vTaskPrioritySet()`
- 3.9.3 函数`uxTaskGetSystemState()`
- 3.9.4 函数 `vTaskGetInfo()`
- 3.9.5 函数 `xTaskGetApplicationTaskTag()`
- 3.9.6 函数 `xTaskGetCurrentTaskHandle()`
- 3.9.7 函数 `xTaskGetHandle()`
- 3.9.8 函数 `xTaskGetIdleTaskHandle()`
- 3.9.9 函数 `uxTaskGetStackHighWaterMark()`
- 3.9.10 函数 `eTaskGetState()`
- 3.9.11 函数 `pcTaskGetName()`
- 3.9.12 函数 `xTaskGetTickCount()`
- 3.9.13 函数 `xTaskGetTickCountFromISR()`
- 3.9.14 函数 `xTaskGetSchedulerState()`
- 3.9.15 函数 `uxTaskGetNumberOfTasks()`
- 3.9.16 函数 `vTaskList()`
- 3.9.17 函数 `vTaskGetRunTimeStats()`
- 3.9.18 函数 `vTaskSetApplicationTaskTag()`
- 3.9.19 函数 `SetThreadLocalStoragePointer()`
- 3.9.20 函数 `GetThreadLocalStoragePointer()`
- 3.10 【实验】任务壮态查询 API 函数的使用
- 3.11【实验】任务运行时间信息统计
- 4. `FreeRTOSConfig.h`文件讲解
- 5. Cortex - M3中断管理
- 6. 列表与列表项
- *. 参考
0. 综述
0.1 单片机的几种运行模式
单片机的三种主要运行模式:
- 轮询系统:即在系统初始化完成后,代码运行在一个死循环中,所有的程序进行轮询工作,按顺序地执行代码,不能完成对外部事件的驱动。
- 前后台系统:在轮询系统上加上中断系统,在轮询执行代码时判断中断是否发生,是则跳转到对应中断服务函数执行相应代码,执行完中断后,返回轮询系统的中断发生处。一般把中断称作前台,轮询系统称作后台。
- 多任务系统:即本文所讲的实时系统,多任务系统的事件响应也是由中断系统完成,但是事件的处理是在任务中完成的,与中断系统一样,任务也具有优先级。事件的响应由一个内核(调度器)管理,各个任务之间可以是互不相干的,统一由调度器来调度运行。
其中前两种称为裸机,裸机系统中的主体是
main
函数中的死循环,在死循环中CPU按顺序执行代码。
在多任务系统中,根据功能的不同,把系统分割成一个个独立且无法返回的函数,这些个函数就是"任务",可叫做"线程"。
- 实时系统:当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统作出快速响应,并控制所有实时任务协调一致运行的嵌入式操作系统。
0.2 任务栈与任务函数
在裸机系统中中断返回的函数地址放在栈中,栈是单片机RAM中一段连续的内存空间,栈的大小由启动文件里的代码配置。
在多任务系统中,每个任务都是独立的,所以每个任务都要分配独立的栈空间,其通常是一个预先定义好的全局数组,这些任务栈也是存于RAM中。
任务函数是一个独立的函数,函数主体无限循环且无返回值。
0.3 任务控制块 —— TCB
任务控制块相当于任务的身份证,里面存有任务的所有信息,如任务的栈、任务名称、任务形参等。它是一个新的数据类型,在os.h
中定义。
struct os_tcb // ucOS-Ⅲ
{
CUP_STK *StkPtr;
CPU_STK_SIZE StkSize;
}
任务栈、任务函数、任务控制块三者联合起来使用就能实现系统的任务调度。而”联系“这一工作由任务创建函数
OSTaskCreate
来实现。该函数在os_task.c
中实现。
- 本文代码适用于STM32F103系列芯片。
RTOS(Real Time Operation System,实时操作系统),又分为硬实时、软实时,前者要求任务必须要在规定时间内完成,不允许任务超时,后者则不是。RTOS常用的有FreeRTOS、UCOS、RTX、RT-Thread、DJYOS等。其核心在于“实时内核”任务处理。RTOS的多任务管理实现了CPU的资源最大化利用,也实现了程序的模块化管理。其可剥夺型内核实现了可随时剥夺其他任务对CPU的使用权,使得CPU总是运行优先级最高的任务。
FreeRTOS是一种嵌入式操作系统,是RTOS的其中一种,Free即免费的意思,而其他系统一般是收费的(如μCOS)。一般把系统分为前端和后端,而在传统无系统的程序中,大循环相当于后端,中断服务函数相当于前端。对比于无系统程序,带操作系统的程序的实时性更好、任务管理更方便。在操作系统中,将要实现的功能分为多个任务,每个任务都是一个简单的程序,一般是一个死循环。
1. FreeRTOS最新源码下载
登录FreeRTOS官网 点击下载FreeRTOS最新源码。
源码文件夹内容如下,其中FreeeRTOS-Plus是FreeeRTOS的一个加强版本,一般使用普通版即可。
2. FreeRTOS代码移植
在1.1节中下载的源码中找到对应芯片的源码,进入Source文件夹,将其内部所有内容复制到代码工程的FreeRTOS文件夹中。在portable文件夹中只需留下 keil、MemMang 和 RVDS 这三个文件夹,其余删除(为了不增加工程文件大小)。对应地,将其他文件导入到对应的工程文件夹中。
- 其中MemMang和RVDS文件夹中的源文件根据实际芯片导入,如STM32F1芯片就导入ARM_CM3中的源文件和
heap_4.c
- 头文件路径就 FreeRTOS\include 和 FreeRTOS\portable\RVDS\ARM_CM3 两个文件夹路径即可
FreeRTOSConfig.h
不属于源代码的一部分,是用户配置的文件,它可以在官方的Demo中找到(比如文件夹 FreeRTOSv9.0.0\FreeRTOS\Demo 中的 CORTEX_STM32F103_Keil 中就可以找到)并添加到工程中,再作实际应用修改- 编译后发现有以下两个Error,其表示有变量/函数被重复定义,分别在
port.o
和stm32f10x_it.o
文件中(.o
文件为对应.c
文件编译后生成的文件,这里不需理会,直接到对应.c
文件中找到对应变量/函数处理即可,找到stm32f10x_it.o
文件中对应的函数屏蔽掉。
Error: L6200E: Symbol SVC_Handler multiply defined (by port.o and stm32f10x_it.o).
Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and stm32f10x_it.o).
- 在头文件
sys.h
中添加宏定义SYSTEM_SUPPORT_OS
//0,不支持os
//1,支持os
#define SYSTEM_SUPPORT_OS 1 //定义系统文件夹是否支持OS
编译后报错有重复定义,同理注释掉 stm32f10x_it.o
文件中的函数即可。
以下是一个例程的文件架构:
3. 任务
任务具有以下几个特性:
- 没有任务数量限制
- 支持任务抢占
- 支持优先级管理
- 每个任务都拥有堆栈,导致RAM使用量增大
3.1 任务调度机制
以前在使用 51、AVR、STM32 单片机裸机(未使用系统的程序)的时候一般都是在 main 函数里面用 while(1)做一个大循环来完成所有的功能。有时候也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环 while(1)作为后台程序。这种程序实时性差,前后台系统各个任务都是排队等着轮流执行,不管这个程序现在有多紧急,没轮到你就只能等着。相当于所有任务(应用程序)的优先级都是一样的。
而RTOS的实时性虽然更高,但多任务处理也带来了任务调度的问题。在 RTOS 系统中的任务调度器就是用于解决该问你。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。
3.2 任务状态
FreeRTOS 中的任务永远处于下面几个状态中的某一个:
- 运行态:当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。
- 就绪态:处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起)、随时可以运行的任务, 但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。
- 阻塞态:如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了延时函数
vTaskDelay()
的话,该任务就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临。 - 挂起态:与阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,相比较于挂起态,进入挂起态的任务是没有超时时间的。任务进入和退出挂起态通过调用函数
vTaskSuspend()
和xTaskResume()
.
3.3 任务优先级
每个任务都可分配到一个从 0~(configMAX_PRIORITIES-1)
的优先级,在FreeRTOS中,优先级数字越大,优先级越高(这一点与UCOS相反)。
// 在FreeRTOSConfig.h文件中有对可使用的最大优先级数量的定义
#define configMAX_PRIORITIES (32) //可使用的最大优先级数量为32
注:
- 优先级0留给空闲任务,创建任务时不要使用
- 软件定时器默认优先级为最高(configMAX_PRIORITIES-1),故创建任务时,也不要使用最高优先级
3.4 任务实现
在FreeRTOS中使用函数 xTaskCreate()
或 xTaskCreateStatic()
来创建任 务,这两个函数的第一个参数 pxTaskCode
,就是这个任务的任务函数。
任务函数就是完成本任务工作的功能函数。FreeRTOS 官方给出的任务函数模板如下:
void vATaskFunction(void *pvParameters) //【1】
{
for( ; ; ) //【2】
{
// 任务要实现的功能代码 【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)
删除此任务。
以下是一个例程的main.c
文件代码,其中的函数start_task()
、led0_task()
、led1_task()
就是实现具体任务的功能函数:
#include "led.h"
#include "delay.h"
#include "sys.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);
/***************************************************************************
** 函数名称 : main
** 功能描述 : 工程入口函数
** 输入变量 : 无
** 返 回 值 :
0:程序执行正常
1:程序执行异常
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 : 描述任务开始函数
***************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
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(); //开启任务调度
}
/***************************************************************************
** 函数名称 : start_task
** 功能描述 : 开始任务的功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 : 描述工程中的所有任务
***************************************************************************/
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);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/***************************************************************************
** 函数名称 : led0_task
** 功能描述 : LED0任务功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 :
***************************************************************************/
void led0_task(void *pvParameters)
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
/***************************************************************************
** 函数名称 : led1_task
** 功能描述 : LED1任务功能函数
** 输入变量 :
** 返 回 值 : 无
** 最后修改人 : xxx
** 最后更新日期: 20210131
** 说 明 :
***************************************************************************/
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
总流程如下图:
3.5 任务控制块
FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结构体来表示描述,这个结构体叫做任务控制块:TCB_t
,在使用函数 xTaskCreate()
创建任务时就会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB
, 新版本重命名为 TCB_t
,此结构体在文件 tasks.c
中有定义如下:
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; //任务堆栈栈顶 /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
Li/ stItem_t xStateListItem; //状态列表项 *< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; //事件列表项 *< Used to reference a task from an event list. */
UBaseType_t uxPriority; // 任务优先级 *< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; // 任务堆栈起始地址 /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
Note Newlib support has been included by popular demand, but is not
used by the FreeRTOS maintainers themselves. FreeRTOS is not
responsible for resulting newlib operation. User must be familiar with
newlib and must provide system-wide implementations of the necessary
stubs. Be warned that (at the time of writing) the current newlib design
implements a system-wide malloc() that must be provided with locks. */
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
/* See the comments above the definition of
tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
在结构体中有很多成员变量,每一个成员变量就是任务的每一个属性。
3.6 任务堆栈
FreeRTOS 之所以能正确的恢复一个被挂起的任务就是因为有任务堆栈,任务调度器在进行任务切换时会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中, 等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方继续运行。
创建任务的时候就需要给任务指定堆栈,如果使用函数 xTaskCreate()
创建任务(动态方法) ,那么任务堆栈就会由函数 xTaskCreate()
自动创建。 如果使用函数 xTaskCreateStatic()
创建任务(静态方法)的话就需要自行定义任务堆栈,然后堆栈首地址作为函数的参数 puxStackBuffer
传递给函数。下面是xTaskCreateStatic()
的参数定义:
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 )
- 堆栈大小:不管是使用函数
xTaskCreate()
还是xTaskCreateStatic()
创建任务都需要指定任务堆栈大小。任务堆栈的数据类型为StackType_t
,它本质上是uint32_t
,在头文件portmacro.h
中有定义。
#define portSTACK_TYPE uint32_t // 4个字节
如果定义某任务的任务堆栈大小为50,即其任务堆栈大小为 50 * 4 * 8 = 1600位
3.7 任务创建与删除(动态方法)
FreeRTOS 的任务创建和删除 API 函数如下图所示:
MPU:内存管理单元
3.7.1 xTaskCreate()
—— 使用动态方法创建函数
xTaskCreate()
:跳到函数本体可看到函数内使用函数pvPortMalloc()
进行动态内存申请。
函数的输入参数如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 要执行的任务函数
const char * const pcName, // 任务名字,一般用于追踪和调试,任务名字长度不能超过 configMAX_TASK_NAME_LEN
const uint16_t usStackDepth, //任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE
void * const pvParameters, // 传递给任务函数的输入参数
UBaseType_t uxPriority, // 任务优先级,范围 0~ configMAX_PRIORITIES-1(数字越大,优先级越高)
TaskHandle_t * const pxCreatedTask ) // 任务句柄,任务创建成功以后会返回此任务的任务句柄(相当于任务创建成功的标志),这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
函数返回值:
pdPASS: 任务创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足
3.7.2 xTaskCreateStatic()
—— 使用静态方法创建函数
其功能与函数xTaskCreate()
相同,也是用来创建任务的,但是使用此函数创建的任务所需的 RAM 需 要 用 用 户 来 提 供 。 如果要使用此函数的话需要将宏configSUPPORT_STATIC_ALLOCATION
定义为 1
函数的输入参数如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth, // 任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer, // 任务堆栈,一般为数组,数组类型要为 StackType_t 类型
StaticTask_t * const pxTaskBuffer ) //任务控制块
返回值:
NULL: 任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生
其他值: 任务创建成功,返回任务的任务句柄
3.7.3 vTaskDelete()
—— 任务删除
被删除的任务再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄。
函数的输入参数如下:
vTaskDelete( TaskHandle_t xTaskToDelete ) // 直接传入任务创建成功后生成的任务句柄,即可删除该任务
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉。
比如某个任务中用户调用函数pvPortMalloc()
分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数vPortFree()
将这 500 字节的内存释放掉,否则会导致内存泄露。
3.7.4 参考代码 —— 动态任务创建与任务删除
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 2 //任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
// while(1)
// {
// RX4_CommandDispose();
// LED_Status();
// LED_Change();
// Button_scan();
// Button_Command_Scan(); // 双启、复位按钮扫描函数
// ReadLEDlightValue();
// foolProofCylinder_Scan();
// }
}
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); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
// LED0=!LED0;
PCA9554_OUT(6,ON);
sprintfU4("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
// vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
sprintfU4("任务1挂起了!\r\n");
vTaskSuspend(Task1Task_Handler);
}
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(6,OFF);
vTaskDelay(1000);
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
// LED1=!LED1;
PCA9554_OUT(8,ON);
sprintfU4("任务2已经执行:%d次\r\n",task2_num);
if(task2_num==10)
{
// vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
sprintfU4("任务1恢复了!\r\n");
vTaskResume(Task1Task_Handler);
}
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,OFF);
vTaskDelay(1000);
}
}
运行结果如下:
任务1已经执行:1次
任务2已经执行:1次
任务1已经执行:2次
任务2已经执行:2次
任务1已经执行:3次
任务2已经执行:3次
任务1已经执行:4次
任务2已经执行:4次
任务1已经执行:5次
任务1挂起了!
任务2已经执行:5次
任务2已经执行:6次
任务2已经执行:7次
任务2已经执行:8次
任务2已经执行:9次
任务2已经执行:10次
任务1恢复了!
任务1已经执行:6次
任务2已经执行:11次
任务1已经执行:7次
任务2已经执行:12次
任务1已经执行:8次
任务2已经执行:13次
任务1已经执行:9次
任务2已经执行:14次
任务1已经执行:10次
任务2已经执行:15次
任务1已经执行:11次
3.7.5 参考代码 —— 静态任务创建与任务删除
- 使用静态任务创建功能,首先需在
FreeRTOSConfig.h
文件中将configSUPPORT_STATIC_ALLOCATION
宏定义为1,开启静态任务创建功能
#define configSUPPORT_STATIC_ALLOCATION 1
- 在静态任务中,内存的分配需要由用户来分配,空闲任务的内存分配由函数
vApplicationGetIdleTaskMemory()
完成,定时器服务任务的内存分配由函数vApplicationGetTimerTaskMemory()
完成
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "FreeRTOS.h"
#include "task.h"
//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
//定时器服务任务堆栈
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
//定时器服务任务控制块
static StaticTask_t TimerTaskTCB;
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
//在静态任务中,内存的分配需要由用户来分配,空闲任务的内存分配由函数`vApplicationGetIdleTaskMemory()`完成,定时器服务任务的内存分配由函数`vApplicationGetTimerTaskMemory()`完成
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器服务任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer:任务控制块内存
//ppxTimerTaskStackBuffer:任务堆栈内存
//pulTimerTaskStackSize:任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
*ppxTimerTaskStackBuffer=TimerTaskStack;
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint32_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )StartTaskStack, //任务堆栈
(StaticTask_t* )&StartTaskTCB); //任务控制块
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint32_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(StackType_t* )Task1TaskStack,
(StaticTask_t* )&Task1TaskTCB);
//创建TASK2任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务,task1和task2运行一次后,删除创建他们的start task
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
LED0 = 1;
printf("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task1Task_Handler);//任务1执行5次删除任务2
printf("任务1删除了任务2!\r\n");
}
vTaskDelay(3000); //延时1s,也就是1000个时钟节拍
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
LED0 = 0;
printf("任务2已经执行:%d次\r\n",task2_num);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
LED0 = 1;
}
}
3.8 任务挂起与恢复
有关 任务挂起与恢复的函数如下表所示:
3.8.1 vTaskSuspend() —— 任务挂起函数
此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume()
或 xTaskResumeFromISR()
.
值得注意的是,任务从挂起到恢复后,任务进入就绪态,然后由任务调度器决定其运行状态,而不是直接进入运行态。
3.8.2 xTaskResumeFromISR() —— 从中断服务函数中恢复一个任务
此函数是 vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。
- 返回值:
pdTRUE
: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。pdFALSE
: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
3.9 与任务相关的API函数
关于FreeRTOS与任务相关的API函数,在FreeRTOS官网中都有详细介绍
3.9.1 函数 uxTaskPriorityGet()
此函数用于获取指定任务的优先级,要使用此函数的条件是将宏 INCLUDE_uxTaskPriorityGet
定义为 1:
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
- 输入参数
xTask
: 要查找的任务的任务句柄 - 返回值: 获取到的对应的任务的优先级
3.9.2 函数 vTaskPrioritySet()
此函数用于改变某一个任务的任务优先级,要使用此函数的条件是将宏INCLUDE_vTaskPrioritySet
定义为 1
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
输入参数:
xTask
: 目标任务的任务句柄uxNewPriority
: 任务要使用的新的优先级,它可以是 0~configMAX_PRIORITIES
– 1
3.9.3 函数uxTaskGetSystemState()
此函数用于获取系统中所有任务的任务壮态,每个任务的壮态信息保存在一个 TaskStatus_t
类型的结构体里,这个结构体里包含了任务的任务句柄、任务名字、堆栈、优先级等信息,要使用此函数的条件是将宏INCLUDE_vTaskPrioritySet
定义为 1:
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime )
输入参数:
pxTaskStatusArray
: 指向TaskStatus_t
结构体类型的数组首地址,每个任务至少需要一个TaskStatus_t
结构体 ,结构体TaskStatus_t
在头文件task.h
中有如下定义:
/* Used with the uxTaskGetSystemState() function to return the state of each task
in the system. */
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; /* 任务句柄 The handle of the task to which the rest of the information in the structure relates. */
const char *pcTaskName; /* 任务名字 A pointer to the task's name. This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
UBaseType_t xTaskNumber; /* 任务编号 A number unique to the task. */
eTaskState eCurrentState; /* 当前任务壮态,eTaskState 是一个枚举类型 The state in which the task existed when the structure was populated. */
UBaseType_t uxCurrentPriority; /* 任务当前的优先级 The priority at which the task was running (may be inherited) when the structure was populated. */
UBaseType_t uxBasePriority; /* 任务基础优先级 The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex. Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
uint32_t ulRunTimeCounter; /* 任务运行的总时间 The total run time allocated to the task so far, as defined by the run time stats clock. See http://www.freertos.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
StackType_t *pxStackBase; /* 堆栈基地址 Points to the lowest address of the task's stack area. */
uint16_t usStackHighWaterMark; /* 从任务创建以来任务堆栈剩余的最小大小,此值如果太小的话说明堆栈有溢出的风险。The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */
} TaskStatus_t;
/* Possible return values for eTaskConfirmSleepModeStatus(). */
uxArraySize
: 保存任务壮态数组的数组的大小pulTotalRunTime
: 用来保存系统总的运行时间,使用此变量的条件是将宏configGENERATE_RUN_TIME_STATS
定义为 1
返回值:统计到的任务壮态的个数,即填写到数组 pxTaskStatusArray
中的个数,此值应该等于函数 uxTaskGetNumberOfTasks()
的返回值。如果参数uxArraySize
太小的话返回值可能为 0
3.9.4 函数 vTaskGetInfo()
此函数用于获取指定的单个任务的壮态,任务的壮态信息填充到参数 pxTaskStatus
中,这个参数也是 TaskStatus_t
类型的。使用此函数的条件是将宏configUSE_TRACE_FACILITY
定义为 1:
void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t * pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )
输入参数:
xTask
: 要查找的任务的任务句柄pxTaskStatus
: 指向类型为TaskStatus_t
的结构体变量xGetFreeStackSpace
: 在结构体TaskStatus_t
中有个字段usStackHighWaterMark
来保存自任务运行以来任务堆栈剩余的历史最小大小,这个值越小说明越接近堆栈溢出,但是计算这个值需要花费一点时间,所以可通过将xGetFreeStackSpace
设置为pdFALSE
来跳过这个步骤,当设置为pdTRUE
的时候就会检查堆栈的历史剩余最小值eState
: 结构体TaskStatus_t
中有个字段eCurrentState
用来保存任务运行壮态,这个字段是eTaskState
类型的,这是个枚举类型,在头文件task.h
中有定义:
/* Task states returned by eTaskGetState. */
typedef enum
{
eRunning = 0, /* 运行壮态 A task is querying the state of itself, so must be running. */
eReady, /* 就绪态 The task being queried is in a read or pending ready list. */
eBlocked, /* 阻塞态 The task being queried is in the Blocked state. */
eSuspended, /* 挂起态 The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
eDeleted, /* 任务被删除 The task being queried has been deleted, but its TCB has not yet been freed. */
eInvalid /* 无效 Used as an 'invalid state' value. */
} eTaskState;
注:获取任务运行壮态会耗费不少时间,所以为了加快函数
vTaskGetInfo()
的执行速度,结构体TaskStatus_t
中的字段eCurrentState
由用户直接赋值,参数eState
就是要赋的值。如果不在乎这点时间,那么可以将eState
设置为eInvalid
,这样任务的壮态信息就由函数vTaskGetInfo()
去想办法获取。
3.9.5 函数 xTaskGetApplicationTaskTag()
此函数用于获取任务的 Tag
(标签)值,它保存在任务控制块中成员变量 pxTaskTag
中。标签的功能由用户自行决定,此函数就是用来获取这个标签值,FreeRTOS 系统内核是不会使用到这个标签的。使用此函数的前提是将宏 configUSE_APPLICATION_TASK_TAG
定义为1:
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask )
{
TCB_t *xTCB;
TaskHookFunction_t xReturn;
/* If xTask is NULL then we are setting our own task hook. */
if( xTask == NULL )
{
xTCB = ( TCB_t * ) pxCurrentTCB;
}
else
{
xTCB = ( TCB_t * ) xTask;
}
/* Save the hook function in the TCB. A critical section is required as
the value can be accessed from an interrupt. */
taskENTER_CRITICAL();
{
xReturn = xTCB->pxTaskTag;
}
taskEXIT_CRITICAL();
return xReturn;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
/*-----------------------------------------------------------*/
- 输入参数
xTask
: 目标标签值的任务对应的任务句柄,如果为 NULL 就获取当前正在运行的任务标签值。 - 返回值: 任务的标签值
3.9.6 函数 xTaskGetCurrentTaskHandle()
用于获取当前任务的任务句柄,其实获取的就是任务控制块,在前面讲解任务创建函数时说过任务句柄就是任务控制。 使用此函数的前提是将宏INCLUDE_xTaskGetCurrentTaskHandle
定义为 1:
#if ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) )
TaskHandle_t xTaskGetCurrentTaskHandle( void )
{
TaskHandle_t xReturn;
/* A critical section is not required as this is not called from
an interrupt and the current TCB will always be the same for any
individual execution thread. */
xReturn = pxCurrentTCB;
return xReturn;
}
#endif /* ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) ) */
/*-----------------------------------------------------------*/
- 返回值: 当前任务的任务句柄
3.9.7 函数 xTaskGetHandle()
根据任务名字获取其任务句柄,在使用函数 xTaskCreate()
或xTaskCreateStatic()
创建任务时都会给任务分配一个任务名,而该函数就是使用这个任务名来查询其对应的任务句柄。使用此函数的前提是将宏 INCLUDE_xTaskGetHandle
定义为 1:
#if ( INCLUDE_xTaskGetHandle == 1 )
TaskHandle_t xTaskGetHandle( const char *pcNameToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
UBaseType_t uxQueue = configMAX_PRIORITIES;
TCB_t* pxTCB;
/* Task names will be truncated to configMAX_TASK_NAME_LEN - 1 bytes. */
configASSERT( strlen( pcNameToQuery ) < configMAX_TASK_NAME_LEN );
vTaskSuspendAll();
{
/* Search the ready lists. */
do
{
uxQueue--;
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) &( pxReadyTasksLists[ uxQueue ] ), pcNameToQuery );
if( pxTCB != NULL )
{
/* Found the handle. */
break;
}
} while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
/* Search the delayed lists. */
if( pxTCB == NULL )
{
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxDelayedTaskList, pcNameToQuery );
}
if( pxTCB == NULL )
{
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxOverflowDelayedTaskList, pcNameToQuery );
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( pxTCB == NULL )
{
/* Search the suspended list. */
pxTCB = prvSearchForNameWithinSingleList( &xSuspendedTaskList, pcNameToQuery );
}
}
#endif
#if( INCLUDE_vTaskDelete == 1 )
{
if( pxTCB == NULL )
{
/* Search the deleted list. */
pxTCB = prvSearchForNameWithinSingleList( &xTasksWaitingTermination, pcNameToQuery );
}
}
#endif
}
( void ) xTaskResumeAll();
return ( TaskHandle_t ) pxTCB;
}
#endif /* INCLUDE_xTaskGetHandle */
/*-----------------------------------------------------------*/
- 输入参数
pcNameToQuery
: 任务名(字符串) - 返回值:
- NULL: 没有对应的任务
- 其他值: 任务名 pcNameToQuery 所对应的任务句柄
3.9.8 函数 xTaskGetIdleTaskHandle()
用于返回空闲任务的任务句柄,使用此函数的前提是将宏INCLUDE_xTaskGetIdleTaskHandle
定义为 1:
#if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
TaskHandle_t xTaskGetIdleTaskHandle( void )
{
/* If xTaskGetIdleTaskHandle() is called before the scheduler has been
started, then xIdleTaskHandle will be NULL. */
configASSERT( ( xIdleTaskHandle != NULL ) );
return xIdleTaskHandle;
}
#endif /* INCLUDE_xTaskGetIdleTaskHandle */
/*----------------------------------------------------------*/
- 返回值: 空闲任务的任务句柄
3.9.9 函数 uxTaskGetStackHighWaterMark()
每个任务都有自己的堆栈,堆栈的总大小在创建任务的时候就确定了,此函数用于检查任务从创建到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大。FreeRTOS 把这个历史剩余最小值叫做“高水位线”。此函数相对来说会多耗费一点时间,所以在代码调试阶段可以使用,产品发布的时候最好不要使用。使用此函数的前提是将宏INCLUDE_uxTaskGetStackHighWaterMark
定义为 1:
#if ( INCLUDE_uxTaskGetStackHighWaterMark == 1 )
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
{
TCB_t *pxTCB;
uint8_t *pucEndOfStack;
UBaseType_t uxReturn;
pxTCB = prvGetTCBFromHandle( xTask );
#if portSTACK_GROWTH < 0
{
pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
}
#else
{
pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
}
#endif
uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );
return uxReturn;
}
#endif /* INCLUDE_uxTaskGetStackHighWaterMark */
/*-----------------------------------------------------------*/
- 输入参数
xTask
: 要查询的任务的任务句柄,当这个参数为 NULL 时,表示查询自身任务(即调用函数uxTaskGetStackHighWaterMark()
的任务)的“高水位线” - 返回值: 任务堆栈的“高水位线”值,即堆栈的历史剩余最小值
3.9.10 函数 eTaskGetState()
用于查询某个任务的运行壮态,如运行态、阻塞态、挂起态、就绪态等,返回值是个枚举类型。使用此函数的前提是将宏 INCLUDE_eTaskGetState定义
为 1:
#if( ( INCLUDE_eTaskGetState == 1 ) || ( configUSE_TRACE_FACILITY == 1 ) )
eTaskState eTaskGetState( TaskHandle_t xTask )
{
eTaskState eReturn;
List_t *pxStateList;
const TCB_t * const pxTCB = ( TCB_t * ) xTask;
configASSERT( pxTCB );
if( pxTCB == pxCurrentTCB )
{
/* The task calling this function is querying its own state. */
eReturn = eRunning;
}
else
{
taskENTER_CRITICAL();
{
pxStateList = ( List_t * ) listLIST_ITEM_CONTAINER( &( pxTCB->xStateListItem ) );
}
taskEXIT_CRITICAL();
if( ( pxStateList == pxDelayedTaskList ) || ( pxStateList == pxOverflowDelayedTaskList ) )
{
/* The task being queried is referenced from one of the Blocked
lists. */
eReturn = eBlocked;
}
#if ( INCLUDE_vTaskSuspend == 1 )
else if( pxStateList == &xSuspendedTaskList )
{
/* The task being queried is referenced from the suspended
list. Is it genuinely suspended or is it block
indefinitely? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL )
{
eReturn = eSuspended;
}
else
{
eReturn = eBlocked;
}
}
#endif
#if ( INCLUDE_vTaskDelete == 1 )
else if( ( pxStateList == &xTasksWaitingTermination ) || ( pxStateList == NULL ) )
{
/* The task being queried is referenced from the deleted
tasks list, or it is not referenced from any lists at
all. */
eReturn = eDeleted;
}
#endif
else /*lint !e525 Negative indentation is intended to make use of pre-processor clearer. */
{
/* If the task is not in any other state, it must be in the
Ready (including pending ready) state. */
eReturn = eReady;
}
}
return eReturn;
} /*lint !e818 xTask cannot be a pointer to const because it is a typedef. */
#endif /* INCLUDE_eTaskGetState */
/*-----------------------------------------------------------*/
- 输入参数
xTask
: 要查询的任务的任务句柄 - 返回值: 返回值为
eTaskState
类型,是个枚举类型,在头文件task.h
中定义
3.9.11 函数 pcTaskGetName()
根据某个任务的任务句柄来查询这个任务对应的任务名:
char *pcTaskGetName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
TCB_t *pxTCB;
/* If null is passed in here then the name of the calling task is being
queried. */
pxTCB = prvGetTCBFromHandle( xTaskToQuery );
configASSERT( pxTCB );
return &( pxTCB->pcTaskName[ 0 ] );
}
/*-----------------------------------------------------------*/
- 输入参数
xTaskToQuery
: 要查询的任务的任务句柄,此参数为 NULL 时表,示查询自身任务(调用函数pcTaskGetName()
)的任务名字 - 返回值: 返回任务所对应的任务名
3.9.12 函数 xTaskGetTickCount()
用于查询任务调度器从启动到现在时间计数器 xTickCount
的值。xTickCount
是系统的时钟节拍值,并不是真实的时间值。每个进入一次滴答定时器中断, xTickCount
就会加 1,一秒钟滴答定时器中断多少次取决于宏 configTICK_RATE_HZ
.
理论上 xTickCount
存在溢出问题,但是这个溢出对于 FreeRTOS 的内核没有影响,但是如果用户的应用程序有使用到的话就要考虑溢出。什么时候溢出取决于宏 configUSE_16_BIT_TICKS
,当此宏为 1 时 xTixkCount
就是个 16 位的变量,当为 0 时就是个 32 位的变量:
TickType_t xTaskGetTickCount( void )
{
TickType_t xTicks;
/* Critical section required if running on a 16 bit processor. */
portTICK_TYPE_ENTER_CRITICAL();
{
xTicks = xTickCount;
}
portTICK_TYPE_EXIT_CRITICAL();
return xTicks;
}
/*-----------------------------------------------------------*/
- 返回值: 时间计数器
xTickCount
的值
3.9.13 函数 xTaskGetTickCountFromISR()
此函数是 xTaskGetTickCount()
的中断级版本,用于在中断服务函数中获取时间计数器xTickCount
的值:
TickType_t xTaskGetTickCountFromISR( void )
{
TickType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
/* RTOS ports that support interrupt nesting have the concept of a maximum
system call (or maximum API call) interrupt priority. Interrupts that are
above the maximum system call priority are kept permanently enabled, even
when the RTOS kernel is in a critical section, but cannot make any calls to
FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h
then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has been
assigned a priority above the configured maximum system call priority.
Only FreeRTOS functions that end in FromISR can be called from interrupts
that have been assigned a priority at or (logically) below the maximum
system call interrupt priority. FreeRTOS maintains a separate interrupt
safe API to ensure interrupt entry is as fast and as simple as possible.
More information (albeit Cortex-M specific) is provided on the following
link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portTICK_TYPE_SET_INTERRUPT_MASK_FROM_ISR();
{
xReturn = xTickCount;
}
portTICK_TYPE_CLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
/*-----------------------------------------------------------*/
- 返回值: 时间计数器
xTickCount
的值
3.9.14 函数 xTaskGetSchedulerState()
用于获取 FreeRTOS 的任务调度器运行情况,即运行、关闭、还是挂起要。使用此函数的前提是将宏 INCLUDE_xTaskGetSchedulerState
必须为 1:
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
BaseType_t xTaskGetSchedulerState( void )
{
BaseType_t xReturn;
if( xSchedulerRunning == pdFALSE )
{
xReturn = taskSCHEDULER_NOT_STARTED;
}
else
{
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
xReturn = taskSCHEDULER_RUNNING;
}
else
{
xReturn = taskSCHEDULER_SUSPENDED;
}
}
return xReturn;
}
#endif /* ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ) */
/*-----------------------------------------------------------*/
- 返回值:
taskSCHEDULER_NOT_STARTED
: 调度器未启动 , 调度器的启动通过函数vTaskStartScheduler()
来完成,所以在函数vTaskStartScheduler()
未调用之前调用函数xTaskGetSchedulerState()
的话就会返回此值taskSCHEDULER_RUNNING
: 调度器正在运行taskSCHEDULER_SUSPENDED
: 调度器挂起
3.9.15 函数 uxTaskGetNumberOfTasks()
数用于查询系统当前存在的任务数量:
UBaseType_t uxTaskGetNumberOfTasks( void )
{
/* A critical section is not required because the variables are of type
BaseType_t. */
return uxCurrentNumberOfTasks;
}
/*-----------------------------------------------------------*/
- 返回值: 当前系统中存在的任务数量, 此值=挂起态的任务+阻塞态的任务+就绪态的任务 +空闲任务+运行态的任务
3.9.16 函数 vTaskList()
创建一个表格来描述每个任务的详细信息:
其中:
- Name: 创建任务的时候给任务分配的名字
- State: 任务的壮态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态
- Priority:任务优先级
- Stack: 任务堆栈的“高水位线”,就是堆栈历史最小剩余大小
- Num: 任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分
#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) )
void vTaskList( char * pcWriteBuffer )
{
TaskStatus_t *pxTaskStatusArray;
volatile UBaseType_t uxArraySize, x;
char cStatus;
/*
* PLEASE NOTE:
*
* This function is provided for convenience only, and is used by many
* of the demo applications. Do not consider it to be part of the
* scheduler.
*
* vTaskList() calls uxTaskGetSystemState(), then formats part of the
* uxTaskGetSystemState() output into a human readable table that
* displays task names, states and stack usage.
*
* vTaskList() has a dependency on the sprintf() C library function that
* might bloat the code size, use a lot of stack, and provide different
* results on different platforms. An alternative, tiny, third party,
* and limited functionality implementation of sprintf() is provided in
* many of the FreeRTOS/Demo sub-directories in a file called
* printf-stdarg.c (note printf-stdarg.c does not provide a full
* snprintf() implementation!).
*
* It is recommended that production systems call uxTaskGetSystemState()
* directly to get access to raw stats data, rather than indirectly
* through a call to vTaskList().
*/
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
function is executing. */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! if
configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
equate to NULL. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) );
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, NULL );
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
switch( pxTaskStatusArray[ x ].eCurrentState )
{
case eReady: cStatus = tskREADY_CHAR;
break;
case eBlocked: cStatus = tskBLOCKED_CHAR;
break;
case eSuspended: cStatus = tskSUSPENDED_CHAR;
break;
case eDeleted: cStatus = tskDELETED_CHAR;
break;
default: /* Should not get here, but it is included
to prevent static checking errors. */
cStatus = 0x00;
break;
}
/* Write the task name to the string, padding with spaces so it
can be printed in tabular form more easily. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
/* Write the rest of the string. */
sprintf( pcWriteBuffer, "\t%c\t%u\t%u\t%u\r\n", cStatus, ( unsigned int ) pxTaskStatusArray[ x ].uxCurrentPriority, ( unsigned int ) pxTaskStatusArray[ x ].usStackHighWaterMark, ( unsigned int ) pxTaskStatusArray[ x ].xTaskNumber );
pcWriteBuffer += strlen( pcWriteBuffer );
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_TRACE_FACILITY == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) ) */
/*----------------------------------------------------------*/
- 输入参数
pcWriteBuffer
: 保存任务壮态信息表的存储区。存储区要足够大来保存任务状态信息表
3.9.17 函数 vTaskGetRunTimeStats()
FreeRTOS 可以通过相关的配置来统计任务的运行时间信息,任务的运行时间信息提供了每个任务获取到 CPU 使用权的总时间。函数 vTaskGetRunTimeStats()
会将统计到的信息填充到一个表里面,这个表里提供了每个任务的运行时间和其所占总时间的百分比:
使用此函数的前提是将宏configGENERATE_RUN_TIME_STATS
和 configUSE_STATS_FORMATTING_FUNCTIONS
定义为 1 . 然后还需要实现以下几个定义:
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
:此宏用来初始化一个外设来提供时间统计功能所需的时基,一般是定时器/计数器。这个时基的分辨率一定要比 FreeRTOS的系统时钟高,一般这个时基的时钟精度比系统时钟的高 10~20 倍即可portGET_RUN_TIME_COUNTER_VALUE()
或portALT_GET_RUN_TIME_COUNTER_VALUE(Time)
:这两个宏实现其中一个就行
,它们都是用于提供当前的时基的时间值
#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) )
void vTaskGetRunTimeStats( char *pcWriteBuffer )
{
TaskStatus_t *pxTaskStatusArray;
volatile UBaseType_t uxArraySize, x;
uint32_t ulTotalTime, ulStatsAsPercentage;
#if( configUSE_TRACE_FACILITY != 1 )
{
#error configUSE_TRACE_FACILITY must also be set to 1 in FreeRTOSConfig.h to use vTaskGetRunTimeStats().
}
#endif
/*
* PLEASE NOTE:
*
* This function is provided for convenience only, and is used by many
* of the demo applications. Do not consider it to be part of the
* scheduler.
*
* vTaskGetRunTimeStats() calls uxTaskGetSystemState(), then formats part
* of the uxTaskGetSystemState() output into a human readable table that
* displays the amount of time each task has spent in the Running state
* in both absolute and percentage terms.
*
* vTaskGetRunTimeStats() has a dependency on the sprintf() C library
* function that might bloat the code size, use a lot of stack, and
* provide different results on different platforms. An alternative,
* tiny, third party, and limited functionality implementation of
* sprintf() is provided in many of the FreeRTOS/Demo sub-directories in
* a file called printf-stdarg.c (note printf-stdarg.c does not provide
* a full snprintf() implementation!).
*
* It is recommended that production systems call uxTaskGetSystemState()
* directly to get access to raw stats data, rather than indirectly
* through a call to vTaskGetRunTimeStats().
*/
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
function is executing. */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! If
configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
equate to NULL. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) );
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* For percentage calculations. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0 )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* What percentage of the total run time has the task used?
This will always be rounded down to the nearest integer.
ulTotalRunTimeDiv100 has already been divided by 100. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* Write the task name to the string, padding with
spaces so it can be printed in tabular form more
easily. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage );
}
#endif
}
else
{
/* If the percentage is zero here then the task has
consumed less than 1% of the total run time. */
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#endif
}
pcWriteBuffer += strlen( pcWriteBuffer );
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) ) */
/*-----------------------------------------------------------*/
更多关于该函数的使用,详见3.11节实验
3.9.18 函数 vTaskSetApplicationTaskTag()
用于设置某个任务的标签值 ,这个标签值的具体函数和用法由用户自行决定,FreeRTOS 内核不会使用这个标签值,使用此函数的前提是将宏configUSE_APPLICATION_TASK_TAG
定义为 1:
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
void vTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_t pxHookFunction )
{
TCB_t *xTCB;
/* If xTask is NULL then it is the task hook of the calling task that is
getting set. */
if( xTask == NULL )
{
xTCB = ( TCB_t * ) pxCurrentTCB;
}
else
{
xTCB = ( TCB_t * ) xTask;
}
/* Save the hook function in the TCB. A critical section is required as
the value can be accessed from an interrupt. */
taskENTER_CRITICAL();
xTCB->pxTaskTag = pxHookFunction;
taskEXIT_CRITICAL();
}
#endif /* configUSE_APPLICATION_TASK_TAG */
/*-----------------------------------------------------------*/
输入参数:
xTask
: 要设置标签值的任务,若此值为 NULL,表示设置自身任务的标签值pxHookFunction
: 要设置的标签值,这是一个TaskHookFunction_t
类型的函数指针,但是也可以设置为其他值
3.9.19 函数 SetThreadLocalStoragePointer()
用于设置线程本地存储指针的值,每个任务都有自己的指针数组来作为线程本地存储,使用这些线程本地存储可用来在任务控制块中存储一些应用信息,这些信息只属于任务自己 。 线程本地存储指针数组的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS
来决定。
使用此函数的前提是将宏configNUM_THREAD_LOCAL_STORAGE_POINTERS
定义为非 0,宏的具体值是本地存储指针数组的大小:
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
void vTaskSetThreadLocalStoragePointer( TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue )
{
TCB_t *pxTCB;
if( xIndex < configNUM_THREAD_LOCAL_STORAGE_POINTERS )
{
pxTCB = prvGetTCBFromHandle( xTaskToSet );
pxTCB->pvThreadLocalStoragePointers[ xIndex ] = pvValue;
}
}
#endif /* configNUM_THREAD_LOCAL_STORAGE_POINTERS */
/*-----------------------------------------------------------*/
输入参数:
xTaskToSet
: 要设置线程本地存储指针的任务的任务句柄,如果是 NULL ,表示设置任务自身的线程本地存储指针xIndex
: 要设置的线程本地存储指针数组的索引pvValue
: 要存储的值
3.9.20 函数 GetThreadLocalStoragePointer()
用于获取线程本地存储指针的值, 使用此函数的前提是将宏configNUM_THREAD_LOCAL_STORAGE_POINTERS
定义为非 0:
void *pvTaskGetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery, BaseType_t xIndex )
- 输入参数:
xTaskToSet
: 要获取的线程本地存储指针的任务句柄,如果是 NULL ,表示获取任务自身的线程本地存储指针xIndex
: 要获取的线程本地存储指针数组的索引
- 返回值: 获取到的线程本地存储指针的值
3.10 【实验】任务壮态查询 API 函数的使用
#include "main.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 128 //任务堆栈大小
TaskHandle_t Led0Task_Handler;//任务句柄
void led0_task(void *pvParameters);//任务函数
#define QUERY_TASK_PRIO 3//任务优先级
#define QUERY_STK_SIZE 256 //任务堆栈大小
TaskHandle_t QueryTask_Handler;//任务句柄
void query_task(void *pvParameters);//任务函数
char InfoBuffer[1000]; //保存信息的数组
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
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);
//创建QUERY任务
xTaskCreate((TaskFunction_t )query_task,
(const char* )"query_task",
(uint16_t )QUERY_STK_SIZE,
(void* )NULL,
(UBaseType_t )QUERY_TASK_PRIO,
(TaskHandle_t* )&QueryTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//led0任务函数
void led0_task(void *pvParameters)
{
while(1)
{
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//query任务函数
void query_task(void *pvParameters)
{
u32 TotalRunTime;
UBaseType_t ArraySize,x;
TaskStatus_t *StatusArray;
char TaskInfo[10];
eTaskState TaskState;
TaskHandle_t TaskHandle;
TaskStatus_t TaskStatus;
//第一步:函数uxTaskGetSystemState()的使用
printf("/********第一步:函数uxTaskGetSystemState()的使用**********/\r\n");
ArraySize=uxTaskGetNumberOfTasks(); //获取系统任务数量
StatusArray=pvPortMalloc(ArraySize*sizeof(TaskStatus_t));//申请内存
if(StatusArray!=NULL) //内存申请成功
{
ArraySize=uxTaskGetSystemState((TaskStatus_t* )StatusArray, //任务信息存储数组
(UBaseType_t )ArraySize, //任务信息存储数组大小
(uint32_t* )&TotalRunTime);//保存系统总的运行时间
printf("任务名\t\t优先级\t\t任务编号\t\t\r\n");
for(x=0;x<ArraySize;x++)
{
//通过串口打印出获取到的系统任务的有关信息,比如任务名称、任务优先级和任务编号。
printf("%s\t\t%d\t\t\t%d\t\t\t\r\n",
StatusArray[x].pcTaskName,
(int)StatusArray[x].uxCurrentPriority,
(int)StatusArray[x].xTaskNumber);
}
}
vPortFree(StatusArray); //释放内存
printf("/**************************结束***************************/\r\n");
printf("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1!=0) delay_ms(10); //等待KEY_UP键按下
//第二步:函数vTaskGetInfo()的使用
printf("/************第二步:函数vTaskGetInfo()的使用**************/\r\n");
TaskHandle=xTaskGetHandle("led0_task"); //根据任务名获取任务句柄。
//获取LED0_Task的任务信息
vTaskGetInfo((TaskHandle_t )TaskHandle, //任务句柄
(TaskStatus_t* )&TaskStatus, //任务信息结构体
(BaseType_t )pdTRUE, //允许统计任务堆栈历史最小剩余大小
(eTaskState )eInvalid); //函数自己获取任务运行壮态
//通过串口打印出指定任务的有关信息。
printf("任务名: %s\r\n",TaskStatus.pcTaskName);
printf("任务编号: %d\r\n",(int)TaskStatus.xTaskNumber);
printf("任务壮态: %d\r\n",TaskStatus.eCurrentState);
printf("任务当前优先级: %d\r\n",(int)TaskStatus.uxCurrentPriority);
printf("任务基优先级: %d\r\n",(int)TaskStatus.uxBasePriority);
printf("任务堆栈基地址: %#x\r\n",(int)TaskStatus.pxStackBase);
printf("任务堆栈历史剩余最小值:%d\r\n",TaskStatus.usStackHighWaterMark);
printf("/**************************结束***************************/\r\n");
printf("按下INPUT2继续!\r\n\r\n\r\n");
while(INPUT2!=0) delay_ms(10); //等待KEY_UP键按下
//第三步:函数eTaskGetState()的使用
printf("/***********第三步:函数eTaskGetState()的使用*************/\r\n");
TaskHandle=xTaskGetHandle("query_task"); //根据任务名获取任务句柄。
TaskState=eTaskGetState(TaskHandle); //获取query_task任务的任务壮态
memset(TaskInfo,0,10);
switch((int)TaskState)
{
case 0:
sprintf(TaskInfo,"Running");
break;
case 1:
sprintf(TaskInfo,"Ready");
break;
case 2:
sprintf(TaskInfo,"Suspend");
break;
case 3:
sprintf(TaskInfo,"Delete");
break;
case 4:
sprintf(TaskInfo,"Invalid");
break;
}
printf("任务壮态值:%d,当前壮态为:%s\r\n",TaskState,TaskInfo);
printf("/**************************结束**************************/\r\n");
printf("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1!=0) delay_ms(10); //等待KEY_UP键按下
//第四步:函数vTaskList()的使用
printf("/*************第三步:函数vTaskList()的使用*************/\r\n");
vTaskList(InfoBuffer); //获取所有任务的信息
printf("%s\r\n",InfoBuffer); //通过串口打印所有任务的信息
printf("/**************************结束**************************/\r\n");
while(1)
{
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
运行结果:
3.11【实验】任务运行时间信息统计
注:函数
vTaskGetRunTimeStats()
相对来说会很耗时间,所以不要太过于频繁的调用此函数,测试阶段可以使用此函数来分析任务的运行情况。还有运行时间不是真正的运行时间,真正的时间值要乘以50us .
main.c
#include "main.h"
#define START_TASK_PRIO 1//任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler;//任务句柄
void start_task(void *pvParameters);//任务函数
#define TASK1_TASK_PRIO 2//任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler;//任务句柄
void task1_task(void *pvParameters);//任务函数
#define TASK2_TASK_PRIO 3//任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler;//任务句柄
void task2_task(void *pvParameters);//任务函数
#define RUNTIMESTATS_TASK_PRIO 4//任务优先级
#define RUNTIMESTATS_STK_SIZE 128 //任务堆栈大小
TaskHandle_t RunTimeStats_Handler;//任务句柄
void RunTimeStats_task(void *pvParameters);//任务函数
char RunTimeInfo[400]; //保存任务运行时间信息
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
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);
//创建RunTimeStats任务
xTaskCreate((TaskFunction_t )RunTimeStats_task,
(const char* )"RunTimeStats_task",
(uint16_t )RUNTIMESTATS_STK_SIZE,
(void* )NULL,
(UBaseType_t )RUNTIMESTATS_TASK_PRIO,
(TaskHandle_t* )&RunTimeStats_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
PCA9554_OUT(8,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(8,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
sprintfU4("Task1 run time:%d\r\n@_@",task1_num);
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
PCA9554_OUT(1,OFF);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
PCA9554_OUT(1,ON);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
sprintfU4("Task2 run time:%d\r\n@_@",task2_num);
}
}
//RunTimeStats任务
void RunTimeStats_task(void *pvParameters)
{
while(1)
{
// sprintfU4("RunTimeStats_task running\r\n");
if(INPUT1 == 0)
{
memset(RunTimeInfo,0,400); //信息缓冲区清零
vTaskGetRunTimeStats(RunTimeInfo); //获取任务运行时间信息
sprintfU4("任务名\t\t运行时间\t运行所占百分比\r\n");
sprintfU4("%s\r\n",RunTimeInfo);
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
timer.c
//FreeRTOS时间统计所用的节拍计数器
volatile unsigned long long FreeRTOSRunTimeTicks;
//初始化TIM3使其为FreeRTOS的时间统计提供时基
void ConfigureTimeForRunTimeStats(void)
{
//定时器3初始化,定时器时钟为72M,分频系数为72-1,所以定时器3的频率
//为72M/72=1M,自动重装载为50-1,那么定时器周期就是50us
FreeRTOSRunTimeTicks=0;
TIM3_Int_Init(50-1,72-1); //初始化TIM3
}
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级4级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
FreeRTOSRunTimeTicks++;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
FreeRTOSConfig.h
/***************************************************************************************************************/
/* FreeRTOS与运行时间和任务状态收集有关的配置选项 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS 1 //为1时启用运行时间统计功能
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()//定时器3提供时间统计的时基,频率为10K,即周期为100us
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks //获取时间统计时间值
#define configUSE_TRACE_FACILITY 1 //为1启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
//prvWriteNameToBuffer(),vTaskList(),
//vTaskGetRunTimeStats()
运行结果:
4. FreeRTOSConfig.h
文件讲解
FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用#define
来实现的。在 FreeRTOS 的官方 demo 中,每个工程都有一个 FreeRTOSConfig.h 文件,在使用FreeRTOS时可参考这个文件,甚至直接复制粘贴使用。
4.1 以INCLUDE_
开始的宏
使用INCLUDE_
开头的宏用来表示使能或失能FreeRTOS 中相应的 API 函数(配置FreeRTOS中可选的API函数)。
比如将宏
INCLUDE_vTaskPrioritySet
设置为 0 时表示失能函数vTaskPrioritySet()
,反之则是使能该函数。
- 如在
FreeRTOSConfig.h
文件中有定义:#define INCLUDE_vTaskPrioritySet 1
- 在
task.c
文件中就有对应的预定义语句:#if ( INCLUDE_vTaskPrioritySet == 1 ) // 若INCLUDE_vTaskPrioritySet 为 1 则编译下列语句,否则跳过 ... #endif /* INCLUDE_vTaskPrioritySet */ ```
4.2 以config
开始的宏
config
开始的宏和INCLUDE_
开始的宏一样,都是用来完成 FreeRTOS 的配置和裁剪。
以下列出其中部分一些宏定义:
/***************************************************************************************************************/
/* FreeRTOS基础配置配置选项 */
/***************************************************************************************************************/
#define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程(协程式内核官方已停止更新)
#define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的)
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 //1启用特殊方法来选择下一个要运行的任务
//一般是硬件计算前导零指令,如果所使用的
//MCU没有这些硬件指令的话此宏应该设置为0!
#define configUSE_TICKLESS_IDLE 0 //1启用低功耗tickless模式(低功耗管理模式)
#define configUSE_QUEUE_SETS 1 //为1时启用队列集
#define configCPU_CLOCK_HZ (SystemCoreClock) //CPU频率
#define configTICK_RATE_HZ (1000) //时钟节拍频率,这里设置为1000,周期就是1ms
#define configMAX_PRIORITIES (32) //可使用的最大优先级
#define configMINIMAL_STACK_SIZE ((unsigned short)130) //空闲任务使用的堆栈大小
#define configMAX_TASK_NAME_LEN (16) //任务名字字符串长度
#define configUSE_16_BIT_TICKS 0 //系统节拍计数器变量数据类型,
//1表示为16位无符号整形,0表示为32位无符号整形
#define configIDLE_SHOULD_YIELD 1 //为1时空闲任务放弃CPU使用权给其他同优先级的用户任务(空闲任务就是当CPU不在执行其他任务时,去执行的任务,FreeRTOS会自动创建一个空闲任务,即FreeRTOS至少会存在一个任务)
#define configUSE_TASK_NOTIFICATIONS 1 //为1时开启任务通知功能,默认开启
#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时使用计数信号量
4.3 动态内存申请
当FreeRTOSConfig.h
文件中configSUPPORT_DYNAMIC_ALLOCATION
被定义为1时,则支持动态内存申请。
/***************************************************************************************************************/
/* FreeRTOS与内存申请有关配置选项 */
/***************************************************************************************************************/
#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请
#define configTOTAL_HEAP_SIZE ((size_t)(20*1024)) //系统所有总的堆大小(根据实际单片机内存而定)
- 在
heap_4.c
文件中,有关于动态内存申请和释放的函数定义,其文件名分别为void *pvPortMalloc( size_t xWantedSize )
和void vPortFree( void *pv )
,其作用相当于标准C库中的Malloc
函数。
- 同理,在 在
heap_4.c
文件中有关于内存池的定义static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
,其大小由FreeRTOSConfig.h
文件中的configTOTAL_HEAP_SIZ
定义。
4.4 软件定时器
当FreeRTOSConfig.h
文件中configUSE_TIMERS
被定义为1时,则启用软件定时器。
/***************************************************************************************************************/
/* FreeRTOS与软件定时器有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_TIMERS 1 //为1时启用软件定时器
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器优先级(软件定时器通过任务来管理,则需要配置任务优先级,一般配置为最高的任务优先级)
#define configTIMER_QUEUE_LENGTH 5 //软件定时器队列长度
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) //软件定时器任务堆栈大小
5. Cortex - M3中断管理
📕参考资料:《Cortex - M3 权威指南》—— 异常、NVIC与中断控制 两节
在STM32芯片中,中断由硬件产生,当中断产生后 ,CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC).
Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。
NVIC:嵌套向量中断控制器
5.1 异常响应系统
Cortex‐M3 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中,编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断(一共240个)。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的(但不能编程为负数)。另外,优先级数值越小,优先级越高。
注:所有能打断正常执行流的事件都称为异常
可见1、2、3号的优先级为负数,其优先级是写定的,不可修改。而表7.2 中可见,系统外部中断一共有240个。
在FreeRTOS中重点关注第14、15号的异常。
5.1.1 优先级分组的定义
由上一节可知,优先级的数值越小,优先级越高。CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。但其中有 3 个系统异常(见表7.2):复位,NMI 以及硬 fault,它们具有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。
Cortex-M 处理器有三个固定优先级和 256 个(见下图,中断优先级寄存器被定义为8位长,2^8=256)可编程的优先级,最多有 128 个抢占等级(优先级分组:为了使抢占机能变得更加可控,CM3把256个可编程优先级分成高低两段,分别是抢占优先级、亚优先级),但实际的优先级数量是由芯片厂商来决定的,为了精简设计,芯片厂商在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,但不管用多少位来表达优先级,都是 MSB 对齐的,如图 4.1.3.1 就是使用三位来表达优先级。不管用多少位来表达优先级,都是 MSB(最高有效位) 对齐的,如下图就是使用前三位来表达优先级。
关于中断优先级寄存器 IP的定义,查看1.5.1.2节
如上图,Bit0~Bit4 没有实现,所以读值总是为零,写入操作也无效。因此,对于高 3 位的情况,可是使用的优先级就是 8个(2^3=8):0X00(最高优先级)、0X20(0010 0000)、0X40(0100 0000)、0X60(0110 0000)、0X80(1000 0000)、0XA0(1010 0000)、0XC0(1100 0000) 和 0XE0(1110 0000) .❗注意:多少个有效位是芯片厂商来决定的!比如 STM32M3 就选择了 高4 位作为优先级!那么它可配置的优先级数量就是16个(2^4=16)
如下图为CM3中表达抢占优先级和亚优先级的关系,默认设置为分组0,即第0位表示亚优先级,第1~7位表示抢占优先级。
其中,这些分组可以表达的优先级分组最多为128个,即默认分组0下,1~7位表示抢占优先级,其能表达的数量为128个抢占优先级(2^7=128)
❗❗❗ 但是,STM32M3 处理器只选用了高4位作为优先级,其可配置的可编程优先级数量只有16个,那么对于下表,分组0到分组2是无效的!其他芯片类同。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4,将设置写入到AIRCR(应用程序中断及复位控制寄存器)中
// 在misc.h中,有关于中断优先级分组的定义
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority 抢占优先级 对应上图中的分组7
4 bits for subpriority 子优先级*/
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority 抢占优先级 对应上图中的分组3
0 bits for subpriority */
5.1.2 关于中断优先级寄存器 IP
在头文件startup_stm32f10x_hd.s
文件中,有对中断优先级寄存器IP(Interrupt Priority Register (8Bit wide))的定义。
__Vectors DCD __initial_sp ; Top of Stack // IP[0]
DCD Reset_Handler ; Reset Handler // IP[1] 同理以下以此类推
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
// 上面16个是系统默认寄存器,固定不可编程
// 下面是外部中断寄存器,可编程
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
5.1.3 中断屏蔽寄存器组 PRIMASK、FAULTMASK、BASEPRI
- PRIMASK:用于除能在 NMI 和硬 fault 之外的所有异常。
- FAULTMASK:用于除能在 NMI之外的所有异常。
- BASEPRI(FreeRTOS中最常用):已知在FreeRTOS中,优先级号越小,优先级越高。BASEPRI 寄存器可实现只掩蔽优先级低于某一阈值的中断,被屏蔽的中断其优先级在数字上大于等于某个数,这个数就存储在BASEPRI 寄存器中。也即是说,如果BASEPRI中写0,将取消屏蔽任何中断。下面只讲解BASEPRI寄存器。
- FreeRTOS中对中断开关操作使用的函数是:
portDISABLE_INTERRUPTS()
和portENABLE_INTERRUPTS()
,它们定义在头文件portmacro.h
中,
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
//其中
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; // configMAX_SYSCALL_INTERRUPT_PRIORITY参数定义在FreeRTOSConfig.h中
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI // 往BASEPRI寄存器中写值
dsb
isb
}
}
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
在头文件FreeRTOSConfig.h
中,有如下以config定义的与中断有关的配置选项,如下面所设置,那么中断优先级0~4是系统不可管理的,用户不可以使用FreeRTOS中的关于中断优先级0 ~4的API函数,而中断优先级5 ~15是系统可管理的。
/***************************************************************************************************************/
/* FreeRTOS与中断有关的配置选项 */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS // stm32 使用4位优先级,即 __NVIC_PRIO_BITS = 4
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 // 中断最低优先级,stm32 是0-15共16个中断优先级,所以这里写15,此数值根据芯片而定
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级,这里设置为5,即高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) // 设置 PendSV 和滴答定时器的中断优先级
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) // 低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不可管理的,中断服务函数也不能调用 FreeRTOS 的 API 函数
以 STM32 为例,有 16 (2^4=16)个优先级,0 为最高优先级,15 为最低优先级,配置如下:
configMAX_SYSCALL_INTERRUPT_PRIORITY==5
configKERNEL_INTERRUPT_PRIORITY==15
那么实现的中断配置如下:
5.2 NVIC —— 嵌套向量中断控制器
在core_cm3.h
头文件中,可以找到 NVIC 的定义如下:
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
#define NVIC ((NVIC_Type *) NVIC_BASE) /*!< NVIC configuration struct */
可知NVIC_BASE
的基础地址为 0xE000E000 + 0x0100 = 0xE000 E100
,
其中结构体NVIC_Type
定义为:
/** @addtogroup CMSIS_CM3_NVIC CMSIS CM3 NVIC
memory mapped structure for Nested Vectored Interrupt Controller (NVIC)
@{
*/
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register,中断使能寄存器,其开始地址为 0xE000 E100 */
uint32_t RESERVED0[24]; /* 保留的24个32位数据 */
/* 上面一共8+24=32个数组元素,每个数组元素占4个字节,则32个数组元素一共占32*4=128个字节,128的十六进制就是 0x80 */
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register,中断除能寄存器,其开始地址为0xE000 E100 + 0x80 = 0xE000 E180 */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register,中断悬起寄存器 */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register 中断解悬寄存器 */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register 活动状态寄存器 */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
/*@}*/ /* end of group CMSIS_CM3_NVIC */
5.2.1 中断的使能与除能
由1.5.1节中的表7.2外部中断清单可知,一共有240个外部中断,每个中断拥有一对使能位/除能位,这 240 对使能位/除能位分布在 8 对 32 位寄存器中(32*8=256,足够存储240对数据位)。
在中断使能寄存器__IO uint32_t ISER[8];
中,该数组大小定义为8,由于数据类型被定义为uint32_t
,即每个数组元素为32位,8个数组元素就是256位(32*8=256,足够存放240个数据位)。
想要使能一个中断,就写 1 到对应 SETENA
的位中;欲除能一个中断,就写 1 到对应的 CLRENA
位中;若往它们中写 0,不会有任何效果。通过这种方式,使能/除能中断时只需把“当事位”写成1,其它的位可以全部为0。
5.2.2 中断的悬起与解悬
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND
)”和“中断悬起清除寄存器(CLRPEND
)”来读取,还可以手动写它们来悬起中断。
悬起寄存器和“解悬”寄存器也可以有 8 对,其用法和用量都与前面介绍的使能/除能寄存器完全相同
5.2.3 中断优先级
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位。那么4 个相临的优先级寄存器就拼成一个 32 位寄存器。根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。
系统异常优先级寄存器如下表所示,重点常用的为PRI_14
和PRI_15
.
注意:由于0xE000 ED20
到0xE000 ED23
4个字节拼成一个32位寄存器,故在操作PRI_14
和PRI_15
时,其操作地址从0xE000 ED20
开始。
5.2.4 活动状态
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。活动状态寄存器的定义,与前面讲的使能/除能和悬起/解悬寄存器相同,只是不再成对出现。它们也能按字/半字/字节访问,但他们是只读的,如下表所示:
5.3 【实验】中断测试实验
在 FreeRTOS 中优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断会被屏蔽掉,高于的就不会,那么下面就写个简单的例程测试一下,使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,再查看两个定时器的输出情况。
- main文件
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 2 //任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 128 //任务堆栈大小
TaskHandle_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
TIM3_Int_Init(10000-1,7200-1); //初始化定时器3,定时器周期1S
TIM5_Int_Init(10000-1,7200-1); //初始化定时器5,定时器周期1S
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
xTaskCreate((TaskFunction_t )interrupt_task, //任务函数
(const char* )"interrupt_task", //任务名称
(uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num+=1;
if(total_num==5)
{
sprintfU4(".............关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5s
sprintfU4(".............打开中断.............\r\n"); //打开中断
portENABLE_INTERRUPTS();
total_num = 0;
}
PCA9554_OUT(7,ON);
vTaskDelay(500);
PCA9554_OUT(7,OFF);
vTaskDelay(500); // 延时共1s后循环
}
}
定时器文件
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义参数结构体
NVIC_InitTypeDef NVIC_InitStructure; // 定义参数结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //定时器3时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级4级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//通用定时器5中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器5!
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义参数结构体
NVIC_InitTypeDef NVIC_InitStructure; // 定义参数结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
//定时器TIM5初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5; //先占优先级5级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM5, ENABLE); //使能TIM5
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
sprintfU4("TIM3输出.......\r\n");
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
{
sprintfU4(".......TIM5输出\r\n");
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
串口打印结果:
6. 列表与列表项
列表是 FreeRTOS 中的一个数据结构,列表用来跟踪 FreeRTOS中的任务。与列表相关的全部东西都在文件 list.c
和 list.h
中,其原理类似于数据结构中的链表。
6.1 列表结构体
在 list.h
中定义了一个叫 List_t 的结构体如下:
/*
* Definition of the type of queue used by the scheduler.
*/
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;
其中:
listFIRST_LIST_INTEGRITY_CHECK_VALUE
、listSECOND_LIST_INTEGRITY_CHECK_VALUE
:用于检查列表的完整性,其使用的前提是在FreeRTOSConfig.h
文件中,对变量configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES
进行置1,但是默认不开启,基本不使用uxNumberOfItems
:用于记录列表中的列表项数量pxIndex
:用于记录当前列表中各列表项的索引号,以用于遍历列表,它所指向的列表项就是要遍历的开始列表项,也就是说它所指向的列表项就代该表列的表头xListEnd
:表示列表中最后一个列表项,用来表示列表结束
6.2 列表项
列表项就是存放在列表中的项,其在文件 list.h
中有定义:
/*
* Definition of the only type of object that a list can contain.
*/
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */
void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */
void * configLIST_VOLATILE pvContainer; /*< Pointer to the list in which this list item is placed (if any). */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
其中:
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
、listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
:用于检查列表的完整性,其使用的前提是在FreeRTOSConfig.h
文件中,对变量configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES
进行置1,但是默认不开启,基本不使用xItemValue
: 列表项的值pxNext
:指向下一个列表项pxPrevious
:指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能pvOwner
:记录此列表项归谁拥有,通常是任务控制块pvContainer
:用于记录此列表项属于哪个列表
关于
pvOwner
与pvContainer
的区别,举个通俗的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的
pvOwner
属性值就是老王,小王的pvContainer
属性值就是二年级。
6.3 迷你列表项
FreeRTOS中的另一种列表项类型,其在文件 list.h
中有定义,其功能与列表项一样,相当于列表项的简化版,目的是防止列表项占用太多内存:
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
6.4 列表项相关API函数
6.4.1 列表初始化
列表的初始化其实就是初始化列表结构体List_t
中的各个成员变量,列表的初始化通过使函数 vListInitialise()
来完成,此函数定义在 list.c
中:
/*-----------------------------------------------------------
* PUBLIC LIST API documented in list.h
*----------------------------------------------------------*/
void vListInitialise( List_t * const pxList )
{
/* The list structure contains a list item which is used to mark the
end of the list. To initialise the list the list end is inserted
as the only list entry. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
/* The list end value is the highest possible value in the list to
ensure it remains at the end of the list. */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* The list end next and previous pointers point to itself so we know
when the list is empty. */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* Write known values into the list if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
/*-----------------------------------------------------------*/
其中:
pxIndex
:列表项的索引,因为在列表初始化时,列表中只有一个列表项xListEnd
,所以在列表初始化时,pxIndex
指向xListEnd
注:
xListEnd
的数据类型是迷你列表项,同理,由于xListEnd
是一个迷你列表项,那么它本身也需要进行初始化,初始化方法一样采样对其结构体进行赋值的方式:
xListEnd.xItemValue
:xListEnd
的列表项值初始化为portMAX_DELAY
,portMAX_DELAY
是个宏,在头文件portmacro.h
中有定义。根据不同的 MCU ,portMAX_DELAY
值不相同,可以为 0xffff(16位MCU)或者 0xffffffffUL(32位MCU),这里使用的STM32F1的是32位MCUxListEnd.pxNext
:指向下一个列表项,因为列表中只有一个列表项xListEnd
,因此pxNext
只能指向自身xListEnd.pxNext
:与xListEnd.pxNext
同理uxNumberOfItems
:当前列表中有几个列表项,初始化是0个
注:迷你列表项
xListEnd
不计入列表中的列表项
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
、listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
:用于检查初始化列表项的完整性, 它们只有宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES
为 1 时有效。同样的根据所选的MCU 不同其写入的值也不同,可以为 0x5a5a(16位MCU) 或者 0x5a5a5a5aUL(32位MCU),这里使用的STM32F1的是32位MCU
6.4.2 列表项初始化
与列表一样,列表项在使用前也需要初始化,列表项初始化由函数 vListInitialiseItem()
来完成:
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* Make sure the list item is not recorded as being on a list. */
pxItem->pvContainer = NULL;
/* Write known values into the list item if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
其中:
pvContainer
:表示该列表项属于那个列表,由于列表项初始化时未属于任何一个列表,故配置为NULL
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
、listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
:同理是用于检查完整性的
6.4.3 列表项插入
列表项的插入操作通过函数 vListInsert()
来完成:
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; // 获取要插入的列表项值,即获取列表项成员变量 xItemValue 的值,因为要根据这个值来确定列表项要插入的位置
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); //用于检查列表和列表项的完整性
/* Insert the new list item into the list, sorted in xItemValue order.
If the list already contains a list item with the same item value then the
new list item should be placed after it. This ensures that TCB's which are
stored in ready lists (all of which have the same xItemValue value) get a
share of the CPU. However, if the xItemValue is the same as the back marker
the iteration loop below will not end. Therefore the value is checked
first, and the algorithm slightly modified if necessary. */
/* 如果要插入的列表项的值等于 portMAX_DELAY,也就是说列表项值为最大值,直接将列表项插入到列表的末尾 */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* *** NOTE ***********************************************************
If you find your application is crashing here then likely causes are
listed below. In addition see http://www.freertos.org/FAQHelp.html for
more tips, and ensure configASSERT() is defined!
http://www.freertos.org/a00110.html#configASSERT
1) Stack overflow -
see http://www.freertos.org/Stacks-and-stack-overflow-checking.html
2) Incorrect interrupt priority assignment, especially on Cortex-M
parts where numerically high priority values denote low actual
interrupt priorities, which can seem counter intuitive. See
http://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
of configMAX_SYSCALL_INTERRUPT_PRIORITY on
http://www.freertos.org/a00110.html
3) Calling an API function from within a critical section or when
the scheduler is suspended, or calling an API function that does
not end in "FromISR" from an interrupt.
4) Using a queue or semaphore before it has been initialised or
before the scheduler has been started (are interrupts firing
before vTaskStartScheduler() has been called?).
**********************************************************************/
/*否则,就需要在列表中找到自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由
于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。这个查找过程
是按照升序的方式查找列表项插入点的。*/
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
{
/* There is nothing to do here, just iterating to the wanted
insertion position. */
}
}
/*下面四行代码就是将列表项插入到列表中,插入过程和数据结构中双向链表的插入类似。 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* Remember which list the item is in. This allows fast removal of the
item later. */
pxNewListItem->pvContainer = ( void * ) pxList; // 到此步,列表项已插入到列表中,列表项的成员变量 pvContainer 用于记录此列表项属于哪个列表
( pxList->uxNumberOfItems )++; // 列表的成员变量 uxNumberOfItems 加一,表示又添加了一个列表项
}
其中输入参数:
pxList
: 列表项要插入的列表pxNewListItem
: 要插入的列表项,列表项的插入根据xItemValue
的值按照升序的方式排列,而xItemValue
的值根据pxNewListItem
而得
6.4.3.1 列表项插入图解
往一个空列表中插入一个值为40的列表项:(这里的值表示xItemValue
,下同)
- 列表中的
uxNumberOfItems
由NULL变为1,表示空列表中增加了一个列表项 - 列表项中的
pvContainer
变成了 List,表示该列表项归属于列表List
继续往列表中插入一个值为60的列表项,他插在列表项ListItem1之后:
- 列表中的
uxNumberOfItems
由1变为2,表示列表中列表项的数量为2 - 同理ListItem2列表项中的
pvContainer
变成了 List,表示该列表项归属于列表List - 它位于ListItem1之后,xListEnd之前
继续往列表中插入一个值为50的列表项,他插在列表项ListItem1之后、ListItem2之前:
- 列表中的
uxNumberOfItems
由2变为3,表示列表中列表项的数量为3 - 同理ListItem3列表项中的
pvContainer
变成了 List,表示该列表项归属于列表List
6.4.3.2 关于环形列表
以图7.3.2.3 为例,由于pxIndex指向迷你列表项xListEnd,故列表的表头为迷你列表项xListEnd,而迷你列表项xListEnd的pxNext指向ListItem1,表示它的下一个列表项是ListItem1,如此类推,最后表尾ListItem3的pxNext指回迷你列表项xListEnd,形成一个环形列表
6.4.4 列表项末尾插入
列表末尾插入列表项的操作通过函数 vListInsertEnd()
来完成:
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList ); //用于检查列表和列表项的完整性
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* Insert a new list item into pxList, but rather than sort the list,
makes the new list item the last item to be removed by a call to
listGET_OWNER_OF_NEXT_ENTRY(). */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* Remember which list the item is in. */
pxNewListItem->pvContainer = ( void * ) pxList;
( pxList->uxNumberOfItems )++;
}
其中输入参数:
pxList
: 列表项要插入的列表pxNewListItem
: 要插入的列表项
6.4.4.1 列表项末尾插入图解
先准备一个默认的列表,注意与 6.4.3.1节中不同的是,列表的pxIndex
指向的不是迷你列表项xListEnd,而是ListItem1,表示列表List的表头列表项为ListItem1,末尾列表项就是迷你列表项xListEnd
既然已知末尾列表项是迷你列表项xListEnd,那么使用列表项末尾插入函数vListInsertEnd()
去插入列表项,当然是在迷你列表项xListEnd之后插入新的列表项
6.4.5 列表项的删除
列表项的删除通过函数 uxListRemove()
来完成:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in. Obtain the list from the list
item. */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pvContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}
其中
- 输入函数:
pxItemToRemove
: 要删除的列表项 - 返回值:返回删除列表项以后的列表剩余列表项数目
注:列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉
6.4.6 列表的遍历
在6.1节中说过列表结构体 List_t 中的成员变量 pxIndex
是用来遍历列表的,FreeRTOS中用于完成列表的遍历函数是listGET_OWNER_OF_NEXT_ENTRY()
,此函数的用途是从多个同优先级的就绪任务中查找下一个要运行的任务, 每调用一次这个函数,列表的 pxIndex
变量就会指向下一个列表项,并且返回这个列表项的 pxOwner
值。这个函数是一个宏,这个宏在头文件 list.h 中定义:
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* Increment the index to the next item and return the item, ensuring */ \
/* we don't return the marker used at the end of the list. */ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; // 列表的 pxIndex 变量指向下一个列表项
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) //如果 pxIndex 指向了列表的 xListEnd 成员变量,表示到了列表末尾
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; //如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表项,这样就完成了一次对列表的遍历
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; //将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB
}
其中输入参数:
pxTCB
: 用于保存pxIndex
所指向的列表项的pvOwner
值,也就是这个列表项属于谁的,它通常是一个任务的任务控制块pxList
: 表示要遍历的列表
6.5 【实验】列表插入与删除
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define LIST_TASK_PRIO 3//任务优先级
#define LIST_STK_SIZE 128 //任务堆栈大小
TaskHandle_t ListTask_Handler;//任务句柄
void list_task(void *pvParameters);//任务函数
//定义一个测试用的列表和3个列表项
List_t TestList; //测试用列表
ListItem_t ListItem1; //测试用列表项1
ListItem_t ListItem2; //测试用列表项2
ListItem_t ListItem3; //测试用列表项3
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
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);
//创建LIST任务
xTaskCreate((TaskFunction_t )list_task,
(const char* )"list_task",
(uint16_t )LIST_STK_SIZE,
(void* )NULL,
(UBaseType_t )LIST_TASK_PRIO,
(TaskHandle_t* )&ListTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
while(1)
{
PCA9554_OUT(8,ON);
vTaskDelay(250); //延时250ms
PCA9554_OUT(8,ON);
vTaskDelay(250); //延时250ms
}
}
//list任务函数
void list_task(void *pvParameters)
{
//第一步:初始化列表和列表项
vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);
ListItem1.xItemValue=40; //ListItem1列表项值为40
ListItem2.xItemValue=60; //ListItem2列表项值为60
ListItem3.xItemValue=50; //ListItem3列表项值为50
//第二步:打印列表和其他列表项的地址
printf("\r\n");
printf("/*******************列表和列表项地址*******************/\r\n");
printf("项目 地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("ListItem1 %#x \r\n",(int)&ListItem1);
printf("ListItem2 %#x \r\n",(int)&ListItem2);
printf("ListItem3 %#x \r\n",(int)&ListItem3);
printf("/************************结束**************************/\r\n");
printf("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1==1) //等待INPUT1按下
{delay_ms(10);}
//第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem1); //插入列表项ListItem1
printf("/******************添加列表项ListItem1*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT2继续!\r\n\r\n\r\n");
while(INPUT2==1) //等待INPUT2键按下
{delay_ms(10);}
//第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem2); //插入列表项ListItem2
printf("/******************添加列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT3继续!\r\n\r\n\r\n");
while(INPUT3==1) //等待INPUT3键按下
{delay_ms(10);}
//第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem3); //插入列表项ListItem3
printf("/******************添加列表项ListItem3*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT4继续!\r\n\r\n\r\n");
while(INPUT4==1) //等待INPUT3键按下
{delay_ms(10);}
//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
uxListRemove(&ListItem2); //删除ListItem2
printf("/******************删除列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT5继续!\r\n\r\n\r\n");
while(INPUT5==1) //等待INPUT3键按下
{delay_ms(10);}
//第七步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
TestList.pxIndex=TestList.pxIndex->pxNext; //pxIndex向后移一项,这样pxIndex就会指向ListItem1。
vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项ListItem2
printf("/***************在末尾添加列表项ListItem2***************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n\r\n\r\n");
while(1)
{
PCA9554_OUT(8,ON);
vTaskDelay(1000);
PCA9554_OUT(8,OFF);
vTaskDelay(1000);
}
}
运行结果如下:
过程图解如下:
*. 参考
- B站视频:正点原子FreeRTOS手把手教学-基于STM32
- 正点原子开源电子网 - FreeRTOS区
- 正点原子资料下载中心
- 正点原子 STM32F1 FreeRTOS 开发手册 V1.1
- ⭐FreeRTOS官网
- FreeRTOS官方文档
- Cortex-M3权威指南