第18章 SysTick—系統定時器—零死角玩轉STM32-F429系列


第18章     SysTick—系統定時器

全套200集視頻教程和1000PDF教程請到秉火論壇下載: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寄存器匯總

寄存器名稱

寄存器描述

CTRL

SysTick控制及狀態寄存器

LOAD

SysTick重裝載數值寄存器

VAL

SysTick當前數值寄存器

CALIB

SysTick校准數值寄存器

表 182 SysTick控制及狀態寄存器

位段

名稱

類型

復位值

描述

16

COUNTFLAG

R/W

0

如果在上次讀取本寄存器后, SysTick 已經計到
0,則該位為 1

2

CLKSOURCE

R/W

0

時鍾源選擇位,0=AHB/81=處理器時鍾AHB

1

TICKINT

R/W

0

1=SysTick倒數計數到 0時產生 SysTick異常請
求,0=數到 0 時無動作。也可以通過讀取COUNTFLAG標志位來確定計數器是否遞減到0

0

ENABLE

R/W

0

SysTick 定時器的使能位

表 183 SysTick 重裝載數值寄存器

位段

名稱

類型

復位值

描述

23:0

RELOAD

R/W

0

當倒數計數至零時,將被重裝載的值

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.
The frequency of this clock is HCLK/8

30

SKEW

R

1

SKEW flag: Indicates whether the TENMS value is exact. Reads as one. Calibration
value for the 1 ms inexact timing is not known because TENMS is not known. This can affect
the suitability of SysTick as a software real time clock

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.
If calibration information is not known, calculate the calibration value required from thefrequency of the processor clock or external clock

系統定時器的校准數值寄存器在定時實驗中不需要用到。有關各個位的描述這里引用手冊里面的英文版本,比較晦澀難懂,暫時不知道這個寄存器用來干什么。有研究過的朋友可以交流,起個拋磚引玉的作用。

18.3 SysTick定時實驗

利用SysTick產生1s的時基,LED1s的頻率閃爍。

18.3.1 硬件設計

SysTick屬於單片機內部的外設,不需要額外的硬件電路,剩下的只需一個LED燈即可。

18.3.2 軟件設計

這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_SysTick.cbsp_ 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中的三個寄存器:LOADVALCTRL,有關具體的部分看代碼注釋即可。

配置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這個外設的寄存器:SHPRxx=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_BITS4,那計算結果就等於15,可以看出系統定時器此時設置的優先級在內核外設中是最低的。

1 // 設置系統定時器中斷優先級

2 NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);

但是,問題來了,剛剛我們只是學習了內核的外設的優先級配置。如果我同時使用了systick和片上外設呢?而且片上外設也剛好需要使用中斷,那systick的中斷優先級跟外設的中斷優先級怎么設置?會不會因為systick是內核里面的外設,所以它的中斷優先級就一定比內核之外的外設的優先級高?

從《STM32中斷應用概覽》這章我們知道,外設在設置中斷優先級的時候,首先要分組,然后設置搶占優先級和子優先級。而systick這類內核的外設在配置的時候,只需要配置一個寄存器即可,取值范圍為0~15。既然配置方法不同,那如何區分兩者的優先級?下面舉例說明。

比如配置一個外設的中斷優先級分組為2,搶占優先級為1,子優先級也為1systick的優先級為固件庫默認配置的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()中我們等待TimingDelay0,當TimingDelay0的時候表示延時時間到。變量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 }

主函數中初始化了LEDSysTick,然后在一個while循環中以1s的頻率讓LED閃爍。

上面的實驗,我們是使用了中斷,而且經過多個函數的調用,還使用了全局變量,理解起來挺費勁的,其實還有另外一種更簡潔的寫法。我們知道,systickcounterreload值往下遞減到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的延時?


免責聲明!

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



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