【FreeRTOS】任务创建

参考博客:

ESP-IDF FreeRTOS 任务创建分析 - [Genius] - 博客园 (cnblogs.com)

1.什么是任务

1)独立的无法返回的函数称为任务

2)任务是无线循环

3)无返回数据

2.任务的实现过程

1.定义任务栈

裸机程序:统一分配到一个栈里面,栈就是RAM里面的一个一段连续的空间。栈的大小一般在启动文件或者链接文件里面定义的,最后由c库函数main初始化。【比如stm32的startup_stm32f10x_hd.s文件中】

操作系统:每一个任务都是独立的,所以每一个任务都有各自的栈空间。这个栈一般是全局数组也可以是动态分配的一段内存空间,也是存储在RAM。

2.定义任务函数

不能返回,无限循环

3.定义任务控制块:TCB

1)存储任务的全部信息

2)xStateListItem:相当于我们的任务是衣服,这个变量是衣架

4.列表:List_t

列表项:ListItem_t

迷你列表项:MiniListItem_t

typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	//列表项的个数
	configLIST_VOLATILE UBaseType_t uxNumberOfItems;
	//当前列表项索引号
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
	//指向最后一个列表项
	MiniListItem_t xListEnd;							/*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;

 

5.列表项

列表可以看做所有列表项的集合,就跟链表看做所有结点的集合一样

1.普通列表项:ListItem_t

功能最齐全

struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	//列表项的值
	configLIST_VOLATILE TickType_t xItemValue;			/*< The value being listed.  In most cases this is used to sort the list in descending order. */
	//指向前一个链表项
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< Pointer to the next ListItem_t in the list. */
	//指向下一个链表项
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< Pointer to the previous ListItem_t in the list. */
	//指明此时列表项属于谁,指向我们这个任务的任务控制块
	void * pvOwner;										/*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
	//指定我们这个列表项属于哪一个列表
	void * configLIST_VOLATILE pvContainer;				/*< Pointer to the list in which this list item is placed (if any). */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t;					/* For some reason lint wants this as two separate definitions. */

2.迷你列表项:MiniListItem_t

功能较少

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	//列表项的值
	configLIST_VOLATILE TickType_t xItemValue;
	//指向下一个列表项
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	//指向上一个列表项
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

6.列表初始化

void vListInitialise( List_t * const pxList )
{
	/* The list structure contains a list item which is used to mark the
	end of the list.  To initialise the list the list end is inserted
	as the only list entry. */
	//表示此时列表只有一个列表项
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			/*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */

	/* The list end value is the highest possible value in the list to
	ensure it remains at the end of the list. */
	//给最后一个列表项赋值
	pxList->xListEnd.xItemValue = portMAX_DELAY;

	/* The list end next and previous pointers point to itself so we know
	when the list is empty. */
	//因为此时列表中只有一个列表项,所以最后一个列表项的next指向他自己
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	/*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
	//因为此时列表中只有一个列表项,所以最后一个列表项的pre指向他自己
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
	//列表中的最后一个列表项不计入我们的列表项计数值,所以初始化个数为0
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	//用于完整性检查的字段
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

 7.列表项初始化

为什么就初始化了一个成员变量??

其他的成员变量什么时候初始化呢?这是因为列表项要根据实际使用情况来初始化,比如任务创建函数xTaskCreate()就会对任务堆栈中的xStateListItem和 xEventListItem这两个列表项中的其他成员变量在做初始化。

void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* Make sure the list item is not recorded as being on a list. */
	//表示此时TCB属于哪一个任务的
	pxItem->pvContainer = NULL;

	/* Write known values into the list item if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

3.列表项

1.插入函数:双向链表

	//表示此时输入的列表项==最大值,表示此时应该设置为end的pre
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else//此时输入的列表项<最大值,就要查找应该插入列表中的哪一个位置
	{
	//查找列表项的插入点【升序查询】
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) 
		{
			/* There is nothing to do here, just iterating to the wanted
			insertion position. */
		}

	//找到待出入点的位置,就将待插入值与指针和指针的next联系起来
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

pxindex是当前列表项指针,指向头节点,pxindex->next就是ListItem1

