全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn 編程
野火視頻教程優酷觀看網址:http://i.youku.com/firege ide
本章參考資料《 ARM Cortex™-M4F 技術參考手冊》-4.5 章節SysTick Timer(STK),和4.48章節SHPRx,其中STK這個章節有SysTick的簡介和寄存器的詳細描述。由於SysTick是屬於CM4內核的外設,有關寄存器的定義和部分庫函數都在core_cm4.h這個頭文件中實現。因此學習SysTick的時候能夠參考這兩個資料,一個是文檔,一個是源碼。函數
SysTick—系統定時器是屬於CM4內核中的一個外設,內嵌在NVIC中。系統定時器是一個24bit的向下遞減的計數器,計數器每計數一次的時間爲1/SYSCLK,通常咱們設置系統時鐘SYSCLK等於180M。當重裝載數值寄存器的值遞減到0的時候,系統定時器就產生一次中斷,以此循環往復。學習
由於SysTick是屬於CM4內核的外設,因此全部基於CM4內核的單片機都具備這個系統定時器,使得軟件在CM4單片機中能夠很容易的移植。系統定時器通常用於操做系統,用於產生時基,維持操做系統的心跳。ui
SysTick—系統定時有4個寄存器,簡要介紹以下。在使用SysTick產生定時的時候,只須要配置前三個寄存器,最後一個校準寄存器不須要使用。this
表 181 SysTick寄存器彙總spa
寄存器名稱操作系統 |
寄存器描述設計 |
CTRL3d |
|
LOAD |
SysTick重裝載數值寄存器 |
VAL |
SysTick當前數值寄存器 |
CALIB |
表 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. |
系統定時器的校準數值寄存器在定時實驗中不須要用到。有關各個位的描述這裏引用手冊裏面的英文版本,比較晦澀難懂,暫時不知道這個寄存器用來幹什麼。有研究過的朋友能夠交流,起個拋磚引玉的做用。
利用SysTick產生1s的時基,LED以1s的頻率閃爍。
SysTick屬於單片機內部的外設,不須要額外的硬件電路,剩下的只需一個LED燈便可。
這裏只講解核心的部分代碼,有些變量的設置,頭文件的包含等並無涉及到,完整的代碼請參考本章配套的工程。咱們建立了兩個文件:bsp_SysTick.c和bsp_ SysTick.h文件用來存放SysTick驅動程序及相關宏定義,中斷服務函數放在stm32f4xx_it.h文件中。
一、設置重裝載寄存器的值
二、清除當前數值寄存器的值
三、配置控制與狀態寄存器
SysTick 屬於內核的外設,有關的寄存器定義和庫函數都在內核相關的庫文件core_cm4.h中。
代碼 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_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。不管從搶佔仍是子優先級都比咱們設定的外設的優先級低。若是當兩個的軟件優先級都配置成同樣,那麼就比較他們在中斷向量表中的硬件編號,編號越小,優先級越高。
代碼 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定時器的計數器是向下遞減計數的,計數一次的時間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。
當設置好中斷時間TINT後,咱們能夠設置一個變量t,用來記錄進入中斷的次數,那麼變量t乘以中斷的時間TINT就能夠計算出須要定時的時間。
如今咱們定義一個微秒級別的延時函數,形參爲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遞減一次。
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 }
一、若是修改SysTick的中斷優先級?
二、如何計算SysTick進入一次中斷的時間?
三、如何利用SysTick實現一個1ms的延時?