参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、列表和列表项的简介
1、概述
(1)列表是 FreeRTOS中的一个数据结构,概念上和链表有点类似,用来跟踪FreeRTOS中的任务,列表项就是存放在列表中的项目。
(2)列表相当于链表,列表项相当于节点,FreeRTOS中的列表是一个双向环形链表。

(3)列表的特点:
①列表项间的地址非连续的,是人为的连接到一起的。
②列表项的数目是由后期添加的个数决定的,随时可以改变。
2、列表和列表项的数据结构
(1)有关于列表的代码均存放在文件list.c和list.h中。
(2)列表的结构体:
cpp
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE; /*校验值*/
volatile UBaseType_t uxNumberOfItems; /*列表中的列表项数量*/
ListItem_t * configLIST_VOLATILE pxIndex; /*用于遍历列表项的指针*/
MiniListItem_t xListEnd; /*末尾列表项*/
listSECOND_LIST_INTEGRITY_CHECK_VALUE; /*校验值*/
} List_t;
①在该结构体中包含了两个宏(校验值),这两个宏是确定的已知常量,FreeRTOS通过检查这两个常量的值来判断列表的数据在程序运行过程中是否遭到破坏,该功能一般用于调试, 默认是不开启的。
②成员uxNumberOfItems------用于记录列表中列表项的个数(不包含末尾列表项xListEnd)。
③成员pxIndex------用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项。
④成员xListEnd------一个迷你列表项,排在列表最末尾。

(3)列表项的结构体:
cpp
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE; /* 用于检测列表项的数据完整性 */
configLIST_VOLATILE TickType_t xItemValue; /* 列表项的值 */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* 下一个列表项 */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* 上一个列表项 */
void * pvOwner; /* 列表项的拥有者 */
struct xLIST * configLIST_VOLATILE pxContainer; /* 列表项所在列表 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE; /* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t;
①成员xItemValue------列表项的值,这个值多用于按升序对列表中的列表项进行排序。
②成员pxNext和pxPrevious分别用于指向列表中列表项的下一个列表项和上一个列表项。
③成员pxOwner------用于指向包含列表项的对象(通常是任务控制块)。
④成员pxContainer------用于指向列表项所在列表。

(4)迷你列表项:
cpp
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE; /* 用于检测数据完整性 */
configLIST_VOLATILE TickType_t xItemValue; /* 列表项的值 */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* 上一个列表项 */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* 下一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
①迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项,因此不需要成员pxOwner和pxContainer,以节省内存开销。
②成员xItemValue------列表项的值,这个值多用于按升序对列表中的列表项进行排序。
③成员pxNext和pxPrevious分别用于指向列表中列表项的下一个列表项和上一个列表项。

(5)初始列表插入列表项:
①列表的初始状态:

②列表插入一个列表项:

③列表再插入一个列表项:

二、列表相关API函数介绍
1、列表相关API函数概览
|-----------------------|-----------|
| 函数 | 描述 |
| vListInitialise() | 初始化列表 |
| vListInitialiseItem() | 初始化列表项 |
| vListInsertEnd() | 列表末尾插入列表项 |
| vListInsert() | 列表插入列表项 |
| uxListRemove() | 列表移除列表项 |
2、各函数的执行流程
(1)初始化列表使用函数vListInitialise:
①vListInitialise函数内部实现如下所示,函数参数为待初始化的列表指针。

②初始化后的列表结构如下所示。

(2)初始化列表项使用函数vListInitialiseItem:
①vListInitialiseItem函数内部实现如下所示,函数参数为待初始化的列表指针。

②初始化后的列表结构如下所示。

(3)将待插入列表的列表项按照列表项值升序进行排序,并有序地插入到列表中,可使用vListInsert函数。
①vListInsert函数内部实现如下所示,函数参数为列表指针和待插入列表项的指针。

