FreeRTOS的基礎時鐘

在STM32CubeMX啓用FreeRTOS後,在導出代碼時會出現一個如圖4所示的對話框。提示在使用FreeRTOS時,強烈建議將HAL的基礎時鐘設置爲非SysTick定時器。在前面的示例中,我們都是將HAL的基礎時鐘設置爲定時器TIM6,但並未詳細說明這麼做的原因。

在前一節已經介紹了HAL基礎時鐘的作用,以及使用SysTick定時器或TIM6定時器作爲HAL基礎時鐘時的工作原理。通過前面章節對FreeRTOS的介紹,也知道了FreeRTOS需要使用SysTick定時器作爲其基礎時鐘,以產生FreeRTOS的嘀嗒信號,在SysTick的定時中斷裏進行任務狀態檢查,需要時發出任務調度申請。

圖4 在使用FreeRTOS時提示需要使用非SysTick定時器作爲HAL基礎時鐘源

那麼,在使用FreeRTOS時,如果在圖4的對話框中點擊“Yes”,執意使用SysTick作爲HAL的基礎時鐘,生成的代碼編譯後能否正常運行呢?如果使用了TIM6作爲HAL的基礎時鐘,FreeRTOS是如何對SysTick進行初始化,如何對SysTick的中斷進行處理的呢?

對於第一個問題,如果在圖4的對話框中點擊“Yes”,執意使用SysTick作爲HAL的基礎時鐘,生成的代碼編譯後是無法正常運行的,即使FreeRTOS只有一個非常簡單的任務。這種情況下FreeRTOS就沒有基礎時鐘,無法產生嘀嗒信號,所以無法正常運行,其代碼方面的原因在後面解釋。

所以,在使用FreeRTOS時,必須爲HAL設置一個非SysTick定時器作爲HAL的基礎時鐘,SysTick將自動作爲FreeRTOS的基礎時鐘。這是FreeRTOS的移植決定的,因爲SysTick是Cortex-M內核的一個定時器,在整個STM32系列中都是存在的,使用SysTick作爲滴答時鐘進行移植顯然適用性更強,針對不同系列的STM32處理器移植時需要的改動可以最小化。

1. SysTick定時器的初始化

在STM32CubeMX中基於STM32F407ZG創建一個項目,使用TIM6作爲HAL的基礎時鐘,啓用FreeRTOS後NVIC的自動設置結果如圖5所示。定時器TIM6的搶佔優先級爲0,SysTick和PendSV中斷的優先級都設置爲15,而且這3箇中斷都不能被關閉,不能修改優先級。

圖5 啓用FreeRTOS,並使用TIM6作爲HAL的基礎時鐘後的NVIC設置

在FreeRTOS中,系統嘀嗒信號的頻率由參數configTICK_RATE_HZ決定,默認值是1000Hz。SysTick通過定時中斷產生嘀嗒信號,SysTick默認定時週期是1ms。

在main()函數中,執行函數osKernelStart()啓動內核時對SysTick定時器進行初始化設置。跟蹤函數osKernelStart()的代碼,發現最終設置SysTick定時器的是文件port.c中的函數xPortStartScheduler()和vPortSetupTimerInterrupt()。函數xPortStartScheduler()中設置SysTick和PendSV中斷的中斷優先級,函數vPortSetupTimerInterrupt()設置SysTick的定時週期,代碼如下:

	// 設置systick 定時器,產生需要頻率的嘀嗒中斷
	__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
	{
		/* 計算用於配置嘀嗒中斷的需要的常數 */
		#if( configUSE_TICKLESS_IDLE == 1 )     //Tickless低功耗模式,正常情況下爲0
		{
			ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
			xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
			ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
		}
		#endif /* configUSE_TICKLESS_IDLE */
	
		/*停止和清除SysTick的控制寄存器和計數值寄存器 */
		portNVIC_SYSTICK_CTRL_REG = 0UL;
		portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
	
		/* 配置SysTick,使其以設定的頻率產生中斷*/
		portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
		portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
	}

