手把手教你FreeRTOS源码详解(二)——任务管理

2023-05-16

FreeRTOS源码解析集合(全网最详细)
手把手教你FreeRTOS源码解析(一)——内存管理
手把手教你FreeRTOS源码详解(二)——任务管理
手把手教你FreeRTOS源码详解(三)——队列
手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量

任务管理:

  • 1、任务创建
    • 1.1 任务控制块TCB
    • 1.2 任务创建xTaskCreate
    • 1.3 初始化任务prvInitialiseNewTask
    • 1.4 添加新任务至就序链表prvAddNewTaskToReadyList
    • 1.5 初始化任务链表prvInitialiseTaskLists
  • 2、任务删除
    • 2.1 链表项删除uxListRemove
  • 3、延时函数vTaskDelay
    • 3.1 prvAddCurrentTaskToDelayedList

1、任务创建

1.1 任务控制块TCB

在FreeRTOS中,每个任务都有一个属于自己的任务控制块,方便对任务进行管理,TCB结构体源码如下(已经删去了一些条件编译):

typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;	/*·指向任务栈顶*/
	ListItem_t			xStateListItem;		/* 表示任务状态的链表(就绪, 阻塞, 挂起 )*/
	ListItem_t			xEventListItem;		/* 指向事件链表中的某一任务*/
	UBaseType_t			uxPriority;			/*任务的优先级,0为最低优先级 */
	StackType_t			*pxStack;			/*指向栈的起始地址 */
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];/*保存任务名称*/
} tskTCB;

1.2 任务创建xTaskCreate

首先我们来看一下任务创建函数xTaskCreate的参数。

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )

pxTaskCode:函数指针,指向要执行的函数
pcName:任务的名称
usStackDepth:任务栈大小,注意单位为字(4个字节)
pvParameters:传递给任务函数的参数
uxPriority:任务的优先级
pxCreatedTask :任务句柄,用于管理任务

在FreeRTOS中,很多项目是根据堆栈的增长方向来的配置的,这使FreeRTOS有了更好的兼容性。堆栈的增长方向有向下增长向上增长两种。
向下增长:高地址向低地址增长。
向上增长:低地址向高地址增长。

在这里插入图片描述
在任务创建之初,需要分别给TCB任务控制块和任务分配内存空间,对于堆栈增长方向的不同,给TCB任务控制块和任务分配内存空间的顺序也有所差异。
源码如下:

#if( portSTACK_GROWTH > 0 )
		{
			/* 为TCB结构体申请内存堆  */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* 为任务申请内存堆 */
				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;

			/* 为任务申请内存堆 */
			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 )
			{
				/* 为TCB结构体申请内存堆 */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

				if( pxNewTCB != NULL )
				{
					/* 用TCB结构体的成员变量来指向任务内存堆 */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* 由于TCB结构体创建失败,因此任务堆不能使用,释放任务内存堆 */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}

portSTACK_GROWTH>0表示堆栈向上增长,先申请TCB任务控制块的堆栈空间,再申请任务堆栈;反之,如果堆栈向下增长,先申请任务堆栈,再申请TCB任务控制块的堆栈空间。按这样的顺序申请堆栈空间,堆栈的扩展就不会覆盖掉TCB任务控制块的内容,Cortex-M采用的是向下增长
以向下增长为例,若先申请TCB任务控制块堆栈空间,再申请任务堆栈(谁先申请堆栈谁的地址更低):
在这里插入图片描述
这样任务堆栈的扩展可能会覆盖掉TCB区域,若先申请任务堆栈,再申请TCB任务控制块的堆栈空间:
在这里插入图片描述
任务控制块就没有被覆盖的危险了。堆栈向上增长也类似。

堆栈申请完成,判断任务控制块内存是否申请成功(申请失败返回NULL)

if( pxNewTCB != NULL )

如果是动态创建的任务,则进行标记,便于后面删除任务

#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/*将任务标记为动态创建,以便于后面删除任务 */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}

初始化新任务以及将任务添加至就绪列表–后续讲解

prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );

1.3 初始化任务prvInitialiseNewTask

首先计算出任务栈顶地址(堆栈增长方向不同计算方法不同),再进行地址对齐。
若堆栈向下增长(STM32采用这种增长方式):

#if( portSTACK_GROWTH < 0 )
{
	/*计算栈顶地址*/
	pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	/*将地址作8字节对齐--&(~0x0007)*/
	pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */

	/* Check the alignment of the calculated top of stack is correct. */
	configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}

