關於系統滴答定時器SysTick_Config()解析

Cortex-M0 Systick

上圖是LPC1114系統滴答定時器(SysTick)的結構圖。系統滴答定時器位於Cortex-M0內核中,也就是說,不論是LPC1114,還是其他的Cortex-M0內核單片機,都有這個系統定時器。其存在的主要目的是爲嵌入式操作系統提供100Hz(即10ms)的定時節拍。當然,也可以做爲其它的普通定時等其他用途。下面是LPC1114用戶手冊上列舉出的一些用途,你可以瞭解瞭解。

  • 可編程設置頻率的RTOS 定時器(例如100 Hz),調用一個SysTick 服務程序。
  • 用於核時鐘的高速報警定時器。
  • 簡單計數器。軟件可使用它測量時間 (如:完成任務所需時間、已使用時間)。
  • 基於丟失 / 命中期限控制的內部時鐘源。控制和狀態寄存器中的COUNTFLAG 位域,
    可用於決定一個動作是否在設定的期限內完成,作爲動態時鐘管理控制環的一部分。

一、寄存器

系統定時器使用起來非常簡單。它一共有4個寄存器:SYST_CSR、SYST_RVR、SYST_CVR、SYST_CALIB。定義如下所示:

Systick寄存器

4個寄存器中,校準寄存器SYST_CALIB不用我們考慮,出廠前就配置好了。這時,就剩下3個寄存器了。一共需要配置3個寄存器就可以完成工作的模塊,你想想會很難使用嗎?英文不好的同學,請看下面的寄存器翻譯:

  • SYST_CSR寄存器,就是系統定時器控制和狀態寄存器
  • SYST_RVR寄存器,就是系統定時器重載值寄存器
  • SYST_CVR寄存器,就是系統定時器當前值寄存器

1.SYST_CSR寄存器

SYST_CSR寄存器

翻譯成中文的:

SYST_CSR中文

CSR寄存器用到的位有4個,bit0用於是否開啓定時器,bit1用於是否產生中斷,bit2用於選擇定時器的時鐘源是等於主時鐘還是等於主時鐘的一半,bit16是定時器的狀態。

2.SYST_RVR寄存器

SYST_RVR寄存器

翻譯成中文的:

SYST_RVR中文

RVR寄存器用到bit0~23,即24位數,這個值是定時器倒計時的初值,打開定時器以後,值會從此值倒計時到0,因爲倒計時到0以後,又會從此值開始倒計時,所以定義裏面叫這個寄存器位重載值。

3.SYST_CVR寄存器

SYSR_CVR寄存器英文

翻譯成中文:

SYST_CVR寄存器中文

CVR寄存器用到bit0~23,即24位數,這是一個狀態寄存器,當定時器開始運作,這個值在不斷地變化,從RVR寄存器獲取初值以後,倒計時到0.

二、如何調用Keil自帶的系統定時器函數

系統自帶的Systick函數,由CMSIS(關於什麼是CMSIS,去百度搜吧)提供,位於core_cm0.h文件,你可以在LPC1114工程中,如下地方找到:

core_cm0文件

雙擊上圖紅色框內的文件名稱,打開對應文件。在core_cm0.h文件的最底部,有一個函數,如下所示:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */

  SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

此函數就是CMSIS提供的系統定時器控制函數SysTick_Config()。在使用的時候,可以直接調用,函數有一個參數ticks。由函數內部的語句
“SysTick->LOAD  = ticks – 1;”知道,ticks就是LOAD值,即重載值,表示兩次中斷的計數。

例如,要產生10ms的中斷,可以在程序中如下調用函數:

Systick_Config(SystemCoreClock/100);

函數參數中的SystemCoreClock是當前主頻的值,假如現在的主頻是50MHz,SystemCoreClock就是50 000 000 ,50 000 000 /100=500 000。我們把參數帶進去以後,LOAD=499 999,也就是說,定時器開始運行後,定時器的值會從499 999遞減到0,進入中斷函數,然後再次從499 999 遞減到0,如此循環。

這時候,你心中會有一個大大的問號:“爲什麼從499 999遞減到0就是10ms?”接下來,瑞生給你解答,其實很簡單,不信聽我說。

定時器運行,要知道“爲什麼從499 999遞減到0就是10ms”,只要知道定時器每遞減一個值需要多長時間就可以了。知道每遞減一個值需要多長時間,那麼遞減500 000下,需要多長時間,就知道了。

要知道每遞減一個值需要多長時間,就需要知道當前定時器運行的時鐘是多少。由寄存器CSR知道,定時器的時鐘有兩種,一種是等於主頻,一種是等於主頻的二分之一,由CSR寄存器中的bit2決定。

函數中用到的寄存器名稱和我們手冊上給出的名稱不太一樣,但是你要知道,名稱就是個代號,實際調用的其實是名稱背後的寄存器地址。函數中LOAD就是我們之前說的RSR,VAL就是我們之前說的CVR,CTRL就是我們之前說的CSR。

函數中,對控制寄存器的bit0 bit1 bit2都置1,對照前面的寄存器定義可知,時鐘設置爲等於主頻,打開系統定時器中斷,允許定時器運行。

我們知道了時鐘,就知道定時器每遞減一個值需要的時間了,即:1/SystemCoreClock 秒,換算成毫秒即:(1/SystemCoreClock)*1000=1000/SystemCoreClock毫秒,即每遞減一個值,耗時1000/SystemCoreClock毫秒。所以如果要使得10ms定時,即10/(1000/SystemCoreClock)=SystemCoreClock/100,回頭看看前面定時10ms的參數,是不是這個值呢。以此類推,需要定時多長時間,你可以自己算一個參數帶進去了,需要注意的是,LOAD值是個24位數,帶進去的數不要超過24位數的最大值。還有一個需要注意的地方,就是LOAD值最小255,當你給LOAD值帶進去小於255值,LOAD會自動變成255。

三、系統定時器中斷函數怎麼寫

系統定時器的中斷函數名稱如下所示:

void SysTick_Handler(void)  
{                              
                                                
}

有的童鞋會問,函數名稱可以自己改嗎?答案是不可以改,非要自己改一個,需要一定的步驟。接下來瑞生給你解答。

打開一個工程,雙擊startup_LPC11xx.h文件打開

startup_LPC11xx

在第74行,你可以看到系統定時器中斷函數的名稱,如下所示:

中斷函數名稱

你不僅可以看到系統定時器中斷函數的名稱,所有的中斷函數的名稱,都已經寫好了,在用其它模塊的中斷時,到這個地方找就對了。還有前面那個是否可以自己改的問題,你把這個地方的名稱改了,就可以在.c文件中使用你修改後的名稱了,不過爲了程序的移植性統一性閱讀性,瑞生建議大家不要修改。

四、寫一個毫秒延時函數delay_ms()

1.自己配置寄存器(假設當前主頻爲50MHz)

static volatile uint32_t TimeTick = 0;
          
void SysTick_Handler(void)               // 中斷函數  
{
    TimeTick++;
}

void delay_ms(uint32_t ms)  // 參數最大帶入671
{
    SysTick->LOAD =    25000*ms-1;     
    SysTick->VAL   =     0;                  
    SysTick->CTRL  |=  ((1<<1)|(1<<0));   // 開定時器,開中斷
    while(!TimeTick);
    TimeTick = 0;
    SysTick->CTRL =0;   // 關定時器
}

爲什麼主頻爲50MHz時,上面函數中與ms乘的數是25000?我在上面已經講過了,這裏我再講一次,非常簡單哦。CTRL寄存器bit2默認是0,也就是說默認的系統定時器時鐘是主頻的1/2,即25 000 000。所以定時器每遞減1,耗時1/25 000 000秒,換成毫秒爲單位即:1/25000毫秒。所以每毫秒需要的定時器遞減數爲1/(1/25000)=25000,即你把25000-1給了LOAD寄存器,就是1ms進一次中斷,2ms就是2*25000-1,以此類推,SysTick->LOAD=(25000*ms-1)。

接下來讓我們來看看,爲什麼參數最大值是677?我們知道8位值,最大是255,16位數,最大值是65535,那麼24位值最大值是多少呢?打開你的電腦上的計算器,在科學型模式下,16進制輸入FFFFFF,再轉換成十進制,得到16777215,沒錯,這就是24位值的最大值。用16777215+1然後除以25000得到最大值671。如果帶入超過671的數,就會超過24位最大值,使得程序不可用。

這時候,又有人會抱怨,你這函數最大才能延時671毫秒,那我要延時1秒,豈不是不能用此函數了?瑞生說呀,你媽小時候是教育你將來不要做一個不三不四的人,但是你也不能這麼二吧!你要延時1秒鐘,用兩次延時500毫秒不就可以了麼!

delay_ms(500);
delay_ms(500);

2.自己配置寄存器(適應不同主頻)

static volatile uint32_t TimeTick = 0;
          
void SysTick_Handler(void)               // 中斷函數  
{
    TimeTick++;
}

void delay_ms(uint32_t ms)  
{
    SysTick->LOAD =    (SystemCoreClock/2000)*ms-1;     
    SysTick->VAL   =     0;                  
    SysTick->CTRL  |=  ((1<<1)|(1<<0));   // 開定時器,開中斷
    while(!TimeTick);
    TimeTick = 0;
    SysTick->CTRL =0;   // 關定時器
}

爲了在不同的主頻下使用此函數,而不用每次去修改不同主頻下的那個值(例如50MHz對應的25000),我們把LOAD賦值語句改成了上面函數中所示。SystemCoreClock是當前的主頻。

3.利用Keil自帶系統定時器配置函數寫毫秒延時函數delay_ms()

void SysTick_Handler(void)  
{                              
  msTicks++;                                                 
}

void delay_ms(uint32_t ms)
{
  SysTick_Config((SystemCoreClock/1000)*ms); 
  while(!msTicks);
  msTicks = 0;
  SysTick->CTRL =0;   // 關定時器
}

爲什麼前面的函數SystemCoreClock是除以2000,而這裏是除以1000?答:因爲前面的函數中,系統定時器時鐘使用主頻的二分之一,而Keil自帶的配置是使用主頻

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