第18章 SysTick—系統定時器
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料《 ARM Cortex™-M4F 技術參考手冊》-4.5 章節SysTick Timer(STK),和4.48章節SHPRx,其中STK這個章節有SysTick的簡介和寄存器的詳細描述。因為SysTick是屬於CM4內核的外設,有關寄存器的定義和部分庫函數都在core_cm4.h這個頭文件中實現。所以學習SysTick的時候可以參考這兩個資料,一個是文檔,一個是源碼。
18.1 SysTick簡介
SysTick—系統定時器是屬於CM4內核中的一個外設,內嵌在NVIC中。系統定時器是一個24bit的向下遞減的計數器,計數器每計數一次的時間為1/SYSCLK,一般我們設置系統時鍾SYSCLK等於180M。當重裝載數值寄存器的值遞減到0的時候,系統定時器就產生一次中斷,以此循環往復。
因為SysTick是屬於CM4內核的外設,所以所有基於CM4內核的單片機都具有這個系統定時器,使得軟件在CM4單片機中可以很容易的移植。系統定時器一般用於操作系統,用於產生時基,維持操作系統的心跳。
18.2 SysTick寄存器介紹
SysTick—系統定時有4個寄存器,簡要介紹如下。在使用SysTick產生定時的時候,只需要配置前三個寄存器,最后一個校准寄存器不需要使用。
表 181 SysTick寄存器匯總
表 182 SysTick控制及狀態寄存器
名稱 |
類型 |
復位值 |
描述 |
|
16 |
COUNTFLAG |
R/W |
0 |
如果在上次讀取本寄存器后, SysTick 已經計到 |
2 |
CLKSOURCE |
R/W |
0 |
時鍾源選擇位,0=AHB/8,1=處理器時鍾AHB |
1 |
TICKINT |
R/W |
0 |
1=SysTick倒數計數到 0時產生 SysTick異常請 |
0 |
ENABLE |
R/W |
0 |
SysTick 定時器的使能位 |
表 183 SysTick 重裝載數值寄存器
表 184 SysTick當前數值寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
23:0 |
CURRENT |
R/W |
0 |
讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在SysTick控制及狀態寄存器中的COUNTFLAG 標志 |
表 185 SysTick校准數值寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
31 |
NOREF |
R |
0 |
NOREF flag. Reads as zero. Indicates that a separate reference clock is provided. |
30 |
SKEW |
R |
1 |
SKEW flag: Indicates whether the TENMS value is exact. Reads as one. Calibration |
23:0 |
TENMS |
R |
0 |
Calibration value. Indicates the calibration value when the SysTick counterruns on HCLK max/8 as external clock. The value is product dependent, please refer to theProduct Reference Manual, SysTick Calibration Value section. When HCLK is programmed atthe maximum frequency, the SysTick period is 1ms. |
系統定時器的校准數值寄存器在定時實驗中不需要用到。有關各個位的描述這里引用手冊里面的英文版本,比較晦澀難懂,暫時不知道這個寄存器用來干什么。有研究過的朋友可以交流,起個拋磚引玉的作用。
18.3 SysTick定時實驗
利用SysTick產生1s的時基,LED以1s的頻率閃爍。
18.3.1 硬件設計
SysTick屬於單片機內部的外設,不需要額外的硬件電路,剩下的只需一個LED燈即可。
18.3.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_SysTick.c和bsp_ SysTick.h文件用來存放SysTick驅動程序及相關宏定義,中斷服務函數放在stm32f4xx_it.h文件中。
1. 編程要點
1、設置重裝載寄存器的值
2、清除當前數值寄存器的值
3、配置控制與狀態寄存器
2. 代碼分析
SysTick 屬於內核的外設,有關的寄存器定義和庫函數都在內核相關的庫文件core_cm4.h中。
SysTick配置庫函數
代碼 181SysTick配置庫函數
1 __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
2 {
3 // 不可能的重裝載值,超出范圍
4 if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
5 return (1UL);
6 }
7
8 // 設置重裝載寄存器
9 SysTick->LOAD = (uint32_t)(ticks - 1UL);
10
11 // 設置中斷優先級
12 NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
13
14 // 設置當前數值寄存器
15 SysTick->VAL = 0UL;
16
17 // 設置系統定時器的時鍾源為AHBCLK=180M
18 // 使能系統定時器中斷
19 // 使能定時器
20 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
21 SysTick_CTRL_TICKINT_Msk |
22 SysTick_CTRL_ENABLE_Msk;
23 return (0UL);
24 }
用固件庫編程的時候我們只需要調用庫函數SysTick_Config()即可,形參ticks用來設置重裝載寄存器的值,最大不能超過重裝載寄存器的值2^24,當重裝載寄存器的值遞減到0的時候產生中斷,然后重裝載寄存器的值又重新裝載往下遞減計數,以此循環往復。緊隨其后設置好中斷優先級,最后配置系統定時器的時鍾為180M,使能定時器和定時器中斷,這樣系統定時器就配置好了,一個庫函數搞定。
SysTick_Config()庫函數主要配置了SysTick中的三個寄存器:LOAD、VAL和CTRL,有關具體的部分看代碼注釋即可。
配置SysTick中斷優先級
在SysTick_Config()庫函數還調用了固件庫函數NVIC_SetPriority()來配置系統定時器的中斷優先級,該庫函數也在core_m4.h中定義,原型如下:
1 __STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
2 {
3 if ((int32_t)IRQn < 0) {
4 SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] =
5 (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
6 } else {
7 NVIC->IP[((uint32_t)(int32_t)IRQn)] =
8 (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
9 }
10 }
因為SysTick屬於內核外設,跟普通外設的中斷優先級有些區別,並沒有搶占優先級和子優先級的說法。在STM32F429中,內核外設的中斷優先級由內核SCB這個外設的寄存器:SHPRx(x=1.2.3)來配置。有關SHPRx寄存器的詳細描述可參考《Cortex-M4內核編程手冊》4.4.8章節。下面我們簡單介紹下這個寄存器。
SPRH1-SPRH3是一個32位的寄存器,但是只能通過字節訪問,每8個字段控制着一個內核外設的中斷優先級的配置。在STM32F429中,只有位7:3這高四位有效,低四位沒有用到,所以內核外設的中斷優先級可編程為:0~15,只有16個可編程優先級,數值越小,優先級越高。如果軟件優先級配置相同,那就根據他們在中斷向量表里面的位置編號來決定優先級大小,編號越小,優先級越高。
表 186 系統異常優先級字段
異常 |
字段 |
寄存器描述 |
Memory management fault |
PRI_4 |
SHPR1 |
Bus fault |
PRI_5 |
|
Usage fault |
PRI_6 |
|
SVCall |
PRI_11 |
SHPR2 |
PendSV |
PRI_14 |
SHPR3 |
SysTick |
PRI_15 |
如果要修改內核外設的優先級,只需要修改下面三個寄存器對應的某個字段即可。
圖 181 SHPR1寄存器
圖 182 SHPR2寄存器
圖 183 SHPR3寄存器
在系統定時器中,配置優先級為(1UL << __NVIC_PRIO_BITS) - 1UL),其中宏__NVIC_PRIO_BITS為4,那計算結果就等於15,可以看出系統定時器此時設置的優先級在內核外設中是最低的。
1 // 設置系統定時器中斷優先級
2 NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
但是,問題來了,剛剛我們只是學習了內核的外設的優先級配置。如果我同時使用了systick和片上外設呢?而且片上外設也剛好需要使用中斷,那systick的中斷優先級跟外設的中斷優先級怎么設置?會不會因為systick是內核里面的外設,所以它的中斷優先級就一定比內核之外的外設的優先級高?
從《STM32中斷應用概覽》這章我們知道,外設在設置中斷優先級的時候,首先要分組,然后設置搶占優先級和子優先級。而systick這類內核的外設在配置的時候,只需要配置一個寄存器即可,取值范圍為0~15。既然配置方法不同,那如何區分兩者的優先級?下面舉例說明。
比如配置一個外設的中斷優先級分組為2,搶占優先級為1,子優先級也為1,systick的優先級為固件庫默認配置的15。當我們比較內核外設和片上外設的中斷優先級的時候,我們只需要抓住NVIC的中斷優先級分組不僅對片上外設有效,同樣對內核的外設也有效。我們把systick的優先級15轉換成二進制值就是1111(0b),又因為NVIC的優先級分組2,那么前兩位的11(0b)就是3,后兩位的11(0b)也是3。無論從搶占還是子優先級都比我們設定的外設的優先級低。如果當兩個的軟件優先級都配置成一樣,那么就比較他們在中斷向量表中的硬件編號,編號越小,優先級越高。
SysTick初始化函數
代碼 182 SysTick初始化函數
1 /**
2 * @brief 啟動系統滴答定時器 SysTick
3 * @param 無
4 * @retval 無
5 */
6 void SysTick_Init(void)
7 {
8 /* SystemFrequency / 1000 1ms中斷一次
9 * SystemFrequency / 100000 10us中斷一次
10 * SystemFrequency / 1000000 1us中斷一次
11 */
12 if (SysTick_Config(SystemCoreClock / 100000)) {
13 /* Capture error */
14 while (1);
15 }
16 }
SysTick初始化函數由用戶編寫,里面調用了SysTick_Config()這個固件庫函數,通過設置該固件庫函數的形參,就決定了系統定時器經過多少時間就產生一次中斷。
SysTick中斷時間的計算
SysTick定時器的計數器是向下遞減計數的,計數一次的時間TDEC=1/CLKAHB,當重裝載寄存器中的值VALUELOAD減到0的時候,產生中斷,可知中斷一次的時間TINT=VALUELOAD * TDEC中斷= VALUELOAD/CLKAHB,其中CLKAHB =180MHZ。如果設置為180,那中斷一次的時間TINT=180/180M=1us。不過1us的中斷沒啥意義,整個程序的重心都花在進出中斷上了,根本沒有時間處理其他的任務。
SysTick_Config(SystemCoreClock / 100000))
SysTick_Config()的形我們配置為SystemCoreClock / 100000=180M/100000=1800,從剛剛分析我們知道這個形參的值最終是寫到重裝載寄存器LOAD中的,從而可知我們現在把SysTick定時器中斷一次的時間TINT=1800/180M=10us。
SysTick定時時間的計算
當設置好中斷時間TINT后,我們可以設置一個變量t,用來記錄進入中斷的次數,那么變量t乘以中斷的時間TINT就可以計算出需要定時的時間。
SysTick定時函數
現在我們定義一個微秒級別的延時函數,形參為nTime,當用這個形參乘以中斷時間TINT就得出我們需要的延時時間,其中TINT我們已經設置好為10us。關於這個函數的具體調用看注釋即可。
1 /**
2 * @brief us延時程序,10us為一個單位
3 * @param
4 * @arg nTime: Delay_us( 1 ) 則實現的延時為 1 * 10us = 10us
5 * @retval 無
6 */
7 void Delay_us(__IO u32 nTime)
8 {
9 TimingDelay = nTime;
10
11 while (TimingDelay != 0);
12 }
函數Delay_us()中我們等待TimingDelay為0,當TimingDelay為0的時候表示延時時間到。變量TimingDelay在中斷函數中遞減,即SysTick每進一次中斷即10us的時間TimingDelay遞減一次。
SysTick中斷服務函數
1 void SysTick_Handler(void)
2 {
3 TimingDelay_Decrement();
4 }
中斷復位函數調用了另外一個函數TimingDelay_Decrement(),原型如下:
1 /**
2 * @brief 獲取節拍程序
3 * @param 無
4 * @retval 無
5 * @attention 在 SysTick 中斷函數 SysTick_Handler()調用
6 */
7 void TimingDelay_Decrement(void)
8 {
9 if (TimingDelay != 0x00) {
10 TimingDelay--;
11 }
12 }
TimingDelay的值等於延時函數中傳進去的nTime的值,比如nTime=100000,則延時的時間等於100000*10us=1s。
主函數
1 int main(void)
2 {
3 /* LED 端口初始化 */
4 LED_GPIO_Config();
5
6 /* 配置SysTick 為10us中斷一次,時間到后觸發定時中斷,
7 *進入stm32fxx_it.c文件的SysTick_Handler處理,通過數中斷次數計時
8 */
9 SysTick_Init();
10
11 while (1) {
12
13 LED_RED;
14 Delay_us(100000); // 10000 * 10us = 1000ms
15
16 LED_GREEN;
17 Delay_us(100000); // 10000 * 10us = 1000ms
18
19 LED_BLUE;
20 Delay_us(100000); // 10000 * 10us = 1000ms
21 }
22 }
主函數中初始化了LED和SysTick,然后在一個while循環中以1s的頻率讓LED閃爍。
上面的實驗,我們是使用了中斷,而且經過多個函數的調用,還使用了全局變量,理解起來挺費勁的,其實還有另外一種更簡潔的寫法。我們知道,systick的counter從reload值往下遞減到0的時候,CTRL寄存器的位16:countflag會置1,且讀取該位的值可清0,所有我們可以使用軟件查詢的方法來實現延時。具體代碼見代碼 183和代碼 184,我敢肯定這樣的寫法,初學者肯定會更喜歡,因為它直接,套路淺。
代碼 183 systick 微秒級延時
1 void SysTick_Delay_Us( __IO uint32_t us)
2 {
3 uint32_t i;
4 SysTick_Config(SystemCoreClock/1000000);
5
6 for (i=0; i<us; i++) {
7 // 當計數器的值減小到0的時候,CRTL寄存器的位16會置1
8 while ( !((SysTick->CTRL)&(1<<16)) );
9 }
10 // 關閉SysTick定時器
11 SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
12 }
代碼 184 systick 毫秒級延時
1 void SysTick_Delay_Ms( __IO uint32_t ms)
2 {
3 uint32_t i;
4 SysTick_Config(SystemCoreClock/1000);
5
6 for (i=0; i<ms; i++) {
7 // 當計數器的值減小到0的時候,CRTL寄存器的位16會置1
8 // 當置1時,讀取該位會清0
9 while ( !((SysTick->CTRL)&(1<<16)) );
10 }
11 // 關閉SysTick定時器
12 SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
13 }
另外一種更簡潔的定時編程
在這兩個微秒和毫秒級別的延時函數中,我們還是調用了SysTick_Config這個固件庫函數,有關這個函數的說明具體見代碼 185。配套代碼注釋理解即可。其中SystemCoreClock是一個宏,大小為72000000,如果不想使用這個宏,也可以直接改成數字。
代碼 185 systick 配置函數
1 // 這個固件庫函數在 core_cm3.h中
2 static __INLINE uint32_t SysTick_Config(uint32_t ticks)
3 {
4 // reload 寄存器為24bit,最大值為2^24
5 if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
6
7 // 配置 reload 寄存器的初始值
8 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
9
10 // 配置中斷優先級為 1<<4 -1 = 15,優先級為最低
11 NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
12
13 // 配置 counter 計數器的值
14 SysTick->VAL = 0;
15
16 // 配置systick 的時鍾為 72M
17 // 使能中斷
18 // 使能systick
19 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
20 SysTick_CTRL_TICKINT_Msk |
21 SysTick_CTRL_ENABLE_Msk;
22 return (0);
23 }
18.4 每課一問
1、如果修改SysTick的中斷優先級?
2、如何計算SysTick進入一次中斷的時間?
3、如何利用SysTick實現一個1ms的延時?