【FreeRTOS】任务创建/临界区

参考博客:

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

1.什么是任务

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

2)任务是无线循环

3)无返回数据

任务实际上就是一个无限循环且不带返回值的 C 函数。

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

任务步骤:

1)将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为1

2)定义函数入口参数

3)编写任务函数

此函数创建的任务会立即进入就绪状态,由任务调度器调度即可【根据优先级在进行一个任务调度】

    pxTaskCode:空指针(任务函数)
    pcName:任务名称
    usStackDepth:任务堆栈大小【单位是configSTACK_DEPTH_TYPE==uint16_t,所以这个栈大小默认是2字节】
    pvParameters:传递给任务函数的参数【void *】
    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 */

动态创建任务函数内部实现:

1)申请堆栈内存&&任务控制块内存

2)TCB结构体成员赋值

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

2.静态创建:xTaskCreateStatic

任务步骤:

1)将宏configSUPPORT_STATIC_ALLOCATION 配置为1

2)定义空闲任务和定时器任务堆栈以及TCB

3)实现两个接口函数:

        vApplicationGetIdleTaskMemory()

        vApplicationGetTimerTaskMemory()

{

         为什么看似动态并没有创建空闲任务和定时器任务??其实底层是会动态帮我们创建不需要我们自己手动编写

}

4)定义函数入口参数

5)编写任务函数

    pxTaskCode:空指针(任务函数)
    pcName:任务名称
    usStackDepth:任务堆栈大小【单位是uint32_t--->所以对应默认是4字节】

{

        这个地方与动态申请不一样,动态申请是默认2字节。如果使用两个函数同时传入10,则表示动态申请是10*2,而静态申请是10*4

}
    pvParameters:传递给任务函数的参数
    uxPriority:任务优先级
    puxStackBuffer:任务堆栈                    
    pxCreatedTask:任务控制块

{

        这里也跟动态申请的不一样,动态申请的是使用一个TCB【句柄】,然后用户将参数传入,底层会根据传入的参数进行动态申请。但是静态的只能通过使用堆栈来实现,用户自定义想要的栈大小。

}

 //创建 AppTaskCreate 任务
 AppTaskCreate_Handle = xTaskCreateStatic( (TaskFunction_t)CreateAppTask,        //任务函数(1)
                                           (const char* )"CreateAppTask",        //任务名称(2)
                                           (uint32_t )128,                       //任务堆栈大小 (3)
                                           (void* )NULL,                         //传递给任务函数的参数(4)
                           (UBaseType_t )3,                      //任务优先级 (5)
                                           (StackType_t* )CreateAppTask_Stack,   //任务堆栈(6)
                                           (StaticTask_t* )&CreateAppTask_TCB);  //任务控制块(7)

 if (NULL != CreateAppTask_Handle) //创建成功
    vTaskStartScheduler(); //启动任务,开启调度

 (1): 任务入口函数,即任务函数的名称,需要我们自己定义并且实现。
(2): 任务名字,字符串形式, 最大长度由FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。
(3): 任务堆栈大小,单位为字,在 32 位的处理器下(STM32),一个字等于4个字节,那么任务大小就为 128*4 字节。
(4): 任务入口函数形参,不用的时候配置为0或者NULL即可。
(5): 任务的优先级。优先级范围根据 FreeRTOSConfig.h 中 的 宏configMAX_PRIORITIES 决定, 如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION,这个宏定义,则最多支持 32 个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。
(6): 任务栈起始地址, 只有在使用静态内存的时候才需要提供, 在使用动态内存的时候会根据提供的任务栈大小自动创建。
(7): 任务控制块指针,在使用静态内存的时候,需要给任务初始化函数 xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。

1.vApplicationGetIdleTaskMemory :开启空闲任务

1)为什么需要在静态中编写这个函数??

因为系统不会自动帮我们创建,这个函数的作用【当CPU没有人使用的时候,就会进入空闲任务】

2)最好使用这个:pulIdleTaskStackSize = configMINIMAL_STACK_SIZE

#define IDLE_STACK_DEPTH  128
//静态任务创建使用到的
StackType_t idle_task_stack[ IDLE_STACK_DEPTH ];
StaticTask_t idle_task_tcb;

// 两个接口函数
//空闲任务:当CPU没有人使用的时候,就会进入空闲任务
//实际上传入的三个参数就是我们前面创建启动任务传入的参数
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                        StackType_t ** ppxIdleTaskStackBuffer,
                                        uint32_t * pulIdleTaskStackSize )
{
  * ppxIdleTaskTCBBuffer = &idle_task_tcb;//记得这里要传入的是地址
  * ppxIdleTaskStackBuffer = idle_task_stack;//数组名表示首地址
   //因为空闲任务是以防CPU无任务使用,所以才写的,所以我们最好给他分配的栈空间尽可能的小
  //* pulIdleTaskStackSize = IDLE_STACK_DEPTH;
  * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;//[configMINIMAL_STACK_SIZE:FreeRTOSConfig.h中]
}

 2.vApplicationGetTimerTaskMemory:开启软件定时器

