STM32開發筆記48:STM32F4+DP83848乙太網通訊指南系列(二):系統時鐘
本章為系列指南第二章,主要是介紹一下STM32F4的時鐘配置。時鐘是一個嵌入式產品從零開始開發的基石,一切邏輯都在時鐘的節奏中安靜地彈奏著,時鐘為整個電路帶來了歡快的「心跳」。開發者如果對時鐘沒有控制能力,就會把脈不準整個旋律的節奏,從而導致諸如通訊波特率、通訊時序、延時操作等關鍵功能全都紊亂,系統的構建也就無從談起。
時鐘如此重要,那麼普通開發者,需要對時鐘有多深的認知呢?STM32F4的時鐘配置到底復不復雜?幾行程式碼能搞定?
不要著急,我下面將用最簡單的白話文來剖析STM32的時鐘系統。不過在這之前,我們應該先吃一顆定心丸,因為在STM32中配置時鐘是非常簡單的,簡單到我們甚至不需要寫一行程式碼就能配置好,因為從標準庫3.5版本以後,SystemInit()這一重要函式,已經預設幫你在上電後執行了,都不需要你手動呼叫就幫你配置好時鐘了。
一切根據原理圖
剛才提到,SystemInit()這一配置系統時鐘的重要函式,不需要我們手動呼叫,STM32會幫我們上電後,嵌入到main函式開頭部分自動執行,將我們的系統時鐘配置到168MHz。那為什麼我們還要來學習配置時鐘呢?
因為不同的原理圖使用的器件,引腳接法不一樣,所以如果我們直接使用預設的SystemInit()函式,可能並不能按照我們預期的頻率配置系統時鐘,這時候就需要我們對STM32的時鐘概念做一個瞭解,然後根據我們實際使用的電路原理圖來修改系統預設的SystemInit()函式,從而達到一切可控,一切按預期的配置。
下圖是我手上這塊開發板的原理圖區域性特寫,特寫位置為23、24引腳:
圖中,標明23、24腳為OSC_IN,OSC_OUT,OSC是oscillation的縮寫,中文是「振盪器」的意思,表示這兩個引腳接外部晶振。閒來無事,我們去ST官網下載編號為DS8626的STM32F40x的datasheet求證一下看看。
可以看到,我們的原理圖上,外部晶振使用的是8MHz的,問題來了,STM32官方預設的外部晶振是25MHz的,也就是說,如果我們完全不改配置檔案,系統預設呼叫了SystemInit()函式,直接就會導致系統的「心律失常」。
時鐘樹
關於時鐘樹,我個人認為對於新手來說有些複雜了,但幾乎用不到其中的很多功能,這裡我就懶得貼出完整的時鐘樹圖片了,我們只需要瞭解下面幾個知識點:
每個晶片都有一個自己的最大頻率,這是一個晶片的固有屬性,這個最大頻率代表著這顆晶片處理能力的速度上限,比如STM32F407一般是168MHz。我們可以通過不同的方案配製出這個最大頻率(沒有人願意自己的晶片降頻使用吧)。最最最常用的方式就是使用外部晶振提供一個基礎的頻率,然後在這個頻率上進行加工,最終得到一個168MHz的時鐘出來。給系統提供基礎頻率的外部晶振,一般是MHz級別的,我們稱之為高速外部晶振(HSE),這裡我們開發板上配置的是一顆8MHz的晶振。此外如果系統需要用到RTC時鐘,還需要一個低速外部晶振(LSE),一般是額定頻率32.768KHz,這是後話,此處不表。從8MHz的HSE提高變成168MHz最大頻率的過程,我們叫做「倍頻」,相反從一個高速頻率降成低速頻率的過程,稱之為「分頻」。STM32F4內部通過鎖相環進行倍頻,其原理我沒高興去了解,我們知道有一種叫鎖相環的東西可以倍頻就行。倍頻時涉及到三個常量需要定義,分別是:PLL_M, PLL_N, PLL_P,最終得到系統頻率的公式請牢記:SYSCLOCK = HSE / PPL_M * PPL_N / PPL_P,這個公式對於具體的系統來說,這麼用:168MHz = 8MHz / PPL_M * PPL_N / PPL_P,因此一個合理的配置就是:
PPL_M = 8;
PPL_N = 336;
PPL_P = 2;
如果有另一塊開發板,系統原理圖上標明OSC_IN和OSC_OUT處接的是25MHz晶振,聰明的你一定知道把上面的PPL_M值修改為25就可以了。時鐘配置時,還有一個巨集定義需要定義:PPL_Q,這個是用來配置USB、SD卡讀寫時的頻率的,計算公式是USBClock = HSE / PPL_M * PPL_N /PLL_Q,一般約定USB時鐘頻率小於等於48MHz,因此PPL_Q的範圍也很好確定,如果專案中沒有用到USB和SD卡讀寫,一般這個巨集可以忽略,保持預設值(事實上,預設值是7,336/7=48,剛剛好可以把USB頻率設定為48MHz)。
倍頻巨集定義
根據我們對原理圖,以及對時鐘樹的認知,下面我們就要著手來配置系統時鐘了。之前已經說過,SystemInit()不需要開發者手動呼叫,那麼時鐘樹上的那些引數怎麼配置入參呢。原來是通過巨集定義的方式實現的,檔名為:system_stm32f4xx.c,呃。。。如果你不知道這個檔案,那可得去看一看Keil5建立STM32工程的步驟了,這個檔案在Std標準庫的樣例模板中,具體的路徑為:\STM32F4xx_DSP_StdPeriph_Lib_V1.4.0\Project\STM32F4xx_StdPeriph_Templates,我們一般在Keil中建立STM32的工程,都是複製這個模板工程,標準庫下載地址:http://www.stmicroelectronics.com.cn/en/embedded-software/stsw-stm32065.html。OK,我們開啟這個檔案,看看裡面都有啥:
* 5. This file configures the system clock as follows:
*=============================================================================
*=============================================================================
* Supported STM32F40xxx/41xxx devices
*-----------------------------------------------------------------------------
* System Clock source | PLL (HSE)
*-----------------------------------------------------------------------------
* SYSCLK(Hz) | 168000000
*-----------------------------------------------------------------------------
* HCLK(Hz) | 168000000
*-----------------------------------------------------------------------------
* AHB Prescaler | 1
*-----------------------------------------------------------------------------
* APB1 Prescaler | 4
*-----------------------------------------------------------------------------
* APB2 Prescaler | 2
*-----------------------------------------------------------------------------
* HSE Frequency(Hz) | 25000000
*-----------------------------------------------------------------------------
* PLL_M | 25
*-----------------------------------------------------------------------------
* PLL_N | 336
*-----------------------------------------------------------------------------
* PLL_P | 2
*-----------------------------------------------------------------------------
* PLL_Q | 7
*-----------------------------------------------------------------------------
看,ST寫的註釋非常漂亮是不是,同時我們也注意到官方使用預設HSE確實是25MHz的,如果我們的開發板不做任何配置,直接使用這個配置,時鐘就亂掉了。接下來,我們就不管這些註釋了,直接去修改對應的巨集定義吧(搜尋關鍵字,重新定義巨集):
#define HSE_BYPASS_INPUT_FREQUENCY 8000000
/************************* PLL Parameters *************************************/
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M 8 //HSE是8MHz晶振,PLL分頻係數設定為8
#else /* STM32F411xE */
#if defined (USE_HSE_BYPASS)
#define PLL_M 8
#else /* STM32F411xE */
#define PLL_M 16
#endif /* USE_HSE_BYPASS */
#endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx */
/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
#define PLL_Q 7
#if defined (STM32F40_41xxx)
#define PLL_N 336
//倍頻係數,SYSCLOCK = HSE / PPLM * PPLN / PPLP = 8MHz / 8 * 336 / 2 = 168MHz
/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P 2
#endif /* STM32F40_41xxx */
簡單測試
OK,經過以上的步驟,我們已經將系統時鐘調整為168MHz了,但是成功了嗎?確實是168MHz嗎?我們來做個實驗就可以了,實驗非常簡單,弄個GPIO控制LED小燈,系統裡設定每隔1秒鐘交替閃爍,觀測小燈亮滅的間隙是否差不多為1秒鐘。因為是簡單測試,延時功能我們就不使用定時器或者SysTick了,我們直接用步進法來做延時測試,弄個vu32型別的變數,從0一直累加,直到達到68,000,000,這期間的過程就是1秒鐘。虛擬碼如下:
vu32 i = 0;
int main(){
GPIOInit()...
while(1){
LED = 1;
i = 0;
while(i < 168000000)
i++;
LED = 0;
i = 0;
while(i < 168000000)
i++;
}
}
如果LED小燈能預期按照大概1秒的間隙亮滅的話,就說明我們對STM32的時鐘配置正確了。