1、SysTick(系統滴答定時器)概述ide
操做系統須要一個滴答定時器週期性產生中斷,以產生系統運行的節拍。在中斷服務程序裏,基於優先級調度的操做系統會根據進程優先級切換任務,基於時間片輪轉系統會根據時間片切換任務。總之,滴答定時器是一個操做系統的「心跳」。函數
Cortex-M3在內核部分封裝了一個滴答定時器--SysTick,在以前的ARM內核一般是不會把定時器作進內核,定時器都是SOC廠商本身製做的外設。顯然,Cortex-M3封裝了這麼一個定時器,對於將操做系統移植到不一樣SOC廠商生產的Cortex-M3系類MCU上,帶來了極大的方便。Cortex-M3內核統一了這樣的一個系統滴答定時器,移植操做系統的時候可使用內核的定時器,而忽略掉不一樣廠商生產定時器帶來的分歧。測試
2、SysTick control and status register(STK_CTRL)ui
SysTick的控制是極其簡單的,它的控制和狀態都匯聚在同一個寄存器STK_CTRL上。spa
STK_CTRL的每一位的含義英文解釋都是很清晰的,沒必要多說。須要額外討論的是COUNTFLAG標誌位,這個標誌位表明的含義是:當計數爲0時,也即STK_VAL計數至0時,此標誌位置1。操作系統
通過筆者一番摸索,對此位有更多的見解。指針
COUNTFLAG: code
一、此位與SysTick的中斷無關,不是中斷標誌位,能夠算做事件標誌位(計數至0事件)。blog
二、此位能夠用做軟件查詢進程
三、讀寫此寄存器都會硬件自動更新COUNTFLAG的值,固然此位的值軟件也是能夠改寫的。也就是說,假若咱們輪訓查看COUNTFLAG是否置1(也就是計數是否結束)。當SysTick硬件上計數爲0,COUNTFLAG所以硬件自動置1。在咱們軟件讀取STK_CTRL的時候,其實SysTick的STK_VAL的值已經不是0(由於自動重裝,而且可能計時幾個CLK了)。這個時候咱們讀取到了COUNTFLAG的標誌位的1,同時也將COUNTFLAG自動清零。
3、滴答定時器應用之精準延時函數
一、函數實現思路
函數實現使用「輪詢狀態位COUNTFLAG」實現精準延時節拍10us。
在使用的時候,首先調用函數SysTick_Init配置SysTick的定時週期爲10us。在延時函數中,當啓動定時器後,就調用函數SysTick_GetFlagStatus輪詢是否認時10us結束,若是結束就更新一下延時節拍變量nTime。
因爲SysTick定時器自動重裝計數器初值,並且SysTick_GetFlagStatus在檢測到SET的時候,COUNTFLAG也自動清理。因此軟件沒必要裝定時器初值,也沒必要手動清除標誌位COUNTFLAG。
二、函數實現代碼
#include "bsp_sysTick.h" /** * @brief 讀取SysTick的狀態位COUNTFLAG * @param 無 * @retval The new state of USART_FLAG (SET or RESET). */ static FlagStatus SysTick_GetFlagStatus(void) { if(SysTick->CTRL&SysTick_CTRL_COUNTFLAG_Msk) { return SET; } else { return RESET; } } /** * @brief 清除SysTick的狀態位COUNTFLAG * @param 無 * @retval 無 */ static void SysTick_ClearFlag(void) { SysTick->CTRL &= ~ SysTick_CTRL_COUNTFLAG_Msk; } /** * @brief 配置系統滴答定時器 SysTick * @param 無 * @retval 1 = failed, 0 = successful */ uint32_t SysTick_Init(void) { /* SystemFrequency / 1000 1ms中斷一次 * SystemFrequency / 100000 10us中斷一次 * SystemFrequency / 1000000 1us中斷一次 */ /* 設置定時週期爲10us */ if (SysTick_Config(SystemCoreClock / 100000)) { /* Capture error */ return (1); } /* 關閉滴答定時器且禁止中斷 */ SysTick->CTRL &= ~ (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk); return (0); } /** * @brief us延時程序,10us爲一個單位 * @param * @arg nTime: Delay_us( 1 ) 則實現的延時爲 1 * 10us = 10us * @retval 無 */ void Delay_us(__IO uint32_t nTime) { /* 清零計數器並使能滴答定時器 */ SysTick->VAL = 0; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; for( ; nTime > 0 ; nTime--) { /* 等待一個延時單位的結束 */ while(SysTick_GetFlagStatus() != SET); } /* 關閉滴答定時器 */ SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; }
三、函數的優勢和缺陷
優勢:
使用輪詢法實現精準延時是可靠的,由於硬件自動重裝定時器初值,只要咱們在下一次計數結束(爲0)以前,將節拍計數變量nTime更新,那麼這個延時函數就是可靠的。
使用輪詢法的另外一個好處是沒有用到全局變量,徹底是局部變量搞定了所需功能。假若使用中斷延時,必須利用全局變量給精準延時函數傳遞參數。
缺陷:
因爲使用的是輪詢法,有可能被其餘的中斷打斷,假設其餘的中斷的服務時間有很長,使得「在下一次計數結束(爲0)以前,沒有將節拍計數變量nTime更新」,那麼延時的時間將要增加。
四、注意
此延時函數的最小分辨率不能設置爲1us,最好設置爲>=10us,這是由於輪訓的週期和1us相比具備可比性,時間偏差太大。
4、滴答定時器應用之程序段計時
一、函數實現思路
首先對滴答定時器初始化,計時節拍數是計數器的最大值。在感興趣的程序段開始處,啓動定時器,在程序段的結束處關閉定時器。假若這段時間很長,超過了計數器的計數最大值,就會在中斷函數中對溢出次數進行計數。最終的程序段時間決定於計數器的數據寄存器SysTick->VAL 中的剩餘值和中斷溢出次數。
另外爲了使程序可以對不一樣的程序段或者不一樣狀況下的程序段進行計時,使用了一個結構體定義保存計時數據的結構體類型。在對程序段進行計時的時候,經過一個運行指針指向所要保存的變量中。
二、函數代碼
① User_SysTick.c
/** ****************************************************************************** *計時最小單位:1/72M s *計時最大長度:2^32/72M = 59.65 s *使用方法: *(1) 定義一個保存計時數據的TimingVarTypeDef類型變量Time *(2) 初始化 * SysTick_Time_Init(&Time); *(3) 在while循環中放置啓動/中止函數 * while(1){ * SysTick_Time_Start(); * 測試運行時間的代碼 * SysTick_Time_Stop(); * } ****************************************************************************** */ /* 定義保存未使用DMA時測試程序段運行時間的變量 */ TimingVarTypeDef Time; /* 指針指向當前保存時間的變量 */ TimingVarTypeDef * CurrentTimingVar; /* 系統滴答定時器的中斷次數 */ uint32_t TimeupTimes; /** * @brief 配置系統滴答定時器 SysTick * @param 無 * @retval 1 = failed, 0 = successful */ uint32_t SysTick_Init(void) { /* 設置定時週期爲最大定時數SysTick_LOAD_RELOAD_Msk */ if (SysTick_Config(SysTick_LOAD_RELOAD_Msk)) { /* Capture error */ return (1); } /* 關閉滴答定時器且禁止中斷 */ SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; return (0); } /** * @brief 滴答定時器 SysTick 計時初始化 * @param 初始化計時變量的成員--計時次數 * @retval 無 */ void SysTick_Time_Init(TimingVarTypeDef * TimingVar) { /* 指針指向當前保存時間的變量 */ CurrentTimingVar = TimingVar; /* 計時次數初始化 */ CurrentTimingVar->SetSaveTimesNum = SaveTimesBufNum - 2; } /** * @brief 滴答定時器 SysTick 計時啓動 * @param 無 * @retval 無 */ void SysTick_Time_Start(void) { /* 判斷已經計時次數是否達到設置的計時次數 */ if(CurrentTimingVar->SaveTimesTemp < CurrentTimingVar->SetSaveTimesNum){ /* 滴答定時器的數據寄存器清零 */ SysTick->VAL = 0; /* 滴答定時器中斷次數清零 */ TimeupTimes = 0; /* 啓動滴答定時器 */ SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; } } /** * @brief 滴答定時器 SysTick 計時中止並保存處理數據 * @param 無 * @retval 無 */ void SysTick_Time_Stop(void) { /* 保存已經計時次數 */ uint32_t TimesTemp = CurrentTimingVar->SaveTimesTemp; /* 保存設置計時總次數 */ uint32_t SetSaveTimesNum = CurrentTimingVar->SetSaveTimesNum; uint32_t i,TimeWidthAverageTemp = 0; /* 保存設置計時總次數 */ if(SysTick->CTRL & SysTick_CTRL_ENABLE_Msk) { /* 關閉滴答定時器 */ SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; /* 計算計時總時間 */ CurrentTimingVar->TimeWidth[TimesTemp] = SysTick_LOAD_RELOAD_Msk * TimeupTimes \ + (SysTick_LOAD_RELOAD_Msk - SysTick->VAL + 1); /* 判斷計時次數是否滿 */ if((++TimesTemp) == SetSaveTimesNum) { /* 計算平均值 */ for(i = 0;i < SetSaveTimesNum; i++) { TimeWidthAverageTemp += CurrentTimingVar->TimeWidth[i]; } CurrentTimingVar->TimeWidthAvrage = TimeWidthAverageTemp/SetSaveTimesNum; } /* 已經計時次數變量加1 */ CurrentTimingVar->SaveTimesTemp++; } }
② User_SysTick.h
#define SaveTimesBufNum 4 /* 計時存儲區的大小 */ typedef struct { uint32_t SetSaveTimesNum; /* 設置計時總次數 */ uint32_t SaveTimesTemp; /* 已經計時的次數 */ uint32_t TimeWidth[SaveTimesBufNum]; /* 計時存儲區 */ uint32_t TimeWidthAvrage; /* 平均計時長度 */ } TimingVarTypeDef; /* 計時變量類型 */ extern TimingVarTypeDef Time; extern uint32_t TimeupTimes; extern uint32_t SysTick_Init(void); extern void SysTick_Time_Init(TimingVarTypeDef * TimingVar); extern void SysTick_Time_Start(void); extern void SysTick_Time_Stop(void);
③ stm32f10x_it.c
/** * @brief This function handles SysTick Handler. * @param None * @retval None */ void SysTick_Handler(void) { TimeupTimes++; }
參考資料:《STM32F10xxx Cortex-M3 programming manual.pdf》
《STM32庫開發實戰指南》