嵌入式基础篇 - 第2章 Systick系统定时器
2.1 STM32 的時鐘系統
STM32 芯片為了實現低功耗,設計了一個功能完善但卻非常復雜的時鐘系統。普通的MCU 一般只要配置好 GPIO 的寄存器就可以使用了,但 STM32 還有一個步驟,就是開啟外設時鐘。?
?
圖2-1 STM32的時鐘樹
在 STM32 中,有五個時鐘源,為 HSI、 HSE、 LSI、 LSE、 PLL。 從時鐘頻率來分可以分為高速時鐘源和低速時鐘源,在這 5 個中 HIS, HSE 以及 PLL 是高速時鐘, LSI 和 LSE 是低速時鐘。從來源可分為外部時鐘源和內部時鐘源,外部時鐘源就是從外部通過接晶振的方式獲取時鐘源,其中 HSE 和 LSE 是外部時鐘源,其他的是內部時鐘源。下面我們看看 STM32 的 5 個時鐘源,我們講解順序是按圖中紅圈標示的順序:?
①HSI 是高速內部時鐘, RC 振蕩器,頻率為 8MHz。?
②HSE 是高速外部時鐘,可接石英 /陶瓷諧振器,或者接外部時鐘源,頻率范圍為4MHz~16MHz。 我們的開發板接的是 8M 的晶振。?
③LSI 是低速內部時鐘,RC 振蕩器,頻率為 40kHz。獨立看門狗的時鐘源只能是 LSI,同時 LSI 還可以作為 RTC 的時鐘源。?
④LSE 是低速外部時鐘,接頻率為 32.768kHz 的石英晶體。這個主要是 RTC 的時鐘源。?
⑤PLL 為鎖相環倍頻輸出,其時鐘輸入源可選擇為 HSI/2、HSE 或者 HSE/2。倍頻可選擇為2~16 倍,但是其輸出頻率最大不得超過 72MHz。?
圖中我們用 A~E 標示我們要講解的地方。?
A. MCO 是 STM32 的一個時鐘輸出 IO(PA8),它可以選擇一個時鐘信號輸出, 可以選擇為 PLL 輸出的 2 分頻、 HSI、 HSE、或者系統時鐘。這個時鐘可以用來給外部其他系統提供時鐘源。?
B. 這里是 RTC 時鐘源,從圖上可以看出, RTC 的時鐘源可以選擇 LSI, LSE,以及HSE 的 128 分頻。?
C. 從圖中可以看出 C 處 USB 的時鐘是來自 PLL 時鐘源。 STM32 中有一個全速功能的 USB 模塊,其串行接口引擎需要一個頻率為 48MHz 的時鐘源。該時鐘源只能從 PLL 輸出端獲取,可以選擇為 1.5 分頻或者 1 分頻,也就是,當需要使用 USB模塊時, PLL 必須使能,并且時鐘頻率配置為 48MHz 或 72MHz。?
D. D 處就是 STM32 的系統時鐘 SYSCLK,它是供 STM32 中絕大部分部件工作的時鐘源。系統時鐘可選擇為 PLL 輸出、 HSI 或者 HSE。系統時鐘最大頻率為 72MHz,當然你也可以超頻,不過一般情況為了系統穩定性是沒有必要冒風險去超頻的。?
E. 這里的 E 處是指其他所有外設了。從時鐘圖上可以看出,其他所有外設的時鐘最終來源都是 SYSCLK。 SYSCLK 通過 AHB 分頻器分頻后送給各模塊使用。這些模塊包括:?
①AHB 總線、內核、內存和 DMA 使用的 HCLK 時鐘。?
②通過 8 分頻后送給 Cortex 的系統定時器時鐘,也就是 systick 了。?
③直接送給 Cortex 的空閑運行時鐘 FCLK。?
④送給 APB1 分頻器。 APB1 分頻器輸出一路供 APB1 外設使用(PCLK1,最大頻率 36MHz),另一路送給定時器(Timer)2、 3、 4 倍頻器使用。?
⑤送給 APB2 分頻器。 APB2 分頻器分頻輸出一路供 APB2 外設使用(PCLK2,最大頻率 72MHz),另一路送給定時器(Timer)1 倍頻器使用。?
其中需要理解的是 APB1 和 APB2 的區別, APB1 上面連接的是低速外設,包括電源接口、備份接口、 CAN、 USB、 I2C1、 I2C2、 UART2、 UART3 等等, APB2 上面連接的是高速外設包括 UART1、 SPI1、 Timer1、 ADC1、 ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。?
SystemInit()函數中設置的系統時鐘大小:?
? SYSCLK(系統時鐘) =72MHz?
? AHB 總線時鐘(使用 SYSCLK) =72MHz?
? APB1 總線時鐘(PCLK1) =36MHz?
? APB2 總線時鐘(PCLK2) =72MHz?
? PLL 時鐘 =72MHz?
具體代碼清讀者查看工程文件的system_stm32f10x.c文件。
?
2.2 Systick系統定時器工作原理分析
SysTick 定時器被捆綁在 NVIC 中,用于產生 SysTick 異常(異常號 :15)。在以前,操作系統和所有使用了時基的系統都必須有一個硬件定時器來產生需要的“滴答”中斷,作為整個系統的時基。滴答中斷對操作系統尤其重要。例如,操作系統可以為多個任務分配不同數目的時間片,確保沒有一個任務能霸占系統 ;或者將每個定時器周期的某個時間范圍賜予特定的任務等,操作系統提供的各種定時功能都與這個滴答定時器有關。因此,需要一個定時器來產生周期性的中斷,而且最好還讓用戶程序不能隨意訪問它的寄存器,以維持操作系統“心跳”的節律。?
Cortex-M3 在內核部分包含了一個簡單的定時器——SysTick。因為所有的 CM3 芯片都帶有這個定時器,軟件在不同芯片生產廠商的 CM3 器件間的移植工作就得以簡化。該定時器的時鐘源可以是內部時鐘(FCLK,CM3 上的自由運行時鐘),或者是外部時鐘( CM3 處理器上的 STCLK 信號)。不過,STCLK 的具體來源則由芯片設計者決定,因此不同產品之間的時鐘頻率可能大不相同。因此,需要閱讀芯片的使用手冊來確定選擇什么作為時鐘源。在 STM32 中 SysTick 以 HCLK(AHB 時鐘)或 HCLK/8 作為運行時鐘,見圖 1- 1。?
SysTick 定時器能產生中斷,CM3 為它專門開出一個異常類型,并且在向量表中有它的一席之地。它使操作系統和其他系統軟件在 CM3 器件間的移植變得簡單多了,因為在所有 CM3 產品間,SysTick 的處理方式都是相同的。SysTick 定時器除了能服務于操作系統之外,還能用于其他目的,如作為一個鬧鈴、用于測量時間等。Systick 定時器屬于Cortex 內核部件,可以參考《ARM Cortex-M3 權威指南》((英)JosephYiu 著,宋巖譯,北京航空航天大學出版社出版)或“STM32xxx-Cortex-M3programmingmanual”(這是 ST 官方提供的電子版編程手冊,可以在 ST 官網下載)來了解。
2.3 Systick系統定時器寄存器分析
在傳統的嵌入式系統軟件按中通常實現 Delay(N) 函數的方法為:
for(i = 0; i <= x; i ++); x --- ;?
對于STM32系列微處理器來說,執行一條指令只有幾十個 ns,進行 for 循環時,要實現 N 毫秒的 x 值非常大,而且由于系統頻率的寬廣,很難計算出延時 N 毫秒的精確值。針對 STM32 微處理器,需要重新設計一個新的方法去實現該功能,以實現在程序中使用 Delay(N)。?
Cortex-M3 的內核中包含一個 SysTick 時鐘。SysTick 為一個 24 位遞減計數器,SysTick 設定初值并使能后,每經過 1 個系統時鐘周期,計數值就減 1。計數到 0 時,SysTick 計數器自動重裝初值并繼續計數,同時內部的 COUNTFLAG 標志會置位,觸發中斷 (如果中斷使能情況下)。?
在 STM32 的應用中,使用 Cortex-M3 內核的 SysTick 作為定時時鐘,設定每一毫秒產生一次中斷,在中斷處理函數里對 N 減一,在Delay(N) 函數中循環檢測 N 是否為 0,不為 0 則進行循環等待;若為 0 則關閉 SysTick 時鐘,退出函數。?
注: 全局變量 TimingDelay , 必須定義為 volatile 類型 , 延遲時間將不隨系統時鐘頻率改變。?
STM32中的Systick 部分內容屬于NVIC控制部分,一共有4個寄存器,名稱和地址分別是:?
? STK_CTRL, 0xE000E010 – 控制寄存器?
?
第0位:ENABLE,Systick 使能位?
(0:關閉Systick功能;1:開啟Systick功能)?
第1位:TICKINT,Systick 中斷使能位?
(0:關閉Systick中斷;1:開啟Systick中斷)?
第2位:CLKSOURCE,Systick時鐘源選擇?
(0:使用HCLK/8 作為Systick時鐘;1:使用HCLK作為Systick時鐘)?
第16位:COUNTFLAG,Systick計數比較標志,如果在上次讀取本寄存器后,SysTick 已經數到了0,則該位為1。如果讀取該位,該位將自動清零?
? STK_LOAD, 0xE000E014 – 重載寄存器?
?
Systick是一個遞減的定時器,當定時器遞減至0時,重載寄存器中的值就會被重裝載,繼續開始遞減。STK_LOAD 重載寄存器是個24位的寄存器最大計數0xFFFFFF。?
? STK_VAL, 0xE000E018 – 當前值寄存器?
?
也是個24位的寄存器,讀取時返回當前倒計數的值,寫它則使之清零,同時還會清除在SysTick 控制及狀態寄存器中的COUNTFLAG 標志。?
? STK_CALRB, 0xE000E01C – 校準值寄存器?
?
校準值寄存器提供了這樣一個解決方案:它使系統即使在不同的CM3產品上運行,也能產生恒定的SysTick中斷頻率。最簡單的作法就是:直接把TENMS的值寫入重裝載寄存器,這樣一來,只要沒突破系統極限,就能做到每10ms來一次 SysTick異常。如果需要其它的SysTick異常周期,則可以根據TENMS的值加以比例計算。只不過,在少數情況下, CM3芯片可能無法準確地提供TENMS的值(如, CM3的校準輸入信號被拉低),所以為保險起見,最好在使用TENMS前檢查器件的參考手冊。?
SysTick定時器除了能服務于操作系統之外,還能用于其它目的:如作為一個鬧鈴,用于測量時間等。要注意的是,當處理器在調試期間被喊停( halt)時,則SysTick定時器亦將暫停運作。
?
2.4 Systick系統定時器具體代碼分析
SysTick 庫函數?
? SysTick_CLKSourceConfig 設置 SysTick 時鐘源;?
? SysTick_SetReload 設置 SysTick 重裝載值;?
? SysTick_CounterCmd 使能或者失能 SysTick 計數器;?
? SysTick_ITConfig 使能或者失能 SysTick 中斷;?
? SysTick_GetCounter 獲取 SysTick 計數器的值;?
? SysTick_GetFlagStatus 檢查指定的 SysTick 標志位設置與否。
2.4.1main文件分析
int main(void) { /* LED 端口初始化 */LED_GPIO_Config();/* 配置SysTick 為10us中斷一次 */SysTick_Init(); for(;;) { LED1( ON ); Delay_us(10000); // 10000 * 10us = 100ms //Delay_ms(100); LED1( OFF ); LED2( ON ); Delay_us(10000); // 10000 * 10us = 100ms //Delay_ms(100); LED2( OFF ); LED3( ON ); Delay_us(10000); // 10000 * 10us = 100ms //Delay_ms(100); LED3( OFF ); } }在 main 函數中,SysTick_Init() 和 Delay_us() 這兩個函數比較陌生,它們的功能分別是配置好 SysTick 定時器和進行精確延時。整個 main 函數的流程就是初始化 LED 及SysTick 定時器之后,就進入死循環,輪流點亮 LED1、LED2、LED3,點亮的時間為精確的 100 ms。
2.4.2 stm32f103_SysTick.c文件分析
? 配置并啟動 SysTick?
我們看一下 SysTick_Init() 這個函數,其功能是啟動系統滴答定時器 SysTick ,并將 SysTick 配置為 10 μs 中斷一次。
}
本函數實際上只是調用了 SysTick_Confi g() 函數,它是屬于內核層的 Cortex-M3 通用函數,位于 core_cm3.h 文件中。若調用 SysTick_Confi g() 配置 SysTick 不成功,則進入死循環,初始化 SysTick 成功后,先關閉定時器,在需要的時候再開啟。SysTick_Confi g() 函數無法在STM32 外設固件庫文件中找到其使用方法。所以我們在 Keil 環境下直接跟蹤這個函數到 core_cm3.h 文件,查看函數的定義。
static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ 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 */ }?
在這個函數定義的前面有關于它的注釋,如果我們不想去研究它的具體實現,可以根據這段注釋了解函數的功能 :這個函數啟動了 SysTick ;并把它配置為計數至 0 時引起中斷 ;輸入的參數 ticks 為兩個中斷之間的脈沖數,即相隔 ticks 個時鐘周期會引起一次中斷 ;配置 SysTick 成功時返回 0,出錯時返回 1。但是,這段注釋并沒有告訴我們它把 SysTick 的時鐘設置為 AHB 時鐘還是 AHB/8,這是一個十分關鍵的問題,于是,我們將對這個函數的具體實現進行分析,與大家再分享一下如何分析底層庫函數。分析底層庫函數,要有 SysTick 定時器工作分析的知識準備。?
? 檢查輸入參數?
SysTick_Confi g() 第 3 行代碼是檢查輸入參數 ticks,因為 ticks 是脈沖計數值,要被保存到重載寄存器 STK_LOAD 寄存器中,再由硬件把 STK_LOAD 值加載到當前計數值寄存器 STK_VAL 中使用,STK_LOAD 和 STK_VAL 都是 24 位的,所以當輸入參數 ticks 大于其可存儲的最大值時,將由這行代碼檢查出錯誤并返回。?
? 位指示宏及位屏蔽宏?
檢查 ticks 參數沒有錯誤后,就稍稍處理一下把 ticks-1 賦值給 STK_LOAD 寄存器,要注意的是減 1,若 STK_VAL 從 ticks?1 向下計數至 0,實際上就經過了 ticks 個脈沖。這句賦值代碼使用了宏 SysTick_LOAD_RELOAD_Msk,與其他庫函數類似,這個宏是用來指示寄存器的特定位置或進行位屏蔽的。
?
其中寄存器位指示宏 :SysTick_xxx_Pos ,宏展開后即為 xxx 在相應寄存器中的位置,如控制 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”, 把 無 符 號 長 整 型 數 值(ul) 1 左移 2 位, 得 到 了 一 個 只 有 Bit2 :CLKSOURCE 位被置 1,其他位為 0 的數值,這樣的數值配合位操作 &(按位與)、| (按位或)可以很方便地修改寄存器的某些位。假如控制 CLKSOURCE 需 要 4 個 寄 存 器 位 , 這 個 宏 就 應 該 被 改 為 ( 0xf ul <
?
? 定時時間的計算?
在調用SysTick_Config()函數時,向它輸入的參數為SystemCoreClock / 100000,SystemCoreClock為定義了系統時鐘(SYSCLK)頻率的 宏,即等于 AHB的時鐘頻率。在本書的所有例程中AHB 都是被配置為 72 MHz 的,也就是這個 SystemCoreClock 宏展開為數值 7200 0000。?
根據前面對 SysTick_Confi g() 函數的介紹,它的輸入參數為 SysTick 將要計時的脈沖數,經過 ticks 個脈沖(經過 ticks 個時鐘周期)后將觸發中斷,觸發中斷后又重新開始計數。由此我們可以算出定時的時間,下面為計算公式 :?
T=ticks×(1/f)?
其中,T 為要定時的總時間 ;ticks 為 SysTick_Confi g() 的輸入參數 ;1/ f 即為SysTick 使用的時鐘源的時鐘周期,f 為該時鐘源的時鐘頻率,當時鐘源確定后為常數。?
例如 :本實驗例子中,使用時鐘源為 AHB 時鐘,其頻率被配置為 72 MHz。調用函數時,把 ticks 賦值為 ticks=SystemFrequency / 10 000 =720,表示 720 個時鐘周期中斷一次 ;1/f 是時鐘周期的時間,此時(1/f =1/72 μs),所以最終定時總時間 T=720×(1/72),為720 個時鐘周期,正好是 10 μs。?
SysTick 定時器的定時時間(配置為觸發中斷,即為中斷周期)由 ticks 參數決定,最大定時周期不能超過 224 個。?
? 編寫中斷服務函數?
一旦我們調用了 Delay_us() 函數,SysTick 定時器就被開啟,按照設定好的定時周期遞減計數,當 SysTick 的計數寄存器的值減為 0 時,就進入中斷函數,當中斷函數執行完畢之后重新計時,如此循環,除非它被關閉。
?
使能了 SysTick 之后,就使用 while(TimingDelay != 0)語句等待 TimingDelay 變量變為 0,這個變量是在中斷服務函數中被修改的。因此,我們需要編寫相應的中斷服務程序,在本實驗室中我們配置為 10μs 中斷一次,每次中斷把 TimingDelay 減 1。中斷程序在 stm32f10x_it.c 中實現。
void SysTick_Handler(void) {TimingDelay_Decrement(); }?
SysTick中斷屬于系統異常向量,在stm32f10x_it.c文件中已經默認有了它的中斷服務函數SysTick_Handler(),但內容為空。我們找到這個函數,其調用了用戶函數TimingDelay_Decrement()。后者是由用戶編寫的一個應用程序。
void TimingDelay_Decrement(void) {if (TimingDelay != 0x00){ TimingDelay--;} }?
每次進入 SysTick 中斷就調用一次 TimingDelay_Decrement()函數,使全局變量TimingDelay 自減一次。用戶函數 Delay_us ()在TimingDelay 被減至0時,才退出延時循環,即我們對 TimingDelay 賦的值為要中斷的次數。所以總的延時時間 :?
T 延時 = T 中斷周期 ×TimingDelay?
至此,SysTick 的精確延時功能講解完畢。
參考資料:http://www.makeru.com.cn/ ? ? ? ? ? ? ??創客學院嵌入式學習交流群:561213221
轉載于:https://www.cnblogs.com/huan-huan/p/8967055.html
總結
以上是生活随笔為你收集整理的嵌入式基础篇 - 第2章 Systick系统定时器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 柠萌影视预计2023上半年净利润超1.2
- 下一篇: 心遇如何退出家族