/**
	列表项插入函数
		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 );


	
	//表示此时输入的列表项==最大值,表示此时应该设置为end的pre
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else//此时输入的列表项<最大值,就要查找应该插入列表中的哪一个位置
	{
		
	//查找列表项的插入点【升序查询】
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) 
		{
			/* There is nothing to do here, just iterating to the wanted
			insertion position. */
		}
	}
	//找到待出入点的位置,就将待插入值与指针和指针的next联系起来
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

	/* Remember which list the item is in.  This allows fast removal of the
	item later. */
	//指定此时列表项属于哪一个列表
	pxNewListItem->pvContainer = ( void * ) pxList;
	//列表项数值要+1
	( pxList->uxNumberOfItems )++;
}

2.插入列表末尾

列表项中的index变量是用于遍历列表的,这里我们指:插入列表末尾——-->实际上就是指插入index->pre中。而不是插入end->pre中。

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

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

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

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;

	/* Only effective when configASSERT() is also defined, these tests may catch
	the list data structures being overwritten in memory.  They will not catch
	data errors caused by incorrect configuration or use of FreeRTOS. */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* Insert a new list item into pxList, but rather than sort the list,
	makes the new list item the last item to be removed by a call to
	listGET_OWNER_OF_NEXT_ENTRY(). */
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

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

	/* Remember which list the item is in. */
	pxNewListItem->pvContainer = ( void * ) pxList;

	( pxList->uxNumberOfItems )++;
}

3.列表删除

1)列表项删除后返回的是剩余的列表项个数

2)列表项删除:实际上将列表项从列表中删除,并不会将这个列表项在内存中释放

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
	//获取此时要删除的列表项处于哪一个列表中
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
	//实际上就将将待删除的列表项的pre和next分别指向前一个和后一个
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

	/* Make sure the index is left pointing to a valid item. */
	if( pxList->pxIndex == pxItemToRemove )//表示此时要删除的是index这一项
	{
		//新的index指向待删除的前一个
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	//删除后的列表项的所属列表就没有了,为NULL
	pxItemToRemove->pvContainer = NULL;
	( pxList->uxNumberOfItems )--;
	//返回列表中剩余的列表项个数
	return pxList->uxNumberOfItems;
}

4.列表遍历

1)列表遍历会自动的将index移动到下一位

2)并且返回pxown

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\        //当index遍历到end的时候,会自动将index重新指向第一个列表项
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
    //将新的owner赋值给TCB
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

4.任务创建 

1.动态创建:xTaskCreate

    pxTaskCode:空指针(任务函数)
    pcName:任务名称
    usStackDepth:任务堆栈大小
    pvParameters:传递给任务函数的参数
    uxPriority:任务优先级
    pxCreatedTask:任务控制块

//当这个宏定义(动态申请内存)设置为1,才可以调用这个函数
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

/**
	pxTaskCode:空指针(任务函数)
	pcName:任务名称
	usStackDepth:任务堆栈大小
	pvParameters:传递给任务函数的参数
	uxPriority:任务优先级
	pxCreatedTask:任务控制块
*/
	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
		//任务控制块
	TCB_t *pxNewTCB;
		//任务句柄:指向任务控制块TCB【空指针】
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		#if( portSTACK_GROWTH > 0 )
		{
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

				if( pxNewTCB->pxStack == NULL )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */
		{
		StackType_t *pxStack;

			/* Allocate space for the stack used by the task being created. */
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

			if( pxStack != NULL )
			{
				/* Allocate space for the TCB. */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

				if( pxNewTCB != NULL )
				{
					/* Store the stack location in the TCB. */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created dynamically in case it is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */
			//在真正创建任务之前需要进行的一些初始化操作
			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			//将任务添加到就绪列表中
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

2.静态创建:xTaskCreateStatic

    pxTaskCode:空指针(任务函数)
    pcName:任务名称
    usStackDepth:任务堆栈大小
    pvParameters:传递给任务函数的参数
    uxPriority:任务优先级
    puxStackBuffer:任务堆栈                    
    pxCreatedTask:任务控制块


#if( configSUPPORT_STATIC_ALLOCATION == 1 )

	TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,
									const char * const pcName,
									const uint32_t ulStackDepth,
									void * const pvParameters,
									UBaseType_t uxPriority,
									StackType_t * const puxStackBuffer,
									StaticTask_t * const pxTaskBuffer ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

		configASSERT( puxStackBuffer != NULL );
		configASSERT( pxTaskBuffer != NULL );

		if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
		{
			/* The memory used for the task's TCB and stack are passed into this
			function - use them. */
			pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */
			pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;

			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created statically in case the task is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );
			prvAddNewTaskToReadyList( pxNewTCB );
		}
		else
		{
			xReturn = NULL;
		}

		return xReturn;
	}

#endif /* SUPPORT_STATIC_ALLOCATION */

3.prvInitialiseNewTask

作用:函数prvInitialiseNewTask ()用于完成对任务的初始化

1)列表项的初始化【初始化任务控制块(TCB),配置任务堆栈,保存现场信息,设置任务名称和优先级】--->vListInitialiseItem

2)将设置好的列表项存储到就绪队列---->pxPortInitialiseStack

4.pxPortInitialiseStack

参考博客:

Freertos初始化任务堆栈_pxportinitialisestack-CSDN博客

1) 调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack字段。

2)在FreeRTOS的任务创建过程中,xTaskCreate 或 xTaskCreateStatic 函数会调用 pxPortInitialiseStack 来初始化新任务的堆栈。初始化完成后,新任务就可以被添加到就绪列表中,等待调度器在合适的时间将其切换为运行态。

 //这里说的栈应该是任务堆栈,他们传给cpu的寄存器,相当于这个任务临时再用这个cpu
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */
	//加载寄存器
	pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

5.实现就绪列表

1)定义

2)初始化就绪列表

3)将任务添加到就绪列表中

1.定义就绪列表:pxReadyTasksLists[configMAX_PRIORITIES]

实际上是一个数组

2.初始化就绪列表:prvInitialiseTaskLists

1)初始化绪列表函数【prvAddNewTaskToReadyList(这个函数又在创建任务函数中被调用)】中被调用

2)FreeRTOS使用列表来跟踪和管理任务,包括任务的就绪列表、延迟列表、挂起列表等。prvInitialiseTaskLists函数确保这些列表在系统启动或第一个任务创建时被正确初始化。

3)prvInitialiseTaskLists函数通常只在系统启动或第一个任务创建时被调用一次。在后续的任务创建过程中,FreeRTOS会使用其他函数来管理和维护任务列表。

3.将任务添加到就绪列表:prvAddTaskToReadyList

1)这个函数是在将任务添加到就绪列表函数【prvAddNewTaskToReadyList(这个函数又在创建任务函数中被调用)】中被调用

2)在这个函数底部调用了【vListInsertEnd】,所以表示插入到index->pre中

6.实现调度器

1.启动调度器

1.最外层的调度函数:vTaskStartScheduler

1)调度器是任务调度的核心。

2)主要作用:实现任务的切换。从就绪列表中查找优先级最高的任务执行。

1.启动静态任务

该函数中调用的其他函数:

1)vApplicationGetIdleTaskMemory-->获取空闲任务的任务堆栈和任务控制块内存

2)xTimerCreateTimerTask-->启动软件定时器

2.启动动态任务

2.启动硬件调度:xPortStartScheduler

1)这个函数也是在最外层的任务调度函数【vTaskStartScheduler】中调用

2)专门启动与硬件相关的任务,例如滴答定时器,中断等

3.启动第一个任务:prvStartFirstTask

1)这个函数是在硬件调度的函数【xPortStartScheduler】中被调用

2)这个函数被调用后不会被返回,是汇编编写

3)该函数的作用:

4.真正启动第一个任务:vPortSVCHandler

参考博客:

【FreeRTOS】2. SVC系统调用_freertos svc-CSDN博客

FreeRTOS启动第一个任务和任务的切换实现过程_vportsvchandler-CSDN博客

vPortSVCHandler  与   SVC_Handler的关系

我们在stm32f103xx_it.c中有一个中断函数(中断向量表)【SVC_Handler】这个与我们此时的FreeRTOS的【vPortSVCHandler】实际上是一致的,但是函数不一样无法进行调用。

所以我们只能配置FreeRTOS的配置文件来进行宏定义。

1)FreeRTOS中的vPortSVCHandler函数是一个中断处理函数,用于处理SVC(Supervisor Call)中断。

2)SVC中断是一种特殊的异常/中断类型,在ARM Cortex-M系列微控制器中用于在用户模式和特权模式之间进行切换。

3)当FreeRTOS启动第一个任务时,它会调用prvStartFirstTask函数来初始化MSP(主堆栈指针)并产生SVC系统调用。这会导致执行流程跳转到vPortSVCHandler中断服务函数。

5.真正实现任务切换:xPortPendSVHandler

当系统需要进行任务切换时,例如在调用taskYIELD()函数或者从中断服务例程返回时,FreeRTOS会在适当的时候触发PendSV中断,从而调用xPortPendSVHandler函数进行任务切换。

2.vPortSVCHandler和xPortPendSVHandler

FreeRTOS是一个实时操作系统(RTOS),它特别适用于微控制器和其他小型嵌入式系统。在FreeRTOS中,vPortSVCHandlerxPortPendSVHandler是两个关键的中断处理函数,它们各自在系统中扮演着重要的角色。

7.静态任务的创建

0.基本过程 

1)定义一些我们需要使用到的堆栈,空闲任务堆栈,控制块,控制块句柄等变量

2)main函数

3)初始化相关的硬件

4)创建任务

        创建任务函数

                创建

                删除任务(在内存中进行删除)

参考博客:

FreeRTOS之vTaskDelete()-CSDN博客

5)启动调度器

1.设置相关的宏定义

当我们使用静态全局变量创建任务【configSUPPORT_STATIC_ALLOCATION】设置为1

2.静态创建需要定义的两个函数

静态创建的任务需要自己手动创建内存。【空闲任务内存+时间任务内存】

//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
								   StackType_t **ppxIdleTaskStackBuffer, 
								   uint32_t *pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;
	*ppxIdleTaskStackBuffer=Idle_Task_Stack;
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

//获取定时器任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer	:		任务控制块内存
//ppxTimerTaskStackBuffer:	任务堆栈内存
//pulTimerTaskStackSize	:		任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize)
{
	*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
	*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
	*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}

3.完整代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"


//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);


//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#define TASK1_STK_SIZE 		128  
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);


//任务优先级
#define TASK2_TASK_PRIO		3
//任务堆栈大小	
#define TASK2_STK_SIZE 		128 
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);


/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];

/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;	
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;

//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
								   StackType_t **ppxIdleTaskStackBuffer, 
								   uint32_t *pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;
	*ppxIdleTaskStackBuffer=Idle_Task_Stack;
	*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

//获取定时器任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer	:		任务控制块内存
//ppxTimerTaskStackBuffer:	任务堆栈内存
//pulTimerTaskStackSize	:		任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize)
{
	*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
	*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
	*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}


/*******************************************************************************
* 函 数 名         : main
* 函数功能		   : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	LED_Init();
	USART1_Init(115200);
	
	//创建开始任务
	StartTask_Handler=xTaskCreateStatic((TaskFunction_t	)start_task,		//任务函数
										(const char* 	)"start_task",		//任务名称
										(uint32_t 		)START_STK_SIZE,	//任务堆栈大小
										(void* 		  	)NULL,				//传递给任务函数的参数
										(UBaseType_t 	)START_TASK_PRIO, 	//任务优先级
										(StackType_t*   )StartTaskStack,	//任务堆栈
										(StaticTask_t*  )&StartTaskTCB);	//任务控制块              
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
	/*
		临界区:在进行任务的时候,不会受到其他任务调度的影响
	*/
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
	Task1Task_Handler=xTaskCreateStatic((TaskFunction_t	)task1_task,		
										(const char* 	)"task1_task",		
										(uint32_t 		)TASK1_STK_SIZE,	
										(void* 		  	)NULL,				
										(UBaseType_t 	)TASK1_TASK_PRIO, 	
										(StackType_t*   )Task1TaskStack,	
										(StaticTask_t*  )&Task1TaskTCB);	
    //创建TASK2任务
	Task2Task_Handler=xTaskCreateStatic((TaskFunction_t	)task2_task,		
										(const char* 	)"task2_task",		
										(uint32_t 		)TASK2_STK_SIZE,	
										(void* 		  	)NULL,				
										(UBaseType_t 	)TASK2_TASK_PRIO, 	
										(StackType_t*   )Task2TaskStack,	
										(StaticTask_t*  )&Task2TaskTCB);
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//任务1函数
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}

//任务2函数
void task2_task(void *pvParameters)
{
    while(1)
    {
        LED2=0;
        vTaskDelay(800);
        LED2=1;
        vTaskDelay(200);
    }
}

8.动态任务创建

会自动申请内存空间

1.完整代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"


//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LED1_TASK_PRIO		2
//任务堆栈大小	
#define LED1_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);

//任务优先级
#define LED2_TASK_PRIO		3
//任务堆栈大小	
#define LED2_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);


/*******************************************************************************
* 函 数 名         : main
* 函数功能		   : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	LED_Init();
	USART1_Init(115200);
	
	//创建开始任务
    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();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
      
    //创建LED1任务
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler); 
				
	//创建LED2任务
    xTaskCreate((TaskFunction_t )led2_task,     
                (const char*    )"led2_task",   
                (uint16_t       )LED2_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED2_TASK_PRIO,
                (TaskHandle_t*  )&LED2Task_Handler); 
				
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

//LED1任务函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}

//LED2任务函数
void led2_task(void *pvParameters)
{
    while(1)
    {
        LED2=0;
        vTaskDelay(800);
        LED2=1;
        vTaskDelay(200);
    }
}

9.FreeRTOS启动流程

1.任务创建

1)启动后-->调用复位函数(Reset handler)--->然后再复位函数中调用__main(触发栈和堆)-->然后才进入主函数--->外设初始化--->创建任务(内部会自动申请文件)-->任务调度(创建空闲任务+定时器任务【最后真正启动调度器】)

2)FreeRTOS初始化:初始化相关的板载外设+创建任务+启动调度器

2.任务调度

空闲任务不可以被挂起和删除

3.主函数

1)当正在执行的任务发生阻塞的时候,会将此时的任务挂起,然后去就绪列表中查找当前优先级最高的继续执行。

2)临界区的重要性!!!!

10.临界区

参考博客:

第七章 FreeRTOS的临界区与任务调度器_freertos 创建临界区-CSDN博客​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/408491.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

centos7部署单机项目和自启动

centos7部署单机项目和服务器自启动 1.安装jdk和tomact1.1上传jdk、tomcat安装包1.2解压两个工具包1.3.配置并且测试jdk安装1.4.启动tomcat1.5.防火墙设置1.6配置tomcat自启动 2.安装mysql2.1卸载mariadb&#xff0c;否则安装MySql会出现冲突(先查看后删除再查看)2.2在线下载My…

2023年12月CCF-GESP编程能力等级认证C++编程七级真题解析

一、单选题(共15题,共30分) 第1题 定义变量 double x ,如果下面代码输入为 100 ,输出最接近( )。 A:0 B:-5 C:-8 D:8 答案:B 第2题 对于下面动态规划方法实现的函数,以下选项中最适合表达其状态转移函数的为( )。 A: B: C: D:

[LWC] Components Communication

目录 Overview ​Summary Sample Code 1. Parent -> Child - Public Setter / Property / Function a. Public Property b. Public getters and setters c. Public Methods 2. Child -> Parent - Custom Event 3. Unrelated Components - LMS (Lightning Message…

Vue packages version mismatch 报错解决

问题 npm run dev 运行项目的过程中&#xff0c;报错 Vue packages version mismatch 解决方法 根据报错不难看出是 vue 与 vue-template-compiler 版本产生了冲突&#xff0c;vue 与 vue-template-compiler 的版本是需要匹配的。所以解决的办法就是先修改其中一个的版本将 v…

[C++]宏定义

C/C宏的基本使用方法 宏是C/C所支持的一种语言特性&#xff0c;我对它最初的印象就是它可以替换代码中的符号&#xff0c;最常见的例子便是定义一个圆周率PI&#xff0c;之后在代码中使用 PI 来代替具体圆周率的值。 确实如此&#xff0c;宏提供了一种机制&#xff0c;能够使…

动力学约束下的运动规划算法——两点边界值最优控制问题 OBVP

OBVP 即 optimal bundary value problem&#xff0c;即最优的BVP&#xff0c; BVP 问题其实就是解决 state sampled lattice planning 的基本操作方法。 如果&#xff0c;我们期望无人机从一个状态移动到另一个状态&#xff0c;即给定初始状态和终点状态&#xff0c;求解两个状…

每日五道java面试题之spring篇(五)

目录&#xff1a; 第一题. 使用 Spring 有哪些方式&#xff1f;第二题. 什么是Spring IOC 容器&#xff1f;第三题. 控制反转(IoC)有什么作用?第四题. IOC的优点是什么&#xff1f;第五题. BeanFactory 和 ApplicationContext有什么区别&#xff1f; 第一题. 使用 Spring 有哪…

【Web】CTFSHOW 常用姿势刷题记录(全)

目录 web801 web802 web803 web804 web805 web806 web807 法一&#xff1a;反弹shell 法二&#xff1a;vps外带 web808 web809 web810 web811 web812 web813 web814 web815 web816 web817 web818 web819 web820 web821 web822 web823 web824 web825…

03|Order by与Group by优化

索引顺序依次是 &#xff1a; name,age,position 案例1 EXPLAIN SELECT * FROM employees WHERE name LiLei AND position dev ORDER BY age;分析: 联合索引中只是用到了name字段做等值查询[通过key_len 74可以看出因为name字段的len74]&#xff0c;在这个基础上使用了age进…

Javaweb之SpringBootWeb案例之配置优先级的详细解析

1. 配置优先级 在我们前面的课程当中&#xff0c;我们已经讲解了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;可以通过这三种方式当中…

什么是MapReduce

1.1 MapReduce到底是什么 Hadoop MapReduce是一个软件框架&#xff0c;基于该框架能够容易地编写应用程序&#xff0c;这些应用程序能够运行在由上千个商用机器组成的大集群上&#xff0c;并以一种可靠的&#xff0c;具有容错能力的方式并行地处理上TB级别的海量数据集。这个定…

docker build基本命令

背景 我们经常会构建属于我们应用自己的镜像&#xff0c;这种情况下编写dockerfile文件不可避免&#xff0c;本文就来看一下常用的dockerfile的指令 常用的dockerfile的指令 首先我们看一下docker build的执行过程 ENV指令&#xff1a; env指令用于设置shell的环境变量&am…

DBAPI如何使用数组类型参数

DBAPI如何使用数组类型参数 需求 根据多个id去查询学生信息 API创建 在基本信息标签&#xff0c;创建参数ids &#xff0c;参数类型选择 Array<bigint> 在执行器标签&#xff0c;填写sql&#xff0c;使用in查询 select * from student where id in <foreach ope…

《Docker 简易速速上手小册》第6章 Docker 网络与安全(2024 最新版)

文章目录 6.1 Docker 网络概念6.1.1 重点基础知识6.1.2 重点案例&#xff1a;基于 Flask 的微服务6.1.3 拓展案例 1&#xff1a;容器间的直接通信6.1.4 拓展案例 2&#xff1a;跨主机容器通信 6.2 配置与管理网络6.2.1 重点基础知识6.2.2 重点案例&#xff1a;配置 Flask 应用的…

设计模式学习笔记 - 面向对象 - 7.为什么要多用组合少用继承?如何决定该用组合还是继承?

前言 在面向对象编程中&#xff0c;有一条非常经典的设计原则&#xff1a;组合优于继承&#xff0c;多用组合少用继承。 为什么不推荐使用继承&#xff1f; 组合比继承有哪些优势&#xff1f; 如何判断该用组合还是继承&#xff1f; 为什么不推荐使用继承&#xff1f; 继承…

企业微信怎么变更企业名称?

企业微信变更主体有什么作用&#xff1f;现在很多公司都用企业微信来加客户&#xff0c;有时候辛辛苦苦积累了很多客户&#xff0c;但是公司却因为各种各样的原因需要注销&#xff0c;那么就需要通过企业微信变更主体的方法&#xff0c;把企业微信绑定的公司更改为最新的。企业…

内核解读之内存管理(8)什么是page cache

文章目录 0. 文件系统的层次结构1.什么是page cache2.感观认识page cache3. Page Cache的优缺点3.1 Page Cache 的优势3.2 Page Cache 的劣势 0. 文件系统的层次结构 在了解page cache之前&#xff0c;我们先看下文件系统的层次结构。 1 VFS 层 VFS &#xff08; Virtual Fi…

Gitflow:一种依据 Git 构建的分支管理工作流程模式

文章目录 前言Gitflow 背景Gitflow 中的分支模型Gitflow 的版本号管理简单模拟 Gitflow 工作流 前言 Gitflow 工作流是一种版本控制流程&#xff0c;主要适用于较大规模的团队。这个流程在团队中进行合作时可以避免冲突&#xff0c;并能快速地完成项目&#xff0c;因此在很多软…

人工智能与机器学习行业新闻:颠覆企业运营方式的 AI 趋势

AI 推动业务转型 人工智能 (AI) 和机器学习已经在重塑各行各业的业务模式。AI 通过处理和整合数据支持战略决策的制定&#xff0c;其规模和速度远远超过了人脑。无疑&#xff0c;未来我们还将在 AI 领域取得许多重大突破&#xff0c;而拥有大量数据的行业可能会从人工智能革命…

Mac OS 下载安装与破解Typora

文章目录 下载Typora破解Typora1. 进入安装目录2. 找到并打开Lincense文件3. 修改激活状态4. 重新打开Typora 下载Typora 官网地址&#xff1a;typora官网 下载最新Mac版&#xff0c;正常安装即可 破解Typora 打开typora,可以看到由于未激活&#xff0c;提示使用期限还剩下15…