基于STM32的FreeRTOS学习之列表和列表项(九)

记录一下,方便以后翻阅~

列表和列表项是FreeRTOS的一个数据结构,FreeRTOS大量使用列表和列表项,它是FreeRTOS的基石。

1. 什么是列表和列表项

1.1 列表
列表是FreeRTOS的一个数据结构,被用来跟踪FreeRTOS中的任务。与列表有关的东西都在list.c和list.h文件里。在list.h中定义了一个List_t的结构体:

typedef struct xLIST
{
  listFIRST_LIST_INTEGRITY_CHECK_VALUE	  // 检查列表完整性,将configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设1。默认不开启
  configLIST_VOLATILE UBaseType_t uxNumberOfItems; // 用来记录列表中列表项的数量
  ListItem_t * configLIST_VOLATILE pxIndex;	       // 用来记录当前列表项索引号,用于遍历列表
  MiniListItem_t xListEnd;			               // 列表中最后一个列表项,用来表示列表结束,此变量类型为MiniListItem_t
  listSECOND_LIST_INTEGRITY_CHECK_VALUE		       // 检查列表完整性,将configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设1。默认不开启
} List_t;

列表结构示意图为:
在这里插入图片描述
1.2 列表项
列表项就是存放在列表中的项目,FreeRTOS有两种列表项:列表项和迷你列表项,在list.h中定义:

struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE	
	configLIST_VOLATILE TickType_t xItemValue;	        // xItemValue列表项值	
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		// pxNext指向下一个列表项
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	// pxPrevious指向前一个列表项
	void * pvOwner;			// 记录此列表项归谁拥有,通常是任务控制块							
	void * configLIST_VOLATILE pvContainer;		// 用来记录此列表项归哪个列表。	
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE		
};
typedef struct xLIST_ITEM ListItem_t;				

对于pvContainer再多说两句,在任务控制块TCB_t中有两个变量xStateListItem和xEventListItem,这两个变量类型是ListItem_t,即这两个成员变量是列表项。以xStateListItem为例,当创建一个任务后,xStateListItem的pvOwner变量指向这个任务的任务控制块,表示xStateListItem属于此任务。当任务就绪态以后,xStateListItem的变量pvContainer就指向就绪列表,表面此列表项在就绪列表中。
列表项结构示意图如下:
在这里插入图片描述
1.3 迷你列表项
迷你列表项在list.h中定义:

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
	configLIST_VOLATILE  TickType_t	   xItemValue;        // xItemValue记录列表项值
	struct xLIST_ITEM * configLIST_VOLATILE  pxNext;      // pxNext指向下一个列表项
	struct xLIST_ITEM * configLIST_VOLATILE  pxPrevious;  // pxPrevious指向上一个列表项
}
typedef struct xMINI_LIST_ITEM  MiniListItem_t;           // 

迷你列表项结构示意图如下:
在这里插入图片描述

2. 列表和列表项初始化

2.1 列表初始化
新创建或定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体List_t中的各个成员变量,列表的初始化通过使函数vListInitialise()来完成,此函数在list.c中有定义:

void vListInitialise( List_t * const pxList )
{
    // xListEnd用来表示列表的末尾,而pxIndex表示索引号,此时列表只有一个列表项,就是xListEnd,所以pxIndex指向xListEnd。
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );  
	// xListEnd列表项值初始化为portMAX_DELAY(在portmacro.h中定义)。本教程为0xffffffffUL
	pxList->xListEnd.xItemValue = portMAX_DELAY;
	// 初始化列表项xListEnd的pxNext变量,因为此列表只有一个列表项,因此pxNext指向自身
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	
	// 初始化xListEnd的pxPrevious变量,指向xListEnd自身
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
	// 由于此时没有其他的列表项,因此uxNumberOfItems为0,注意:这里不算xListEnd
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
	// 以下两行用于完整性检查字段,只有configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES为1时有效。STM32写入的是0x5a5a5a5aUL
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

列表初始化如下图:
在这里插入图片描述
2.2 列表项初始化
通列表一样,列表项在使用的时候也需要初始化,列表项初始化由函数vListInitialiseItem()函数完成:

void vListInitialiseItem( ListItem_t * const pxItem )
{
	pxItem->pvContainer = NULL;    // 初始化pvContainer为NULL
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

列表项的初始化只是将列表项成员变量pvContainer初始化为NULL,并给用于完整性检查的变量赋值。

3. 列表项插入

3.1 列表项插入函数分析
列表项的插入操作用vListInsert()完成:

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 );
	// 判断插入列表项的值是否等于portMAX_DELAY,即列表项最大值,是的话就插入列表的最末尾
	if( xValueOfInsertion == portMAX_DELAY )
	// 获取要插入点,xListEnd表示列表末尾,初始化时xListEnd=portMAX_DELAY,此时要插入的列表项的列表值也是portMAX_DELAY,放在前面
	{	pxIterator = pxList->xListEnd.pxPrevious;	}
	else
	{
		// 否则,需要在列表中一个一个的找位置,for循环就是找位置的过程,当找到合适的列表项位置时就跳出。
		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;
	// 列表项已经插入到列表中,那么成员变量pvContainer也记录此列表项属于哪个列表
	pxNewListItem->pvContainer = ( void * ) pxList;
	// 列表的成员变量uxNumberOfItems加一,表示添加一个列表项
	( pxList->uxNumberOfItems )++;
}

3.2 列表项插入过程示意图

1)插入值为40的列表项
在一个空列表List中插入一个列表值为40的列表项ListItem1,完成后如图:
在这里插入图片描述
由图可知,列表List和列表项ListItem1中,uxNumberOfItem值为1,表示现在列表中有一个列表项,pvContainer值为List,表示该列表项属于List,此列表是一个环形列表!

2)再插入值为60的列表项
完成后如下图:
在这里插入图片描述
列表项是按照升序的方式插入的,所以ListItem2是插入到ListItem1的后面、xListEnd的前面。

3)在插入值为50的列表项
完成后如下图所示:
在这里插入图片描述
按照升序排列的方式,ListItem3放到ListItem1和ListItem2中间。

4. 列表项末尾插入

4.1 列表项末尾插入函数分析
列表末尾插入列表项的操作通过函数vListInsertEnd()实现:

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t * const pxIndex = pxList->pxIndex;
	listTEST_LIST_INTEGRITY( pxList );              // 列表完整性检查
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );  // 列表项完整性检查
	// 将要插入的列表项插入列表末尾,往列表的末尾添加列表项,由成员变量pxIndex确定,pxIndex指向列表项代表列表头,新列表项插入到pxIndex前面
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;
	mtCOVERAGE_TEST_DELAY();
	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;
	pxNewListItem->pvContainer = ( void * ) pxList;   // 标记新的列表项pxNewListItem,属于列表PxList
	( pxList->uxNumberOfItems )++;                    // 记录列表中的列表项数目的变量加一
}

4.2 列表项末尾插入图示

1)默认列表
在插入列表项前准备一个默认列表:
在这里插入图片描述
注意pxIndex指向的列表项为ListItem1。

2) 插入值为50的列表项
在上面的列表中插入值为50的列表项ListItem3,插入完成后如图:
在这里插入图片描述
列表List的pxIndex指向列表项的ListItem1,因此用vListInsertEnd()函数插入ListItem3的话就会在ListItem1的前面插入。

5. 列表项的删除

列表项的删除通过函数uxListRemove()完成,如下:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	// 读取列表项成员变量pvContainer获知此列表项处于哪个列表中
	List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
	// 与下面一行代码完成列表项的删除
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
	mtCOVERAGE_TEST_DELAY();
	if( pxList->pxIndex == pxItemToRemove )
	// 如果pxIndex正好指向要删除的列表项,那么删除后要重新给pxIndex找对象,新对象是被删除的列表项的前一个列表项
	{	pxList->pxIndex = pxItemToRemove->pxPrevious;	}
	else	{	mtCOVERAGE_TEST_MARKER();	}
	pxItemToRemove->pvContainer = NULL;  // 被删除的列表项的成员变量pvContainer清零
	( 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 );																			
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;	// PxIndex变量指向下一个列表项
	// 如果pxIndex指向列表的xListEnd成员变量,表示到列表末尾						
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	
	{	// 如果到列表末尾的话就跳过xListEnd,pxIndex再一次重新指向处于列表头的列表项,完成一次对列表的遍历																					
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						
	}																						
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;	// 将pxIndex所指向的新列表项的pvOwner赋值为pxTCB										
}

此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是基于STM32FreeRTOS的万年历代码的一个简单示例: ```c #include "stm32f4xx.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" #include "lcd.h" #include "rtc.h" #include "calendar.h" // 定义任务句柄 xTaskHandle xTaskDisplayHandle, xTaskInputHandle, xTaskRTCUpdateHandle; // 定义信号量句柄 xSemaphoreHandle xSemaphoreInput; // 定义输入缓冲区和指针 char cInputBuffer[32] = {'\0'}; uint8_t ucInputIndex = 0; // 显示任务 void vTaskDisplay(void *pvParameters) { char cBuffer[32] = {'\0'}; for (;;) { // 获取当前时间 RTC_DateTypeDef Date; RTC_TimeTypeDef Time; RTC_GetDate(RTC_Format_BIN, &Date); RTC_GetTime(RTC_Format_BIN, &Time); // 格式化时间字符串 sprintf(cBuffer, "%04d-%02d-%02d %02d:%02d:%02d", Date.RTC_Year + 2000, Date.RTC_Month, Date.RTC_Date, Time.RTC_Hours, Time.RTC_Minutes, Time.RTC_Seconds); // 清屏并显示时间 LCD_Clear(); LCD_DisplayString(0, 0, "STM32F4 Calendar"); LCD_DisplayString(1, 0, cBuffer); // 暂停任务,等待1秒 vTaskDelay(1000 / portTICK_RATE_MS); } } // 输入任务 void vTaskInput(void *pvParameters) { char cBuffer[32] = {'\0'}; uint8_t ucKey = 0; BaseType_t xResult; for (;;) { // 等待信号量 xSemaphoreTake(xSemaphoreInput, portMAX_DELAY); // 获取输入缓冲区和指针 memcpy(cBuffer, cInputBuffer, sizeof(cInputBuffer)); ucInputIndex = 0; // 处理输入 if (strcmp(cBuffer, "date") == 0) { // 获取当前日期并显示 RTC_DateTypeDef Date; RTC_GetDate(RTC_Format_BIN, &Date); sprintf(cBuffer, "%04d-%02d-%02d", Date.RTC_Year + 2000, Date.RTC_Month, Date.RTC_Date); LCD_DisplayString(3, 0, cBuffer); } else if (strcmp(cBuffer, "time") == 0) { // 获取当前时间并显示 RTC_TimeTypeDef Time; RTC_GetTime(RTC_Format_BIN, &Time); sprintf(cBuffer, "%02d:%02d:%02d", Time.RTC_Hours, Time.RTC_Minutes, Time.RTC_Seconds); LCD_DisplayString(3, 0, cBuffer); } else if (strcmp(cBuffer, "set date") == 0) { // 设置日期 xResult = xTaskCreate(vTaskCalendar, "Calendar", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); if (xResult != pdPASS) { LCD_DisplayString(3, 0, "Error: Failed to create calendar task"); } } else if (strcmp(cBuffer, "help") == 0) { // 显示帮助信息 LCD_DisplayString(3, 0, "date - show date"); LCD_DisplayString(4, 0, "time - show time"); LCD_DisplayString(5, 0, "set date - set date"); LCD_DisplayString(6, 0, "help - show help"); } else { // 显示错误信息 LCD_DisplayString(3, 0, "Error: Invalid command"); } // 清空输入缓冲区 memset(cInputBuffer, 0, sizeof(cInputBuffer)); } } // RTC更新任务 void vTaskRTCUpdate(void *pvParameters) { for (;;) { // 等待RTC秒更新 xSemaphoreTake(xSemaphoreRTC, portMAX_DELAY); // 发送信号量以触发输入任务 xSemaphoreGive(xSemaphoreInput); } } int main(void) { BaseType_t xResult; // 初始化硬件 LCD_Init(); RTC_Init(); // 创建信号量 xSemaphoreInput = xSemaphoreCreateBinary(); // 创建任务 xResult = xTaskCreate(vTaskDisplay, "Display", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &xTaskDisplayHandle); if (xResult != pdPASS) { LCD_DisplayString(0, 0, "Error: Failed to create display task"); while (1); } xResult = xTaskCreate(vTaskInput, "Input", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &xTaskInputHandle); if (xResult != pdPASS) { LCD_DisplayString(0, 0, "Error: Failed to create input task"); while (1); } xResult = xTaskCreate(vTaskRTCUpdate, "RTC Update", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 3, &xTaskRTCUpdateHandle); if (xResult != pdPASS) { LCD_DisplayString(0, 0, "Error: Failed to create RTC update task"); while (1); } // 启动调度器 vTaskStartScheduler(); // 不应该运行到这里 while (1); } ``` 这个示例代码包括三个任务: - 显示任务:每秒钟更新一次LCD显示器上的时间。 - 输入任务:等待用户输入命令,根据命令执行相应的操作。 - RTC更新任务:等待RTC秒更新中断,并发送信号量以触发输入任务。 这个示例代码还包括一个信号量和一个输入缓冲区,以便在输入任务中处理用户输入。用户可以通过按键或串口发送命令,来与万年历进行交互。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天亮继续睡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值