代码编织梦想

FreeRTOS笔记(一)

裸机系统与多任务系统

裸机系统

裸机系统通常分成轮询系统和前后台系统。

轮询系统

​ 轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。

​ 轮询系统是一种非常简单的软件结构,通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。

​ 如果加入了按键操作等需要检测外部信号的事件用来模拟紧急报警,那么整个系统的实时响应能力并不会那么好。

示例 1

int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* 无限循环 */
    for (;;) {
        /* 处理事情 1 */
        DoSomething1();
        /* 处理事情 2 */
        DoSomething2();
        /* 处理事情 3 */
        DoSomething3();
    }
}
前后台系统

​ 相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里称为前台, main 函数里面的无限循环称为后台。

​ 在顺序执行后台程序的时候,如果有中断来临,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序里面标记事件,如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回到后台程序里面处理。

​ 前后台系统确保了事件不会丢失,再加上中断具有可嵌套的功能,这可以大大的提高程序的实时响应能力。在大多数的中小型项目中,前后台系统运用的好,堪称有操作系统的效果。

示例 2

int flag1 = 0;
int flag2 = 0;
int flag3 = 0;
int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* 无限循环 */
    for (;;) {
        if (flag1) {
            /* 处理事情 1 */
            DoSomething1();
        }
        if (flag2) {
            /* 处理事情 2 */
            DoSomething2();
        }
        if (flag3) {
            /* 处理事情 3 */
            DoSomething3();
        }
    }
}
void ISR1(void)
{
    /* 置位标志位 */
    flag1 = 1;
    /* 如果事件处理时间很短,则在中断里面处理
    如果事件处理时间比较长,在回到前台处理 */
    DoSomething1();
}
void ISR2(void)
{
    /* 置位标志位 */
    flag2 = 1;
    /* 如果事件处理时间很短,则在中断里面处理
    如果事件处理时间比较长,在回到前台处理 */
    DoSomething2();
}
void ISR3(void)
{
    /* 置位标志位 */
    flag3 = 1;
    /* 如果事件处理时间很短,则在中断里面处理
    如果事件处理时间比较长,在回到前台处理 */
    DoSomething3();
}

多任务系统

​ 相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。

​ 在多任务系统中, 任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。

​ 相比前后台系统,多任务系统的实时性又被提高了。

示例 3

int flag1 = 0;
int flag2 = 0;
int flag3 = 0;
int main(void)
{
    /* 硬件相关初始化 */
    HardWareInit();
    /* OS 初始化 */
    RTOSInit();
    /* OS 启动,开始多任务调度,不再返回 */
    RTOSStart();
}
void ISR1(void)
{
    /* 置位标志位 */
    flag1 = 1;
}
void ISR2(void)
{
    /* 置位标志位 */
    flag2 = 2;
}
void ISR3(void)
{
    /* 置位标志位 */
    flag3 = 1;
}
void DoSomething1(void)
{
    /* 无限循环,不能返回 */
    for (;;) {
        /* 任务实体 */
        if (flag1) {
        }
    }
}
void DoSomething2(void)
{
    /* 无限循环,不能返回 */
    for (;;) {
        /* 任务实体 */
        if (flag2) {
        }
    }
}
void DoSomething3(void)
{
    /* 无限循环,不能返回 */
    for (;;) {
        /* 任务实体 */
        if (flag3) {
        }
    }
}

​ 相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。

​ 每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担心每个功能模块之间是否存在干扰。

​ 整个系统随之带来的额外开销就是操作系统占据的那一丁点的 FLASH 和 RAM。现如今,单片机的 FLASH 和 RAM 是越来越大,完全足以抵挡 RTOS 那点开销。

表 1 轮询、前后台和多任务系统软件模型区别

模型事件响应事件处理特点
轮询系统主程序主程序轮询响应事件,轮询处理事件
前后台系统中断主程序实时响应事件,轮询处理事件
多任务系统中断任务实时响应事件,实时处理事件

列表和列表项(list & list item)

​ 列表和列表项对应 C 语言当中的链表和节点。

​ 链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表。

单向链表

图 1 单向链表

​ 节点本身必须包含一个节点指针,用于指向后一个节点,除了这个节点指针是必须有的之外,节点本身还可以携带一些私有信息如单个的数据、数组、指针数据和自定义的结构体数据类型等等信息,因此常用结构体来表示节点,见示例 4。

示例 4 节点结构体定义

