系統定時器SysTick


在LPC824內部有一個特殊的定時器——系統定時器(SysTick),它位於Cortex-M0+內核里面,是ARM內核的一部分,主要用來給操作系統提供時間片輪轉的定時,一般固定為10ms的定時,所以中文也稱它為“嘀嗒”定時器(也稱“心跳”定時器)。在不跑操作系統時,可以把它當作普通定時器來用,一般用來進行程序延時。在前面的第一個演示示例中就用到過,下面就來討論一下如何運用SysTick來提供延時。
系統定時器也位於“私有外設總線”(Private peripheral bus)內,其地址為0xE000E010~0xE000E0FF。下面先來看一下SysTick的內部結構,如下圖所示。
 
從上圖中可以看出,SysTick定時器的位長度是24位,即最長的計數次數為16777216次,且計數為倒數計數形式,遞減到0時產生中斷請求。計數的脈沖可直接取系統時鍾,也可取半系統時鍾。下表給出了和SysTick相關的寄存器。
上表中,SYST_CSR是系統定時器的控制寄存器,負責SysTick的啟動、中斷使能、輸入時鍾選擇、溢出標志讀取等操作;SYST_RVR是系統定時器的初始值重載寄存器,負責SysTick的24位初值載入;SYST_CVR是系統定時器的當前值寄存器,負責獲取SysTick的24位當前計數值,當對該寄存器進行寫操作時,該寄存器的數值將會被清零;SYST_CALIB是系統定時器的校准值寄存器,負責SysTick的校准。
下面給出的是上表中控制寄存器SYST_CSR的全部位結構,其字節地址為0xE000E010。
(1)第0位(ENABLE)是使能SysTick位,置1啟動計數,置0關閉計數。
(2)第1位(TICKINT)是SysTick的中斷使能位,置1使能中斷,置0禁止中斷。
(3)第2位(CLKSOURCE)是輸入時鍾的選擇位,置1時選擇系統時鍾做為計數脈沖,置0時選擇半系統時鍾做為計數脈沖的參考時鍾。
(4)第3到15位為保留位,不能對它們寫1。
(5)第16位(COUNTFLAG)是溢出標志位,當計數的值遞減到0時,該位被置1,在讀取該值后自動清零。
(6)第17到31位為保留位,不能對它們寫1。
SYST_RVR是系統定時器的初始值重載寄存器,負責SysTick的24位初值載入。下表給出了它的全部位結構,其字節地址為0xE000E014。

(1)第0到23位(RELOAD)為重載的初值,即當計數器計數到0后重載到計數器中的值,一共24位。
(2)第24到31位為保留位,不能對它們寫1。
SYST_CVR是系統定時器的當前值寄存器,負責獲取SysTick的24位當前計數值,當對該寄存器進行寫操作時,該寄存器的數值將會被清零。下表給出了它的全部位結構,其字節地址為0xE000E018。

(1)第0到23位(CURRENT)為SysTick的當前值,一共24位。
(2)第24到31位為保留位,不能對它們寫1。
SYST_CALIB是系統定時器的校准值寄存器,負責SysTick的校准。下表給出了它的全部位結構,其字節地址為0xE000E01C。

 

(1)第31位(NOREF)指示處理器是否有基准時鍾,由芯片廠家出廠時決定。該位讀出值為1,指明無獨立的基准時鍾。
(2)第30位(SKEW)指示TENMS值是否精確。由於TENMS不可知,因此10ms不精確計時的校准值不能確定,這會影響SysTick作為軟件實時時鍾的適用性。該位讀出值為1,指明未提供TENMS值。
(3)第24到29位為保留位。
(4)第0到23位(TENMS)在發生系統時鍾扭曲錯誤時,在10ms時序下的校正值。該位讀出值為0,指明校准值未知。

下面給出系統時鍾SysTick的結構體定義。
typedef struct
{
  __IOM uint32_t CTRL;
  __IOM uint32_t LOAD;
  __IOM uint32_t VAL;
  __IM    uint32_t CALIB;
} SysTick_Type;

注意,在上述定義中,結構體成員CTRL對應SYST_CSR寄存器,LOAD對應SYST_RVR寄存器,VAL對應SYST_CVR寄存器,CALIB對應SYST_CALIB寄存器。結構體中並沒有全部采用SysTick中的寄存器名稱。

