FreeRTOS 学习笔记(6)—— 列表和列表项

7 篇文章 0 订阅
7 篇文章 0 订阅

一、列表和列表项介绍

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() 的运行过程的,分析过程和前五步一样。这里就不做分析了,可根据串口调试助手输出的信息做分析。

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值