struct node
{
    struct node *next; /* 指向链表的下一个节点 */
    char data1; /* 单个的数据 */
    unsigned char array[]; /* 数组 */
    unsigned long *prt /* 指针数据 */
    struct userstruct data2; /* 自定义结构体类型数据 */
    /* ...... */
}

​ 除了 struct node *next 这个节点指针之外,剩下的成员都可以理解为节点携带的数据,但是这种方法很少用。

​ 通常的做法是节点里面只包含一个用于指向下一个节点的指针。要通过链表存储的数据内嵌一个节点即可(并非所有节点都有相同的数据值结构),这些要存储的数据通过这个内嵌的节点即可挂接到链表中,如图 2所示,代码见示例 5。

图 2 节点内嵌在一个数据结构中

示例 5

/* 节点定义 */
struct node
{
    struct node *next; /* 指向链表的下一个节点 */
}
struct userstruct
{
    /* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
    struct node *next;
    /* 各种各样......,要存储的数据 */
}

双向链表

​ 双向链表与单向链表的区别就是节点中有两个节点指针,分别指向前后两个节点,如图 3 所示。

图 3 双向链表

图 4 链表与数组对比

  • 链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。
  • 数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分,但是为了方便节点的插入和删除操作会人为的规定一个根节点。

FreeRTOS中链表的实现

一、实现链表节点

1 定义链表节点数据结构

​ FreeRTOS 中与链表相关的操作均在 list.h 和 list.c 这两个文件中实现。

示例 6 list.h—链表节点的数据结构定义

#ifndef __LIST_H__
#define __LIST_H__

#include "portmacro.h"		//经过重定义的数据类型放在 portmacro.h
#include "FreeRTOSConfig.h" //TickType_t该宏在 FreeRTOSConfig.h 

struct xLIST_ITEM
{
	TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
	struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
	struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
	void * pvOwner; /* 指向拥有该节点的内核对象,通常是 TCB */
	void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */

#endif

示例 7 portmacro.h—重定义数据类型

#ifndef __PORTMACRO_H__
#define __PORTMACRO_H__

#include "stdint.h"
#include "stddef.h"

/* 数据类型重定义 */
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long

typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;

#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif

#endif /* PORTMACRO_H */

示例 8 FreeRTOSConfig.h—宏定义

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#define configUSE_16_BIT_TICKS 0

#endif /* FREERTOS_CONFIG_H */

2 链表节点初始化

​ 链表节点初始化函数在 list.c 中实现。

示例 9 list.c—链表节点初始化

#include "list.h"


void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItem->pvContainer = NULL;
}

将pvContainer 初始化为空表示该节点还没有插入到任何链表,如图 5。

图 5 节点初始化

#### 二、实现链表根节点
1 定义链表根节点数据结构

图 6 根节点示意图

/* mini节点结构体定义,作为双向链表的结尾
   因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
	TickType_t xItemValue;                      /* 辅助值,用于帮助节点做升序排列 */
	struct xLIST_ITEM *  pxNext;                /* 指向链表下一个节点 */
	struct xLIST_ITEM *  pxPrevious;            /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;  /* 最小节点数据类型重定义 */


/* 链表结构体定义 */
typedef struct xLIST
{
	UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */
	ListItem_t *  pxIndex;			/* 链表节点索引指针 */
	MiniListItem_t xListEnd;		/* 链表最后一个节点 */
} List_t;
2 链表根节点初始化

图 7 根节点初始化

/*********************初始化********************************/

/* 链表根节点初始化 */
void vListInitialise( List_t * const pxList )
{
	/* 将链表索引指针指向最后一个节点 */
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );

	/* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */
	pxList->xListEnd.xItemValue = portMAX_DELAY;

    /* 将最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空 */
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

	/* 初始化链表节点计数器的值为0,表示链表为空 */
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}


/* 节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItem->pvContainer = NULL;
}


/**********************************************************/

3 将节点插入到链表的尾部

图 8 将节点插入到链表

图 9 将节点插入到链表的尾部

/* 将节点插入到链表的尾部 */
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t * const pxIndex = pxList->pxIndex;		//指向最后一个节点

	pxNewListItem->pxNext = pxIndex;					//①
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;	//②
	pxIndex->pxPrevious->pxNext = pxNewListItem;		//③
	pxIndex->pxPrevious = pxNewListItem;				//④

	/* 记住该节点所在的链表 */
	pxNewListItem->pvContainer = ( void * ) pxList;		//⑤

	/* 链表节点计数器++ */
	( pxList->uxNumberOfItems )++;						//⑥
}

4 将节点按照升序排列插入到链表

