STM32使用systick定時器定義硬體精準延時函式
阿新 • • 發佈:2018-12-27
前言
- 博文基於STM32F103ZET6和標準韌體庫V3.5.0在MDK5環境下開發;
- 本博文討論的是晶片不執行作業系統的情況下完成1s的延時功能;
- 如有不足之處還請多多指教;
SysTick—系統滴答定時器是什麼?
是一個24位的硬體倒計數定時器;
SysTick的功能是什麼?(分兩種情況)
- 晶片執行作業系統(UCOS)情況下做:為作業系統(例如UCOS)提供硬體上的定時中斷(滴答中斷),作為整個系統的時基,為多個任務分配不同的時間片,確保不會出現一個任務霸佔系統的情況;或者把每個定時器週期的某個時間範圍賜予特定的任務等;還有作業系統提供的各種定時功能;
- 不執行作業系統,單純做定時器:提供精準的定時功能;
SysTick的特點
- 和以往的外設定時器不同,SysTick這個定時器以及相關暫存器在CM3核心裡。《STM32使用手冊》裡對並沒有涉及SysTick的內容,只能去《Cortex-M3權威指南》看(PDF的133頁);寄存的可定址,且暫存器被定義在程式包中的core_m3.c 中,涉及相關的程式設計時一定要注意;
- 可以工作在晶片的睡眠狀態下;
- SysTick被捆綁在NVIC(中斷向量控制器)中,可以產生異常(異常號:15),這是在作業系統下為系統提供時間基準的必要條件;
- 每個CM3核心都含有一個SysTick,這樣方便程式移植;
- SysTick時鐘源的選擇可以來自於外部,也可以來自於內部;使用來自外部的時鐘源的時候要根據具體晶片生產廠商的手冊;(本博文采用外部時鐘源)
SysTick的四個相關暫存器(圖片摘取《Cortex-M3權威指南》)
CTRL控制及狀態暫存器
描述中解釋的都很清楚,這裡強調兩個重要的點:
- 位[16]:只能讀,不能寫,算是一個狀態位;如果計數器達到0,則讀入為1;當讀取或清除當前計數器值時,將自動清除為0;
- 地址:0xE000E010 ,這是本暫存器地址,一會程式設計的時候要用到;
LOAD重灌載數值暫存器
VAL當前值暫存器
CALIB校準數值暫存器(這個暫存器在本部落格中用不到)
SysTick時鐘源的選擇(兩個)
在紅框處,其實我是有疑問的:紅框處提供的有3個時鐘源HCLK,HCLK/8,FCLK;從名字上來看,FCLK似乎就是上面提到的SysTick的CTRL暫存器中時鐘源可選擇的核心時鐘;但是從標準庫函式提供的函式名上來看,提供核心時鐘的是HCLK時鐘源,提供外部時鐘的是HCLK/8時鐘源;看如下程式碼:
/*此段為我自己寫的,為初始化SysTick*/
void delay_Init(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //設定SysTick時鐘源為外部時鐘源--HCLK/8;
fac_us=SystemCoreClock/8000000; //系統核心時鐘,即CM3的時鐘;
fac_ms=(u16)fac_us*1000;
}
/*此段程式碼為庫檔案 misc.c中關於配置SysTick時鐘源的函式*/
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; //0x00000004 選擇時鐘源為內部時鐘源
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; //0xFFFFFFFB 選擇時鐘源為外部時鐘源
}
}
/*此段程式碼是misc.h檔案中的關於時鐘源的選項*/
#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))
從以上三段程式碼來看: SysTick_CLKSource_HCLK_Div8 和 SysTick_CLKSource_HCLK是配置CTRL時鐘源選擇的值,由於這兩個符號裡都包含HCLK,所以我認為,SysTick的時鐘源就來自於上面時鐘框圖中的HCLK和HCLK/8(先這麼理解著,即使理解的不對,並不影響這個程式的進行,因為HCLK和FCLK的時鐘頻率是一樣的,只是名字不同而已)
好了,關係都捋清了,只差程式碼了;
SysTick的配置和1s延時
配置步驟:(這一個函式的執行從頭到尾就是1s)
- 配置SysTick時鐘源;
- 獲取SysTick計數器遞減週期(每個週期計數器減一),這個需要計算;
- 根據上面的遞減週期配置過載暫存器LOAD;
- 清零當前值暫存器VAL;
- 使能定時器(使能CTRL暫存器使能位);
- 等待VAL遞減到0,從而CTRL暫存器的計數標誌位COUNTFLAG被置1;
- 延時完成,函式的最後關閉定時器(關閉CTRL暫存器使能位);
- 函式結束;
所以綜合敘述一下就是,先配置時鐘源,有了時鐘源就能確定當前值暫存器VAL遞減週期,然後根據遞減週期與1s之間的算數關係配置過載暫存器,然後使能暫存器後只需檢測CTRL暫存器的計數標誌位即可,
1s延時程式碼:
#include <stm32f10x.h>
u32 fac_us;
u32 fac_ms;
void delay_Init(void)
{ /*
配置時鐘源為外部HCLK/8,經標準庫初始化後的HCLK = 72MHz,
即SysTick計數器的時鐘源頻率為72MHz/8 = 9MHz,即每9M個方波就是1s(秒基準),
每9000個脈衝1ms(毫秒基準),每9個秒衝就是1us(微妙基準);
所以我們可以認為當前值暫存器VAL的遞減週期就是:每(1/9)us就減1;即每經過1us,經過了9個方波;
*/
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SystemCoreClock/8000000; //最終fac_us=9;
fac_ms=fac_us*1000; //fac_ms = 9000;
}
void delay_us(u32 nus)
{ //注意這裡的SysTick變數是結構體指標變數;
SysTick->LOAD=nus*fac_us; //將要定時的時間(nus*fac_us)裝載過載暫存器;
SysTick->VAL =0x00; //清零當前值暫存器;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //使能控制暫存器(CTRL),開啟定時器;
while((SysTick->CTRL) >> 16 != 1); //等待被重新轉載後的當前值暫存器遞減到0,從而使得控制暫存器的計數標誌位被置1;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos; //關閉計數器(注意運算子是|=,這一點一定要特別注意,因為此處配置的是CTRL暫存器,小心把其他位也給配置了,下面的程式也是一樣)
}
void delay_ms(u32 nms) //含義和上面一樣; 當nms=1000是即可獲得1s延時;
{
SysTick->LOAD=nms*fac_ms;
SysTick->VAL =0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
while((SysTick->CTRL) >> 16 != 1);
SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;
}