STM32 芯片爲了實現低功耗,設計了一個功能完善但卻很是複雜的時鐘系統。普通的MCU 通常只要配置好 GPIO 的寄存器就可使用了,但 STM32 還有一個步驟,就是開啓外設時鐘。 css
在 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/二、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)二、 三、 4 倍頻器使用。
⑤送給 APB2 分頻器。 APB2 分頻器分頻輸出一路供 APB2 外設使用(PCLK2,最大頻率 72MHz),另外一路送給定時器(Timer)1 倍頻器使用。
其中須要理解的是 APB1 和 APB2 的區別, APB1 上面鏈接的是低速外設,包括電源接口、備份接口、 CAN、 USB、 I2C一、 I2C二、 UART二、 UART3 等等, APB2 上面鏈接的是高速外設包括 UART一、 SPI一、 Timer一、 ADC一、 ADC二、全部普通 IO 口(PA~PE)、第二功能 IO 口等。
SystemInit()函數中設置的系統時鐘大小:
SYSCLK(系統時鐘) =72MHz
AHB 總線時鐘(使用 SYSCLK) =72MHz
APB1 總線時鐘(PCLK1) =36MHz
APB2 總線時鐘(PCLK2) =72MHz
PLL 時鐘 =72MHz
具體代碼清讀者查看工程文件的system_stm32f10x.c文件。編程
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 官網下載)來了解。函數
在傳統的嵌入式系統軟件按中一般實現 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 – 控制寄存器 ui
第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 – 重載寄存器 spa
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定時器亦將暫停運做。3d
SysTick 庫函數
SysTick_CLKSourceConfig 設置 SysTick 時鐘源;
SysTick_SetReload 設置 SysTick 重裝載值;
SysTick_CounterCmd 使能或者失能 SysTick 計數器;
SysTick_ITConfig 使能或者失能 SysTick 中斷;
SysTick_GetCounter 獲取 SysTick 計數器的值;
SysTick_GetFlagStatus 檢查指定的 SysTick 標誌位設置與否。調試
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 定時器以後,就進入死循環,輪流點亮 LED一、LED二、LED3,點亮的時間爲精確的 100 ms。
配置並啓動 SysTick
咱們看一下 SysTick_Init() 這個函數,其功能是啓動系統滴答定時器 SysTick ,並將 SysTick 配置爲 10 μs 中斷一次。
void SysTick_Init(void) { /* SystemFrequency / 100000 10us中斷一次 * SystemFrequency / 1000000 1us中斷一次*/ // if (SysTick_Config(SystemFrequency / 100000)) // ST3.0.0庫版本 if (SysTick_Config(SystemCoreClock / 100000)) // ST3.5.0庫版本 { /* Capture error */ while (1); } // 關閉滴答定時器 SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; }
}
本函數實際上只是調用了 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 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 在相應寄存器中的位置,如控制 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->CTRL |= SysTick_CTRL_ENABLE_Msk; // 使能滴答定時器 SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; // 關閉滴答定時器
定時時間的計算
在調用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 時,就進入中斷函數,當中斷函數執行完畢以後從新計時,如此循環,除非它被關閉。
void Delay_us(__IO u32 nTime) { TimingDelay = nTime; // 使能滴答定時器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; while(TimingDelay != 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