【STM32】SysTick滴答定時器(delay延時函式講解)
STM32F1xx官方資料:
《Cortex-M3權威指南-中文》-第8章最後一個小節:Systick定時器
SysTick定時器
Systick定時器,是一個簡單的定時器,對於CM3、CM4核心晶片,都有Systick定時器。Systick定時器常用來做延時,或者實時系統的心跳時鐘。這樣可以節省MCU資源,不用浪費一個定時器。比如UCOS中,分時複用,需要一個最小的時間戳,一般在STM32+UCOS系統中,都採用Systick做UCOS心跳時鐘。
Systick定時器就是系統滴答定時器,一個24 位的倒計數定時器,計到0 時,將從RELOAD 暫存器中自動重灌載定時初值。只要不把它在SysTick 控制及狀態暫存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定時器被捆綁在NVIC中,用於產生SYSTICK異常(異常號:15)。Systick中斷的優先順序也可以設定。
實際上,Systick就是一個定時器而已,只是它放在了NVIC中,主要的目的是為了給作業系統提供一個硬體上的中斷,稱之為滴答中斷作業系統進行運轉的時候,也會有時間節拍。它會根據節拍來工作,把整個時間段分成很多小小的時間片,而每個任務每次只能執行一個時間片的時間長度,超時就退出給別的任務執行,這樣可以確保任何一個任務都不會霸佔作業系統提供的各種定時功能,都與這個滴答定時器有關。因此,需要一個定時器來產生週期性的中斷,而且最好還讓使用者程式不能隨意訪問它的暫存器,以維持作業系統的節拍。只要不把它在SysTick控制及狀態暫存器中的使能位清除,就一直執行。
SysTick相關暫存器
SysTick有四個暫存器,分別為CTRL(控制與狀態暫存器)、LOAD(自動重灌載值暫存器)、VAL(當前值暫存器)、CALIB(校準值暫存器)。
在MDK的core_m3.h檔案中定義了一個結構體SysTick_Type,裡面也包括了這四個暫存器。
typedef struct { __IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */ __IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */ __IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */ __I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */ } SysTick_Type;
它們的各位描述如下面的表格所述:
位段 | 名稱 | 型別 | 復位值 | 描述 |
16 | COUNTFLAG | R | 0 | 如果在上次讀取本暫存器後,SysTick已經數到了0,是該位為1.如果讀 取該位,該位自動清零。 |
2 | CLKSOURCE | R/W | 0 | 0 外部時鐘源(STCLK) 1 核心時鐘(FCLK) |
1 | TICKINT | R/W | 0 | 1 SysTick倒數到0時產生SysTick異常請求 0 數到0時無動作 |
0 | ENABLE | R/W | 0 | SysTick定時器的使能位 |
對於STM32,外部時鐘源(STCLK)是HCLK(AHB匯流排時鐘)的1/8,核心時鐘(FCLK)是HCLK(AHB匯流排時鐘)。
位段 | 名稱 | 型別 | 復位值 | 描述 |
23:0 | RELOAD | R/W | 0 | 當倒數至0是,將被重新裝載的值 |
位段 | 名稱 | 型別 | 復位值 | 描述 |
23:0 | CORRENT | R/Wc | 0 | 讀取時返回當前倒計數的值,寫它則使之清零,同時還會清 除在CTRL暫存器的COUNTFLAG位 |
SysTick相關庫函式
SysTick_CLKSourceConfig(),這是Systick的時鐘源選擇,直接配置CTRL暫存器的值。假設HCLK為72MHz,選用外部時鐘源,那麼SysTick的時鐘即9MHz。這就意味著,SysTick的計數器VAL每減1代表時間過去了1/9us。
具體的定義在misc.c檔案中。
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;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
SysTick_Config(uint32_t ticks) ,這是SysTick的初始化函式,時鐘為HCLK,並開啟SysTick中斷。其中函式的引數表示兩次中斷之間時間間隔期間的SysTick週期,即兩次中斷之間有多少個SysTick週期。
具體的定義在core_cm3.h檔案中。
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
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 */
}
void SysTick_Handler(void),這是SysTick的中斷服務函式。我們舉一個例子,利用中斷的方式實現delay延時函式,見下面的程式:
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{ …
if (SysTick_Config(SystemCoreClock / 1000)) //systick時鐘為HCLK,中斷時間間隔1ms
{
while (1);
}
while(1)
{ Delay(200);//200ms
…
}
}
Delay延時函式講解
之前利用中斷實現延時函式,但是一直使用中斷會造成資源的浪費,不建議這樣實現,我們利用查詢的方式實現delay延時。下面主要介紹Delay延時函式的實現:
delay_init()
首先是delay_init(),延時初始化函式。利用Syst_CLKSourceConfig()函式選擇SysTick時鐘源,選擇外部時鐘(HCLK的1/8);同時初始化fac_us和fac_ms兩個變數。
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘 HCLK/8
fac_us=SystemCoreClock/8000000; //為系統時鐘的1/8,實際上也就是在計算1usSysTick的VAL減的數目
fac_ms=(u16)fac_us*1000; //代表每個ms需要的systick時鐘數,即每毫秒SysTick的VAL減的數目
}
delay_ms()
其次,delay_ms(),此函式用來延時指定的ms。
此時要注意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;
}while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達,看CTRL的第16位(COUNTFLAG)是否為1,看STRL的第0位(ENABLE)是否為1
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
這段程式碼其實就是先把延時的時間換算成SysTick的時鐘週期數,然後寫入LOAD暫存器。然後清空當前暫存器VAL的內容,再開啟倒數功能。等倒數結束即延時了nms、最後關閉SysTick,清空VAL的值,實現一次延時的操作。
這裡特別說一下,temp&0x01,這一句用來判斷SysTick定時器是否還處在開啟的狀態,可以防止SysTick被意外關閉導致的死迴圈。
delay_us()
最後,delay_us(),此函式用來延時指定的us。具體的邏輯和上面一個函式類似,就不介紹了。
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; //清空計數器
}
delay延時的相關函式在SYSTEM資料夾下的delay子資料夾,在使用delay_ms()或者delay_us()函式之前一定不要忘記先初始化delay_init()。