第18章 SysTick—系統定時器
本章參考資料《Cortex?-M7內核編程手冊》-4.4 章節SysTick Timer(STK),和4.38章節SHPRx,其中STK這個章節有SysTick的簡介和寄存器的詳細描述。因為SysTick是屬於CM7內核的外設,有關寄存器的定義和部分庫函數都在core_cm7.h這個頭文件中實現。所以學習SysTick的時候可以參考這兩個資料,一個是文檔,一個是源碼。
18.1 SysTick簡介
SysTick—系統定時器是屬於CM7內核中的一個外設,內嵌在NVIC中。系統定時器是一個24bit的向下遞減的計數器,計數器每計數一次的時間為1/SYSCLK,一般我們設置系統時鐘SYSCLK等於
因為SysTick是屬於CM7內核的外設,所以所有基於CM7內核的單片機都具有這個系統定時器,使得軟件在CM7單片機中可以很容易的移植。系統定時器一般用於操作系統,用於產生時基,維持操作系統的心跳。
18.2 SysTick寄存器介紹
SysTick—系統定時有4個寄存器,簡要介紹如下。在使用SysTick產生定時的時候,只需要配置前三個寄存器,最後一個校準寄存器不需要使用。
表 18-1 SysTick寄存器匯總
寄存器名稱 |
寄存器描述 |
CTRL |
SysTick控制及狀態寄存器 |
LOAD |
SysTick重裝載數值寄存器 |
VAL |
SysTick當前數值寄存器 |
CALIB |
SysTick校準數值寄存器 |
表 18-2 SysTick控制及狀態寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
16 |
COUNTFLAG |
R/W |
0 |
如果在上次讀取本寄存器後, SysTick 已經計到 |
2 |
CLKSOURCE |
R/W |
0 |
時鐘源選擇位,0=外部時鐘,1=處理器時鐘AHB |
1 |
TICKINT |
R/W |
0 |
1=SysTick倒數計數到 0時產生 SysTick異常請 |
0 |
ENABLE |
R/W |
0 |
SysTick 定時器的使能位 |
表 18-3 SysTick 重裝載數值寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
23:0 |
RELOAD |
R/W |
0 |
當倒數計數至零時,將被重裝載的值 |
表 18-4 SysTick當前數值寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
23:0 |
CURRENT |
R/W |
0 |
讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在SysTick控制及狀態寄存器中的COUNTFLAG 標誌 |
表 18-5 SysTick校準數值寄存器
位段 |
名稱 |
類型 |
復位值 |
描述 |
31 |
NOREF |
R |
0 |
指示是否有參考時鐘提供給處理器 0:提供參考時鐘 1:不提供參考時鐘 如果器件不提供參考時鐘,SYST_CSR.CLKSOURCE標誌位為1,不可改寫。 |
30 |
SKEW |
R |
1 |
S指示TENMS的值是否精確 0:TENMS是精確值 1:TENMS不是精確值或者不提供 不精確的TENMS值可以影響作為軟件實時時鐘節拍器的適用性。 |
23:0 |
TENMS |
R |
0 |
重新加載 10ms (100Hz) 計時的值, 受系統時鐘偏差的錯誤。如果值讀取為零, 校準值未知。 |
系統定時器的校準數值寄存器在定時實驗中不需要用到。有研究過的朋友可以交流,起個拋磚引玉的作用。
18.3 SysTick定時實驗
利用SysTick產生1s的時基,LED以1s的頻率閃爍。
18.3.1 硬件設計
SysTick屬於單片機內部的外設,不需要額外的硬件電路,剩下的只需一個LED燈即可。
18.3.2 軟件設計
這裏只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_SysTick.c和bsp_ SysTick.h文件用來存放SysTick驅動程序及相關宏定義,中斷服務函數放在stm32f7xx_it.c文件中。
1. 編程要點
1、設置重裝載寄存器的值
2、清除當前數值寄存器的值
3、配置控制與狀態寄存器
2. 代碼分析
SysTick 屬於內核的外設,有關的寄存器定義和庫函數都在內核相關的庫文件core_cm7.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函數封裝好的庫函數HAL_SYSTICK_Config ()即可,形參ticks用來設置重裝載寄存器的值,最大不能超過重裝載寄存器的值224,當重裝載寄存器的值遞減到0的時候產生中斷,然後重裝載寄存器的值又重新裝載往下遞減計數,以此循環往復。緊隨其後設置好中斷優先級,最後配置系統定時器的時鐘為216MHz,使能定時器和定時器中斷,這樣系統定時器就配置好了,一個庫函數搞定。
SysTick_Config()庫函數主要配置了SysTick中的三個寄存器:LOAD、VAL和CTRL,有關具體的部分看代碼註釋即可。其中還調用了固件庫函數NVIC_SetPriority()來配置系統定時器的中斷優先級,該庫函數也在core_m7.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屬於內核外設,跟普通外設的中斷優先級有些區別,並沒有搶占優先級和子優先級的說法。在STM32F767中,內核外設的中斷優先級由內核SCB這個外設的寄存器:SHPRx(x=1.2.3)來配置。有關SHPRx寄存器的詳細描述可參考《Cortex-M7內核編程手冊》4.3.8章節。下面我們簡單介紹下這個寄存器。
SPRH1-SPRH3是一個32位的寄存器,但是只能通過字節訪問,每8個字段控制著一個內核外設的中斷優先級的配置。在STM32F767中,只有位7:3這高四位有效,低四位沒有用到,所以內核外設的中斷優先級可編程為:0~15,只有16個可編程優先級,數值越小,優先級越高。如果軟件優先級配置相同,那就根據他們在中斷向量表裏面的位置編號來決定優先級大小,編號越小,優先級越高。
表 18-6 系統異常優先級字段
異常 |
字段 |
寄存器描述 |
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 |
如果要修改內核外設的優先級,只需要修改下面三個寄存器對應的某個字段即可。
圖 18-1 SHPR1寄存器
圖 18-2 SHPR2寄存器
圖 18-3 SHPR3寄存器
在系統定時器中,配置優先級為(1UL << __NVIC_PRIO_BITS) - 1UL),其中宏__NVIC_PRIO_BITS為4,那計算結果就等於15,可以看出系統定時器此時設置的優先級在內核外設中是最低的。
1 // 設置系統定時器中斷優先級
2 NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
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 (HAL_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 =216MHz。如果設置為216,那中斷一次的時間TINT=216/216MHz =1us。不過1us的中斷沒啥意義,整個程序的重心都花在進出中斷上了,根本沒有時間處理其他的任務。
SysTick_Config(SystemCoreClock / 100000))
SysTick_Config()的形我們配置為SystemCoreClock / 100000=216MHz /100000=2160,從剛剛分析我們知道這個形參的值最終是寫到重裝載寄存器LOAD中的,從而可知我們現在把SysTick定時器中斷一次的時間TINT=2160/216MHz =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 /* 系統時鐘初始化成216 MHz */
4 SystemClock_Config();
5 /* LED 端口初始化 */
6 LED_GPIO_Config();
7 /* 配置SysTick 為10us中斷一次,
8 時間到後觸發定時中斷,
9 *進入stm32f7xx_it.
10 c文件的SysTick_Handler處理,通過數中斷次數計時
11 */
12 SysTick_Init();
13
14 while (1) {
15
16 LED_RED;
17 Delay_us(100000); // 10000 * 10us = 1000ms
18
19 LED_GREEN;
20 Delay_us(100000); // 10000 * 10us = 1000ms
21
22 LED_BLUE;
23 Delay_us(100000); // 10000 * 10us = 1000ms
24
25 }
26 }
主函數中初始化了LED和SysTick,然後在一個while循環中以1s的頻率讓LED閃爍。
第18章 SysTick—系統定時器