SysTick定時器組的基址為0xE000E000,所以要將基址指針強制轉換為上述結構體,還要加上下面的定義。
#define SCS_BASE            (0xE000E000UL)
#define SysTick_BASE        (SCS_BASE +  0x0010UL)
#define SysTick             ((SysTick_Type   *)     SysTick_BASE  )
對於系統定時器SysTick產生的中斷,也有特定的入口函數,形式如下所示。
 void SysTick_Handler(void)
{
       系統定時中斷服務程序部分
}
由上述可見,其實系統定時器本身就是一個普通的定時器,在實際應用時可按以下順序進行操作。
(1)給SYST_RVR寄存器寫入定時器的初始值。
(2)寫SYST_CVR寄存器對計數值進行清零。
(3)設置SYST_CSR寄存器選擇相應的時鍾源,啟動定時並使能中斷。
SysTick定時的時長由系統時鍾頻率、系統時鍾選擇和載入的初始值共同決定。假設系統時鍾為24MHz,默認情況下SysTick的CLKSOURCE值為0,即選擇半系統時鍾頻率。這樣,輸入給定時器計數的時鍾就是24÷2=12MHz,計數周期為1/(12MHz)=1/12us,則計12次就是1us的定時,但實際上SysTick采用的是倒數計數方式,即從最大值依次遞減計數直到0產生溢出信號。所以12次計數實際上是0~11次,即最大值要減1,即12-1=11。同時要注意,由於計數位寬是24位,所以最大計數值不能超過2的24次方(即16777216),由此,可得出微秒級的初始值計算公式,如下:
LOAD=((12*us)-1),其中us取值范圍1~1398000
同理可得出毫秒級的初始值計算公式:
LOAD=((12000*ms)-1),其中ms取值范圍1~1398
上升到一般情況,定時初始值可用下面的公式來計算:
上式中,系統時鍾SysClk的單位是MHz,CLKSOURCE的值是0或1,得到的是微秒級別的定時,要注意LOAD的值不能大於16777216。
下面來看一個使用SysClk實現的毫秒級延時函數,代碼如下:
void delay_ms(uint32_t ms)
{
    SysTick->LOAD = (((12000)*ms)-1);         //載入初始值
    SysTick->VAL = 0;                                   //寫當前值寄存器使其清零
    SysTick->CTRL |= (1<<0);                        //啟動定時器,選擇半系統時鍾
    while(!(SysTick->CTRL & 0x10000));        //循環查詢,等待定時時間到
    SysTick->CTRL &= ~(1<<0);                    //關閉定時器
}
在程序中,通過“while(!(SysTick ->CTRL & 0x10000));”這句來等待定時器溢出,其實就是循環查詢控制寄存器SYST_CSR中的COUNTFLAG位(第16位)是否被置1,若為0則循環等待直到1為止。注意,本函數延時的最大時長為1398ms,即傳遞給它的參數不能超過1398,否則不能實現延時功能。
下面再來看一個流水燈的例子,要求實現一個8位的流水燈,時間間隔為100ms。電路延用第一個演示示例中的電路原理圖,參考代碼如下:
#include <LPC82x.h>

//************************端口初始化***********************************
void Port_init(void)
{
LPC_GPIO_PORT->DIR0 = 0x1FFFFFFF; //設置端口為輸出方向
LPC_GPIO_PORT->PIN0 = 0x1FFFFFFF; //關閉所有LED
LPC_GPIO_PORT->CLR0 = (1<<7); //點亮第1個LED
}
//************************定時器初始化*********************************
void SysTick_init(void)
{
SysTick->LOAD = (((12000)*100)-1);
SysTick->VAL = 0;
SysTick->CTRL |= ((1<<1)|(1<<0));
}
//***************************主函數************************************
int main(void)
{
Port_init(); //調用端口初始化
SysTick_init(); //調用定時器初始化
while(1)
{
;
}
}
//************************定時器中斷***********************************
void SysTick_Handler(void)
{
static uint8_t i=0;
switch(i++)
{
case 0:
LPC_GPIO_PORT->SET0 = (1<<7); //熄滅第1個LED
LPC_GPIO_PORT->CLR0 = (1<<13); //點亮第2個LED
break;
case 1:
LPC_GPIO_PORT->SET0 = (1<<13); //熄滅第2個LED
LPC_GPIO_PORT->CLR0 = (1<<16); //點亮第3個LED
break;
case 2:
LPC_GPIO_PORT->SET0 = (1<<16); //熄滅第3個LED
LPC_GPIO_PORT->CLR0 = (1<<17); //點亮第4個LED
break;
case 3:
LPC_GPIO_PORT->SET0 = (1<<17); //熄滅第4個LED
LPC_GPIO_PORT->CLR0 = (1<<19); //點亮第5個LED
break;
case 4:
LPC_GPIO_PORT->SET0 = (1<<19); //熄滅第5個LED
LPC_GPIO_PORT->CLR0 = (1<<27); //點亮第6個LED
break;
case 5:
LPC_GPIO_PORT->SET0 = (1<<27); //熄滅第6個LED
LPC_GPIO_PORT->CLR0 = (1<<28); //點亮第7個LED
break;
case 6:
LPC_GPIO_PORT->SET0 = (1<<28); //熄滅第7個LED
LPC_GPIO_PORT->CLR0 = (1<<18); //點亮第8個LED
break;
case 7:
LPC_GPIO_PORT->SET0 = (1<<18); //熄滅第8個LED
LPC_GPIO_PORT->CLR0 = (1<<7); //點亮第1個LED
i = 0; //計數清零
break;
}
}

在上述程序中,由於8個LED並沒有接到LPC824相鄰的引腳,因此使用了針對引腳的位操作方式來實現流水燈效果。把程序編譯后下載到LPC824中,給系統上電,可看到接到端口上的8個LED在閃爍流動。


免責聲明!

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



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