FreeRTOS學習筆記——SysTick中斷

主機環境:Windows

開發環境:MDK4.7.2

FreeRTOS版本:FreeRTOS8.1.2

目標環境:STM32F030C8T6

FreeRTOS中關於時間的管理分爲兩部分:一部分是任務的延時管理;前面敘述過一些,還有一部分就是SysTick中斷,管理任務的延時時間。SysTick是由STM32內核提供的,時鐘源可選,用於產生FreeRTOS所需要的系統時鐘,且是由用戶可配的,用戶在FreeRTOSConfig.h文件中配置configCPU_CLOCK_HZ以及configTICK_RATE_HZ兩個宏來設置系統時鐘,產生時間片時間,系統每隔固定時間進入SysTick中斷處理時間。

有關時鐘配置的初始化函數在port.c文件中,如下

void prvSetupTimerInterrupt( void )
{
	/* Configure SysTick to interrupt at the requested rate. */
	*(portNVIC_SYSTICK_LOAD) = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
	*(portNVIC_SYSTICK_CTRL) = portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE;
}
配置的是portNVIC_SYSTICK_LOAD和portNVIC_SYSTICK_CTRL兩個寄存器,有關SYSTICK寄存器的說明可以在armv6-m體系結構參考手冊中查看


LOAD寄存器是裝載的計數值由用戶配置,CTRL寄存器配置SYSTICK計數器的時鐘源以及是否產生中斷,且是否使能該計數器,該函數在調度器啓動時調用,即在xPortStartScheduler()函數中調用,啓動該計數器,這樣在計數值爲0時進入SysTick中斷函數,即xPortSysTickHandler()中斷函數

void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;

	ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* Pend a context switch. */
			*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
在該中斷函數中首先記錄中斷屏蔽位,並關閉中斷,在處理完後恢復中斷屏蔽位並打開中斷,如果在處理完後需要產生任務切換,則進行一次任務切換調用,

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 )
	{
		/* Increment the RTOS tick, switching the delayed and overflowed
		delayed lists if it wraps to 0. */
		++xTickCount;

		{
			/* Minor optimisation.  The tick count cannot change in this
			block. */
			const TickType_t xConstTickCount = xTickCount;

			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;
						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->xGenericListItem ) );

						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->xGenericListItem ) );

						/* 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;
}
xTaskIncrementTick()函數看名字就曉得它的主要工作就是對時鐘計數器進行更新工作即對xTickCount進行更新,延時任務鏈表也是在該函數中進行管理的,進入該函數中首先判斷調度器是否處於掛起狀態,當調度器沒有掛起正常運行時對xTickCount進行加1操作,這裏又申請了一個xTickCount的副本xConstTickCount記錄當前xTickCount的值,感覺xTickCount的值在這裏不會更改那。。。根據xConstTickCount的值判斷其是否溢出,如果溢出的話需要交換兩個延時任務鏈表,計數器溢出表明通用延時任務鏈表中的任務已經處理完畢,剩下的都是處理溢出延時任務鏈表,通過調用taskSWITCH_DELAYED_LISTS()實現

#define taskSWITCH_DELAYED_LISTS()	\
{					\
	List_t *pxTemp;			\
					\
	/* The delayed tasks list should be empty when the lists are switched. */	\
	configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );			\
											\
	pxTemp = pxDelayedTaskList;							\
	pxDelayedTaskList = pxOverflowDelayedTaskList;					\
	pxOverflowDelayedTaskList = pxTemp;						\
	xNumOfOverflows++;								\
	prvResetNextTaskUnblockTime();							\
}
將兩個延時任務鏈表指針pxDelayedTaskList和pxOverflowDelayedTaskList進行交換,更新溢出次數,同時更新下一任務喚醒時間

static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
	{
		/* The new current delayed list is empty.  Set
		xNextTaskUnblockTime to the maximum possible value so it is
		extremely unlikely that the
		if( xTickCount >= xNextTaskUnblockTime ) test will pass until
		there is an item in the delayed list. */
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		/* The new current 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 should be removed
		from the Blocked state. */
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xGenericListItem ) );
	}
}
回到中斷處理函數中,如果計數器沒得溢出,在正常計數的話,跟將其與下一任務喚醒時間進行比較,查看是否有任務需要被喚醒,當計數器的值大於等於下一任務喚醒時間時表明需要有任務被喚醒,當延時任務鏈表不爲空時遍歷該鏈表(在任務中我們可能並不需要任務阻塞即延時任務鏈表爲空,但時鐘計數器一直在跑,所以需要將下一任務喚醒時間設置爲最大值,一旦有了延時任務則對其進行更新),將延時任務鏈表中延時時間小於等於xConstTickCount的任務都從延時任務鏈表中移除,如果任務也處於事件鏈表中,也同樣溢出,添加到就緒鏈表中,在所有需要被喚醒的任務添加完畢後,根據下一個延時任務的延時時間更新下一任務喚醒時間,xNextTaskUnblockTime的值始終爲下一個被喚醒的任務的喚醒時間。如果系統允許搶佔式調用的話,還要判斷喚醒任務的優先級是否需要產生任務切換。由於FreeRTOS在相同優先級下是支持時間片輪詢調度的,因此如果有多個就緒任務的優先級是相同的且是最高的,則在每次SysTick中斷完畢後執行一次任務切換,即標記xSwitchRequired爲pdTRUE。關於相同優先級具體是怎麼調度的,現在還不清楚,以後看看再說吧,如果我們定義了configUSE_TICK_HOOK=1,即使用時鐘鉤子函數,在uxPendedTicks爲0時會進入vApplicationTickHook()函數,該函數由用戶自己編寫,uxPendedTicks的用處在下面一段會體現,到此調度器正常運行時的SysTick中斷處理就梳理完畢。當調度器掛起時,會進入另外一段不是對xTickCount進行加1操作了,而是對uxPendedTicks進行加1操作,即調度器掛起時我們是沒有對延時任務鏈表進行處理的,而是記錄掛起的時鐘數,調度器掛起時時鐘鉤子函數正常運行,最後函數返回是否需要任務切換。uxPendedTicks就跟我們之前的任務延時相關了,查看之前的想TaskResumeAll()函數,有一段代碼

/* 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. */
if( uxPendedTicks > ( UBaseType_t ) 0U )
{
	while( uxPendedTicks > ( UBaseType_t ) 0U )
	{
		if( xTaskIncrementTick() != pdFALSE )
		{
			xYieldPending = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		--uxPendedTicks;
	}
}
在這裏檢測當uxPendedTicks不爲0時,即調度器喚醒時檢測在其掛起期間經歷了幾個時鐘,掛起了幾次時鐘就調用幾次TaskIncrementTick()函數以維持正確的時鐘,保證任務在正確的時間喚醒。雖然調度器掛起時沒有處理延時任務鏈表,但在其恢復時處理了。


發佈了98 篇原創文章 · 獲贊 101 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章