STM32理论 —— FreeRTOS(内核控制、时钟、队列)

文章目录

  • 1. 系统内核控制
    • 1.1 相关API函数
      • 1.1.1 函数 `taskYIELD()`
      • 1.1.2 函数 `taskENTER_CRITICAL()`
      • 1.1.3 函数 `taskEXIT_CRITICAL()`
      • 1.1.4 函数 `taskENTER_CRITICAL_FROM_ISR()`
      • 1.1.5 函数 `taskEXIT_CRITICAL_FROM_ISR()`
      • 1.1.6 函数 `taskDISABLE_INTERRUPTS()`
      • 1.1.7 函数 `taskENABLE_INTERRUPTS()`
      • 1.1.8 函数 `vTaskStartScheduler()`
      • 1.1.9 函数 `vTaskEndScheduler()`
      • 1.1.10 函数 `vTaskSuspendAll()`
      • 1.1.11 函数 `xTaskResumeAll()`
      • 1.1.12 函数 `vTaskStepTick()`
  • 2. 时钟节拍
    • 2.1 FreeRTOS的延时函数
      • 2.1.1 函数`vTaskDelay()`
      • 2.1.2 函数`vTaskDelayUntil()`
    • 2.2 系统时钟 - 系统节拍定时器(滴答定时器)
  • 3. 队列
    • 3.1 数据存储与队列操作过程
    • 3.3 核心代码
      • 3.3.1 队列结构体
      • 3.3.2 队列创建函数
    • 3.2 出队/入队阻塞

1. 系统内核控制


FreeRTOS 中有一些只供系统内核使用,用户应用程序一般不允许使用的函数,这些 API 函数叫做系统内核控制函数

在 FreeRTOS 官网可以找到这些函数
STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第1张图片

1.1 相关API函数

1.1.1 函数 taskYIELD()

此函数用于进行任务切换,此函数本质上是一个宏,其定义在portmacro.h中,当该函数被调用时,系统自动切换下一个优先级最高的任务

/* Scheduler utilities. */
#define portYIELD()																\
{																				\
	/* Set a PendSV to request a context switch. 执行一次PenSV中断*/								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

1.1.2 函数 taskENTER_CRITICAL()

进入临界区,用于任务函数中,本质上是一个宏,最终其指向如下函数:

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS(); // 关闭所有中断
	uxCriticalNesting++; // 零阶段嵌套计数器加 1

	/* 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 );
	}
}

注:

  1. 临界区(或叫临界段)指那些必须完整运行,不能被打断的代码段(例如某些外设的初始化需要严格的时序,初始化过程中不能被打断)
  2. 在任务函数中进入/退出临界区,就用1.1.2/ 1.1.3节的函数,反之,在中断服务函数中进入/退出临界区,就用1.1.4/ 1.1.5节的函数
  3. 临界区是支持嵌套的,故调用了多少次vPortEnterCritical()进入,就需要调用多少次taskEXIT_CRITICAL()退出
  4. FreeRTOS 在进入临界区代码时需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界区代码,这些代码都加了临界区代码保护,用户在写自己的程序时有些地方也需要添加临界区进行代码保护
  5. 注意临界区内代码一定要精简!快进快出!因为进入临界区会关闭中断,这样会导致优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时响应

1.1.3 函数 taskEXIT_CRITICAL()

退出临界区,用于任务函数中,本质上是一个宏,最终其指向如下函数:

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;// 零阶段嵌套计数器减 1
	if( uxCriticalNesting == 0 ) // 判断是否完全退出临界区(因为临界区支持嵌套)
	{
		portENABLE_INTERRUPTS();// 打开所有中断
	}
}

1.1.4 函数 taskENTER_CRITICAL_FROM_ISR()

进入临界区,用于中断服务函数中,这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY,此函数本质上是一个宏

1.1.5 函数 taskEXIT_CRITICAL_FROM_ISR()

退出临界区,用于中断服务函数中,这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY,此函数本质上是一个宏

1.1.6 函数 taskDISABLE_INTERRUPTS()

关闭可屏蔽的中断,此函数本质上是一个宏

1.1.7 函数 taskENABLE_INTERRUPTS()

打开可屏蔽的中断,此函数本质上是一个宏

1.1.8 函数 vTaskStartScheduler()

开启任务调度器,一般在创建开始任务后进行开启任务调度器

1.1.9 函数 vTaskEndScheduler()

关闭任务调度器,一般不需要调用,在调用函数vTaskStartScheduler()后(即任务调度器被开启后),系统会在适当的时间自动调用该函数进行关闭任务调度器,在task. h中对其有如下描述:

/**
 * task. h	
 * 
void vTaskEndScheduler( void );
* * NOTE: At the time of writing only the x86 real mode port, which runs on a PC in place of DOS, implements this function. * * ...... * **/

意思是此函数仅用于 X86 架构的处理器,调用此函数后所有系统时钟都会停止,所有创建的任务都会自动被删除,多任务性能关闭。可以调用函数vTaskStartScheduler()来重新开启任务调度器。此函数在文件 tasks.c 中有如下定义:

void vTaskEndScheduler( void )
{
	/* Stop the scheduler interrupts and call the portable scheduler end
	routine so the original ISRs can be restored if necessary.  The port
	layer must ensure interrupts enable	bit is left in the correct state. */
	portDISABLE_INTERRUPTS();
	xSchedulerRunning = pdFALSE;
	vPortEndScheduler();
}

1.1.10 函数 vTaskSuspendAll()

挂起任务调度器,调用此函数不需要关闭可屏蔽中断即可挂起任务调度器,此函数在文件tasks.c 中有如下定义:

void vTaskSuspendAll( void )
{
	/* A critical section is not required as the variable is of type
	BaseType_t.  Please read Richard Barry's reply in the following link to a
	post in the FreeRTOS support forum before reporting this as a bug! -
	http://goo.gl/wu4acr */
	++uxSchedulerSuspended;
}

此函数只是简单的将变量 uxSchedulerSuspended 加一,uxSchedulerSuspended 是挂起嵌套计数器,调度器挂起是支持嵌套的。使用函数 xTaskResumeAll()可以恢复任务调度器,调用了几次 vTaskSuspendAll()挂起调度器,同样的也得调用几次 xTaskResumeAll()才能最终恢复任务调度器

1.1.11 函数 xTaskResumeAll()

用于将任务调度器从挂起壮态恢复:

BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	/* It is possible that an ISR caused a task to be removed from an event
	list while the scheduler was suspended.  If this was the case then the
	removed task will have been added to the xPendingReadyList.  Once the
	scheduler has been resumed it is safe to move all the pending ready
	tasks from this list into their appropriate ready list. */
	taskENTER_CRITICAL();  // 进入临界区
	{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* Move any readied tasks from the pending list into the
				appropriate ready list. */
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );

					/* If the moved task has a priority higher than the current
					task then a yield must be performed. */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )
				{
					/* A task was unblocked while the scheduler was suspended,
					which may have prevented the next unblock time from being
					re-calculated, in which case re-calculate it now.  Mainly
					important for low power tickless implementations, where
					this can prevent an unnecessary exit from low power
					state. */
					prvResetNextTaskUnblockTime();
				}

				/* If any ticks occurred while the scheduler was suspended then
				they should be processed now.  This ensures the tick count does
				not	slip, and that any delayed tasks are resumed at the correct
				time. */
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
							if( xTaskIncrementTick() != pdFALSE )
							{
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;
						} while( uxPendedCounts > ( UBaseType_t ) 0U );

						uxPendedTicks = 0;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( xYieldPending != pdFALSE )
				{
					#if( configUSE_PREEMPTION != 0 )
					{
						xAlreadyYielded = pdTRUE;
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();   // 退出临界区

	return xAlreadyYielded;
}

1.1.12 函数 vTaskStepTick()

在使用 FreeRTOS 的低功耗 tickless 模式时会用到 , 即宏configUSE_TICKLESS_IDLE 为 1 时. 当使能低功耗 tickless 模式以后在执行空闲任务的时候系统时钟节拍中断就会停止运行,系统时钟中断停止运行的这段时间必须得补上,这个工作就是由函数 vTaskStepTick()来完成,此函数在文件 tasks.c 中有定义:

	void vTaskStepTick( const TickType_t xTicksToJump )
	{
		/* Correct the tick count value after a period during which the tick
		was suppressed.  Note this does *not* call the tick hook function for
		each stepped tick. */
		configASSERT( ( xTickCount + xTicksToJump ) <= xNextTaskUnblockTime );
		xTickCount += xTicksToJump;
		traceINCREASE_TICK_COUNT( xTicksToJump );
	}

2. 时钟节拍


2.1 FreeRTOS的延时函数

2.1.1 函数vTaskDelay()

vTaskDelay()是相对延时函数,使用该函数的条件是将宏INCLUDE_vTaskDelay定义为1:

注:“相对”是相对于任务而言,实际准确的延时时间根据任务中的代码运行时间而定, 它不能保证每次任务的执行时间, 任务代码可能每次执行的时间不一,那么任务执行的时间也不一

#if ( INCLUDE_vTaskDelay == 1 )

	void vTaskDelay( const TickType_t xTicksToDelay )
	{
	BaseType_t xAlreadyYielded = pdFALSE;

		/* A delay time of zero just forces a reschedule. */
		if( xTicksToDelay > ( TickType_t ) 0U ) // 判断延时时间是否大于 0 (不能为负值)
		{
			configASSERT( uxSchedulerSuspended == 0 );
			vTaskSuspendAll(); // 挂起任务调度器,挂起所有任务
			{
				traceTASK_DELAY();

				/* A task that is removed from the event list while the
				scheduler is suspended will not get placed in the ready
				list or removed from the blocked list until the scheduler
				is resumed.

				This task cannot be in an event list as it is the currently
				executing task. */
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); // 将当前调用延时函数的任务添加到延时列表pxDelayedTaskList 或  pxOverflowDelayedTaskList() 中
			}
			xAlreadyYielded = xTaskResumeAll(); // 恢复任务调度器
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE ) // 如果上面的函数 xTaskResumeAll()没有进行任务调度,就会返回pdFALSE ,在此处进行一次任务调度
		{
			portYIELD_WITHIN_API();  // 进行一次任务调度
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelay */

其中函数prvAddCurrentTaskToDelayedList()定义如下:

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; // 获取系统时钟节拍

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		/* About to enter a delayed list, so ensure the ucDelayAborted flag is
		reset to pdFALSE so it can be detected as having been set to pdTRUE
		when the task leaves the Blocked state. */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* Remove the task from the ready list before adding it to the blocked list
	as the same list item is used for both lists. */
	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 );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		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 ) );
		}
		else
		{
			/* Calculate the time at which the task should be woken if the event
			does not occur.  This may overflow but this doesn't matter, the
			kernel will manage it correctly. */
			xTimeToWake = xConstTickCount + xTicksToWait; // 计算唤醒时间

			/* The list item will be inserted in wake time order. */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount ) // 判断是否发生溢出
			{
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); // 将当前任务放置到延时溢出列表
			}
			else
			{
				/* The wake time has not overflowed, so the current block list
				is used. */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); // 将当前任务放置到延时列表

				/* If the task entering the blocked state was placed at the
				head of the list of blocked tasks then xNextTaskUnblockTime
				needs to be updated too. */
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	{
		/* Calculate the time at which the task should be woken if the event
		does not occur.  This may overflow but this doesn't matter, the kernel
		will manage it correctly. */
		xTimeToWake = xConstTickCount + xTicksToWait;

		/* The list item will be inserted in wake time order. */
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

		if( xTimeToWake < xConstTickCount )
		{
			/* Wake time has overflowed.  Place this item in the overflow list. */
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* The wake time has not overflowed, so the current block list is used. */
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			/* If the task entering the blocked state was placed at the head of the
			list of blocked tasks then xNextTaskUnblockTime needs to be updated
			too. */
			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}

2.1.2 函数vTaskDelayUntil()

调用函数会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用该函数,使用该函数的条件是将宏INCLUDE_vTaskDelayUntil定义为1:

#if ( INCLUDE_vTaskDelayUntil == 1 )

	void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
	{
	TickType_t xTimeToWake;
	BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

		configASSERT( pxPreviousWakeTime );
		configASSERT( ( xTimeIncrement > 0U ) );
		configASSERT( uxSchedulerSuspended == 0 );

		vTaskSuspendAll(); // 挂起任务调度器
		{
			/* Minor optimisation.  The tick count cannot change in this
			block. */
			const TickType_t xConstTickCount = xTickCount;

			/* Generate the tick time at which the task wants to wake. */
			xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

			if( xConstTickCount < *pxPreviousWakeTime )
			{
				/* The tick count has overflowed since this function was
				lasted called.  In this case the only time we should ever
				actually delay is if the wake time has also	overflowed,
				and the wake time is greater than the tick time.  When this
				is the case it is as if neither time had overflowed. */
				if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* The tick time has not overflowed.  In this case we will
				delay if either the wake time has overflowed, and/or the
				tick time is less than the wake time. */
				if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}

			/* Update the wake time ready for the next call. */
			*pxPreviousWakeTime = xTimeToWake;

			if( xShouldDelay != pdFALSE )
			{
				traceTASK_DELAY_UNTIL( xTimeToWake );

				/* prvAddCurrentTaskToDelayedList() needs the block time, not
				the time to wake, so subtract the current tick count. */
				prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		xAlreadyYielded = xTaskResumeAll(); // 恢复任务调度器

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelayUntil */

输入参数:

  1. pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil 的话需要将 pxPreviousWakeTime 初始化进入任务的 while()循环体的时间点值。在以后的运行中函数 vTaskDelayUntil()会自动更新 pxPreviousWakeTime的值
  2. xTimeIncrement:任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节拍数)

代码中各参数的关系:
STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第2张图片
其中(3)为其他任务在运行。任务的延时时间是 xTimeIncrement,这个延时时间是相对于 pxPreviousWakeTime 的,即任务总的执行时间(1)一定要小于任务的延时时间 xTimeIncrement,即如果使用 vTaskDelayUntil()的话,任务相当于任务的执行周期永远都是 xTimeIncrement,而任务总会在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行,因此该函数也叫做绝对延时函数


应用举例:

// 有某一任务函数如下
void TestTask( void * pvParameters )
{
	TickType_t PreviousWakeTime;
	//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
	
	const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 ); //设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数
	PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值
	for( ;; )
	{
		/******************************************************************/
		/*************************任务主体*********************************/
		/******************************************************************/
		//调用函数 vTaskDelayUntil 进行延时
		vTaskDelayUntil( &PreviousWakeTime, TimeIncrement); // 第一次调用函数vTaskDelayUntil 时,需要给它一个PreviousWakeTime
	} 
}

注:如果有更高优先级或者中断的话,尽管在该绝对延时函数中还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到它运行,所以使用函数vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态

2.2 系统时钟 - 系统节拍定时器(滴答定时器)

不管什么系统,其正常运行都需要一个系统时钟节拍,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每进入一次滴答定时器中断, xTickCount 就会加一,具体操作过程在函数 xTaskIncrementTick()中完成:

滴答定时器Systick是个24位向下计数器,其直属于Cortex-M内核,关于滴答定时器的详细介绍,在《Cortex-M3权威指南》的 SysTick定时器 一节中有详细介绍。

BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;

	/* Called by the portable layer each time a tick interrupt occurs.
	Increments the tick then checks to see if the new tick value will cause any
	tasks to be unblocked. */
	traceTASK_INCREMENT_TICK( xTickCount );
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		/* Minor optimisation.  The tick count cannot change in this
		block. */
		const TickType_t xConstTickCount = xTickCount + 1;

		/* Increment the RTOS tick, switching the delayed and overflowed
		delayed lists if it wraps to 0. */
		xTickCount = xConstTickCount;

		if( xConstTickCount == ( TickType_t ) 0U )
		{
			taskSWITCH_DELAYED_LISTS();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* See if this tick has made a timeout expire.  Tasks are stored in
		the	queue in the order of their wake time - meaning once one task
		has been found whose block time has not expired there is no need to
		look any further down the list. */
		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			for( ;; )
			{
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
				{
					/* The delayed list is empty.  Set xNextTaskUnblockTime
					to the maximum possible value so it is extremely
					unlikely that the
					if( xTickCount >= xNextTaskUnblockTime ) test will pass
					next time through. */
					xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
					break;
				}
				else
				{
					/* The delayed list is not empty, get the value of the
					item at the head of the delayed list.  This is the time
					at which the task at the head of the delayed list must
					be removed from the Blocked state. */
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

					if( xConstTickCount < xItemValue )
					{
						/* It is not time to unblock this item yet, but the
						item value is the time at which the task at the head
						of the blocked list must be removed from the Blocked
						state -	so record the item value in
						xNextTaskUnblockTime. */
						xNextTaskUnblockTime = xItemValue;
						break;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* It is time to remove the item from the Blocked state. */
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );

					/* Is the task waiting on an event also?  If so remove
					it from the event list. */
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* Place the unblocked task into the appropriate ready
					list. */
					prvAddTaskToReadyList( pxTCB );

					/* A task being unblocked cannot cause an immediate
					context switch if preemption is turned off. */
					#if (  configUSE_PREEMPTION == 1 )
					{
						/* Preemption is on, but a context switch should
						only be performed if the unblocked task has a
						priority that is equal to or higher than the
						currently executing task. */
						if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
						{
							xSwitchRequired = pdTRUE;
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					#endif /* configUSE_PREEMPTION */
				}
			}
		}

		/* Tasks of equal priority to the currently running task will share
		processing time (time slice) if preemption is on, and the application
		writer has not explicitly turned time slicing off. */
		#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
		{
			if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
			{
				xSwitchRequired = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

		#if ( configUSE_TICK_HOOK == 1 )
		{
			/* Guard against the tick hook being called when the pended tick
			count is being unwound (when the scheduler is being unlocked). */
			if( uxPendedTicks == ( UBaseType_t ) 0U )
			{
				vApplicationTickHook();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_TICK_HOOK */
	}
	else
	{
		++uxPendedTicks;

		/* The tick hook gets called at regular intervals, even if the
		scheduler is locked. */
		#if ( configUSE_TICK_HOOK == 1 )
		{
			vApplicationTickHook();
		}
		#endif
	}

	#if ( configUSE_PREEMPTION == 1 )
	{
		if( xYieldPending != pdFALSE )
		{
			xSwitchRequired = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_PREEMPTION */

	return xSwitchRequired;
}

3. 队列


在没有RTOS时,一个任务或一个中断服务和另外一个任务进行消息/数据传递的过程,一般利用全局变量实现。

但在操作系统中使用全局变量则会涉及到“资源管理”的问题。

FreeRTOS 为此提供一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息/数据传递。队列可存储有限的、大小固定的数据项目。保存在队列中的数据,叫队列项目。队列所能保存的最大数据项目数叫队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息,所以也可叫做消息队列。

如下图,创建一个队列,定义数据项目的大小为10 bits,一共8个数据项目,那么该队列的大小就是8,队列长度就是10 bits
STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第3张图片

3.1 数据存储与队列操作过程

数据从一个队列发送到另一个队列中会导致数据拷贝,即在队列中存储的是数据的原始值(类似于全局变量),而不是原数据的引用(即只传递数据的指针,这种传递叫引用传递),这个也叫做值传递。


队列操作过程:

  1. 创建队列:如下图,创建一个大小为4(队列内有4个队列项目),队列长度为4个字节(C中 int类型为4个字节)的队列
    STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第4张图片
  2. 向队列发送消息:任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是3 . 前面说了向队列中发送消息采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再次被使用,赋其他的值。
    STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第5张图片
    任务 A 又向队列发送了一个消息,即新的 x 的值 20。此时队列剩余长度为 2
    STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第6张图片
  3. 从队列中读取消息:(这里采用数据先进先出的方式FIFO)任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 . 任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加 1 ,变成 3 . 如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2 .
    STM32理论 —— FreeRTOS(内核控制、时钟、队列)_第7张图片

3.3 核心代码

3.3.1 队列结构体

该结构体在文件 queue.c 中定义:

/*
 * Definition of the queue used by the scheduler.
 * Items are queued by copy, not reference.  See the following link for the
 * rationale: http://www.freertos.org/Embedded-RTOS-Queues.html
 */
typedef struct QueueDefinition
{
	int8_t *pcHead;					//指向队列存储区开始地址。
	int8_t *pcTail;					//指向队列存储区最后一个字节
	int8_t *pcWriteTo;			//指向存储区中下一个空闲区域。

	union							/* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
	{
		int8_t *pcReadFrom;			//当用作队列的时候指向最后一个出队的队列项首地址
		UBaseType_t uxRecursiveCallCount;;//当用作递归互斥量的时候用来记录递归互斥量被调用的次数
	} u;

	List_t xTasksWaitingToSend;		//等待发送任务列表,那些因为队列满而导致入队失败而进入阻塞态的任务就会挂到此列表上
	List_t xTasksWaitingToReceive;	//等待接收任务列表,那些因为队列空而导致出队失败而进入阻塞态的任务就会挂到此列表上。

	volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
	UBaseType_t uxLength;			//创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量
	UBaseType_t uxItemSize;		//创建队列时指定的每个队列项(消息)最大长度,单位字节

	volatile int8_t cRxLock;		//当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
	volatile int8_t cTxLock;		//当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	//如果使用静态存储的话此字段设置为 pdTURE
	#endif

	#if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

3.3.2 队列创建函数

  1. xQueueCreate() : 创建队列,动态方法,输入参数与返回值如下:
    • 输入参数1:uxQueueLength,要创建的队列的队列长度,这里是队列的项目数
    • 输入参数2:uxItemSize: 队列中每个项目(消息)的长度,单位为字节
    • 返回值:队列创捷成功以后返回的队列句柄,若返回 NULL 则表示队列创建失败

3.2 出队/入队阻塞

出队就是任务就从队列中读取消息,进行出队时,若队列中数据为空,暂时读取不到任何消息,则产生出队阻塞。

同理,入队就是是向队列中发送消息。进行入队时,若队列中已满,暂时不能向队列发送消息,则产生入队阻塞。

阻塞时间的单位是时钟节拍数。

假设任务 A 用于处理串口接收到的数据,串口接收到数据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但若此时队列
Q为空,即还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那他会根据出队阻塞时间作出以下三种操作:

  1. 阻塞时间为 0 ,即不阻塞,即没有数据的话就马上返回任务并继续执行后面的代码
  2. 阻塞时间为 0 ~ portMAX_DELAY,即当任务没有从队列中获取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还没有接收到数据则就退出阻塞态,返回任务并接着运行后面的代码,反之若在阻塞时间内接收到了数据就立即返回,继续执行任务中后面的代码
  3. 阻塞时间为 portMAX_DELAY,任务就会一直进入阻塞态等待,直到接收到数据为止

:(P23-28:09)

你可能感兴趣的:(STM32,rtos,freertos,stm32,嵌入式)