1)先打开对应的宏【这个宏可以在启动调度器函数中查看到】

FreeRTOSconfig.h

//开启软件定时器
#define configUSE_TIMERS    1
#define configTIMER_TASK_PRIORITY(configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH 5
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
#define TIME_STACK_DEPTH  128
//静态任务创建使用到的
StackType_t time_task_stack[ TIME_STACK_DEPTH ];
StaticTask_t time_task_tcb;
//定时器任务
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
                                         StackType_t ** ppxTimerTaskStackBuffer,
                                         uint32_t * pulTimerTaskStackSize )
{
  * ppxTimerTaskTCBBuffer = &time_task_tcb; 
  * ppxTimerTaskStackBuffer = time_task_stack;
  pulTimerTaskStackSize = TIME_STACK_DEPTH; 
}

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)主要作用:实现任务的切换。从就绪列表中查找优先级最高的任务执行。

3)实际上启动任务调度器:都会先调用空闲任务

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.静态创建需要定义的两个函数

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

2)vApplicationGetIdleTaskMemory()【当全部任务都进入了阻塞状态】与 vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分配。

3)vApplicationGetIdleTaskMemory

//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,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)配置相关的.h文件,打开一个参数

2)启动任务--->创建其他任务【表示写一个大的任务,这个任务里面包含很多小任务,方便我们进行管理】

3)xCreateTask,最后在删除本身这个任务,避免程序卡死

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博客

taskENTER_CRITICAL  和   vPortEnterCritical

实际上这两个进入临界区函数没有什么本质上的区别,实际上通过宏定义taskENTER_CRITICAL 调用了vPortEnterCritical

参考博客:

https://www.cnblogs.com/zhangzhiwei122/p/15889168.html

#define taskENTER_CRITICAL()               portENTER_CRITICAL()
    #define portENTER_CRITICAL()                      vPortEnterCritical()

 vPortEnterCritical函数内部实际是将中断失能

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in "FromISR" can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}
#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()
    static portFORCE_INLINE void vPortRaiseBASEPRI( void )
    {
        uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
            msr basepri, ulNewBASEPRI
            dsb
            isb
/* *INDENT-ON* */
        }
    }

使用临界区为什么不直接失能中断??

通过上面的两个函数 vPortEnterCritical 和 vPortExitCritical 可以看出,进入临界段和退出临界段是通过函数调用开关中断函数 portENABLE_INTERRUPTS 和portDISABLE_INTERRUPTS 实现的。 细心的读者还会发现上面的这两个函数都对变量 uxCriticalNesting 进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们 的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。

11、FreeRTOS任务创建注意点

0.动态创建  VS   静态创建

https://www.cnblogs.com/The-explosion/p/13942283.html

1.裸机延时  VS  FreeRTOS延时

1.裸机延时:裸机延时(比如:HAL_Delay()等)这些延时是等待延时。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务 。

2.FreeRTOS延时:延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。

2.任务栈

1.裸机中的任务栈:全局变量统统放在一个叫栈的地方,栈是单片机 RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定, 最后由 C 库函数_main 进行初始化。【裸机中的全部栈变量都存储在一个地方】

2.FreeRTOS中的任务栈:每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组, 也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。

3.字节对齐问题

在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中是以 8 字节大小对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定义的一个宏,其值为 8,就是配置为按 8 字节对齐,当然用户可以选择按 1、 2、 4、 8、 16、 32 等字节对齐,目前默认为 8

#if portBYTE_ALIGNMENT == 8
  #define portBYTE_ALIGNMENT_MASK ( 0x0007 )
#endif

pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) &( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

/* 检查计算出的堆栈顶部的对齐方式是否正确。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack &( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

4.启动任务调度

 当任务创建好后,是处于任务就绪 ,在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个任务就是在启动任务调度器中实现,每个操作系统,任务调度器只启动一次,之后就不会再次执行了, FreeRTOS 中启动任务调度器的函数是 vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管理都由FreeRTOS 管理,此时才是真正进入实时操作系统中的第一步。

vTaskStartScheduler();  // 启动任务,开启调度

5.动态任务的内存分配

1)创建一个动态内存任务,任务使用的栈和任务控制块是在创建任务的时候FreeRTOS 动态分配的,并不是预先定义好的全局变量。

2)任务控制块和任务栈的内存空间都是从内部的 SRAM 里面分配的,具体分配到哪个地址由编译器决定。

3)FreeRTOS 做法是在 SRAM 里面定义一个大数组,也就是堆内存,供 FreeRTOS 的动态内存分配函数使用,在第一次使用的时候,系统会将定义的堆内存进行初始化,这些代码在 FreeRTOS 提供的内存管理方案中实现(heap_1.c、heap_2.c、 heap_4.c 等)。

//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024)) (1)
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; (2)
//如果这是第一次调用 malloc 那么堆将需要初始化, 以设置空闲块列表
if (pxEnd == NULL )
{
    prvHeapInit(); (3)
} 
else
{
    mtCOVERAGE_TEST_MARKER();
}

(1):堆内存的大小为configTOTAL_HEAP_SIZE,在FreeRTOSConfig.h 中由我们自己定义,configSUPPORT_DYNAMIC_ALLOCATION 这个宏定义在使用 FreeRTOS 操作系统的时候必须开启。
(2):从内部 SRAMM 里面定义一个静态数组 ucHeap,大小由configTOTAL_HEAP_SIZE 这个宏决定, 目前定义为 36KB。定义的堆大小不能超过内部SRAM 的总大小。
(3):如果这是第一次调用 malloc 那么需要将堆进行初始化,以设置空闲块列表,方便以后分配内存,初始化完成之后会取得堆的结束地址,在 MemMang 中的5 个内存分配 heap_x.c 文件中实现。

6.FreeRTOS启动方式1:

第一种我称之为万事俱备, 只欠东风法。这种方法是在 main 函数中将硬件初始化,RTOS 系统初始化,所有任务的创建这些都弄好,这个我称之为万事都已经准备好。最后只欠一道东风,即启动 RTOS 的调度器,开始多任务的调度。

(1):硬件初始化。硬件初始化这一步还属于裸机的范畴,我们可以把需要使用到的硬件都初始化好而且测试好,确保无误。
(2): RTOS 系统初始化。比如 RTOS 里面的全局变量的初始化,空闲任务的创建等。不同的 RTOS,它们的初始化有细微的差别
(3):创建各种任务。这里把所有要用到的任务都创建好,但还不会进入调度,因为这个时候 RTOS 的调度器还没有开启。
(4):启动 RTOS 调度器,开始任务调度。这个时候调度器就从刚刚创建好的任务中选择一个优先级最高的任务开始运行。
(5) (6):任务实体通常是一个不带返回值的无限循环的 C 函数,函数体必须有阻塞的情况出现,不然任务(如果优先权恰好是最高)会一直在 while 循环里面执行,导致其它任务没有执行的机会。

7.FreeRTOS启动方式2:

第二种我称之为小心翼翼, 十分谨慎法。这种方法是在 main 函数中将硬件和 RTOS 系统先初始化好,然后创建一个启动任务后就启动调度器,然后在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除。

(1):硬件初始化。来到硬件初始化这一步还属于裸机的范畴,我们可以把需要使用到的硬件都初始化好而且测试好,确保无误。
(2): RTOS 系统初始化。比如 RTOS 里面的全局变量的初始化,空闲任务的创建等。不同的 RTOS,它们的初始化有细微的差别。
(3):创建一个开始任务。然后在这个初始任务里面创建各种应用任务。
(4):启动 RTOS 调度器,开始任务调度。这个时候调度器就去执行刚刚创建好的初始任务。
(5):我们通常说任务是一个不带返回值的无限循环的 C 函数,但是因为初始任务的特殊性,它不能是无限循环的,只执行一次后就关闭。在初始任务里面我们创建我们需要的各种任务。
(6):创建任务。每创建一个任务后它都将进入就绪态,系统会进行一次调度,如果新创建的任务的优先级比初始任务的优先级高的话,那将去执行新创建的任务,当新的任务阻塞时再回到初始任务被打断的地方继续执行。反之,则继续往下创建新的任务,直到所有任务创建完成。
(7):各种应用任务创建完成后,初始任务自己关闭自己,使命完成。
(8) (9):任务实体通常是一个不带返回值的无限循环的 C 函数,函数体必须有阻塞的情况出现,不然任务(如果优先权恰好是最高)会一直在 while 循环里面执行,其它任务没有执行的机会。

8.静态创建为什么要对空闲和定时器函数进行定义??

1)因为需要用户自己手动创建。而在动态任务创建中,操作系统会自动的创建空闲和定时器函数

2)实际上,如果同时定义了静态宏和动态宏,会默认先判断是否为静态宏。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值