(轉)STM32 SysTick 滴答定時器

參考:https://blog.csdn.net/guosir_/article/details/78389472

             https://www.cnblogs.com/dustinzhu/p/4149906.html

             http://www.51hei.com/bbs/dpj-133694-1.html

SysTick滴答定時器

一、功能

SysTick定時器是一個簡單的定時器,CM3\CM4內核芯片都具備此定時器。SysTick定時器常用來做延時,採用實時系統時則用來做系統時鐘。

無論用作延時還是用作系統心跳時鐘,不需要太複雜的功能,SysTick即可勝任。

二、實現原理

SysTick定時器是一個24位的倒計數,當倒計數爲0時,將從RELOAD寄存器中取值作爲定時器的初始值,同時可以選擇在這個時候產生中斷(異常號:15)。

例如從RELOAD的值爲999,那麼當倒計數爲0時,就會從復位爲999繼續倒計數。

        只要不把它在SysTick控制及狀態寄存器中的使能位清楚,就永不停息,即使在睡眠模式下也能繼續工作。

三、SysTick寄存器(在 core_cm3.h 有定義,凡是 M3 內核的單片機都是一樣的)

#define SysTick             ((SysTick_Type *)       SysTick_BASE) 

#define SysTick_BASE        (SCS_BASE +  0x0010)

#define SCS_BASE            (0xE000E000) 

typedef struct

{

   __IO uint32_t CTRL;  // 控制及狀態寄存器

   __IO uint32_t LOAD;   // 重裝載數值寄存器

   __IO uint32_t VAL; // 當前計數數值寄存器

   __I  uint32_t CALIB;  // 校準寄存器

} SysTick_Type;

SysTick->CTRL: (可通過 SysTick_CLKSourceConfig() 函數設置)

COUNTFLAG(16)R: 計數標誌位

當SysTick數到0,則該位被硬件置 1,當讀取該位時,將被硬件清零

CLKSOURCE(2)R/W: 時鐘源設置

1 = 外部時鐘源(STCLK) (AHB總線時鐘的1/8(HCLK/8))

0 = 內核時鐘(FCLK)  (AHB總線時鐘的頻率(HCLK))

TICKINT(1)R/W: 中斷使能位

1 = SysTick 倒數到0時產生 SysTick 異常請求

0 = 數到 0 時無動作

ENABLE(0)R/W: SysTick 定時器使能位

(當中斷被使能後,需要關注 void SysTick_Handler(void) 函數)

SysTick_Type->LOAD: (SysTick_Config() 函數會設置該寄存器)

RELOAD(23:0)R/W: 重裝載數值寄存器

當SysTick數到0,將被重裝載的值

SysTick_Type->VAL: (SysTick_Config() 函數會設置該寄存器)

CURRENT(23:0)R/Wc: 當前計數數值寄存器

讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在 SysTick 控制及狀態寄存器中的 COUNTFLAG 標誌。

 

與systick相關的寄存器的說明

SysTick寄存器說明在《Cortex-M3權威指南》(chap8.SysTick定時器章節)有說明

一共有4個寄存器,名稱和地址分別是:

STK_CTRL,        0xE000E010  --  控制寄存器
STK_LOAD,     0xE000E014  --  重載寄存器
STK_VAL,        0xE000E018  --  當前值寄存器
STK_CALRB,   0xE000E01C  --   校準值寄存器

位16:當前值寄存器遞減到0,位16置1

位2:時鐘源選擇位

              0:使用外部參考時鐘

              1:使用內核時鐘

位1:使能SysTick中斷,當前值寄存器遞減到0時產生中斷

位0:SysTick時鐘使能

首先看STK_CTRL控制寄存器:寄存器內有4個位t具有意義

第0位:ENABLE,Systick 使能位  (0:關閉Systick功能;1:開啓Systick功能)
第1位:TICKINT,Systick 中斷使能位    (0:關閉Systick中斷;1:開啓Systick中斷)
第2位:CLKSOURCE,Systick時鐘源選擇  (0:使用HCLK/8 作爲Systick時鐘;1:使用HCLK作爲Systick時鐘)
第3位:COUNTFLAG,Systick計數比較標誌,如果在上次讀取本寄存器後,SysTick 已經數到了0,則該位爲1。如果讀取該位,該位將自動清零

STK_LOAD  重載寄存器:

Systick是一個遞減的定時器,當定時器遞減至0時,重載寄存器中的值就會被重裝載,繼續開始遞減。STK_LOAD  重載寄存器是個24位的寄存器最大計數0xFFFFFF。

當前值寄存器爲0時,自動將重裝載值重裝到當前值計數器,重裝載值的大小需要自己設置

STK_VAL當前值寄存器:

也是個24位的寄存器,讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在SysTick 控制及狀態寄存器中的COUNTFLAG 標誌

可讀可寫,當計數器使能時,這個寄存器的值開始遞減,使用前後注意清零


STK_CALRB  校準值寄存器:

這個寄存器好像目前的水平我還用不到,大體意思明白點,把英文說明放這吧:

位31 NOREF :1=沒有外部參考時鐘(STCLK 不可用)0=外部參考時鐘可用

位30 SKEW:1=校準值不是準確的1ms 0=校準值是準確的1ms

位[23:0] :校準值

Indicates the calibration value when the SysTick counter runs on HCLK max/8 as external clock. The value is product dependent, please refer to the Product Reference Manual, SysTick Calibration Value section. When HCLK is programmed at the maximum frequency, the SysTick period is 1ms. If calibration information is not known, calculate the calibration value required from the frequency of the processor clock or external clock.

SysTick邏輯圖

四、庫函數分析

misc.c

#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
      SysTick->CTRL |= SysTick_CLKSource_HCLK; // 設置 CLKSOURCE 爲 1
  }
  else
  {
      SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;      // 設置 CLKSOURCE 爲 0
  }
}

core_cm3.c

#define SysTick_LOAD_RELOAD_Pos             0                                           
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)
typedef enum IRQn
{
//...
SysTick_IRQn                = -1, 
//...
}IRQn_Type;
#define __NVIC_PRIO_BITS          4
#define SysTick_CTRL_CLKSOURCE_Pos          2                                           
#define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)          

#define SysTick_CTRL_TICKINT_Pos            1                                           
#define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)            

#define SysTick_CTRL_ENABLE_Pos             0                                           
#define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos) 

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
  // 設置計數值爲 ticks - 1 
  // 原因1:視頻說是執行這些代碼需要時間,所以減少一個節拍
  // 原因2:我認爲是因爲 SysTick 的倒計數到 0,例如設置 1000 ,那麼範圍就應該是 999 ~ 0。
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;     
  // 設置中斷優先級
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); 
  SysTick->VAL   = 0;                                
  // 設置時鐘源爲外部時鐘源,同時開啓中斷、並使能 SysTick 定時器
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                   
  return (0);                                                 
}

五、延時應用

1、中斷方式

static __IO uint32_t TimingDelay;

void SysTick_Init(void)
{ 
    // 注意,這裏systick時鐘爲HCLK,中斷時間間隔1ms 
    if (SysTick_Config(SystemCoreClock / 1000)) 
    {
        while (1);
    }

    // 先關閉滴答定時器,用的時候再打開 
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; 
}

void Delay(__IO uint32_t nTime)
{ 
   TimingDelay = nTime;

    // 使能滴答定時器 
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;

   while(TimingDelay != 0);
}

/* 中斷服務函數 */
void SysTick_Handler(void)
{
    if (TimingDelay != 0x00) 
    { 
        TimingDelay--;
    }
}

int main(void)
{  
// ...
   
    SysTick_Init();

    while(1)
    {
        Delay(200);//2ms
    // ...
    }
}

SysTick_Config(SystemCoreClock / 1000): (原代碼這裏假設是採用時鐘源爲 HCLK)

這裏設置的是 72000000Hz / 1000 = 72000 ticks,也就是說 SysTick 從 (72000-1) 開始倒數。

每倒數完 72000 個節拍就觸發一次中斷。

一個節拍的時間爲:72000000 / 72000 = 1000us == 1ms

SysTick_Config((SystemCoreClock / 8000000) * 1000 * 1): 

SysTick_Config() 會設置時鐘源爲 HCLK/8 所以實際應用中不能按照上述代碼的參數。

SystemCoreClock / 8000000: 1us 的節拍數

1us的節拍數 * 1000: 則爲 1ms 的節拍數

1ms 的節拍數 * 1: 設置 1ms 一個SysTick中斷,即從 ((SystemCoreClock / 8000000) * 1000 * 1) - 1 開始倒數。

2、輪詢方式

static u8  fac_us=0; //us延時倍乘數    
static u16 fac_ms=0; //ms延時倍乘數

void delay_init()
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘  HCLK/8
    fac_us = SystemCoreClock/8000000;         // 爲系統時鐘的1/8  1us = 72000000 /                 8000000 =  9 個節拍
    fac_ms = (u16)fac_us*1000; // 1ms 需要 9 * 1000 = 9000 個節拍
}

//延時 nus 微秒         
void delay_us(u32 nus)
{
    u32 temp;
    SysTick->LOAD=nus*fac_us;  //時間加載     
    SysTick->VAL=0x00;          //清空計數器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;                //開始倒數
    do
    {
    temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達  
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//關閉計數器
    SysTick->VAL =0X00;                     //清空計數器  
}

//延時nms
//注意nms的範圍
//SysTick->LOAD爲24位寄存器,所以,最大延時爲:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK單位爲Hz,nms單位爲ms
//對72M條件下,nms<=1864 
void delay_ms(u16 nms)
{        
    u32 temp;    
    SysTick->LOAD=(u32)nms*fac_ms;   //時間加載(SysTick->LOAD爲24bit)
    SysTick->VAL =0x00;                       //清空計數器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;  //開始倒數 
    do
    {
    temp=SysTick->CTRL;
                //等待時間到達,這裏使用了一個小技巧,通過(temp&0x01)檢查 SysTick 的使能位, 避免 Systick 定時器被關閉而導致無限循環 
    }while((temp&0x01)&&!(temp&(1<<16)));  
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器
    SysTick->VAL =0X00;                      //清空計數器        
}

 

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