STM32之系統滴答定時器


一、SysTick(系統滴答定時器)概述

  操作系統需要一個滴答定時器周期性產生中斷,以產生系統運行的節拍。在中斷服務程序里,基於優先級調度的操作系統會根據進程優先級切換任務,基於時間片輪轉系統會根據時間片切換任務。總之,滴答定時器是一個操作系統的“心跳”。

  Cortex-M3在內核部分封裝了一個滴答定時器--SysTick,在之前的ARM內核通常是不會把定時器做進內核,定時器都是SOC廠商自己制作的外設。顯然,Cortex-M3封裝了這么一個定時器,對於將操作系統移植到不同SOC廠商生產的Cortex-M3系類MCU上,帶來了極大的方便。Cortex-M3內核統一了這樣的一個系統滴答定時器,移植操作系統的時候可以使用內核的定時器,而忽略掉不同廠商生產定時器帶來的分歧。

二、SysTick control and status register(STK_CTRL)

  SysTick的控制是極其簡單的,它的控制和狀態都匯聚在同一個寄存器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、函數實現思路

  函數實現使用“輪詢狀態位COUNTFLAG”實現精准延時節拍10us。

  在使用的時候,首先調用函數SysTick_Init配置SysTick的定時周期為10us。在延時函數中,當啟動定時器后,就調用函數SysTick_GetFlagStatus輪詢是否定時10us結束,如果結束就更新一下延時節拍變量nTime。

  由於SysTick定時器自動重裝計數器初值,而且SysTick_GetFlagStatus在檢測到SET的時候,COUNTFLAG也自動清理。所以軟件不必裝定時器初值,也不必手動清除標志位COUNTFLAG。

2、函數實現代碼 

 #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;
}
View Code

3、函數的優點和缺陷

優點:

  使用輪詢法實現精准延時是可靠的,因為硬件自動重裝定時器初值,只要我們在下一次計數結束(為0)之前,將節拍計數變量nTime更新,那么這個延時函數就是可靠的。

  使用輪詢法的另一個好處是沒有用到全局變量,完全是局部變量搞定了所需功能。倘若使用中斷延時,必須利用全局變量給精准延時函數傳遞參數。

缺陷:

  由於使用的是輪詢法,有可能被其他的中斷打斷,假設其他的中斷的服務時間有很長,使得“在下一次計數結束(為0)之前,沒有將節拍計數變量nTime更新”,那么延時的時間將要增長。

4、注意

  此延時函數的最小分辨率不能設置為1us,最好設置為>=10us,這是因為輪訓的周期和1us相比具有可比性,時間誤差太大。

四、滴答定時器應用之程序段計時

1、函數實現思路

  首先對滴答定時器初始化,計時節拍數是計數器的最大值。在感興趣的程序段開始處,啟動定時器,在程序段的結束處關閉定時器。倘若這段時間很長,超過了計數器的計數最大值,就會在中斷函數中對溢出次數進行計數。最終的程序段時間決定於計數器的數據寄存器SysTick->VAL 中的剩余值和中斷溢出次數。

  另外為了使程序能夠對不同的程序段或者不同情況下的程序段進行計時,使用了一個結構體定義保存計時數據的結構體類型。在對程序段進行計時的時候,通過一個運行指針指向所要保存的變量中。  

2、函數代碼  

① 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++;    
     }
}
View Code

② 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);
View Code

③ stm32f10x_it.c

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
    TimeupTimes++;
}
View Code

 

參考資料:《STM32F10xxx Cortex-M3 programming manual.pdf》

     《STM32庫開發實戰指南》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM