STM32之系統(tǒng)滴答定時器
一、SysTick(系統(tǒng)滴答定時器)概述
本文引用地址:http://2s4d.com/article/201611/318497.htm操作系統(tǒng)需要一個滴答定時器周期性產生中斷,以產生系統(tǒng)運行的節(jié)拍。在中斷服務程序里,基于優(yōu)先級調度的操作系統(tǒng)會根據進程優(yōu)先級切換任務,基于時間片輪轉系統(tǒng)會根據時間片切換任務??傊未鸲〞r器是一個操作系統(tǒng)的“心跳”。
Cortex-M3在內核部分封裝了一個滴答定時器--SysTick,在之前的ARM內核通常是不會把定時器做進內核,定時器都是SOC廠商自己制作的外設。顯然,Cortex-M3封裝了這么一個定時器,對于將操作系統(tǒng)移植到不同SOC廠商生產的Cortex-M3系類MCU上,帶來了極大的方便。Cortex-M3內核統(tǒng)一了這樣的一個系統(tǒng)滴答定時器,移植操作系統(tǒng)的時候可以使用內核的定時器,而忽略掉不同廠商生產定時器帶來的分歧。
二、SysTick control and status register(STK_CTRL)
SysTick的控制是極其簡單的,它的控制和狀態(tài)都匯聚在同一個寄存器STK_CTRL上。
STK_CTRL的每一位的含義英文解釋都是很清晰的,不必多說。需要額外討論的是COUNTFLAG標志位,這個標志位代表的含義是:當計數為0時,也即STK_VAL計數至0時,此標志位置1。
經過筆者一番摸索,對此位有更多的看法。
COUNTFLAG:
1、此位與SysTick的中斷無關,不是中斷標志位,可以算作事件標志位(計數至0事件)。
2、此位可以用作軟件查詢
3、讀寫此寄存器都會硬件自動更新COUNTFLAG的值,當然此位的值軟件也是可以改寫的。也就是說,倘若我們輪訓查看COUNTFLAG是否置1(也就是計數是否結束)。當SysTick硬件上計數為0,COUNTFLAG因此硬件自動置1。在我們軟件讀取STK_CTRL的時候,其實SysTick的STK_VAL的值已經不是0(因為自動重裝,并且可能計時幾個CLK了)。這個時候我們讀取到了COUNTFLAG的標志位的1,同時也將COUNTFLAG自動清零。
三、滴答定時器應用之精準延時函數
1、函數實現思路
函數實現使用“輪詢狀態(tài)位COUNTFLAG”實現精準延時節(jié)拍10us。
在使用的時候,首先調用函數SysTick_Init配置SysTick的定時周期為10us。在延時函數中,當啟動定時器后,就調用函數SysTick_GetFlagStatus輪詢是否定時10us結束,如果結束就更新一下延時節(jié)拍變量nTime。
由于SysTick定時器自動重裝計數器初值,而且SysTick_GetFlagStatus在檢測到SET的時候,COUNTFLAG也自動清理。所以軟件不必裝定時器初值,也不必手動清除標志位COUNTFLAG。
2、函數實現代碼
#include "bsp_sysTick.h"/*** @brief 讀取SysTick的狀態(tài)位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的狀態(tài)位COUNTFLAG* @param 無* @retval 無*/static void SysTick_ClearFlag(void){ SysTick->CTRL &= ~ SysTick_CTRL_COUNTFLAG_Msk;}/*** @brief 配置系統(tǒng)滴答定時器 SysTick* @param 無* @retval 1 = failed, 0 = successful*/uint32_t SysTick_Init(void){/* SystemFrequency / 1 1ms中斷一次* SystemFrequency / 100 10us中斷一次* SystemFrequency / 1 1us中斷一次*//* 設置定時周期為10us */if (SysTick_Config(SystemCoreClock / 100)) { /* 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;}
3、函數的優(yōu)點和缺陷
優(yōu)點:
使用輪詢法實現精準延時是可靠的,因為硬件自動重裝定時器初值,只要我們在下一次計數結束(為0)之前,將節(jié)拍計數變量nTime更新,那么這個延時函數就是可靠的。
使用輪詢法的另一個好處是沒有用到全局變量,完全是局部變量搞定了所需功能。倘若使用中斷延時,必須利用全局變量給精準延時函數傳遞參數。
缺陷:
由于使用的是輪詢法,有可能被其他的中斷打斷,假設其他的中斷的服務時間有很長,使得“在下一次計數結束(為0)之前,沒有將節(jié)拍計數變量nTime更新”,那么延時的時間將要增長。
4、注意
此延時函數的最小分辨率不能設置為1us,最好設置為>=10us,這是因為輪訓的周期和1us相比具有可比性,時間誤差太大。
四、滴答定時器應用之程序段計時
1、函數實現思路
首先對滴答定時器初始化,計時節(jié)拍數是計數器的最大值。在感興趣的程序段開始處,啟動定時器,在程序段的結束處關閉定時器。倘若這段時間很長,超過了計數器的計數最大值,就會在中斷函數中對溢出次數進行計數。最終的程序段時間決定于計數器的數據寄存器SysTick->VAL中的剩余值和中斷溢出次數。
另外為了使程序能夠對不同的程序段或者不同情況下的程序段進行計時,使用了一個結構體定義保存計時數據的結構體類型。在對程序段進行計時的時候,通過一個運行指針指向所要保存的變量中?! ?/p>
2、函數代碼
① User_SysTick.c
/*********************************************************************************計時最小單位:1/72M s*計時最大長度:2^32/72M = 59.65 s*使用方法:*(1) 定義一個保存計時數據的TimingVarTypeDef類型變量Time*(2) 初始化* SysTick_Time_Init(&Time);*(3) 在while循環(huán)中放置啟動/停止函數* while(1){* SysTick_Time_Start();* 測試運行時間的代碼* SysTick_Time_Stop();* }*******************************************************************************//* 定義保存未使用DMA時測試程序段運行時間的變量 */TimingVarTypeDef Time;/* 指針指向當前保存時間的變量 */TimingVarTypeDef * CurrentTimingVar; /* 系統(tǒng)滴答定時器的中斷次數 */uint32_t TimeupTimes;/*** @brief 配置系統(tǒng)滴答定時器 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 /* 計時存儲區(qū)的大小 */typedef struct {uint32_t SetSaveTimesNum; /* 設置計時總次數 */uint32_t SaveTimesTemp; /* 已經計時的次數 */uint32_t TimeWidth[SaveTimesBufNum]; /* 計時存儲區(qū) */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庫開發(fā)實戰(zhàn)指南》
評論