②列表项插入列表的过程举例:
1\]将列表项xList_Item1(数值为40)插入列表List中。  \[2\]在前面的基础上,将列表项xList_Item2(数值为60)插入列表List中。  \[3\]在前面的基础上,将列表项xList_Item3(数值为50)插入列表List中。  (4)将待插入列表的列表项插入到列表pxIndex指针指向的列表项前面(无序的插入),可使用vListInsertEnd函数。 ①vListInsertEnd函数内部实现如下所示,函数参数为列表指针和待插入列表项的指针。  ②列表项插入列表的过程举例: \[1\]将列表项xList_Item1(数值为40)插入列表List中。  \[2\]将列表项xList_Item2(数值为30)插入列表List中。  (5)将列表项从列表项所在列表中移除,可使用uxListRemove函数。 ①uxListRemove函数内部实现如下所示,函数参数为待移除的列表项的指针,返回值为移除列表项移除后其所在列表剩余列表项的数量。  ②删除列表项的过程举例:将列表项xList_Item2移除。  ## 三、列表项的插入和删除实验 ### 1、原理图与实验目标 (1)原理图:  (2)实验目标: ①设计2个任务------start_task、task3: \[1\]start_task:用于创建task3任务,然后删除自身。 \[2\]task3:调用列表和列表项相关API函数,并且通过串口输出相应的信息,进行观察。 ②预期实验现象: \[1\]程序下载到板子上后,板子上无任何明显现象。 \[2\]串口助手中输出相关调试信息。 ### 2、实验步骤 (1)将"任务创建和删除的动态方法实验"的工程文件夹复制一份,在拷贝版中进行实验。 (2)在stm32教程中找到"串口发送(单片机通过串口向电脑发送数据)"实验,复制其中的Serial.c文件和Serial.h文件,将其添加到本实验的项目中,如下图所示。  (3)删除FreeRTOS_experiment.c文件中关于task1和task2的内容,并更改task3函数的具体实现,如下所示。 ```cpp #include "FreeRTOS.h" #include "task.h" #include "Serial.h" //宏定义 #define START_TASK_STACK_SIZE 128 //start_task任务的堆栈大小 #define START_TASK_PRIO 1 //start_task任务的优先级 #define TASK3_STACK_SIZE 128 //task3任务的堆栈大小 #define TASK3_PRIO 4 //task3任务的优先级 //任务函数声明 void start_task(void); void task3(void); //任务句柄 TaskHandle_t start_task_handler; //start_task任务的句柄 TaskHandle_t task3_handler; //task3任务的句柄 void FreeRTOS_Test(void) { //创建任务start_task xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针 "start_task", //任务名字 START_TASK_STACK_SIZE, //任务堆栈大小,单位为字 NULL, //传递给任务函数的参数 START_TASK_PRIO, //任务优先级 (TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块 ); //开启任务调度器 vTaskStartScheduler(); } void start_task(void) { //进入临界区(临界区保护,就是保护那些不想被打断的程序段) taskENTER_CRITICAL(); //创建任务task3 xTaskCreate((TaskFunction_t)task3, //指向任务函数的指针 "task3", //任务名字 TASK3_STACK_SIZE, //任务堆栈大小,单位为字 NULL, //传递给任务函数的参数 TASK3_PRIO, //任务优先级 (TaskHandle_t *) &task3_handler //任务句柄,就是任务的任务控制块 ); //删除任务自身 vTaskDelete(NULL); //退出临界区 taskEXIT_CRITICAL(); } List_t TestList; /* 定义测试列表 */ ListItem_t ListItem1; /* 定义测试列表项1 */ ListItem_t ListItem2; /* 定义测试列表项2 */ ListItem_t ListItem3; /* 定义测试列表项3 */ void task3(void) { vListInitialise(&TestList); /* 初始化列表 */ vListInitialiseItem(&ListItem1); /* 初始化列表项1 */ vListInitialiseItem(&ListItem2); /* 初始化列表项2 */ vListInitialiseItem(&ListItem3); /* 初始化列表项3 */ ListItem1.xItemValue = 40; ListItem2.xItemValue = 60; ListItem3.xItemValue = 50; /* 第二步:打印列表和其他列表项的地址 */ Serial_Printf("/**************第二步:打印列表和列表项的地址**************/\r\n"); Serial_Printf("项目\t\t\t地址\r\n"); Serial_Printf("TestList\t\t0x%p\t\r\n", &TestList); Serial_Printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex); Serial_Printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd)); Serial_Printf("ListItem1\t\t0x%p\t\r\n", &ListItem1); Serial_Printf("ListItem2\t\t0x%p\t\r\n", &ListItem2); Serial_Printf("ListItem3\t\t0x%p\t\r\n", &ListItem3); Serial_Printf("/**************************结束***************************/\r\n"); Serial_Printf("\r\n/*****************第三步:列表项1插入列表******************/\r\n"); vListInsert((List_t* )&TestList, /* 列表 */ (ListItem_t*)&ListItem1); /* 列表项 */ Serial_Printf("项目\t\t\t\t地址\r\n"); Serial_Printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext)); Serial_Printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext)); Serial_Printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious)); Serial_Printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious)); Serial_Printf("/**************************结束***************************/\r\n"); /* 第四步:列表项2插入列表 */ Serial_Printf("\r\n/*****************第四步:列表项2插入列表******************/\r\n"); vListInsert((List_t* )&TestList, /* 列表 */ (ListItem_t*)&ListItem2); /* 列表项 */ Serial_Printf("项目\t\t\t\t地址\r\n"); Serial_Printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext)); Serial_Printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext)); Serial_Printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext)); Serial_Printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious)); Serial_Printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious)); Serial_Printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious)); Serial_Printf("/**************************结束***************************/\r\n"); /* 第五步:列表项3插入列表 */ Serial_Printf("\r\n/*****************第五步:列表项3插入列表******************/\r\n"); vListInsert((List_t* )&TestList, /* 列表 */ (ListItem_t*)&ListItem3); /* 列表项 */ Serial_Printf("项目\t\t\t\t地址\r\n"); Serial_Printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext)); Serial_Printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext)); Serial_Printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext)); Serial_Printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext)); Serial_Printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious)); Serial_Printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious)); Serial_Printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious)); Serial_Printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious)); Serial_Printf("/**************************结束***************************/\r\n"); /* 第六步:移除列表项2 */ Serial_Printf("\r\n/*******************第六步:移除列表项2********************/\r\n"); uxListRemove((ListItem_t* )&ListItem2); /* 移除列表项 */ Serial_Printf("项目\t\t\t\t地址\r\n"); Serial_Printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext)); Serial_Printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext)); Serial_Printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext)); Serial_Printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious)); Serial_Printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious)); Serial_Printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious)); Serial_Printf("/**************************结束***************************/\r\n"); /* 第七步:列表末尾添加列表项2 */ Serial_Printf("\r\n/****************第七步:列表末尾添加列表项2****************/\r\n"); TestList.pxIndex = &ListItem1; vListInsertEnd((List_t* )&TestList, /* 列表 */ (ListItem_t* )&ListItem2); /* 列表项 */ Serial_Printf("项目\t\t\t\t地址\r\n"); Serial_Printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex); Serial_Printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext)); Serial_Printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext)); Serial_Printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext)); Serial_Printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext)); Serial_Printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious)); Serial_Printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious)); Serial_Printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious)); Serial_Printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious)); Serial_Printf("/************************实验结束***************************/\r\n"); while(1) { vTaskDelay(1000); } } ``` (4)修改main.c文件,在其中添加如下代码。 ```cpp #include "stm32f10x.h" // Device header #include "FreeRTOS.h" #include "task.h" #include "FreeRTOS_experiment.h" #include "Serial.h" int main(void) { /*模块初始化*/ Serial_Init(); //串口初始化 FreeRTOS_Test(); while (1) { } } ``` (5)程序完善好后点击"编译",然后将程序下载到开发板上,打开串口助手分析信息。 ### 3、程序执行流程 (1)main函数全流程: ①初始化串口模块。 ②调用FreeRTOS_Test函数。  (2)测试函数全流程: ①创建任务start_task。 ②开启任务调度器。  (3)多任务调度执行阶段(发生在开启任务调度器以后): ①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务创建任务task3,然后删除自身,接着退出临界区,让出CPU资源。  ②除了空闲任务以外,CPU只需执行task3任务,执行流程较简单,这里不再赘述。 (4)task3任务的详细流程: ①在全局中已定义测试列表及测试列表项,task3首先对它们进行初始化。 ②打印列表和其他列表项的地址,此时列表的数据结构及串口助手的输出内容如下。   ③将列表项1插入列表,此时列表的数据结构及串口助手的输出内容如下。   ④将列表项2插入列表,此时列表的数据结构及串口助手的输出内容如下。   ⑤将列表项3插入列表,此时列表的数据结构及串口助手的输出内容如下。   ⑥将列表项2从列表中移除,此时列表的数据结构及串口助手的输出内容如下。   ⑦列表数据结构的pxIndex从指向xListEnd变为指向列表项1,然后将列表项2从列表末尾(即列表项1的前面)插入列表,此时列表的数据结构及串口助手的输出内容如下。  