图 10 将节点按照升序排列插入到链表

/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	
	/* 获取节点的排序辅助值 */
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

	/* 寻找节点要插入的位置 */
	if( xValueOfInsertion == portMAX_DELAY )
	{
		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->pvContainer = ( void * ) pxList;		//⑤

	/* 链表节点计数器++ */
	( pxList->uxNumberOfItems )++;						//⑥
}
5 将节点从链表删除

图 11 将节点从链表删除

/*********************删除节点********************************/

/* 将节点从链表中删除 */
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	/* 获取节点所在的链表 */
	List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	/* Make sure the index is left pointing to a valid item. */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}

	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItemToRemove->pvContainer = NULL;
	
	/* 链表节点计数器-- */
	( pxList->uxNumberOfItems )--;

	/* 返回链表中剩余节点的个数 */
	return pxList->uxNumberOfItems;
}
/*********************************************************/
6 带参宏函数
/*
************************************************************************
*                                宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
/* 获取节点拥有者 */
#define listGET_LIST_ITEM_OWNER( pxListItem )	( ( pxListItem )->pvOwner )

/* 初始化节点排序辅助值 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )	( ( pxListItem )->xItemValue = ( xValue ) )

/* 获取节点排序辅助值 */
#define listGET_LIST_ITEM_VALUE( pxListItem )	( ( pxListItem )->xItemValue )

/* 获取链表根节点的节点计数器的值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext->xItemValue )

/* 获取链表的入口节点 */
#define listGET_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext )

/* 获取链表的第一个节点 */
#define listGET_NEXT( pxListItem )	( ( pxListItem )->pxNext )

/* 获取链表的最后一个节点 */
#define listGET_END_MARKER( pxList )	( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList )	( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )

/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )

/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
	List_t * const pxConstList = ( pxList );											    \
	/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,
    如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	/* 当前链表为空 */                                                                       \
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	/* 获取节点的OWNER,即TCB */                                                             \
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											 \
}

#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )

示例10 list.h

#ifndef LIST_H
#define LIST_H
/*
************************************************************************
*                                头文件
************************************************************************
*/
#include "FreeRTOS.h"



/*
************************************************************************
*                                结构体定义
************************************************************************
*/
/* 节点结构体定义 */
struct xLIST_ITEM
{
	TickType_t xItemValue;             /* 辅助值,用于帮助节点做顺序排列 */			
	struct xLIST_ITEM *  pxNext;       /* 指向链表下一个节点 */		
	struct xLIST_ITEM *  pxPrevious;   /* 指向链表前一个节点 */	
	void * pvOwner;					   /* 指向拥有该节点的内核对象,通常是TCB */
	void *  pvContainer;		       /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t;  /* 节点数据类型重定义 */



/* mini节点结构体定义,作为双向链表的结尾
   因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
	TickType_t xItemValue;                      /* 辅助值,用于帮助节点做升序排列 */
	struct xLIST_ITEM *  pxNext;                /* 指向链表下一个节点 */
	struct xLIST_ITEM *  pxPrevious;            /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;  /* 最小节点数据类型重定义 */


/* 链表结构体定义 */
typedef struct xLIST
{
	UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */
	ListItem_t *  pxIndex;			/* 链表节点索引指针 */
	MiniListItem_t xListEnd;		/* 链表最后一个节点 */
} List_t;


/*
************************************************************************
*                                宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
/* 获取节点拥有者 */
#define listGET_LIST_ITEM_OWNER( pxListItem )	( ( pxListItem )->pvOwner )

/* 初始化节点排序辅助值 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )	( ( pxListItem )->xItemValue = ( xValue ) )

/* 获取节点排序辅助值 */
#define listGET_LIST_ITEM_VALUE( pxListItem )	( ( pxListItem )->xItemValue )

/* 获取链表根节点的节点计数器的值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext->xItemValue )

/* 获取链表的入口节点 */
#define listGET_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext )

/* 获取链表的第一个节点 */
#define listGET_NEXT( pxListItem )	( ( pxListItem )->pxNext )

/* 获取链表的最后一个节点 */
#define listGET_END_MARKER( pxList )	( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList )	( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )

/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )

/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
	List_t * const pxConstList = ( pxList );											    \
	/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,
    如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	/* 当前链表为空 */                                                                       \
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	/* 获取节点的OWNER,即TCB */                                                             \
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											 \
}

#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )

/*
************************************************************************
*                                函数声明
************************************************************************
*/
void vListInitialise( List_t * const pxList );
void vListInitialiseItem( ListItem_t * const pxItem );
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );

#endif /* LIST_H */

示例11 list.c

#include "list.h"


/*********************初始化********************************/

/* 链表根节点初始化 */
void vListInitialise( List_t * const pxList )
{
	/* 将链表索引指针指向最后一个节点 */
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );

	/* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */
	pxList->xListEnd.xItemValue = portMAX_DELAY;

    /* 将最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空 */
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

	/* 初始化链表节点计数器的值为0,表示链表为空 */
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}


/* 节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItem->pvContainer = NULL;
}


/**********************************************************/


/*********************插入节点********************************/

/* 将节点插入到链表的尾部 */
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t * const pxIndex = pxList->pxIndex;

	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;
	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;

	/* 记住该节点所在的链表 */
	pxNewListItem->pvContainer = ( void * ) pxList;

	/* 链表节点计数器++ */
	( pxList->uxNumberOfItems )++;
}


/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	
	/* 获取节点的排序辅助值 */
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

	/* 寻找节点要插入的位置 */
	if( xValueOfInsertion == portMAX_DELAY )
	{
		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->pvContainer = ( void * ) pxList;

	/* 链表节点计数器++ */
	( pxList->uxNumberOfItems )++;
}


/**********************************************************/


/*********************删除节点********************************/

/* 将节点从链表中删除 */
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	/* 获取节点所在的链表 */
	List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	/* Make sure the index is left pointing to a valid item. */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}

	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItemToRemove->pvContainer = NULL;
	
	/* 链表节点计数器-- */
	( pxList->uxNumberOfItems )--;

	/* 返回链表中剩余节点的个数 */
	return pxList->uxNumberOfItems;
}

三、链表节点插入实验

示例 12 main.c

  
/*
*************************************************************************
*                             包含的头文件
*************************************************************************
*/
#include "list.h"

/*
*************************************************************************
*                              全局变量
*************************************************************************
*/

/* 定义链表根节点 */
struct xLIST       List_Test;

/* 定义节点 */
struct xLIST_ITEM  List_Item1;
struct xLIST_ITEM  List_Item2;
struct xLIST_ITEM  List_Item3;



/*
************************************************************************
*                                main函数
************************************************************************
*/
/*
* 注意事项:1、该工程使用软件仿真,debug需选择 Ude Simulator
*           2、在Target选项卡里面把晶振Xtal(Mhz)的值改为25,默认是12,
*              改成25是为了跟system_ARMCM3.c中定义的__SYSTEM_CLOCK相同,确保仿真的时候时钟一致
*/
int main(void)
{	
	
    /* 链表根节点初始化 */
    vListInitialise( &List_Test );
    
    /* 节点1初始化 */
    vListInitialiseItem( &List_Item1 );
    List_Item1.xItemValue = 1;
    
    /* 节点2初始化 */    
    vListInitialiseItem( &List_Item2 );
    List_Item2.xItemValue = 2;
    
    /* 节点3初始化 */
    vListInitialiseItem( &List_Item3 );
    List_Item3.xItemValue = 3;
    
    /* 将节点插入链表,按照升序排列 */
    vListInsert( &List_Test, &List_Item2 );    
    vListInsert( &List_Test, &List_Item1 );
    vListInsert( &List_Test, &List_Item3 );    
    
    for(;;)
	{
		/* 啥事不干 */
	}
}

图 12 仿真结果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/m0_48830645/article/details/107582475

基于GEC6818智能家居的实现--点亮LED灯-爱代码爱编程

嵌入式Linux学习篇: Linux内核模块 Linux内核模块----Linux Kernel Module。 在Linux内核中,驱动程序是以模块的形式存在,每个驱动程序都是一个个独立的模块。模块之间可以是独立的,不相关的。 通俗而言:Linux的设备驱动程序是存放在Linux内核模块中的,我们设计驱动程序之前,先要设计一个Linux内核模块

第三阶段应用层——2.4 视频监控—从0写USB摄像头驱动(3)-实现数据传输(完善)-爱代码爱编程

视频监控—从0写USB摄像头驱动(3)-实现数据传输(完善) 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统参考资料:USB_Video_Example 1.5、UVC 1.5 Class specifica

字符设备驱动系列之——第一个驱动-爱代码爱编程

字符设备驱动系列之——第一个驱动 说明环境配置正文一、file_operation结构体插曲(关于打印级别)二、入出口函数和模块许可证入口函数出口函数模块许可证完整的代码清单一三、测试程序四、驱动程序改进完整的代码清单二(改进后的代码) 说明 该系列文章为《韦东山嵌入式Linux之第2期_驱动大全》的学习笔记,是自己用的一个记录,也是对所学内

TPYBoard中编译MicroPython并利用DfuSe经行固件烧录-爱代码爱编程

前景提要 ======================================================================== 从对Ubuntu虚拟机的网络配置到了利用MicroPython开发物联网终端,这一过程即艰辛又有趣,基本上都是书上看一遍,电脑上打一遍,问题错一片,解决方法找一夜,最后博客水一篇。(我的设备阿里云Ub

嵌入式之NB-IoT开发与应用01【移动通信网络发展概述、NB-IoT应用案例、物联网生态系统-解决方案、智慧消防项目需求分析及系统设计】-爱代码爱编程

学习网址: 嵌入式之NB-IoT开发与应用 目   录 P1 1.01-01 NB-IoT课程介绍(P1) NB-IoT是什么? NB-IoT能够干什么? 1、移动通信网络发展概述 移动通信网络-1G 移动通信网络-2G 移动通信网络-3G 移动通信网络-4G 移动通信网络-5G 移动通信网络总结 NB-IoT发展历程

新概念模拟电路——晶体管构建的放大电路静态分析【Fire Man】-爱代码爱编程

欢迎来到精神小火君的科技主义教室! Ps:记录模电学习路上的艰苦旅程,加油UPUPUP! 目录 欢迎来到精神小火君的科技主义教室!一、晶体管引入与特性Section1.基本放大电路的构建(1) 通过一个简单的放大电路实现对一个微小正弦波的放大Section2.耦合(1) 什么是耦合?(2)阻容耦合(3)仿真演示Section3.晶体管的四种放大状态

FreeRTOS+CubeMX系列第一篇——初识FreeRTOS-爱代码爱编程

文章目录 一.关于FreeRTOS二.FreeRTOS的特点三.如何在CubeMX上配置FreeRTOS四.FreeRTOS文档资料五.同系列博客 一.关于FreeRTOS 1.什么是FreeRTOS? FreeRTOS是一个迷你的实时操作系统内核。是一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功

物联网嵌入式系统:FreeRTOS任务挂起和恢复-爱代码爱编程

1.任务挂起和恢复 要使用着些API则需要使能宏定义: INCLUDE_vTaskSuspend、INCLUDE_xTaskResumeFromISR 1.1任务挂起 任务挂起:将任务控制块、堆栈保存,然后将任务停止,当任务需要开始运行的时候,则继续之前的状态开始运行,无需重新创建。 任务删除:那么就是将当前的任务控制块、堆栈都释放掉,然后停止运

FreeRTOS 常用的几个函数-爱代码爱编程

1)vTaskSuspend(TaskHandle_t     Task_ID) :挂起指定任务。被挂起的任务绝不会得到CPU的使用权,不管该任务具有什么优先        级。        使用实例      static TaskHandle_t     LED_Task_Handle = NULL;/* LED 任务句柄    */      

FreeRTOS任务状态切换(就绪、挂起、运行、删除、恢复、延时、阻塞)-小结-爱代码爱编程

1. 任务的状态切换 FreeRTOS中任务的状态可分为:未创建态、就绪态、运行态、挂起态、延时态五种状态。 下图总结了一个任务可能出现的任务转换流程: a: 调用xTaskCreate()函数将新建一个任务,新建的任务会加入到就绪列表,若新建的任务的优先级足够高,调度器会立即将CPU资源分配给他,使它进入运行态。 b: 调度器检查就绪列表中优

FreeRTOS笔记(二)-爱代码爱编程

FreeRTOS笔记(二)—静态任务 文章目录 FreeRTOS笔记(二)---静态任务一、任务定义二、任务创建2.1 定义任务栈2.2 定义任务函数2.3 定义任务控制块2.4 实现任务创建函数三、实现就绪列表3.1 定义就绪列表3.2 就绪列表初始化3.3 将任务插入到就绪列表四、实现调度器4.1 启动调度器4.2 任务切换五、main函数六

FreeRTOS代码风格和数据类型-爱代码爱编程

一、数据类型(Data types) FreeRTOS在不同的平台下面的文件夹里都有一个独一无二的头文件“portmacro.h”,它里面定义了两个针对平台的数据类型。它们分别是TickType_t和BaseType_t。 二、变量命名规则: FreeRTOS的变量名称采用前缀来标识变量的类型。对应关系如下如果是无符号类型,那么变量名称的前面就会多