開發(fā)環(huán)境:
MDK:Keil 5.30
開發(fā)板:GD32F207I-EVAL
MCU:GD32F207IK
Cortex-M的內(nèi)核中包含Systick定時器了,只要是Cortex-M系列的MCU就會有Systick,因此這是通用的,下面詳細(xì)分析。
1 Systick工作原理分析
SysTick 定時器被捆綁在 NVIC 中,用于產(chǎn)生 SysTick 異常(異常號 :15)。在以前,操作系統(tǒng)和所有使用了時基的系統(tǒng)都必須有一個硬件定時器來產(chǎn)生需要的“滴答”中斷,作為整個系統(tǒng)的時基。滴答中斷對操作系統(tǒng)尤其重要。例如,操作系統(tǒng)可以為多個任務(wù)分配不同數(shù)目的時間片,確保沒有一個任務(wù)能霸占系統(tǒng) ;或者將每個定時器周期的某個時間范圍賜予特定的任務(wù)等,操作系統(tǒng)提供的各種定時功能都與這個滴答定時器有關(guān)。因此,需要一個定時器來產(chǎn)生周期性的中斷,而且最好還讓用戶程序不能隨意訪問它的寄存器,以維持操作系統(tǒng)“心跳”的節(jié)律。
Cortex-M3 在內(nèi)核部分包含了一個簡單的定時器——SysTick。因為所有的 CM3 芯片都帶有這個定時器,軟件在不同芯片生產(chǎn)廠商的 CM3 器件間的移植工作就得以簡化。該定時器的時鐘源可以是內(nèi)部時鐘(FCLK,CM3 上的自由運(yùn)行時鐘),或者是外部時鐘。不過,外部時鐘的具體來源則由芯片設(shè)計者決定,因此不同產(chǎn)品之間的時鐘頻率可能大不相同。因此,需要閱讀芯片的使用手冊來確定選擇什么作為時鐘源。在 GD32 中SysTick 以 HCLK(AHB 時鐘)或 HCLK/8 作為運(yùn)行時鐘,見上圖。
SysTick 定時器能產(chǎn)生中斷,CM3 為它專門開出一個異常類型,并且在向量表中有它的一席之地。它使操作系統(tǒng)和其他系統(tǒng)軟件在 CM3 器件間的移植變得簡單多了,因為在所有 CM3 產(chǎn)品間,SysTick 的處理方式都是相同的。SysTick 定時器除了能服務(wù)于操作系統(tǒng)之外,還能用于其他目的,如作為一個鬧鈴、用于測量時間等。 Systick 定時器屬于Cortex 內(nèi)核部件 ,可以參考《ARM Cortex-M3 權(quán)威指南》((英)JosephYiu 著,宋巖譯,北京航空航天大學(xué)出版社出版)來了解。
2 Systick寄存器分析
在傳統(tǒng)的嵌入式系統(tǒng)軟件按中通常實現(xiàn) Delay(N) 函數(shù)的方法為:
for(i = 0; i <= x; i ++);
x --- ;
對于GD32系列微處理器來說,執(zhí)行一條指令只有幾十個 ns,進(jìn)行 for 循環(huán)時,要實現(xiàn) N 毫秒的 x 值非常大,而且由于系統(tǒng)頻率的寬廣,很難計算出延時 N 毫秒的精確值。針對GD32 微處理器,需要重新設(shè)計一個新的方法去實現(xiàn)該功能,以實現(xiàn)在程序中使用 Delay(N)。
Cortex-M3 的內(nèi)核中包含一個 SysTick 時鐘。SysTick 為一個 24 位遞減計數(shù)器,SysTick 設(shè)定初值并使能后,每經(jīng)過 1 個系統(tǒng)時鐘周期,計數(shù)值就減 1。計數(shù)到 0 時,SysTick 計數(shù)器自動重裝初值并繼續(xù)計數(shù),同時內(nèi)部的 COUNTFLAG 標(biāo)志會置位,觸發(fā)中斷 (如果中斷使能情況下)。
在 GD32 的應(yīng)用中,使用 Cortex-M3 內(nèi)核的 SysTick 作為定時時鐘,設(shè)定每一毫秒產(chǎn)生一次中斷,在中斷處理函數(shù)里對 N 減一,在Delay(N) 函數(shù)中循環(huán)檢測 N 是否為 0,不為 0 則進(jìn)行循環(huán)等待;若為 0 則關(guān)閉 SysTick 時鐘,退出函數(shù)。
注: 全局變量 TimingDelay , 必須定義為 volatile 類型 , 延遲時間將不隨系統(tǒng)時鐘頻率改變。
Cortex-M3中的Systick部分內(nèi)容屬于NVIC控制部分,一共有4個寄存器,名稱和地址分別是:
- STK_CTRL,0xE000E010--控制寄存器
第0位:ENABLE,Systick 使能位
(0:關(guān)閉Systick功能;1:開啟Systick功能)
第1位:TICKINT,Systick 中斷使能位
(0:關(guān)閉Systick中斷;1:開啟Systick中斷)
第2位:CLKSOURCE,Systick時鐘源選擇
(0:使用HCLK/8 作為Systick時鐘;1:使用HCLK作為Systick時鐘)
第16位:COUNTFLAG,Systick計數(shù)比較標(biāo)志,如果在上次讀取本寄存器后,SysTick 已經(jīng)數(shù)到了0,則該位為1。如果讀取該位,該位將自動清零
- STK_LOAD, 0xE000E014--重載寄存器
Systick是一個遞減的定時器,當(dāng)定時器遞減至0時,重載寄存器中的值就會被重裝載,繼續(xù)開始遞減。STK_LOAD 重載寄存器是個24位的寄存器最大計數(shù)0xFFFFFF。
- STK_VAL, 0xE000E018--當(dāng)前值寄存器
也是個24位的寄存器,讀取時返回當(dāng)前倒計數(shù)的值,寫它則使之清零,同時還會清除在SysTick控制及狀態(tài)寄存器中的COUNTFLAG標(biāo)志。
- STK_CALRB, 0xE000E01C--校準(zhǔn)值寄存器
校準(zhǔn)值寄存器提供了這樣一個解決方案:它使系統(tǒng)即使在不同的CM3產(chǎn)品上運(yùn)行,也能產(chǎn)生恒定的SysTick中斷頻率。最簡單的作法就是:直接把TENMS的值寫入重裝載寄存器,這樣一來,只要沒突破系統(tǒng)極限,就能做到每10ms來一次 SysTick異常。如果需要其它的SysTick異常周期,則可以根據(jù)TENMS的值加以比例計算。只不過,在少數(shù)情況下, CM3芯片可能無法準(zhǔn)確地提供TENMS的值(如, CM3的校準(zhǔn)輸入信號被拉低),所以為保險起見,最好在使用TENMS前檢查器件的參考手冊。
SysTick定時器除了能服務(wù)于操作系統(tǒng)之外,還能用于其它目的:如作為一個鬧鈴,用于測量時間等。要注意的是,當(dāng)處理器在調(diào)試期間被喊停( halt)時,則SysTick定時器亦將暫停運(yùn)作。
3 Systick定時器實現(xiàn)
SysTick屬于Cortex-M內(nèi)核的部分,因此其相關(guān)的定義在core_cm3.h文件中。
3.1 main文件分析
主函數(shù)如下:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
//systick init
sysTick_init();
/* configure LED1 GPIO port */
led_init(LED1);
/* configure LED2 GPIO port */
led_init(LED2);
/* configure LED3 GPIO port */
led_init(LED3);
/* configure LED4 GPIO port */
led_init(LED4);
while(1)
{
/* turn on LED1, turn off LED4 */
led_on(LED1);
led_off(LED4);
/*delay 500ms*/
delay_ms(500);
/* turn on LED2, turn off LED1 */
led_on(LED2);
led_off(LED1);
/*delay 500ms*/
delay_ms(500);
/* turn on LED3, turn off LED2 */
led_on(LED3);
led_off(LED2);
/*delay 500ms*/
delay_ms(500);
/* turn on LED4, turn off LED3 */
led_on(LED4);
led_off(LED3);
/*delay 500ms*/
delay_ms(500);
}
}
在 main 函數(shù)中,sysTick_init和 delay_us() 這兩個函數(shù)比較陌生,它們的功能分別是配置好 SysTick 定時器和進(jìn)行精確延時。整個 main 函數(shù)的流程就是初始化 LED 及SysTick 定時器之后,就進(jìn)入死循環(huán),點(diǎn)亮LED的時間為精確的 500 ms。
3.2 gd32f207i_systick_eval.c文件分析
- 配置并啟動 SysTick
我們看一下systick_init()這個函數(shù),其功能是啟動系統(tǒng)滴答定時器 SysTick。
/*
brief SysTick init
param[in] none
param[out] none
retval none
*/
void sysTick_init(void)
{
/* SystemFrequency / 1000 1ms中斷一次
* SystemFrequency / 100000 10us中斷一次
* SystemFrequency / 1000000 1us中斷一次
*/
/* setup systick timer for 1000Hz interrupts */
if(SysTick_Config(SystemCoreClock / 100000U)){
/* capture error */
while(1){
}
}
// 關(guān)閉滴答定時器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
本函數(shù)實際上只是調(diào)用了 SysTick_Config() 函數(shù),它是屬于內(nèi)核層的 Cortex-M3 通用函數(shù),位于 core_cm3.h 文件中。若調(diào)用 SysTick_Config() 配置 SysTick 不成功,則進(jìn)入死循環(huán),初始化 SysTick 成功后,先關(guān)閉定時器,在需要的時候再開啟。SysTick_Config() 函數(shù)無法在GD32 外設(shè)固件庫文件中找到其使用方法。所以我們在 Keil 環(huán)境下直接跟蹤這個函數(shù)到 core_cm3.h 文件,查看函數(shù)的定義。
/** \\brief System Tick Configuration
The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\\param [in] ticks Number of ticks between two interrupts.
\\return 0 Function succeeded.
\\return 1 Function failed.
\\note When the variable __Vendor_SysTickConfig is set to 1, then the
function SysTick_Config is not included. In this case, the file device.h
must contain a vendor-specific implementation of this function.
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = ticks - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
在這個函數(shù)定義的前面有關(guān)于它的注釋,如果我們不想去研究它的具體實現(xiàn),可以根據(jù)這段注釋了解函數(shù)的功能 :這個函數(shù)啟動了 SysTick ;并把它配置為計數(shù)至 0 時引起中斷 ;輸入的參數(shù) ticks 為兩個中斷之間的脈沖數(shù),即相隔 ticks 個時鐘周期會引起一次中斷 ;配置 SysTick 成功時返回 0,出錯時返回 1。但是,這段注釋并沒有告訴我們它把 SysTick 的時鐘設(shè)置為 AHB 時鐘還是 AHB/8,這是一個十分關(guān)鍵的問題,于是,我們將對這個函數(shù)的具體實現(xiàn)進(jìn)行分析,與大家再分享一下如何分析底層庫函數(shù)。分析底層庫函數(shù),要有 SysTick 定時器工作分析的知識準(zhǔn)備。
- 檢查輸入?yún)?shù)
SysTick_Config() 第 3 行代碼是檢查輸入?yún)?shù) ticks,因為 ticks 是脈沖計數(shù)值,要被保存到重載寄存器 STK_LOAD 寄存器中,再由硬件把 STK_LOAD 值加載到當(dāng)前計數(shù)值寄存器 STK_VAL 中使用,STK_LOAD 和 STK_VAL 都是 24 位的,所以當(dāng)輸入?yún)?shù) ticks 大于其可存儲的最大值時,將由這行代碼檢查出錯誤并返回。
- 位指示宏及位屏蔽宏
檢查 ticks 參數(shù)沒有錯誤后,就稍稍處理一下把 ticks-1 賦值給 STK_LOAD 寄存器,要注意的是減 1,若 STK_VAL 從 ticks?1 向下計數(shù)至 0,實際上就經(jīng)過了 ticks 個脈沖。這句賦值代碼使用了宏 SysTick_LOAD_RELOAD_Msk,與其他庫函數(shù)類似,這個宏是用來指示寄存器的特定位置或進(jìn)行位屏蔽的。
/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_COUNTFLAG_Pos 16 /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk (1ul << SysTick_CTRL_COUNTFLAG_Pos) /*!< SysTick CTRL: COUNTFLAG Mask */
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) /*!< SysTick LOAD: RELOAD Mask */
/* SysTick Current Register Definitions */
#define SysTick_VAL_CURRENT_Pos 0 /*!< SysTick VAL: CURRENT Position */
#define SysTick_VAL_CURRENT_Msk (0xFFFFFFul << SysTick_VAL_CURRENT_Pos) /*!< SysTick VAL: CURRENT Mask */
/* SysTick Calibration Register Definitions */
#define SysTick_CALIB_NOREF_Pos 31 /*!< SysTick CALIB: NOREF Position */
#define SysTick_CALIB_NOREF_Msk (1ul << SysTick_CALIB_NOREF_Pos) /*!< SysTick CALIB: NOREF Mask */
#define SysTick_CALIB_SKEW_Pos 30 /*!< SysTick CALIB: SKEW Position */
#define SysTick_CALIB_SKEW_Msk (1ul << SysTick_CALIB_SKEW_Pos) /*!< SysTick CALIB: SKEW Mask */
#define SysTick_CALIB_TENMS_Pos 0 /*!< SysTick CALIB: TENMS Position */
#define SysTick_CALIB_TENMS_Msk (0xFFFFFFul << SysTick_VAL_CURRENT_Pos) /*!< SysTick CALIB: TENMS Mask */
/*@}*/ /* end of group CMSIS_CM3_SysTick */
其中寄存器位指示宏 :SysTick_xxx_Pos ,宏展開后即為 xxx 在相應(yīng)寄存器中的位置,如控制 SysTick 時鐘源的 SysTick_CTRL_CLKSOURCE_Pos ,宏展開為 2,這個寄存器位正是寄存器 STK_CTRL 中的 Bit2。
而寄存器位屏蔽宏 :SysTick_xxx_Msk,宏展開是 xxx 的位全部置 1 后,左移SysTick_xxx_Pos 位。如控制 SysTick 時鐘源的 SysTick_CTRL_CLKSOURCE_Msk,宏展開為“1ul << SysTick_CTRL_CLKSOURCE_Pos”, 把無符號長整型數(shù)值(ul) 1 左移 2 位, 得 到 了 一 個 只 有 Bit2 :CLKSOURCE 位被置 1,其他位為 0 的數(shù)值,這樣的數(shù)值配合位操作 &(按位與)、| (按位或)可以很方便地修改寄存器的某些位。假如控制 CLKSOURCE 需 要 4 個寄存器位,這個宏就應(yīng)該被改為( 0xf ul <
- 配置中斷向量及重置 STK_VAL 寄存器
回到 SysTick_Config() 函數(shù),接下來調(diào)用了 NVIC_SetPriority () 函數(shù)并配置了 SysTick中斷,如果想修改SysTick的優(yōu)先級,也可以在外部使用 NVIC 配置 SysTick 中斷。配置好SysTick 中斷后把 STK_VAL 寄存器重新賦值為 0(在使能 SysTick 時,硬件會把存儲在STK_LOAD 寄存器 中的 ticks 值加載給它)。
- 配置 SysTick 時鐘為 AHB
在這段代碼最后,向 STK_CTRL 寄存器寫入了 SysTick 的控制參數(shù),配置為使用AHB 時鐘,使能計數(shù)至 0 時引起中斷,使能 SysTick。執(zhí)行了這行代碼,SysTick 就開始運(yùn)行并進(jìn)行脈沖計數(shù)了。
若想要使用 AHB/8 作為時鐘,可以直接在SysTick_Config()函數(shù)中對SysTick->CTRL進(jìn)行修改,當(dāng)然最好自定義sysTick_init()函數(shù)中修改。
- 使能、關(guān)閉定時器
由于調(diào)用 SysTick_Config() 函數(shù)之后,SysTick 定時器就被開啟了,但我們在初始化的時候并不希望這樣,而是根據(jù)需要再開啟。所以在 sysTick_init() 函數(shù)中,調(diào)用完SysTick_Config() 并配置好后,應(yīng)先把定時器關(guān)閉了。SysTick 的開啟和關(guān)閉由寄存器STK_CTRL 的 Bit0 :ENABLE 位來控制,使用位屏蔽宏以操作寄存器的方式實現(xiàn)。
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 使能滴答定時器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; // 關(guān)閉滴答定時器
- 定時時間的計算
在調(diào)用SysTick_Config()函數(shù)時,向它輸入的參數(shù)為SystemCoreClock / 100000,SystemCoreClock為定義了系統(tǒng)時鐘(SYSCLK)頻率的宏,即等于 AHB的時鐘頻率。在本書的所有例程中AHB 都是被配置為 120MHz 的,也就是這個 SystemCoreClock 宏展開為數(shù)值 12000 0000。
根據(jù)前面對 SysTick_Config() 函數(shù)的介紹,它的輸入?yún)?shù)為 SysTick 將要計時的脈沖數(shù),經(jīng)過 ticks 個脈沖(經(jīng)過 ticks 個時鐘周期)后將觸發(fā)中斷,觸發(fā)中斷后又重新開始計數(shù)。由此我們可以算出定時的時間,下面為計算公式 :
T=ticks×(1/f)
其中,T 為要定時的總時間 ;ticks 為 SysTick_Config() 的輸入?yún)?shù) ;1/ f 即為SysTick 使用的時鐘源的時鐘周期,f 為該時鐘源的時鐘頻率,當(dāng)時鐘源確定后為常數(shù)。
本例中使用時鐘源為 AHB 時鐘,其頻率被配置為 120 MHz。調(diào)用函數(shù)時,把 ticks 賦值為 ticks=SystemFrequency / 100000 =1200,表示 1200 個時鐘周期中斷一次 ;1/f 是時鐘周期的時間,此時1/f =1/120 us,所以最終定時總時間 T=1200x(1/120),為1200 個時鐘周期,正好是 10us。
SysTick 定時器的定時時間(配置為觸發(fā)中斷,即為中斷周期)由 ticks 參數(shù)決定,最大定時周期不能超過 224 個。
- 編寫中斷服務(wù)函數(shù)
一旦我們調(diào)用了 delay_us() 函數(shù),SysTick 定時器就被開啟,按照設(shè)定好的定時周期遞減計數(shù),當(dāng) SysTick 的計數(shù)寄存器的值減為 0 時,就進(jìn)入中斷函數(shù),當(dāng)中斷函數(shù)執(zhí)行完畢之后重新計時,如此循環(huán),除非它被關(guān)閉。
/*
brief delay a time
param[in] count: count
param[out] none
retval none
*/
void delay_us(uint32_t count)
{
delay = count;
// 使能滴答定時器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(0U != delay){
}
}
使能了 SysTick 之后,就使用while(0U != delay)語句等待 delay 變量變?yōu)?0,這個變量是在中斷服務(wù)函數(shù)中被修改的。因此,我們需要編寫相應(yīng)的中斷服務(wù)程序,在本實驗室中我們配置為 10us 中斷一次,每次中斷把 delay 減 1。中斷程序在 gd32f10x_it.c 中實現(xiàn)。
void SysTick_Handler(void)
{
delay_decrement ();
}
SysTick中斷屬于系統(tǒng)異常向量,在gd32f10x_it.c文件中已經(jīng)默認(rèn)有了它的中斷服務(wù)函數(shù)SysTick_Handler(),但內(nèi)容為空。我們找到這個函數(shù),其調(diào)用了用戶函數(shù)delay_decrement()。后者是由用戶編寫的一個應(yīng)用程序。
/*
brief delay decrement
param[in] none
param[out] none
retval none
*/
void delay_decrement(void)
{
if(0U != delay){
delay--;
}
}
每次進(jìn)入 SysTick 中斷就調(diào)用一次 delay_decrement()函數(shù),使全局變量delay 自減一次。用戶函數(shù) delay_us ()在delay 被減至0時,才退出延時循環(huán),即我們對 delay 賦的值為要中斷的次數(shù)。所以總的延時時間 :
T 延時 = T 中斷周期 x delay
至此,SysTick 的精確延時功能講解完畢。
4 實驗現(xiàn)象
將編譯好的程序下載到板子中,可以看到LED燈不同地閃爍。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1336瀏覽量
40084 -
定時器
+關(guān)注
關(guān)注
23文章
3218瀏覽量
113682 -
Cortex-M
+關(guān)注
關(guān)注
2文章
227瀏覽量
29660 -
Systick
+關(guān)注
關(guān)注
0文章
62瀏覽量
13009 -
GD32
+關(guān)注
關(guān)注
7文章
400瀏覽量
23974
發(fā)布評論請先 登錄
相關(guān)推薦
評論