pxNewTCB->pxStack为任务块堆栈的起始地址,pxTopOfStack 指向当前任务块堆栈的栈顶。
在这里插入图片描述

若堆栈向上增长

/* 栈向上增长 */
#else /* portSTACK_GROWTH */
{
	/*指向任务栈顶*/
	pxTopOfStack = pxNewTCB->pxStack;
	/* Check the alignment of the stack buffer is correct. */
	configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

	/* 指向任务栈底 */
	pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}

pxTopOfStack 指向当前任务块堆栈的栈顶,pxNewTCB->pxEndOfStack指向任务块的栈底。
在这里插入图片描述
用for循环将输入的任务名赋值给TCB结构体的成员变量pcTaskName

/* 在TCB结构体中保存任务的名字 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
	pxNewTCB->pcTaskName[ x ] = pcName[ x ];

	/* 自己定义的任务名字是不定长的,若定义的任务名短于configMAX_TASK_NAME_LEN,则赋值完(检测到0)就跳出该循环 */
	if( pcName[ x ] == 0x00 )
	{
		break;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

最后在任务名的结尾加上字符串结束标识符’\0’

pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

检测任务优先级是否过大,若超过最高优先级,则置为最高优先级,任务的最高优先级为configMAX_PRIORITIES -1,如configMAX_PRIORITIES 为5时,优先级可取0,1,2,3,4

if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
	uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
	mtCOVERAGE_TEST_MARKER();
}

将任务优先级赋值给TCB结构体

pxNewTCB->uxPriority = uxPriority;

初始化TCB结构体中的任务状态表、事件表

vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

设置TCB控制块状态表的成员变量pvOwner,是属于pxNewTCB的

listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

设置TCB控制块任务表成员变量xItemValue的值

listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );

设置TCB控制块任务表的成员变量pvOwner,是属于pxNewTCB的

listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

如果存在任务句柄(输入的任务句柄不为NULL),则将任务句柄指向任务控制块

	if( ( void * ) pxCreatedTask != NULL )
{
	/*	将任务句柄指向任务控制块TCB */
	*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}

1.4 添加新任务至就序链表prvAddNewTaskToReadyList

先进入临界区,防止中断打断就绪链表更新
taskENTER_CRITICAL为最强的临界保护,直接屏蔽了中断,使用时应尽量保证临界区较小一些
vTaskSuspendAll仅仅挂起了任务调度器,即关闭了任务调度器,防止任务之间的资源抢夺

taskENTER_CRITICAL();

当前任务数加一

uxCurrentNumberOfTasks++;

当前没有任务,或任务均被挂起,则将该任务设置为将执行的任务,并且如果是第一次创建任务,则需要初始化任务状态链表

	if( pxCurrentTCB == NULL )
	{
		/* 如果没有其它任务,或者其它任务均被挂起 - 将该任务设置为将执行的任务 */
		pxCurrentTCB = pxNewTCB;
		/*第一次创建任务*/
		if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
		{
			/* 第一次创建任务,初始化任务链表 */
			prvInitialiseTaskLists();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

如果不是第一次创建任务,且任务调度器未执行,该任务为优先级最高的任务,则将其设置为当前任务

		if( xSchedulerRunning == pdFALSE )
		{
			if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
			{
				pxCurrentTCB = pxNewTCB;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

任务数加一,该任务数用作任务的编号

	uxTaskNumber++;
	#if ( configUSE_TRACE_FACILITY == 1 )
	{
		/* 初始化TCB任务控制块编号 */
		pxNewTCB->uxTCBNumber = uxTaskNumber;
	}
	#endif /* configUSE_TRACE_FACILITY */

将任务添加至就绪列表

prvAddTaskToReadyList( pxNewTCB )

prvAddTaskToReadyList( pxNewTCB )实际调用的函数是vListInsertEnd,即采用尾插法,在链表的尾部插入元素

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )

pxList:列表项要插入的列表
pxNewListItem :要插入的列表项

若任务调度正在执行,创建的任务优先级高于正在执行的任务,则正在执行的任务“让步”于新任务

if( xSchedulerRunning != pdFALSE )
{
	/* 若创建的任务优先级高于正在执行的任务,则正在执行的任务“让步”于新任务 */
	if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
	{
		taskYIELD_IF_USING_PREEMPTION();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

1.5 初始化任务链表prvInitialiseTaskLists

在FreeRTOS中,一个任务有多种状态,每种状态对应一个链表,将任务置于不同的状态,实质上就是将任务添加至对应的状态链表。
我们首先来看一下链表初始化函数vListInitialise( List_t * const pxList )
pxIndex表示列表项的索引号,初始状态时,链表中只有xListEnd一个元素,因此pxIndex指向xListEnd

pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );	

在链表中,按照xItemValue升序排列的,xItemValue越小代表优先级越高
将 pxList->xListEnd.xItemValue设置为最大,保证xListEnd始终在链表的最后位置

pxList->xListEnd.xItemValue = portMAX_DELAY;

任务链表为双向链表,对其进行初始化

pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

uxNumberOfItems记录链表中元素的个数,刚初试化链表,元素个数为0

pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

分析完链表初始化函数后,我们回到任务链表初始化函数prvInitialiseTaskLists
不同优先级的就绪任务,阻塞任务,挂起任务以及已经删除但为释放内存的任务都有对应的链表。
遍历初始化不同优先级的就绪任务链表

for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
	vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}

初始化阻塞任务链表

vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );

初始化就绪任务过渡链表,此时调度器关闭,一些任务进入就绪状态,但是任务还未放入就绪链表中,等待任务调度器开始,进行一次新的调度

vListInitialise( &xPendingReadyList );

初始化任务删除链表

vListInitialise( &xTasksWaitingTermination );

初始化挂起任务链表

vListInitialise( &xSuspendedTaskList );

2、任务删除

同样,删除任务首先进入临界区。

taskENTER_CRITICAL();

如果输入句柄为NULL,则获取当前运行任务的句柄,否则pxTCB就为输入句柄

pxTCB = prvGetTCBFromHandle( xTaskToDelete );

prvGetTCBFromHandle实质如下:

( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )

将任务从链表中删除,若删除后链表中的任务数为0,则清除相应就绪链表的就绪标志位

if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
		{
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}

任务是否在等待某个事件,如果是,则将其放置于相应的链表

if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
		{
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}

如果删除的任务为正在运行的任务,将需要删除的任务插入xTasksWaitingTermination中,在空闲函数里面来释放内存

			/*如果删除的任务为正在运行的任务*/
		if( pxTCB == pxCurrentTCB )
		{
			/* 将需要删除的任务插入xTasksWaitingTermination中,在空闲函数里面来释放内存*/
			vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

			/* uxDeletedTasksWaitingCleanUp为删除链表xTasksWaitingTermination中的任务数 */
			++uxDeletedTasksWaitingCleanUp;

			/* 删除任务钩子函数,用户自己实现 */
			portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
		}

若删除的任务不是正在运行的任务,则直接删除

else
		{
		/*正在运行的任务数减1*/
			--uxCurrentNumberOfTasks;
		/*	删除TCB结构体,释放堆栈和任务控制块内存*/
			prvDeleteTCB( pxTCB );

			/* 重置下一个任务唤醒时间,避免下一个唤醒的任务为当前删除的任务*/
			prvResetNextTaskUnblockTime();
		}

退出临界区

taskEXIT_CRITICAL();

如果删除的是正在运行的任务那么就需要强制进行一次任务切换。

		if( xSchedulerRunning != pdFALSE )
	{
		if( pxTCB == pxCurrentTCB )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			/*强制进行任务切换*/
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

2.1 链表项删除uxListRemove

首先获取需要删除的任务处于哪一个链表中

List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

将任务从链表中删除,即将任务前后两个任务连接起来。

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

如果pxIndex指向要被删除的任务,则将pxIndex指向要被删除任务的前一项

if( pxList->pxIndex == pxItemToRemove )
{
	pxList->pxIndex = pxItemToRemove->pxPrevious;
}

将被被删除对象的成员变量pvContainer清空

	pxItemToRemove->pvContainer = NULL;

删除后,链表中任务数减1

( pxList->uxNumberOfItems )--;

最后返回链表中的任务数目

return pxList->uxNumberOfItems;

3、延时函数vTaskDelay

延时函数首先判断延时时间是否大于0

if( xTicksToDelay > ( TickType_t ) 0U )

挂起任务调度器

vTaskSuspendAll();

挂起任务调度器实质就是将uxSchedulerSuspended加1,当uxSchedulerSuspended大于0时,即不会进行任务调度,当uxSchedulerSuspended为0时就会进行任务调度。

void vTaskSuspendAll( void )
{
	++uxSchedulerSuspended;
}

将需要延时的任务添加至延时列表

prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );

重新恢复任务调度器,并用xAlreadyYielded获取其返回值,若为pdFALSE则未进行任务调度,需要在后续进行任务调度

xAlreadyYielded = xTaskResumeAll();

若未进行任务调度,在此处强制进行任务调度

	if( xAlreadyYielded == pdFALSE )
	{
		portYIELD_WITHIN_API();
	}

3.1 prvAddCurrentTaskToDelayedList

vTaskDelay函数的本质是调用prvAddCurrentTaskToDelayedList,将任务添加至对应的延时、阻塞链表,同样,首先将任务从就绪列表中移除,移除后并所移除的就绪列表中是否还有其余就绪的任务,若剩余就绪任务数为0,则清除该列表的就绪标志位

if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
	/* The current task must be in a ready list, so there is no need to
	check, and the port reset macro can be called directly. */
	portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}

这里我们只考虑允许阻塞的情况,即宏INCLUDE_vTaskSuspend=1时
如果延时时间为最大,且允许阻塞时,直接将任务添加至阻塞列表中去,xCanBlockIndefinitely 为pdTRUE即为允许阻塞

	if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
	{
		/* Add the task to the suspended task list instead of a delayed task
		list to ensure it is not woken by a timing event.  It will block
		indefinitely. */
		vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
	}

如果阻塞时间不为最大时,首先计算出唤醒任务的时间,xConstTickCount为执行任务prvAddCurrentTaskToDelayedList的时间,xTicksToWait为延时时间,xTimeToWake为唤醒时间

xTimeToWake = xConstTickCount + xTicksToWait;

将xTimeToWake写入任务列表的状态列表成员变量xItemValue中

listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

xTimeToWake为TickType_t类型,为uint_16型,当xTimeToWake<xConstTickCount时,即发生了溢出,则将任务添加至溢出链表pxOverflowDelayedTaskList中

if( xTimeToWake < xConstTickCount )
			{
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}

如果没有发生溢出,则将任务添加至链表pxDelayedTaskList中

vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) )

全局变量xNextTaskUnblockTime保存着下一个任务的唤醒时间,如果xTimeToWake<xNextTaskUnblockTime时,即有任务需更快的被唤醒,则更新xNextTaskUnblockTime为xTimeToWake

			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你FreeRTOS源码详解(二)——任务管理 的相关文章

  • 详解FreeRTOS中的软件定时器

    软件定时器用于让某个任务定时执行 或者周期性执行 比如设定某个时间后执行某个函数 或者每隔一段时间执行某个函数 由软件定时器执行的函数称为软件定时器的回调函数 参考资料 Mastering the FreeRTOS Real Time Ke
  • 【FreeRtos学习笔记】STM32 CubeMx——Timers(定时器)

    目录 1 软件定时器 2 示例程序 2 1 例程功能 2 2 步骤 2 3 实验结果 2 4 函数讲解 1 软件定时器 定时器是MCU常用的外设 我们在学习各种单片机时必然会学习它的硬件定时器 但是 MCU自带的硬件定时器资源是有限的 而且
  • FreeRTOS快速上手

    FreeRTOS使用 一 源码下载和移植文件提取 1 1 源码下载 在网站https sourceforge net projects freertos 可以找到freertos最新的源码 1 2 移植文件提取 根据第一步 我们会得到一个f
  • FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)

    我们在前面单独介绍过FreeRTOS的任务通知和消息队列 但是在FreeRTOS中任务间的通讯还有信号量 邮箱 事件组标志等可以使用 这篇文章就这些成员与消息队列和任务通知的关系进行说明分析 增加邮箱部分 任务通知发送消息 Demo 202
  • FreeRTOS学习笔记 6 - 互斥量

    目录 1 创建 2 获取 3 释放 4 测试 FreeRTOS不支持调度方式的设置 所以下面2个宏定义可以随意设置值 define RTOS IPC FLAG FIFO 0x00 define RTOS IPC FLAG PRIO 0x01
  • FreeRTOS学习(八) 延时函数

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 FreeRTOS延时函数有两个 分别是 vTaskDelay vTaskDelayUntil 1 vTaskDelay 任务相对延时 函数原型
  • 【FreeRTOS开发问题】FreeRTOS内存溢出

    FreeRTOS内存溢出 如下图所示 FreeRTOS编译完成后可以看到 系统提示无法分配内存到堆 Objects Template axf Error L6406E No space in execution regions with A
  • STM32移植FreeRTOS的Tips

    转自 http bbs armfly com read php tid 7140 1 在FreeRTOS的demo文件夹中拷贝对应的FreeRTOSConfig h文件后 需要加入一行 define configUSE MUTEXES 1
  • 【FreeRTOS】队列的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • 基于HAL库的FREERTOS----------一.任务

    FreeROTS 就是一个免费的 RTOS 类系统 这里要注意 RTOS 不是指某一个确定的系统 而是指一类系统 比如 UCOS FreeRTOS RTX RT Thread 等这些都是 RTOS 类操作系统 FreeRTOS 是 RTOS
  • Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

    我用的是F103ZET6的板子 移植成功后 编译出现两个错误是关于stm32f10x it c 里 void SVC Handler void void PendSV Handler void 两个函数的占用问题 随后编译出现以下两个问题
  • freertos————互斥锁

    线程安全 多线程程序处于一个多变的环境 可访问的全局变量和堆数据随时可能被其他的线程改变 多个线程同时访问一个共享数据 可能造成严重的后果 出现问题的是之前移植了一个freemodbus的从站 多个任务访问全局变量保持寄存器区 导致最后读出
  • FreeRTOS笔记(十)中断

    中断 当CPU在执行某一事件A时 发生另外一个更重要紧急的事件B请求CPU去处理 产生了中断 于是CPU暂时中断当前正在执行的事件A任务而对对事件B进行处理 CPU处理完事件B后再返回之前中断的位置继续执行原来的事件A 这一过程统称为中断
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • 【FreeRTOS 事件】任务通知事件

    普通任务通知事件创建创建及运行 参阅安富莱电子demo define BIT 0 1 lt lt 0 define BIT 1 1 lt lt 1 static TaskHandle t xHandleTaskUserIF NULL sta
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • FreeRTOS 配置TICK_RATE_HZ

    我使用的是带有 5 4 版 FreeRTOS 的 MSP430f5438 我有一个有趣的问题 我无法弄清楚 基本上 当我将 configTICK RATE HZ 设置为不同的值时 LED 闪烁得更快或更慢 它应该保持相同的速率 我将 con
  • 使用 GCC 编译器的 ARM 内核的堆栈回溯(当存在 MSP 到 PSP 切换时)

    核心 ARM Cortex M4 编译器 GCC 5 3 0 ARM EABI 操作系统 免费 RTOS 我正在使用 gcc 库函数 Unwind Reason Code Unwind Backtrace Unwind Trace Fn v
  • FreeRTOS 匈牙利表示法 [重复]

    这个问题在这里已经有答案了 我是 RTOS 和 C 编程的新手 而且我仍在习惯 C 的良好实践 因此 我打开了一个使用 FreeRTOS 的项目 我注意到操作系统文件使用匈牙利表示法 我知道一点符号 但面临一些新的 标准 FreeRTOS
  • C++ freeRTOS任务,非静态成员函数的无效使用

    哪里有问题 void MyClass task void pvParameter while 1 this gt update void MyClass startTask xTaskCreate this gt task Task 204

