单片机的三种主要运行模式:
其中前两种称为裸机,裸机系统中的主体是
main
函数中的死循环,在死循环中CPU按顺序执行代码。
在多任务系统中,根据功能的不同,把系统分割成一个个独立且无法返回的函数,这些个函数就是"任务",可叫做"线程"。
在裸机系统中中断返回的函数地址放在栈中,栈是单片机RAM中一段连续的内存空间,栈的大小由启动文件里的代码配置。
在多任务系统中,每个任务都是独立的,所以每个任务都要分配独立的栈空间,其通常是一个预先定义好的全局数组,这些任务栈也是存于RAM中。
任务函数是一个独立的函数,函数主体无限循环且无返回值。
任务控制块相当于任务的身份证,里面存有任务的所有信息,如任务的栈、任务名称、任务形参等。它是一个新的数据类型,在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)。一般把系统分为前端和后端,而在传统无系统的程序中,大循环相当于后端,中断服务函数相当于前端。对比于无系统程序,带操作系统的程序的实时性更好、任务管理更方便。在操作系统中,将要实现的功能分为多个任务,每个任务都是一个简单的程序,一般是一个死循环。
登录FreeRTOS官网 点击下载FreeRTOS最新源码。
源码文件夹内容如下,其中FreeeRTOS-Plus是FreeeRTOS的一个加强版本,一般使用普通版即可。
在1.1节中下载的源码中找到对应芯片的源码,进入Source文件夹,将其内部所有内容复制到代码工程的FreeRTOS文件夹中。在portable文件夹中只需留下 keil、MemMang 和 RVDS 这三个文件夹,其余删除(为了不增加工程文件大小)。对应地,将其他文件导入到对应的工程文件夹中。
heap_4.c
FreeRTOSConfig.h
不属于源代码的一部分,是用户配置的文件,它可以在官方的Demo中找到(比如文件夹 FreeRTOSv9.0.0\FreeRTOS\Demo 中的 CORTEX_STM32F103_Keil 中就可以找到)并添加到工程中,再作实际应用修改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
文件中的函数即可。
任务具有以下几个特性:
以前在使用 51、AVR、STM32 单片机裸机(未使用系统的程序)的时候一般都是在 main 函数里面用 while(1)做一个大循环来完成所有的功能。有时候也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环 while(1)作为后台程序。这种程序实时性差,前后台系统各个任务都是排队等着轮流执行,不管这个程序现在有多紧急,没轮到你就只能等着。相当于所有任务(应用程序)的优先级都是一样的。
而RTOS的实时性虽然更高,但多任务处理也带来了任务调度的问题。在 RTOS 系统中的任务调度器就是用于解决该问你。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。
vTaskDelay()
的话,该任务就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临。vTaskSuspend()
和 xTaskResume()
.每个任务都可分配到一个从 0~(configMAX_PRIORITIES-1)
的优先级,在FreeRTOS中,优先级数字越大,优先级越高(这一点与UCOS相反)。
// 在FreeRTOSConfig.h文件中有对可使用的最大优先级数量的定义
#define configMAX_PRIORITIES (32) //可使用的最大优先级数量为32
注:
- 优先级0留给空闲任务,创建任务时不要使用
- 软件定时器默认优先级为最高(configMAX_PRIORITIES-1),故创建任务时,也不要使用最高优先级
在FreeRTOS中使用函数 xTaskCreate()
或 xTaskCreateStatic()
来创建任 务,这两个函数的第一个参数 pxTaskCode
,就是这个任务的任务函数。
任务函数就是完成本任务工作的功能函数。FreeRTOS 官方给出的任务函数模板如下:
void vATaskFunction(void *pvParameters) //【1】
{
for( ; ; ) //【2】
{
// 任务要实现的功能代码 【3】
vTaskDelay(); //【4】
}
/* 不能从任务函数中返回或退出 ,从任务函数中返回或退出的话就会调用configASSERT(),前提是你定义了 configASSERT()。如果一定要从任务函数中退出的话那一定 要调用函数 vTaskDelete(NULL)来删除此任务。*/
vTaskDelete(NULL); //【5】
}
其中:
void
类型,而且任务的参数也是 void
指针类型的。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);
}
}
总流程如下图:
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;
在结构体中有很多成员变量,每一个成员变量就是任务的每一个属性。
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位
FreeRTOS 的任务创建和删除 API 函数如下图所示:
MPU:内存管理单元
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: 任务创建失败,因为堆内存不足
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 的时候会导致这个错误的发生
其他值: 任务创建成功,返回任务的任务句柄
vTaskDelete()
—— 任务删除被删除的任务再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄。
函数的输入参数如下:
vTaskDelete( TaskHandle_t xTaskToDelete ) // 直接传入任务创建成功后生成的任务句柄,即可删除该任务
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉。
比如某个任务中用户调用函数pvPortMalloc()
分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数vPortFree()
将这 500 字节的内存释放掉,否则会导致内存泄露。
#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次
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;
}
}
有关 任务挂起与恢复的函数如下表所示:
此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume()
或 xTaskResumeFromISR()
.
值得注意的是,任务从挂起到恢复后,任务进入就绪态,然后由任务调度器决定其运行状态,而不是直接进入运行态。
此函数是 vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。
pdTRUE
: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。pdFALSE
: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。关于FreeRTOS与任务相关的API函数,在FreeRTOS官网中都有详细介绍
uxTaskPriorityGet()
此函数用于获取指定任务的优先级,要使用此函数的条件是将宏 INCLUDE_uxTaskPriorityGet
定义为 1:
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
xTask
: 要查找的任务的任务句柄vTaskPrioritySet()
此函数用于改变某一个任务的任务优先级,要使用此函数的条件是将宏INCLUDE_vTaskPrioritySet
定义为 1
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
输入参数:
xTask
: 目标任务的任务句柄uxNewPriority
: 任务要使用的新的优先级,它可以是 0~ configMAX_PRIORITIES
– 1uxTaskGetSystemState()
此函数用于获取系统中所有任务的任务壮态,每个任务的壮态信息保存在一个 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
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()
去想办法获取。
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 就获取当前正在运行的任务标签值。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 ) ) */
/*-----------------------------------------------------------*/
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
: 任务名(字符串)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 */
/*----------------------------------------------------------*/
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()
的任务)的“高水位线”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
中定义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()
)的任务名字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
的值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
的值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
: 调度器挂起uxTaskGetNumberOfTasks()
数用于查询系统当前存在的任务数量:
UBaseType_t uxTaskGetNumberOfTasks( void )
{
/* A critical section is not required because the variables are of type
BaseType_t. */
return uxCurrentNumberOfTasks;
}
/*-----------------------------------------------------------*/
vTaskList()
#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
: 保存任务壮态信息表的存储区。存储区要足够大来保存任务状态信息表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节实验
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
类型的函数指针,但是也可以设置为其他值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
: 要存储的值GetThreadLocalStoragePointer()
用于获取线程本地存储指针的值, 使用此函数的前提是将宏configNUM_THREAD_LOCAL_STORAGE_POINTERS
定义为非 0:
void *pvTaskGetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery, BaseType_t xIndex )
xTaskToSet
: 要获取的线程本地存储指针的任务句柄,如果是 NULL ,表示获取任务自身的线程本地存储指针xIndex
: 要获取的线程本地存储指针数组的索引#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个时钟节拍
}
}
运行结果:
注:函数
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()
FreeRTOSConfig.h
文件讲解FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用#define
来实现的。在 FreeRTOS 的官方 demo 中,每个工程都有一个 FreeRTOSConfig.h 文件,在使用FreeRTOS时可参考这个文件,甚至直接复制粘贴使用。
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 */ ```
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时使用计数信号量
当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
定义。当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) //软件定时器任务堆栈大小
参考资料:《Cortex - M3 权威指南》—— 异常、NVIC与中断控制 两节
在STM32芯片中,中断由硬件产生,当中断产生后 ,CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC).
Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。
NVIC:嵌套向量中断控制器
Cortex‐M3 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中,编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断(一共240个)。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的(但不能编程为负数)。另外,优先级数值越小,优先级越高。
注:所有能打断正常执行流的事件都称为异常
可见1、2、3号的优先级为负数,其优先级是写定的,不可修改。而表7.2 中可见,系统外部中断一共有240个。
在FreeRTOS中重点关注第14、15号的异常。
由上一节可知,优先级的数值越小,优先级越高。CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。但其中有 3 个系统异常(见表7.2):复位,NMI 以及硬 fault,它们具有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。
Cortex-M 处理器有三个固定优先级和 256 个(见下图,中断优先级寄存器被定义为8位长,2^8=256)可编程的优先级,最多有 128 个抢占等级(优先级分组:为了使抢占机能变得更加可控,CM3把256个可编程优先级分成高低两段,分别是抢占优先级、亚优先级),但实际的优先级数量是由芯片厂商来决定的,为了精简设计,芯片厂商在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,但不管用多少位来表达优先级,都是 MSB 对齐的,如图 4.1.3.1 就是使用三位来表达优先级。不管用多少位来表达优先级,都是 MSB(最高有效位) 对齐的,如下图就是使用前三位来表达优先级。
如上图,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 */
在头文件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
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
那么实现的中断配置如下:
在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 */
由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。
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND
)”和“中断悬起清除寄存器(CLRPEND
)”来读取,还可以手动写它们来悬起中断。
悬起寄存器和“解悬”寄存器也可以有 8 对,其用法和用量都与前面介绍的使能/除能寄存器完全相同
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位。那么4 个相临的优先级寄存器就拼成一个 32 位寄存器。根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。
系统异常优先级寄存器如下表所示,重点常用的为PRI_14
和PRI_15
.
注意:由于0xE000 ED20
到0xE000 ED23
4个字节拼成一个32位寄存器,故在操作PRI_14
和PRI_15
时,其操作地址从0xE000 ED20
开始。
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。活动状态寄存器的定义,与前面讲的使能/除能和悬起/解悬寄存器相同,只是不再成对出现。它们也能按字/半字/字节访问,但他们是只读的,如下表所示:
在 FreeRTOS 中优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断会被屏蔽掉,高于的就不会,那么下面就写个简单的例程测试一下,使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,再查看两个定时器的输出情况。
#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); //清除中断标志位
}
列表是 FreeRTOS 中的一个数据结构,列表用来跟踪 FreeRTOS中的任务。与列表相关的全部东西都在文件 list.c
和 list.h
中,其原理类似于数据结构中的链表。
在 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
:表示列表中最后一个列表项,用来表示列表结束列表项就是存放在列表中的项,其在文件 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
属性值就是二年级。
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;
列表的初始化其实就是初始化列表结构体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与列表一样,列表项在使用前也需要初始化,列表项初始化由函数 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 );
:同理是用于检查完整性的列表项的插入操作通过函数 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
而得往一个空列表中插入一个值为40的列表项:(这里的值表示xItemValue
,下同)
uxNumberOfItems
由NULL变为1,表示空列表中增加了一个列表项pvContainer
变成了 List,表示该列表项归属于列表List继续往列表中插入一个值为60的列表项,他插在列表项ListItem1之后:
uxNumberOfItems
由1变为2,表示列表中列表项的数量为2pvContainer
变成了 List,表示该列表项归属于列表List继续往列表中插入一个值为50的列表项,他插在列表项ListItem1之后、ListItem2之前:
以图7.3.2.3 为例,由于pxIndex指向迷你列表项xListEnd,故列表的表头为迷你列表项xListEnd,而迷你列表项xListEnd的pxNext指向ListItem1,表示它的下一个列表项是ListItem1,如此类推,最后表尾ListItem3的pxNext指回迷你列表项xListEnd,形成一个环形列表
列表末尾插入列表项的操作通过函数 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.3.1节中不同的是,列表的pxIndex
指向的不是迷你列表项xListEnd,而是ListItem1,表示列表List的表头列表项为ListItem1,末尾列表项就是迷你列表项xListEnd
既然已知末尾列表项是迷你列表项xListEnd,那么使用列表项末尾插入函数vListInsertEnd()
去插入列表项,当然是在迷你列表项xListEnd之后插入新的列表项
列表项的删除通过函数 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.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
: 表示要遍历的列表#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);
}
}