函數vPortSetupTimerInterrupt()的功能是配置SysTick定時器相關的寄存器,使其以設定的頻率產生中斷。當參數configUSE_TICKLESS_IDLE的值等於1的時候,也就是使用了Tickless低功耗模式的時候會計算幾個常數,這幾個常數會在Tickless低功耗模式的時候用於嘀嗒計數值的補償。

函數中用到的portNVIC_SYSTICK_CTRL_REG、portNVIC_SYSTICK_LOAD_REG等宏是SysTick相關寄存器的移植定義,在文件port.c中的定義時:

	#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
	#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )
	#define portNVIC_SYSTICK_CURRENT_VALUE_REG	( * ( ( volatile uint32_t * ) 0xe000e018 ) )

查閱Cortex-M4內核技術手冊會發現,這3個宏對應的就是SysTick的控制和狀態寄存器SYST_CSR、重載值寄存器SYST_RVR和當前值寄存器SYST_CVR。

2. SysTick定時器的中斷處理

在使用TIM6作爲HAL基礎時鐘,啓用了FreeRTOS的項目中,會發現在文件stm32f4xx_it.c中沒有SysTick中斷ISR函數SysTick_Handler()的代碼框架。而FreeRTOS要使用SysTick的定時中斷產生嘀嗒信號,必然是要定義ISR函數的。搜索關鍵字SysTick_Handler,發現在文件FreeRTOSConfig.h中有如下的宏定義:

	/*  將FreeRTOS 移植的中斷處理函數映射到CMSIS 的標準ISR函數名  */
	#define vPortSVCHandler     SVC_Handler
	#define xPortPendSVHandler  PendSV_Handler
	
	/* 重要提示: 在使用STM32Cube時,如果HAL的基礎時鐘被設置爲SysTick,下面的定義會被註釋,以免覆蓋HAL定義的ISR函數SysTick_Handler  */
	#define xPortSysTickHandler SysTick_Handler

通過這3個宏定義,FreeRTOS將自己移植的3箇中斷的處理函數與CMSIS的標準ISR函數名關聯起來。例如,SysTick中斷的標準ISR函數名是SysTick_HandlerFreeRTOS移植的函數名就是xPortSysTickHandler

源代碼中有英文註釋特別強調,如果將HAL的基礎時鐘設置爲SysTick,那麼第3個宏定義是會被註釋掉的,以免覆蓋HAL定義的ISR函數SysTick_Handler。但是,這種情況下FreeRTOS就沒有基礎時鐘了,不會產生嘀嗒信號,所以FreeRTOS就無法正常運行了。

FreeRTOS移植的SysTick中斷的處理函數xPortSysTickHandler()是在文件port.c中定義的,其功能是調用函數xTaskIncrementTick()使嘀嗒信號計數值遞增,並且檢查是否需要進行上下文切換。如果需要進行上下文切換,就將PendSV中斷掛起,實際的上下文切換是在PendSV的中斷處理程序裏執行的。函數xTaskIncrementTick()的代碼如下。

	void xPortSysTickHandler( void )
	{
		portDISABLE_INTERRUPTS();
		{
			/* 將RTOS嘀嗒計數值遞增,並檢查是否需要進行上下文切換 */
			if( xTaskIncrementTick() != pdFALSE )
			{
				/*使PendSV中斷掛起,請求上下文切換,上下文切換在PendSV中斷裏執行 */
				portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
			}
		}
		portENABLE_INTERRUPTS();
	}

其中調用的函數xTaskIncrementTick()是在文件task.c中實現的,文件task.c中定義了一個表示嘀嗒信號當前計數值的全局變量xTickCount,函數xTaskIncrementTick()的一個功能就是每次使xTickCount的值加1,在計算延時的時候就會用到全局變量xTickCount。

前一篇 使用SysTick作爲HAL的基礎時鐘

主題   HAL和FreeRTOS的基礎時鐘

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章