随机推荐

  • LeetCode543:二叉树的直径

    要求 给定一棵二叉树 xff0c 你需要计算它的直径长度 一棵二叉树的直径长度是任意两个结点路径长度中的最大值 这条路径可能穿过也可能不穿过根结点 题目解析 这里返回的是 xff1a 两结点之间的路径长度是以它们之间边的数目表示 最大的直径
  • LeetCode560:和为K的子数组

    要求 给你一个整数数组 nums 和一个整数 k xff0c 请你统计并返回 该数组中和为 k 的连续子数组的个数 思路 方法一 xff1a 暴力解 先固定左边界 xff0c 再去枚举右边 span class token keyword
  • LeetCode581:最短无序连续子数组

    要求 给你一个整数数组 nums xff0c 你需要找出一个 连续子数组 xff0c 如果对这个子数组进行升序排序 xff0c 那么整个数组都会变为升序排序 请你找出符合题意的 最短 子数组 xff0c 并输出它的长度 思路 我们可以假设把
  • LeetCoed617:合并二叉树

    要求 给你两棵二叉树 xff1a root1 和 root2 想象一下 xff0c 当你将其中一棵覆盖到另一棵之上时 xff0c 两棵树上的一些节点将会重叠 xff08 而另一些不会 xff09 你需要将这两棵树合并成一棵新二叉树 合并的规
  • LeetCode621:任务调度器

    要求 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表 其中每个字母表示一种不同种类的任务 任务可以以任意顺序执行 xff0c 并且每个任务都可以在 1 个单位时间内执行完 在任何一个单位时间 xff0c CPU 可以完成
  • LeetCode647:回文子串

    要求 给你一个字符串 s xff0c 请你统计并返回这个字符串中 回文子串 的数目 回文字符串 是正着读和倒过来读一样的字符串 子字符串 是字符串中的由连续字符组成的一个序列 具有不同开始位置或结束位置的子串 xff0c 即使是由相同的字符
  • LeetCode739:每日温度

    要求 给定一个整数数组 temperatures xff0c 表示每天的温度 xff0c 返回一个数组 answer xff0c 其中 answer i 是指对于第 i 天 xff0c 下一个更高温度出现在几天后 如果气温在这之后都不会升高
  • 八大排序算法

    介绍 排序也称排序算法 Sort Algorithm xff0c 排序是将一组数据 xff0c 依指定的顺序进行排列的过程 排序分类 1 内部排序 指将需要处理的所有数据都加载到内部存储器 内存 中进行排序 2 外部排序法 数据量过大 xf
  • 时间复杂度和空间复杂度详解及排序算法复杂度

    时间复杂度 度量一个程序 算法 执行时间的两种方法 1 事前估算法 通过分析某个算法的时间复杂度来判断哪个算法更优 2 事后统计法 这种方法可行 xff0c 但是有两个问题 xff1a 一是要想对设计的算法的运行性能进行评测 xff0c 需
  • Java反编译工具JAD的安装与简单使用

    jad介绍 jad 是一个使用非常广泛的 Java 反编译工具 可以将java编译后的class文件反编译成对应的java文件 下载地址 JAD下载地址 xff0c 点击跳转https varaneckas com jad 按照自己的需求
  • python Counter() 函数

    目录 1 以统计列表中的词频为例 2 寻找出现次数最多的 k 个数 刷 leetcode 时发现了可以很方便地统计词频的 Counter 函数 xff0c 有了这个函数就不用手动的使用 for 循环来手动统计词频啦 xff01 Counte
  • C++11 新特性简介

    1 auto auto是旧关键字 xff0c 在C 43 43 11之前 xff0c auto用来声明自动变量 xff0c 表明变量存储在栈 xff0c 很少使用 在C 43 43 11中被赋予了新的含义和作用 xff0c 用于类型推断 a
  • Java反射(Reflex)机制

    反射概述 Reflection 反射 是Java被视为动态语言的关键 xff0c 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息 xff0c 并能直接操作任意对象的内部属性及方法 加载完类之后 xff0c 在
  • 偏航角、俯仰角、滚动角

    偏航角 俯仰角 滚动角 在姿态估计中 xff0c 常常会提到三个概念 xff0c 就是偏航角 俯仰角和滚动角 姿态估计是物体在三维空间内方向的表征 通常描述物体姿态以大地作为参考系 xff08 标准坐标系 xff09 将世界坐标系 xff0
  • 定时器与计数器的区别

    曾经我在北方工业大学复试的时候 xff0c 有个老师问了我个问题 他说同学你好 xff0c 我看你前面一直都在说深度学习的问题 xff0c 请问你对单片机了解吗 xff1f xff0c 请问定时器与计数器的区别是什么 xff1f 我告诉他
  • 编译make px4_fmu-v2_default upload时,报错ERROR Board can accept larger flash images (2080768 bytes)

    编译make px4 fmu v2 default upload时 xff0c 报错ERROR Board can accept larger flash images 2080768 bytes than board config 103
  • vue中实现axios封装

    vue中实现axios封装 为什么要进行axios封装vue项目安装axios封装前的get和post请求封装后的get和post请求 为什么要进行axios封装 实际开发过程中可能有几种环境 xff0c 开发环境 xff0c 测试环境 x
  • 项目实训—场景划分(一)

    1 什么是场景 xff08 Scene xff09 场景作为电影讲故事的关键单元 xff0c 包含了演员的复杂活动及其在物理位置上的互动 识别场景的组成和语义是视觉理解复杂长视频 xff08 如电影 电视剧 娱乐节目和综艺节目 xff09
  • 手把手教你FreeRTOS源码解析(一)——内存管理

    FreeRTOS中一共有5种内存分配的方法 xff0c 分别在文件heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c种 虽然标准C库中的 malloc 和 free 也可以实现动态内存管理 xff0c
  • 手把手教你FreeRTOS源码详解(二)——任务管理

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS