上圖是LPC1114系統滴答定時器(SysTick)的結構圖。系統滴答定時器位於Cortex-M0內核中,也就是說,不論是LPC1114,還是其他的Cortex-M0內核單片機,都有這個系統定時器。其存在的主要目的是爲嵌入式操作系統提供100Hz(即10ms)的定時節拍。當然,也可以做爲其它的普通定時等其他用途。下面是LPC1114用戶手冊上列舉出的一些用途,你可以瞭解瞭解。
- 可編程設置頻率的RTOS 定時器(例如100 Hz),調用一個SysTick 服務程序。
- 用於核時鐘的高速報警定時器。
- 簡單計數器。軟件可使用它測量時間 (如:完成任務所需時間、已使用時間)。
- 基於丟失 / 命中期限控制的內部時鐘源。控制和狀態寄存器中的COUNTFLAG 位域,
可用於決定一個動作是否在設定的期限內完成,作爲動態時鐘管理控制環的一部分。
一、寄存器
系統定時器使用起來非常簡單。它一共有4個寄存器:SYST_CSR、SYST_RVR、SYST_CVR、SYST_CALIB。定義如下所示:
4個寄存器中,校準寄存器SYST_CALIB不用我們考慮,出廠前就配置好了。這時,就剩下3個寄存器了。一共需要配置3個寄存器就可以完成工作的模塊,你想想會很難使用嗎?英文不好的同學,請看下面的寄存器翻譯:
- SYST_CSR寄存器,就是系統定時器控制和狀態寄存器
- SYST_RVR寄存器,就是系統定時器重載值寄存器
- SYST_CVR寄存器,就是系統定時器當前值寄存器
1.SYST_CSR寄存器
翻譯成中文的:
CSR寄存器用到的位有4個,bit0用於是否開啓定時器,bit1用於是否產生中斷,bit2用於選擇定時器的時鐘源是等於主時鐘還是等於主時鐘的一半,bit16是定時器的狀態。
2.SYST_RVR寄存器
翻譯成中文的:
RVR寄存器用到bit0~23,即24位數,這個值是定時器倒計時的初值,打開定時器以後,值會從此值倒計時到0,因爲倒計時到0以後,又會從此值開始倒計時,所以定義裏面叫這個寄存器位重載值。
3.SYST_CVR寄存器
翻譯成中文:
CVR寄存器用到bit0~23,即24位數,這是一個狀態寄存器,當定時器開始運作,這個值在不斷地變化,從RVR寄存器獲取初值以後,倒計時到0.
二、如何調用Keil自帶的系統定時器函數
系統自帶的Systick函數,由CMSIS(關於什麼是CMSIS,去百度搜吧)提供,位於core_cm0.h文件,你可以在LPC1114工程中,如下地方找到:
雙擊上圖紅色框內的文件名稱,打開對應文件。在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文件打開
在第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自帶的配置是使用主頻