一、列表和列表项介绍
1. 列表
列表是 FreeRTOS 中的一个数据结构,概念上和链表类似,用于跟踪 FreeRTOS 的任务。列表相关的内容都在文件 list.c 和 list.h 中。在 list.h 中定义了一个叫 List_t 的结构体,如下:
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;
- listFIRST_LIST_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_INTEGRITY_CHECK_VALUE 用来检查列表完整性,默认不开启这个功能。
- uxNumberOfItems 用来记录列表中列表项的数量。
- pxIndex 用来记录当前列表项索引号,用于遍历列表。
- xListEnd 用来表示列表结束,此变量类型为 MiniListItem_t,这是一个迷你列表项。
列表结构示意图如下图所示:(注意!图中并未列出用于列表完整性检查的成员变量!)
2. 列表项
列表项就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。这两个都在文件 list.h 中有定义。
列表项定义如下:
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;
void * configLIST_VOLATILE pvContainer;
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;
- 两个宏用法和列表一样,用来检查列表项完整性的。
- xItemValue 为列表项值。
- pxNext 指向下一个列表项。
- pxPrevious 指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能。
- pvOwner 记录此链表项归谁拥有,通常是任务控制块。
- pvContainer 用来记录此列表项归哪个列表。
列表项结构示意图如下图所示:(注意!图中并未列出用于列表项完整性检查的成员变量!)
迷你列表项定义如下:
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;
- 两个宏用法和列表一样,用来检查列表项完整性的。
- xItemValue 记录列表列表项值。
- pxNext 指向下一个列表项。
- pxPrevious 指向上一个列表项。
迷你列表项与列表项对比,成员变量只是少了 pvOwner、pvContainer。为何单独整一个迷你列表项呢?主要是避免内存浪费,因为有些情况下我们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会造成内存浪费!
迷你列表项结构示意图如下图所示:(注意!图中并未列出用于迷你列表项完整性检查的成员变量!)
二、列表和列表项相关 API 介绍
- vListInitialise() 初始化列表
- vListInitialiseItem() 初始化列表项
- vListInsert() 列表插入列表项(按照升序插入)
- vListInsertEnd() 列表末尾插入列表项
- uxListRemove() 列表移除列表项
1. 函数 vListInitialise() 初始化列表
新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体 List_t 中的各个成员变量,列表的初始化通过使函数 vListInitialise() 来完成,此函数在 list.c 中有定义,函数如下:
void vListInitialise(List_t * const pxList)
{
/* 初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/* xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/* 初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* 初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
- xListEnd 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,此时列表只有一个列表项,那就是 xListEnd,所以 pxIndex 指向 xListEnd;
- xListEnd 的列表项值初始化为 portMAX_DELAY,portMAX_DELAY 是个宏,在文件 portmacro.h 中有定义;
- 初始化列表项 xListEnd 的 pxNext 变量,因为列表只有一个列表项 xListEnd,因此 pxNext 只能指向自身;
- 初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身;
- 由于此时没有其他的列表项,因此 uxNumberOfItems 为 0。
列表初始化完以后的结构图:(注意!图中为了好分析,将 xListEnd 中的各个成员变量都写了出来!)
2. 函数 vListInitialiseItem() 初始化列表项
同列表一样,列表项在使用的时候也需要初始化,列表项初始化由函数 vListInitialiseItem() 将列表项成员变量 pvContainer 初始化为 NULL,并且给用于完整性检查的变量赋值,函数如下:
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 初始化时,列表项所在列表设为空 */
pxItem->pxContainer = NULL;
/* 初始化用于检测列表项数据完整性的校验值 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
3. 函数 vListInsert() 列表插入列表项(按照升序插入)
列表项的插入操作通过函数 vListInsert() 来完成,函数如下:
/* 函数介绍:pxList要插入的列表 pxNewListItem:要插入该列表的列表项 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * pxIterator;
/* 获取列表项的数值依据数值升序排列 */
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 检查参数是否正确 */
listTEST_LIST_INTEGRITY( pxList );
/* 如果待插入列表项的值为最大值 */
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
if( xValueOfInsertion == portMAX_DELAY )
{
/* 插入的位置为列表 xListEnd 前面 */
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* 遍历列表中的列表项,找到插入的位置 */
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext ) { }
}
/* 将待插入的列表项插入指定位置 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 更新待插入列表项所在列表 */
pxNewListItem->pxContainer = ( void * ) pxList;
/* 更新列表中列表项的数量 */
( pxList->uxNumberOfItems )++;
}
(1)插入值为 40 的列表项
在一个空的列表 List 中插入一个列表值为 40 的列表项 ListItem1,(ListEnd 的 Previous 和 Next 均会指向第一个列表项)插入完成以后如下图所示:
通过观察插入完成以后列表 List 和列表项 ListItem1 中各个成员变量之间的变化,发现列表 List 中的 uxNumberOfItems 变为了 1,表示现在列表中有一个列表项。列表项 ListItem1 中的 pvContainer 变成了 List,表示此列表项属于列表 List。通过图中可以看出,列表是一个环形的,即环形列表!
(2)插入值为 60 的列表项
接着再插入一个值为 60 的列表项 ListItem2,插入完成以后如下图所示:
因为函数 vListInsert() 是按照升序的方式插入的,所以 ListItem2 是插入到 ListItem1 的后面、xListEnd 的前面。同样的,列表 List 的 uxNumberOfItems 再次加 1 变为 2 了,说明此时列表中有两个列表项。
(3)插入值为 50 的列表项
在上面的列表中再插入一个值为 50 的列表项 ListItem3,插入完成以后如下图所示:
因为函数 vListInsert() 是按照升序的方式插入的,所以 ListItem3 应该插入到 ListItem1 和 ListItem2 中间。
4. 函数 vListInsertEnd() 列表末尾插入列表项
列表末尾插入列表项的操作通过函数 vListInsertEnd() 来完成,函数如下:
/* 插入参数:pxListu 要插入的列表 pxNewListItem:要插入的列表项 */
void vListInsertEnd ( List_t * const pxList , ListItem_t * const pxNewListItem )
{
/* 获取列表 pxIndex 指向的列表项 */
ListItem_t * const pxIndex = pxList->pxIndex;
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* 更新待插入列表项的指针成员变量 */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
mtCOVERAGE_TEST_DELAY();
/* 更新列表中原本列表项的指针成员变量 */
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* 更新待插入列表项的所在列表成员变量 */
pxNewListItem->pxContainer = ( void * ) pxList;
/* 更新列表中列表项的数量 */
( pxList->uxNumberOfItems )++;
}
(1)默认列表
注意图中列表的 pxIndex 所指向的列表项,这里为 ListItem1,不再是 xListEnd 了。
(2)插入值为 50 的列表项
在上面的列表中插入一个值为 50 的列表项 ListItem3,插入完成以后如下图所示:
列表 List 的 pxIndex 指向列表项 ListItem1,因此调用函数 vListInsertEnd() 插入 ListItem3 的话就会在 ListItem1 的前面插入。
5. 函数 uxListRemove() 列表移除列表项
列表项的删除通过函数 uxListRemove() 来完成,函数如下:
/* pxItemToRemove:要移除的列表项,因为可以通过当前列表项找到其所属的列表 */
/* 返回值 剩余的列表项的数量 */
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = pxItemToRemove->pxContainer;
/* 从列表中移除列表项 */
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* 如果 pxIndex 正指向待移除的列表项 */
mtCOVERAGE_TEST_DELAY();
if( pxList->pxIndex == pxItemToRemove )
{
/* pxIndex 指向上一个列表项 */
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 将待移除的列表项的所在列表指针清空 */
pxItemToRemove->pxContainer = NULL;
/* 更新列表中列表项的数量 */
( pxList->uxNumberOfItems )--;
/* 返回移除后的列表中列表项的数量 */
return pxList->uxNumberOfItems;
}
注意,列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉!如果这个列表项是动态分配内存的话。
6. 列表的遍历
列表的遍历是通过列表 List_t 中的成员变量 pxIndex 来遍历列表的,FreeRTOS 提供了一个函数来完成列表的遍历,这个函数是 listGET_OWNER_OF_NEXT_ENTRY() 。每调用一次这个函数列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner 变量值。这个函数本质上是一个宏,这个宏在文件 list.h 中如下定义:
/* pxTCB用于保存pxIndex所指向的列表项的pvOwner变量值,即列表属于谁。
通常是一个任务的任务控制块 */
/* pxList表示要遍历的列表 */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
{
List_t * const pxConstList = ( pxList );
/* 列表的pxIndex变量指向下一个列表项 */
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
/* 如果到达了最后一个列表项 */
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )
{
/* 如果到达了最后一个列表项,列表pxIndex重新指向下一个列表项。跳过xListEnd */
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
}
/* 下一个要运行任务的pvOwner给pxTCB */
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
}
三、列表项的插入和删除实验
1. 实验设备
正点原子 Mini STM32F103RCT6 开发板。
2. 实验目的
学习使用 FreeRTOS 列表和列表项相应的操作函数的使用,观察这些操作函数的运行结果和理论知识分析的是否一致。
3. 实验设计
本实验设计 3 个任务:Start_Task、Task1_Task 和 List_Task,这三个任务的任务功能如下:
Start_Task:用来创建其他 2 个任务。
Task1_Task:应用任务 1,控制 LED0 闪烁,用来提示系统正在运行。
List_Task:列表和列表项操作任务,调用列表和列表项相关的 API 函数,并且通过串口输出相应的信息来观察这些 API 函数的运行过程。实验需要用到 KEY_UP 按键,用于控制任务的运行。
4. 实验程序
(1)任务设置
#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 LIST_TASK_PRIO 3 // 任务优先级
#define LIST_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t ListTask_Handler; // 任务句柄
void List_Task(void *pvParameters); // 任务函数声明
(2)列表和列表项的定义
// 定义1个测试列表和3个测试列表项
List_t TestList; // 测试列表
ListItem_t ListItem1; // 测试列表项1
ListItem_t ListItem2; // 测试列表项2
ListItem_t ListItem3; // 测试列表项3
(3)main() 函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 设置系统中断优先级分组4
delay_init(); // 延时函数初始化
uart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
KEY_Init(); // 初始化按键
LCD_Init(); // 初始化LCD
POINT_COLOR = BLACK; // 设置LCD显示颜色为黑色
LCD_DrawRectangle(5, 5, 234, 106); // 画一个矩形
POINT_COLOR = RED; // 设置LCD显示颜色为红色
LCD_ShowString(75, 10, 200, 16, 16, (u8 *)"STM32F103RCT6");
LCD_ShowString(60, 30, 200, 16, 16, (u8 *)"FreeRTOS Examp 6");
LCD_ShowString(50, 50, 200, 16, 16, (u8 *)"List Insert and Del");
LCD_ShowString(90, 70, 200, 16, 16, (u8 *)"Xiao Xie");
LCD_ShowString(80, 90, 200, 16, 16, (u8 *)"2023/07/15");
// 创建开始任务
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 的任务调度器,FreeRTOS 开始运行
}
(4)任务函数
// 开始任务函数
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); // 删除开始任务
taskEXIT_CRITICAL(); // 退出临界区
}
// Task1任务函数
void Task1_Task(void *pvParameters)
{
while (1)
{
LED0 = !LED0;
vTaskDelay(500); // 延时0.5s ==> 500个时钟节拍
}
}
// 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("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("按下 KEY_UP 键继续!\r\n\r\n");
while (KEY_Scan(0) != WKUP_PRES) delay_ms(10); // 等待 KEY_UP 键按下
/* 第三步:向列表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("按下 KEY_UP 键继续!\r\n\r\n");
while (KEY_Scan(0) != WKUP_PRES) delay_ms(10); // 等待 KEY_UP 键按下
/* 第四步:向列表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("按下 KEY_UP 键继续!\r\n\r\n");
while (KEY_Scan(0) != WKUP_PRES) delay_ms(10); // 等待 KEY_UP 键按下
/* 第五步:向列表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("按下 KEY_UP 键继续!\r\n\r\n");
while (KEY_Scan(0) != WKUP_PRES) delay_ms(10); // 等待 KEY_UP 键按下
/* 第六步:删除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("按下 KEY_UP 键继续!\r\n\r\n");
while (KEY_Scan(0) != WKUP_PRES) delay_ms(10); // 等待 KEY_UP 键按下
/* 第七步:删除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");
while(1)
{
LED1 = !LED1;
vTaskDelay(1000); // 延时1s ==> 1000个时钟节拍
}
}
任务函数 list_task() 通过调用与列表和列表项相关的 API 函数来对列表和列表项做相应的操作,并且通过串口打印出每调用一个 API 函数以后列表和列表项的连接信息,通过这些信息我们可以直观的判断出列表项在插入、删除和末尾插入的时候这个列表的变化情况。
5. 实验程序运行结果分析
编译并下载实验代码到开发板中,打开串口调试助手,然后按照任务函数 list_task() 中的步骤一步步的测试分析。
(1)第一步和第二步
第一步和第二步是用来初始化列表和列表项的,并且通过串口输出列表和列表项的地址,这一步是开发板复位后默认运行的,串口调试助手输出信息如下图所示:
由于这些列表和列表项地址前五位都为 0X20000,只有最低 3 位不同,所以我们就用最低 2 位或 3 位代表这些列表和列表项的地址。(注意!列表和列表项的地址在不同的硬件平台或编译软件上是不同的,以自己的实际实验结果为准!)
简单的分析上图可以得到如下信息:
- 列表 TestList 地址为 f4。
- 列表项 ListItem1、ListItem2 和 ListItem3 的地址分别为 108、11c 和 130。
- 列表 TestList 的 xListEnd 地址为 fc。
- 列表 TestList 的 pxIndex 指向地址 fc,而这个地址正是迷你列表项 xListEnd,说明 pxIndex 指向 xListEnd,这与分析列表初始化函数 vListInitialise() 的时候得到的结果是一致的。
(2)第三步
按一下 KEY_UP 键,执行第三步,第三步是向列表 TestList 中插入列表项 ListItem1,列表项 ListItem1 的成员变量 xItemValue 的值为 40。第三步执行完以后串口调试助手输出如下图所示:
分析上图可以得出以下信息:
- xListEnd 的 pxNext 指向地址 108,而 108 是 ListItem1 的地址,说明 xListEnd 的 pxNext 指向 ListItem1。
- 列表项 ListItem1 的 pxNext 指向地址 fc,而 fc 是 xListEnd 的地址,说明 ListItem1 的 pxNext 指向 xListEnd。
- xListEnd 的 pxPrevious 指向地址 108,而 108 是 ListItem1 的地址,说明 xListEnd 的 pxPrevious 指向 ListItem1。
- ListItem1 的 pxPrevious 指向地址 fc,fc 是 xListEnd 的地址,说明 ListItem1 的 pxPrevious 指向 xListEnd。
用简易示意图表示这一步的话如下图所示:
(3)第四步
按一下 KEY_UP 键,执行第四步,第四步是向列表 TestList中 插入列表项 ListItem2,列表项 ListItem2 的成员变量 xItemValue 的值为 60。第四步执行完以后串口调试助手输出如下图所示:
分析上图可以得出以下信息:
- xListEnd 的 pxNext 指向 ListItem1。
- ListItem1 的 pxNext 指向 ListItem2。
- ListItem2 的 pxNext 指向 xListEnd。
- 列表项的 pxPrevious 分析过程类似,后面的步骤中就不做分析了,只看 pxNext 成员变量。
用简易示意图表示这一步的话如下图所示:
(4)第五步
按一下 KEY_UP 键,执行第五步,第五步是向列表 TestList 中插入列表项 ListItem3,列表项 ListItem3 的成员变量 xItemValue 的值为 50。第五步执行完以后串口调试助手输出如下图所示:
分析上图可以得出以下信息:
- xListEnd 的 pxNext 指向 ListItem1。
- ListItem1 的 pxNext 指向 ListItem3。
- ListItem3 的 pxNext 指向 ListItem2。
- ListItem2 的 pxNext 指向 xListEnd。
用简易示意图表示这一步的话如下图所示:
(5)第六步和第七步
这两步是观察函数 uxListRemove() 和 vListInsertEnd() 的运行过程的,分析过程和前五步一样。这里就不做分析了,可根据串口调试助手输出的信息做分析。