天天看點

再造STM32---第十三部分:RCC—使用 HSE/HSI 配置時鐘

       配合《STM32F4xx 中文參考手冊》 RCC 章節一起閱讀,效果會更佳,特别是涉及到寄存器說明的部分。

       RCC : reset clock control 複位和時鐘控制器。本章我們主要講解時鐘部分,特别是要着重了解時鐘樹,了解了時鐘樹, F429 的一切時鐘的來龍去脈都會了如指掌。

13.1 RCC 主要作用—時鐘部分:

       設定系統時鐘 SYSCLK、設定 AHB 分頻因子(決定 HCLK 等于多少) 、 設定 APB2 分頻因子(決定 PCLK2 等于多少)、設定 APB1 分頻因子(決定 PCLK1 等于多少)、設定各個外設的分頻因子;控制 AHB、 APB2 和 APB1 這三條總線時鐘的開啟、控制每個外設的時鐘的開啟。對于 SYSCLK、 HCLK、 PCLK2、 PCLK1 這四個時鐘的配置一般是: HCLK =SYSCLK=PLLCLK = 180M,PCLK1=HCLK/2 = 90M, PCLK1=HCLK/4 = 45M。這個時鐘配置也是庫函數的标準配置,我們用的最多的就是這個。

13.2 RCC 框圖剖析—時鐘樹:

       時鐘樹單純講理論的話會比較枯燥,如果選取一條主線,并輔以代碼,先主後次講解的話會很容易,而且記憶還更深刻。我們這裡選取庫函數時鐘系統時鐘函數:SetSysClock(); 以這個函數的編寫流程來講解時鐘樹,這個函數也是我們用庫的時候預設的系統時鐘設定函數。該函數的功能是利用 HSE 把時鐘設定為: HCLK = SYSCLK=PLLCLK= 180M, PCLK1=HCLK/2 = 90M, PCLK1=HCLK/4 = 45M 下面我們就以這個代碼的流程為主線, 來分析時鐘樹,對應的是圖中的黃色部分,代碼流程在時鐘樹中以數字的大小順序辨別。

再造STM32---第十三部分:RCC—使用 HSE/HSI 配置時鐘

13.2.1 系統時鐘:

1. ①HSE 高速外部時鐘信号:

        HSE 是高速的外部時鐘信号,可以由有源晶振或者無源晶振提供,頻率從 4-26MHZ不等。當使用有源晶振時,時鐘從 OSC_IN 引腳進入, OSC_OUT 引腳懸空,當選用無源晶振時,時鐘從 OSC_IN 和 OSC_OUT 進入,并且要配諧振電容。 HSE 我們使用 25M 的無源晶振。 如果我們使用 HSE 或者 HSE 經過 PLL 倍頻之後的時鐘作為系統時鐘 SYSCLK,當 HSE 故障時候,不僅 HSE 會被關閉, PLL 也會被關閉,此時高速的内部時鐘時鐘信号HSI 會作為備用的系統時鐘,直到 HSE 恢複正常, HSI=16M。

2. ②鎖相環 PLL:

        PLL 的主要作用是對時鐘進行倍頻,然後把時鐘輸出到各個功能部件。 PLL 有兩個,一個是主 PLL,另外一個是專用的 PLLI2S,他們均由 HSE 或者 HSI 提供時鐘輸入信号。

        主 PLL 有兩路的時鐘輸出,第一個輸出時鐘 PLLCLK 用于系統時鐘, F429 裡面最高是 180M,第二個輸出用于 USB OTG FS 的時鐘(48M)、 RNG 和 SDIO 時鐘(<=48M)。專用的 PLLI2S 用于生成精确時鐘,給 I2S 提供時鐘。

        HSE 或者 HSI 經過 PLL 時鐘輸入分頻因子 M(2~63)分頻後,成為 VCO 的時鐘輸入,VCO 的時鐘必須在 1~2M 之間, 我們選擇 HSE=25M 作為 PLL 的時鐘輸入, M 設定為 25,那麼 VCO 輸入時鐘就等于 1M。

        VCO 輸入時鐘經過 VCO 倍頻因子 N 倍頻之後,成為 VCO 時鐘輸出, VCO 時鐘必須在 192~432M 之間。我們配置 N 為 360,則 VCO 的輸出時鐘等于 360M。如果要把系統時鐘超頻,就得在 VCO 倍頻系數 N 這裡做手腳。 PLLCLK_OUTMAX =VCOCLK_OUTMAX/P_MIN = 432/2=216M,即 F429 最高可超頻到 216M。

        VCO 輸出時鐘之後有三個分頻因子: PLLCLK 分頻因子 p, USB OTG FS/RNG/SDIO時鐘分頻因子 Q,分頻因子 R(F446 才有,F429 沒有)。 p 可以取值 2、 4、 6、 8,我們配置為 2,則得到 PLLCLK=180M。 Q 可以取值 4~15,但是 USB OTG FS 必須使用 ,Q=VCO 輸出時鐘 360/48=7.5,出現了小數這明顯是錯誤,權衡之策是是重新配置 VCO 的倍頻因子 N=336,VCOCLK=1M*336=336M, PLLCLK=VCOCLK/2=168M,USBCLK=336/7=48M,細心的讀者應該發現了,在使用 USB 的時候, PLLCLK 被降低到了 168M,不能使用 180M,這實乃 ST 的一個奇葩設計。有關 PLL 的配置有一個專門的RCC PLL 配置寄存器 RCC_PLLCFGR,具體描述看手冊即可。

        PLL 的時鐘配置經過,稍微整理下可由如下公式表達:

        VCOCLK_IN = PLLCLK_IN / M = HSE / 25 = 1M

        VCOCLK_OUT = VCOCLK_IN * N = 1M * 360 = 360M

        PLLCLK_OUT=VCOCLK_OUT/P=360/2=180M

        USBCLK = VCOCLK_OUT/Q=360/7=51.7。暫時這樣配置,到真正使用 USB 的時候會重新配置。

3. ③系統時鐘 SYSCLK:

        系統時鐘來源可以是: HSI、 PLLCLK、 HSE,具體的由時鐘配置寄存器 RCC_CFGR的 SW 位配置。我們這裡設定系統時鐘: SYSCLK = PLLCLK = 180M。 如果系統時鐘是由HSE 經過 PLL 倍頻之後的 PLLCLK 得到,當 HSE 出現故障的時候,系統時鐘會切換為HSI=16M,直到 HSE 恢複正常為止。

4. ④AHB 總線時鐘 HCLK:

        系統時鐘 SYSCLK 經過 AHB 預分頻器分頻之後得到時鐘叫 APB 總線時鐘,即 HCLK,分頻因子可以是:[1,2,4,8,16,64,128,256,512],具體的由時鐘配置寄存器RCC_CFGR 的 HPRE 位設定。片上大部分外設的時鐘都是經過 HCLK 分頻得到,至于 AHB總線上的外設的時鐘設定為多少,得等到我們使用該外設的時候才設定,我們這裡隻需粗線條的設定好 APB 的時鐘即可。 我們這裡設定為 1 分頻,即 HCLK=SYSCLK=180M。

5. ⑤APB2 總線時鐘 HCLK2:

        APB2 總線時鐘 PCLK2 由 HCLK 經過高速 APB2 預分頻器得到,分頻因子可以是:[1,2,4,8,16],具體由時鐘配置寄存器 RCC_CFGR 的 PPRE2 位設定。 HCLK2 屬于高速的總線時鐘,片上高速的外設就挂載到這條總線上,比如全部的 GPIO、 USART1、 SPI1等。至于 APB2 總線上的外設的時鐘設定為多少,得等到我們使用該外設的時候才設定,我們這裡隻需粗線條的設定好 APB2 的時鐘即可。我們這裡設定為 2 分頻,即 PCLK2 =HCLK /2= 90M。

6. ⑥APB1 總線時鐘 HCLK1:

        APB1 總線時鐘 PCLK1 由 HCLK 經過低速 APB 預分頻器得到,分頻因子可以是:[1,2,4,8,16],具體由時鐘配置寄存器 RCC_CFGR 的 PPRE1 位設定。HCLK1 屬于低速的總線時鐘,最高為 45M,片上低速的外設就挂載到這條總線上,比如

USART2/3/4/5、 SPI2/3, I2C1/2 等。至于 APB1 總線上的外設的時鐘設定為多少,得等到我們使用該外設的時候才設定,我們這裡隻需粗線條的設定好 APB1 的時鐘即可。我們這裡設定為 4 分頻,即 PCLK1 = HCLK/4 = 45M。

7. 設定系統時鐘庫函數:

       上面的 6 個步驟對應的設定系統時鐘庫函數如下,為了友善閱讀,已經把跟 429 不相關的代碼删掉,把英文注釋翻譯成了中文,并把代碼标上了序号,總共 6 個步驟。該函數是直接操作寄存器的,有關寄存器部分請參考資料手冊的 RCC 的寄存器描述部分。

/*
* 使用 HSE 時,設定系統時鐘的步驟
* 1、開啟 HSE ,并等待 HSE 穩定
* 2、設定 AHB、 APB2、 APB1 的預分頻因子
* 3、設定 PLL 的時鐘來源
* 設定 VCO 輸入時鐘 分頻因子 m
* 設定 VCO 輸出時鐘 倍頻因子 n
* 設定 PLLCLK 時鐘分頻因子 p
* 設定 OTG FS,SDIO,RNG 時鐘分頻因子 q
* 4、開啟 PLL,并等待 PLL 穩定
* 5、把 PLLCK 切換為系統時鐘 SYSCLK
* 6、讀取時鐘切換狀态位,確定 PLLCLK 被選為系統時鐘
*/
#define PLL_M 25
#define PLL_N 360
#define PLL_P 2
#define PLL_Q 7
/如果要超頻的話,修改 PLL_N 這個宏即可,取值範圍為: 192~432。
oid SetSysClock(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
// ①使能 HSE
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
// 等待 HSE 啟動穩定
do {
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while ((HSEStatus==0)&&(StartUpCounter
!=HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
HSEStatus = (uint32_t)0x01;
} else {
HSEStatus = (uint32_t)0x00;
}
// HSE 啟動成功
if (HSEStatus == (uint32_t)0x01) {
// 調壓器電壓輸出級别配置為 1,以便在器件為最大頻率
// 工作時使性能和功耗實作平衡
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// ②設定 AHB/APB2/APB1 的分頻因子
// HCLK = SYSCLK / 1
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
// PCLK2 = HCLK / 2
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
// PCLK1 = HCLK / 4
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
// ③配置主 PLL 的時鐘來源,設定 M,N,P,Q
// Configure the main PLL
RCC->PLLCFGR = PLL_M|(PLL_N<<6)|
(((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) |
(PLL_Q << 24);
// ④使能主 PLL
RCC->CR |= RCC_CR_PLLON;
// 等待 PLL 穩定
while ((RCC->CR & RCC_CR_PLLRDY) == 0) {
}
/*--------------------------------------------------
// 開啟 OVER-RIDE 模式,以能達到更改頻率
PWR->CR |= PWR_CR_ODEN;
while ((PWR->CSR & PWR_CSR_ODRDY) == 0) {
}
PWR->CR |= PWR_CR_ODSWEN;
while ((PWR->CSR & PWR_CSR_ODSWRDY) == 0) {
}
// 配置 FLASH 預取指,指令緩存,資料緩存和等待狀态
FLASH->ACR = FLASH_ACR_PRFTEN
|FLASH_ACR_ICEN
|FLASH_ACR_DCEN
|FLASH_ACR_LATENCY_5WS;
/*--------------------------------------------------
// ⑤選擇主 PLLCLK 作為系統時鐘源
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
// ⑥讀取時鐘切換狀态位,確定 PLLCLK 選為系統時鐘
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS )
!= RCC_CFGR_SWS_PLL);
{
}
} else {
// HSE 啟動出錯處理
}
}      

13.2.2 其他時鐘:

       通過對系統時鐘設定的講解,整個時鐘樹我們已經把握的有六七成,剩下的時鐘部分我們講解幾個重要的。

1. A、 RTC 時鐘:

       RTCCLK 時鐘源可以是 HSE 1 MHz(HSE 由一個可程式設計的預分頻器分頻)、 LSE 或者 LSI 時鐘。選擇方式是程式設計 RCC 備份域控制寄存器 (RCC_BDCR) 中的 RTCSEL[1:0] 位和 RCC 時鐘配置寄存器 (RCC_CFGR) 中的 RTCPRE[4:0] 位。所做的選擇隻能通過複位備份域的方式修改。 我們通常的做法是由 LSE 給 RTC 提供時鐘,大小為 32.768KHZ。 LSE由外接的晶體諧振器産生,所配的諧振電容精度要求高,不然很容易不起震。

2. B、獨立看門狗時鐘:

       獨立看門狗時鐘由内部的低速時鐘 LSI 提供,大小為 32KHZ。

3. C、 I2S 時鐘:

       I2S 時鐘可由外部的時鐘引腳 I2S_CKIN 輸入,也可由專用的 PLLI2SCLK 提供,具體的由 RCC 時鐘配置寄存器 (RCC_CFGR)的 I2SSCR 位配置。我們在使用 I2S 外設驅動W8978 的時候,使用的時鐘是 PLLI2SCLK,這樣就可以省掉一個有源晶振。

4. D、 PHY 以太網時鐘:

       F429 要想實作以太網功能,除了有本身内置的 MAC 之外,還需要外接一個 PHY 晶片,常見的 PHY 晶片有 DP83848 和 LAN8720,其中 DP83848 支援 MII 和 RMII 接口,LAN8720 隻支援 RMII 接口。 野火 F429 開發闆用的是 RMII 接口,選擇的 PHY 晶片是LAB8720。使用 RMII 接口的好處是使用的 IO 減少了一半,速度還是跟 MII 接口一樣。當使用 RMII 接口時, PHY 晶片隻需輸出一路時鐘給 MCU 即可,如果是 MII 接口, PHY 晶片則需要提供兩路時鐘給 MCU。

5. E、 USB PHY 時鐘:

       F429 的 USB 沒有內建 PHY,要想實作 USB 高速傳輸的話,必須外置 USB PHY 晶片,常用的晶片是 USB3300。當外接 USB PHY 晶片時, PHY 晶片需要給 MCU 提供一個時鐘。外擴 USB3300 會占用非常多的 IO,跟 SDRAM 和 RGB888 的 IO 會複用的很厲害,鑒于 USB 高速傳輸用的比較少, 野火 429 就沒有外擴這個晶片。

6. F、 MCO 時鐘輸出:

       MCO 是 microcontroller clock output 的縮寫,是微控制器時鐘輸出引腳,主要作用是可以對外提供時鐘, 相當于一個有源晶振。 F429 中有兩個 MCO,由 PA8/PC9 複用所得。MCO1 所需的時鐘源通過 RCC 時鐘配置寄存器 (RCC_CFGR) 中的 MCO1PRE[2:0] 和MCO1[1:0]位選擇。 MCO2 所需的時鐘源通過 RCC 時鐘配置寄存器 (RCC_CFGR) 中的MCO2PRE[2:0] 和 MCO2 位選擇。有關 MCO 的 IO、時鐘選擇和輸出速率的具體資訊如下表所示:

時鐘輸出 IO 時鐘來源 最大輸出速率
MCO1 PA8 HSI、 LSE、 HSE、 PLLCLK 100M
MCO2 PC9 HSE、 PLLCLK、 SYSCLK、 PLLI2SCLK 100M

13.3 配置系統時鐘實驗:

13.3.1 使用 HSE:

       一般情況下,我們都是使用 HSE,然後 HSE 經過 PLL 倍頻之後作為系統時鐘。 F429系統時鐘最高為 180M,這個是官方推薦的最高的穩定時鐘,如果你想铤而走險,也可以超頻,超頻最高能到 216M。

       如果我們使用庫函數程式設計,當程式來到 main 函數之前,啟動檔案:startup_stm32f429_439xx.s 已經調用 SystemInit()函數把系統時鐘初始化成 180MHZ,SystemInit()在庫檔案: system_stm32f4xx.c 中定義。如果我們想把系統時鐘設定低一點或者超頻的話,可以修改底層的庫檔案,但是為了維持庫的完整性,我們可以根據時鐘樹的流程自行寫一個。

13.3.2 使用 HSI:

       當 HSE 直接或者間接(HSE 經過 PLL 倍頻)的作為系統時鐘的時候,如果 HSE 發生故障,不僅 HSE 會被關閉,連 PLL 也會被關閉, 這個時候系統會自動切換 HSI 作為系統時鐘,此時 SYSCLK=HSI=16M,如果沒有開啟 CSS 和 CSS 中斷的話,那麼整個系統就隻能在低速率運作,這是系統跟癱瘓沒什麼兩樣。

       如果開啟了 CSS 功能的話,那麼可以當 HSE 故障時,在 CSS 中斷裡面采取補救措施,使用 HSI, 重新設定系統頻率為 180M,讓系統恢複正常使用。但這隻是權宜之計,并非萬全之策,最好的方法還是要采取相應的補救措施并報警,然後修複 HSE。臨時使用 HSI 隻是為了把損失降低到最小,畢竟 HSI 較于 HSE 精度還是要低點。

       F103 系列中,使用 HSI 最大隻能把系統設定為 64M,并不能跟使用 HSE 一樣把系統時鐘設定為 72M,究其原因是 HSI 在進入 PLL 倍頻的時候必須 2 分頻,導緻 PLL 倍頻因子調到最大也隻能到 64M,而 HSE 進入 PLL 倍頻的時候則不用 2 分頻。在 F429 中,無論是使用 HSI 還是 HSE 都可以把系統時鐘設定為 180M,因為 HSE 或者 HSI 在進入 PLL 倍頻的時候都會被分頻為 1M 之後再倍頻。還有一種情況是,有些使用者不想用 HSE,想用 HSI,但是又不知道怎麼用 HSI 來設定系統時鐘,因為調用庫函數都是使用 HSE, 下面我們給出個使用 HSI 配置系統時鐘例子,起個抛磚引玉的作用。

13.3.3 硬體設計

1、 RCC

2、 LED 一個

       RCC 是單片機内部資源,不需要外部電路。通過 LED 閃爍的頻率來直覺的判斷不同系統時鐘頻率對軟體延時的效果。

13.3.4 軟體設計:

       我們編寫兩個 RCC 驅動檔案, bsp_clkconfig.h 和 bsp_clkconfig.c,用來存放 RCC 系統時鐘配置函數。

1. 程式設計要點:

    1、開啟 HSE/HSI , 并等待 HSE/HSI 穩定

    2、設定 AHB、 APB2、 APB1 的預分頻因子

    3、設定 PLL 的時鐘來源, 設定 VCO 輸入時鐘 分頻因子 PLL_M,設定 VCO 輸出時鐘倍頻因子 PLL_N,設定 PLLCLK 時鐘分頻因子             PLL_P,設定 OTG FS,SDIO,RNG時鐘分頻因子 PLL_Q

    4、開啟 PLL,并等待 PLL 穩定

    5、把 PLLCK 切換為系統時鐘 SYSCLK

    6、讀取時鐘切換狀态位,確定 PLLCLK 被選為系統時鐘

2. 代碼分析:

    這裡隻講解核心的部分代碼,有些變量的設定,頭檔案的包含等并沒有涉及到,完整的代碼請參考本章配套的工程。

使用 HSE 配置系統時鐘

代碼 14 HSE 作為系統時鐘來源

/*
* m: VCO 輸入時鐘 分頻因子,取值 2~63
* n: VCO 輸出時鐘 倍頻因子,取值 192~432
* p: SYSCLK 時鐘分頻因子 ,取值 2, 4, 6, 8
* q: OTG FS,SDIO,RNG 時鐘分頻因子,取值 4~15
* 函數調用舉例,使用 HSE 設定時鐘
* SYSCLK=HCLK=180M,PCLK2=HCLK/2=90M,PCLK1=HCLK/4=45M
* HSE_SetSysClock(25, 360, 2, 7);
* HSE 作為時鐘來源,經過 PLL 倍頻作為系統時鐘,這是通常的做法
* 系統時鐘超頻到 216M 爽一下
* HSE_SetSysClock(25, 432, 2, 9);
*/
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)
{
__IO uint32_t HSEStartUpStatus = 0;
// 使能 HSE,開啟外部晶振, 野火 F429 使用 HSE=25M
RCC_HSEConfig(RCC_HSE_ON);
// 等待 HSE 啟動穩定
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS) {
// 調壓器電壓輸出級别配置為 1,以便在器件為最大頻率
// 工作時使性能和功耗實作平衡
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// HCLK = SYSCLK / 1
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// PCLK2 = HCLK / 2
RCC_PCLK2Config(RCC_HCLK_Div2);
// PCLK1 = HCLK / 4
RCC_PCLK1Config(RCC_HCLK_Div4);
// 如果要超頻就得在這裡下手啦
// 設定 PLL 來源時鐘,設定 VCO 分頻因子 m,設定 VCO 倍頻因子 n,
// 設定系統時鐘分頻因子 p,設定 OTG FS,SDIO,RNG 分頻因子 q
RCC_PLLConfig(RCC_PLLSource_HSE, m, n, p, q);
// 使能 PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL 穩定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
/*-----------------------------------------------------*/
//開啟 OVER-RIDE 模式,以能達到更高頻率
PWR->CR |= PWR_CR_ODEN;
while ((PWR->CSR & PWR_CSR_ODRDY) == 0) {
}
PWR->CR |= PWR_CR_ODSWEN;
while ((PWR->CSR & PWR_CSR_ODSWRDY) == 0) {
}
// 配置 FLASH 預取指,指令緩存,資料緩存和等待狀态
FLASH->ACR = FLASH_ACR_PRFTEN
| FLASH_ACR_ICEN
| FLASH_ACR_DCEN
| FLASH_ACR_LATENCY_5WS;
/*-----------------------------------------------------*/
// 當 PLL 穩定之後,把 PLL 時鐘切換為系統時鐘 SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 讀取時鐘切換狀态位,確定 PLLCLK 被選為系統時鐘
while (RCC_GetSYSCLKSource() != 0x08) {
}
} else {
// HSE 啟動出錯處理
while (1) {
}
}
}      

這個函數采用庫函數編寫, 代碼了解參考注釋即可。函數有 4 個形參 m、 n、 p、 q,具體說明如下:

形參 形參說明 取值範圍
m VCO 輸入時鐘 分頻因子 2~63
n VCO 輸出時鐘 倍頻因子 192~432
p PLLCLK 時鐘分頻因子 2/4/6/8
q OTG FS,SDIO,RNG 時鐘分頻因子 4~15

       HSE 我們使用 25M,參數 m 我們一般也設定為 25,是以我們需要修改系統時鐘的時候隻需要修改參數 n 和 p 即可,SYSCLK=PLLCLK=HSE/m*n/p。函數調用舉例: HSE_SetSysClock(25, 360, 2, 7) 把系統時鐘設定為 180M,這個跟庫裡面的系統時鐘配置是一樣的。 HSE_SetSysClock(25, 432, 2, 9)把系統時鐘設定為 216M,這個是超頻,要慎用。

使用 HSI 配置系統時鐘

/*
* m: VCO 輸入時鐘 分頻因子,取值 2~63
* n: VCO 輸出時鐘 倍頻因子,取值 192~432
* p: PLLCLK 時鐘分頻因子 ,取值 2, 4, 6, 8
* q: OTG FS,SDIO,RNG 時鐘分頻因子,取值 4~15
函數調用舉例,使用 HSI 設定時鐘
* SYSCLK=HCLK=180M,PCLK2=HCLK/2=90M,PCLK1=HCLK/4=45M
* HSI_SetSysClock(16, 360, 2, 7);
* HSE 作為時鐘來源,經過 PLL 倍頻作為系統時鐘,這是通常的做法
* 系統時鐘超頻到 216M 爽一下
* HSI_SetSysClock(16, 432, 2, 9);
*/
void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)
{
__IO uint32_t HSIStartUpStatus = 0;
// 把 RCC 外設初始化成複位狀态
RCC_DeInit();
//使能 HSI, HSI=16M
RCC_HSICmd(ENABLE);
// 等待 HSI 就緒
HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
// 隻有 HSI 就緒之後則繼續往下執行
if (HSIStartUpStatus == RCC_CR_HSIRDY) {
// 調壓器電壓輸出級别配置為 1,以便在器件為最大頻率
// 工作時使性能和功耗實作平衡
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// HCLK = SYSCLK / 1
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// PCLK2 = HCLK / 2
RCC_PCLK2Config(RCC_HCLK_Div2);
// PCLK1 = HCLK / 4
RCC_PCLK1Config(RCC_HCLK_Div4);
// 如果要超頻就得在這裡下手啦
// 設定 PLL 來源時鐘,設定 VCO 分頻因子 m,設定 VCO 倍頻因子 n,
// 設定系統時鐘分頻因子 p,設定 OTG FS,SDIO,RNG 分頻因子 q
RCC_PLLConfig(RCC_PLLSource_HSI, m, n, p, q);
// 使能 PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL 穩定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
/*-----------------------------------------------------*/
//開啟 OVER-RIDE 模式,以能達到更高頻率
PWR->CR |= PWR_CR_ODEN;
while ((PWR->CSR & PWR_CSR_ODRDY) == 0) {
}
PWR->CR |= PWR_CR_ODSWEN;
while ((PWR->CSR & PWR_CSR_ODSWRDY) == 0) {
}
// 配置 FLASH 預取指,指令緩存,資料緩存和等待狀态
FLASH->ACR = FLASH_ACR_PRFTEN
| FLASH_ACR_ICEN
|FLASH_ACR_DCEN
|FLASH_ACR_LATENCY_5WS;
/*-----------------------------------------------------*/
// 當 PLL 穩定之後,把 PLL 時鐘切換為系統時鐘 SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 讀取時鐘切換狀态位,確定 PLLCLK 被選為系統時鐘
while (RCC_GetSYSCLKSource() != 0x08) {
}
} else {
// HSI 啟動出錯處理
while (1) {
}
}
}      

這個函數采用庫函數編寫, 代碼了解參考注釋即可。函數有 4 個形參 m、 n、 p、 q,具體說明如下:

形參 形參說明 取值範圍
m VCO 輸入時鐘 分頻因子 2~63
n VCO 輸出時鐘 倍頻因子 192~432
p PLLCLK 時鐘分頻因子 2/4/6/8
q OTG FS,SDIO,RNG 時鐘分頻因子 4~15

HSI 為 16M,參數 m 我們一般也設定為 16,是以我們需要修改系統時鐘的時候隻需要修改參數 n 和 p 即可,SYSCLK=PLLCLK=HSI/m*n/p。函數調用舉例: HSI_SetSysClock(16, 360, 2, 7) 把系統時鐘設定為 180M,這個跟庫裡面的系統時鐘配置是一樣的。 HSI_SetSysClock(16, 432, 2, 9)把系統時鐘設定為 216M,這個是超頻,要慎用。

軟體延時

void Delay(__IO uint32_t nCount)
{
for (; nCount != 0; nCount--);
}      

        軟體延時函數,使用不同的系統時鐘,延時時間不一樣,可以通過 LED 閃爍的頻率來判斷。

MCO 輸出

        在 F429 中, PA8/PC9 可以複用為 MCO1/2 引腳,對外提供時鐘輸出,我們也可以用示波器監控該引腳的輸出來判斷我們的系統時鐘是否設定正确。

代碼 15 MCO GPIO 初始化

// MCO1 PA8 GPIO 初始化
void MCO1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// MCO1 GPIO 配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// MCO2 PC9 GPIO 初始化
void MCO2_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
// MCO2 GPIO 配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}      

      野火 F429 中 PA8 并沒有引出,隻引出了 PC9,如果要用示波器監控 MCO,隻能用PC9。

代碼 16 MCO 輸出時鐘選擇

// MCO1 輸出 PLLCLK
RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_1);
// MCO1 輸出 SYSCLK
RCC_MCO2Config(RCC_MCO2Source_SYSCLK, RCC_MCO1Div_1);      

       我們初始化 MCO 引腳之後,可以直接調用庫函數 RCC_MCOxConfig()來選擇 MCO 時鐘來源,同時還可以分頻,這兩個參數的取值參考庫函數說明即可。

主函數

       在主函數中,可以調用 HSE_SetSysClock()或者 HSI_SetSysClock()這兩個函數把系統時鐘設定成各種常用的時鐘,然後通過 MCO 引腳監控,或者通過 LED 閃爍的快慢體驗不同的系統時鐘對同一個軟體延時函數的影響。

int main(void)
{
// 程式來到 main 函數之前,啟動檔案: statup_stm32f10x_hd.s 已經調用
// SystemInit()函數把系統時鐘初始化成 72MHZ
// SystemInit()在 system_stm32f10x.c 中定義
// 如果使用者想修改系統時鐘,可自行編寫程式修改
// 重新設定系統時鐘,這時候可以選擇使用 HSE 還是 HSI
// 使用 HSE,配置系統時鐘為 180M
HSE_SetSysClock(25, 360, 2, 7);
//系統時鐘超頻到 216M 爽一下,最高是 216M,别往死裡整
//HSE_SetSysClock(25, 432, 2, 9);
// 使用 HSI,配置系統時鐘為 180M
//HSI_SetSysClock(16, 360, 2, 7);
// LED 端口初始化
LED_GPIO_Config();
// MCO GPIO 初始化
MCO1_GPIO_Config();
MCO2_GPIO_Config();
// MCO1 輸出 PLLCLK
RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_1);
// MCO2 輸出 SYSCLK
RCC_MCO2Config(RCC_MCO2Source_SYSCLK, RCC_MCO1Div_1);
while (1) {
LED1( ON ); // 亮
Delay(0x0FFFFF);
LED1( OFF ); // 滅
Delay(0x0FFFFF);
}
}      

13.3.5 下載下傳驗證

       把編譯好的程式下載下傳到開發闆,可以看到設定不同的系統時鐘時, LED 閃爍的快慢不一樣。更精确的資料我們可以用示波器監控 MCO 引腳看到。

13.4 總結:

1、時鐘樹:

再造STM32---第十三部分:RCC—使用 HSE/HSI 配置時鐘

2、HSE時鐘:

HSE: High Speed External Clock signal, 即高速的外部時鐘。

來源:有源晶振(1-50M)價格貴一般不采用、無源晶振( 4-26M)

控制: RCC_CR 時鐘控制寄存器的位16: HSEON控制

3、HSI時鐘:

HSI: Low Speed Internal Clock signal, 即高速的内部時鐘。

來源:晶片内部,大小為16M,當HSE故障時,系統時鐘會自動切換到HSI, 直到HSE啟動成功。

控制: RCC_CR 時鐘控制寄存器的位0: HSION控制

4、鎖相環時鐘PLL:

鎖相環時鐘: PLLCLK

來源: HSI、 HSE。由PLLSRC位配置。

HSE或者HSI先經過一個分頻因子M進行分頻,然後再經過一個倍頻因子N,然後再經過一個分頻因子P,最後成為鎖相環時鐘

PLLCLK = (HSE/M) * N / P = 25/25 * 360 / 2 = 180M

控制: RCC_PLLCFGR :RCC PLL 配置寄存器

PLL48CK: USB_FS、 RANG、 SDIO提供時鐘

5、系統時鐘:

縮寫: SYSCLK,最高為180M, F407最高為168M。

來源: HSI、 HSE, PLLCLK。

控制: RCC_CFGR 時鐘配置寄存器的SW位

6、HCLK時鐘;

HCLK: AHB高速總線時鐘, 最高為180M。為AHB總線的外設提供時鐘、為Cortex系統定時器提供時鐘( SysTick)、為核心提供時鐘( FCLK)。

AHB為advanced high-performance bus。

來源:系統時鐘分頻得到。

控制: RCC_CFGR 時鐘配置寄存器的HPRE位

7、PCLK1時鐘:

PCLK1: APB1低速總線時鐘,最高為45M。 為APB1總線的外設提供時鐘。 2倍頻之後則為APB1總線的定時器提供時鐘,最大為90M。

來源: HCLK分頻得到。

控制: RCC_CFGR 時鐘配置寄存器的PPRE1位

8、PCLK2時鐘:

PCLK2: APB2高速總線時鐘,最高為90M。為APB2總線的外設提供時鐘。 2倍頻之後則為APB2總線的定時器提供時鐘,最大為180M。

來源: HCLK分頻得到。

控制: RCC_CFGR 時鐘配置寄存器的PPRE2位

9、RTC時鐘

RTC:為晶片内部的RTC提供時鐘。

來源: HSE_RTC( HSE分頻得到)、 LSE(外部32.768KHZ的晶體提供)、 LSI( 32KHZ)。

控制: RCC備份域控制寄存器RCC_BDCR: RTCSEL位控制

獨立看門狗時鐘: IWDGCLK,由LSI提供

10、其他時鐘:

I2S時鐘:由外部的引腳I2S_CKIN或者PLLI2SCLK提供。以太網PHY時鐘: 429沒有內建PHY,隻能外接PHY晶片,比如LAN8720,那PHY時鐘就由外部的PHY晶片提供,大小為50M。

USB PHY時鐘: 429的USB沒有內建PHY, 要想實作USB高速傳輸,隻能外接PHY晶片,比如USB33000。那USB PHY時鐘就由外部的PHY晶片提供。

11、MCO時鐘輸出:

MCO:把控制器的時鐘通過外部的引腳輸出,可以外外部的裝置提供時鐘。 MCO1為PA8, MCO2為PC9。

控制: RCC_CFGR 時鐘配置寄存器的MCOX的PREx位

/**
  * @brief  Configures the System clock source, PLL Multiplier and Divider factors, 
  *         AHB/APBx prescalers and Flash settings
  * @Note   This function should be called only once the RCC clock configuration  
  *         is reset to the default reset state (done in SystemInit() function).   
  * @param  None
  * @retval None
  */

/*
* 使用 HSE 時,設定系統時鐘的步驟
* 1、開啟 HSE ,并等待 HSE 穩定
* 2、設定 AHB、 APB2、 APB1 的預分頻因子
* 3、設定 PLL 的時鐘來源
*    設定 VCO 輸入時鐘 分頻因子 m
*    設定 VCO 輸出時鐘 倍頻因子 n
*    設定 PLLCLK 時鐘分頻因子 p
*    設定 OTG FS,SDIO,RNG 時鐘分頻因子 q
* 4、開啟 PLL,并等待 PLL 穩定
* 5、把 PLLCK 切換為系統時鐘 SYSCLK
* 6、讀取時鐘切換狀态位,確定 PLLCLK 被選為系統時鐘
*/
static void SetSysClock(void)
{
#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F401xx) || defined(STM32F446xx)
/******************************************************************************/
/*   PLL (clocked by HSE) used as System clock source鎖相環作為系統時鐘的源   */
/******************************************************************************/
/*-------------------------1-使能HSE,并等待HSE穩定----------------------------------------*/
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* Enable HSE 使能HSE*/
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  /* 等待HSE啟動完成,如果逾時則跳出 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)  //如果啟動成功
  {
    HSEStatus = (uint32_t)0x01;
  }
  else                                     //如果啟動不成功
  {
    HSEStatus = (uint32_t)0x00;
  }
     
  if (HSEStatus == (uint32_t)0x01)    //HSE啟動成功
  {
/*----------------------------------------------------------------------------------------*/
    /* Select regulator voltage output Scale 1 mode */
    /* 選擇電壓調節器輸出為模式1 */
    /* 使能電源接口時鐘 */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;
/*----------------------------2-配置 AHB APB2 APB1 總線的預分頻因子-----------------------*/
    /* HCLK = SYSCLK / 1*/
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F446xx)     
    /* PCLK2 = HCLK / 2*/
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
    
    /* PCLK1 = HCLK / 4*/
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
#endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx || STM32F446xx */

#if defined(STM32F401xx)
    /* PCLK2 = HCLK / 2*/
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK / 4*/
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
#endif /* STM32F401xx */

#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F401xx)
/*----------------------------3-配置 PLL的各種分頻因子,并使能PLL-----------------------*/
    /* Configure the main PLL */
    /* 配置 主 鎖相環 PLL */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
#endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx || STM32F401xx */

#if defined(STM32F446xx)
    /* Configure the main PLL */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24) | (PLL_R << 28);
#endif /* STM32F446xx */    
    
    /* Enable the main PLL */
    /* 使能 PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till the main PLL is ready */
    /* 等待PLL啟動穩定 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
   
#if defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F446xx) 
    /* Enable the Over-drive to extend the clock frequency to 180 Mhz */
/*----------------------------------------------------------------------------------------*/
    /* 打開OVER-Drive模式,為的是達到更高的頻率 */    
    PWR->CR |= PWR_CR_ODEN;
    while((PWR->CSR & PWR_CSR_ODRDY) == 0)
    {
    }
    PWR->CR |= PWR_CR_ODSWEN;
    while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
    {
    }      
    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
    /* 配置 Flash 預取指, 指令緩存, 資料緩存 和等待周期 */   
    FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
#endif /* STM32F427_437x || STM32F429_439xx || STM32F446xx */

#if defined(STM32F40_41xxx)     
    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
    FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
#endif /* STM32F40_41xxx  */

#if defined(STM32F401xx)
    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
    FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
#endif /* STM32F401xx */
    
/*----------------------------4-選擇系統時鐘來源-----------------------------------------*/
    /* Select the main PLL as system clock source */
    /* 選擇主鎖相環時鐘作為系統時鐘 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    /* Wait till the main PLL is used as system clock source */
    /* 等待PLLCLH切換稱系統時鐘 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    {
    }
  }
  else
  { 
    /* HSE啟動失敗後,使用者糾錯的代碼 */    
    /* If HSE fails to start-up, the application will have wrong clock
         configuration. User can add here some code to deal with this error */
  }
#elif defined(STM32F411xE)
#if defined(USE_HSE_BYPASS) 
/******************************************************************************/
/*            PLL (clocked by HSE) used as System clock source                */
/******************************************************************************/
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* Enable HSE and HSE BYPASS */
  RCC->CR |= ((uint32_t)RCC_CR_HSEON | RCC_CR_HSEBYP);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Select regulator voltage output Scale 1 mode */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    /* HCLK = SYSCLK / 1*/
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    /* PCLK2 = HCLK / 2*/
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK / 4*/
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;

    /* Configure the main PLL */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
    
    /* Enable the main PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till the main PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }

    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
    FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;

    /* Select the main PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    /* Wait till the main PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock
         configuration. User can add here some code to deal with this error */
  }
#else /* HSI will be used as PLL clock source */
  /* Select regulator voltage output Scale 1 mode */
  RCC->APB1ENR |= RCC_APB1ENR_PWREN;
  PWR->CR |= PWR_CR_VOS;
  
  /* HCLK = SYSCLK / 1*/
  RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
  
  /* PCLK2 = HCLK / 2*/
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
  
  /* PCLK1 = HCLK / 4*/
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
  
  /* Configure the main PLL */
  RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (PLL_Q << 24); 
  
  /* Enable the main PLL */
  RCC->CR |= RCC_CR_PLLON;
  
  /* Wait till the main PLL is ready */
  while((RCC->CR & RCC_CR_PLLRDY) == 0)
  {
  }
  
  /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
  FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
  
  /* Select the main PLL as system clock source */
  RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
  RCC->CFGR |= RCC_CFGR_SW_PLL;
  
  /* Wait till the main PLL is used as system clock source */
  while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
  {
  }
#endif /* USE_HSE_BYPASS */  
#endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx */  
}
///
//超頻部分代碼/
// m : 2~63
// n :192~432
// p :2、4、6、8
// q :2~15
// SYSCLK = (HSE/m) * n /p = 25/25 * 432 / 2 = 216M 
// HSE_SetSysClk(25,432,2,7)
///
void HSE_SetSysClk(uint32_t m,uint32_t n,uint32_t p,uint32_t q)
{
  __IO uint32_t HSEStartUpStatus = 0;
  // 把RCC外設初始化成複位狀态
  RCC_DeInit(); 
/*-------------------------1-使能HSE,并等待HSE穩定----------------------------------------*/  
  /* 使能HSE 并等待HSE穩定*/
  RCC_HSEConfig(RCC_HSE_ON);
  /*判斷HSE是否啟動成功*/  
  HSEStartUpStatus = RCC_WaitForHSEStartUp();
  
  if( HSEStartUpStatus == SUCCESS )
  {
/*----------------------------------------------------------------------------------------*/    
      /* 選擇電壓調節器輸出為模式1 */
      /* 使能電源接口時鐘 */
      RCC->APB1ENR |= RCC_APB1ENR_PWREN;
      PWR->CR |= PWR_CR_VOS;
/*----------------------------2-配置 AHB APB2 APB1 總線的預分頻因子-----------------------*/
      RCC_HCLKConfig(RCC_SYSCLK_Div1);  /* HCLK = SYSCLK / 1*/
      RCC_PCLK2Config(RCC_HCLK_Div2);   /* PCLK2 = HCLK / 2*/
      RCC_PCLK1Config(RCC_HCLK_Div4);   /* PCLK1 = HCLK / 4*/
/*----------------------------3-配置 PLL的各種分頻因子,并使能PLL-----------------------*/
      RCC_PLLConfig(RCC_PLLSource_HSE, m, n, p, q);  /* 配置 PLL的各種分頻因子 */

      RCC_PLLCmd(ENABLE);    /*使能PLL*/
      while ( ( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) ) == RESET )  /* 等待PLL啟動穩定 */
      {
      }
/*----------------------------------------------------------------------------------------*/
      /* 打開OVER-Drive模式,為的是達到更高的頻率 */
      PWR->CR |= PWR_CR_ODEN;
      while((PWR->CSR & PWR_CSR_ODRDY) == 0)
      {
      }
      PWR->CR |= PWR_CR_ODSWEN;
      while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
      {
      }

      /* 配置 Flash 預取指, 指令緩存, 資料緩存 和等待周期 */
      FLASH->ACR = FLASH_ACR_PRFTEN |
      FLASH_ACR_ICEN   |
      FLASH_ACR_DCEN   |
      FLASH_ACR_LATENCY_5WS;

/*----------------------------4-選擇系統時鐘來源-----------------------------------------*/
      RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);   /* 選擇主鎖相環時鐘作為系統時鐘 */
      while ( ( RCC_GetSYSCLKSource() ) != 0X08 )  /* 等待PLLCLH切換稱系統時鐘 */
      {
      }    
  }
  else{ /* HSE啟動識别,使用者在這裡添加糾錯代碼 */ }
}

///
//超頻部分代碼/
// m : 2~63
// n :192~432
// p :2、4、6、8
// q :2~15
// SYSCLK = (HSI/m) * n /p = 16/16 * 432 / 2 = 216M 
// HSI_SetSysClk(16,432,2,7)
///
void HSI_SetSysClk(uint32_t m,uint32_t n,uint32_t p,uint32_t q)
{
  __IO uint32_t HSIStartUpStatus = 0;
/*-------------------------1-使能HSI 并等待HSI穩定----------------------------------------*/    
    // 把RCC外設初始化成複位狀态
  RCC_DeInit();  
  
  /* 使能HSI 并等待HSI穩定*/
  RCC_HSICmd(ENABLE);  
  HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;  /*判斷HSI是否啟動成功*/  
  
  if( HSIStartUpStatus == RCC_CR_HSIRDY )
  {
/*----------------------------------------------------------------------------------------*/    
      /* 選擇電壓調節器輸出為模式1 */
      /* 使能電源接口時鐘 */
      RCC->APB1ENR |= RCC_APB1ENR_PWREN;
      PWR->CR |= PWR_CR_VOS;
/*----------------------------2-配置 AHB APB2 APB1 總線的預分頻因子-----------------------*/
      RCC_HCLKConfig(RCC_SYSCLK_Div1);  /* HCLK = SYSCLK / 1*/
      RCC_PCLK2Config(RCC_HCLK_Div2);   /* PCLK2 = HCLK / 2*/
      RCC_PCLK1Config(RCC_HCLK_Div4);   /* PCLK1 = HCLK / 4*/
/*----------------------------3-配置 PLL的各種分頻因子,并使能PLL-----------------------*/
      RCC_PLLConfig(RCC_PLLSource_HSI, m, n, p, q);

      RCC_PLLCmd(ENABLE);
      while ( ( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) ) == RESET )
      {
      }
/*----------------------------------------------------------------------------------------*/      
      /* 打開OVER-Drive模式,為的是達到更高的頻率 */
      PWR->CR |= PWR_CR_ODEN;
      while((PWR->CSR & PWR_CSR_ODRDY) == 0)
      {
      }
      PWR->CR |= PWR_CR_ODSWEN;
      while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
      {
      }

      /* 配置 Flash 預取指, 指令緩存, 資料緩存 和等待周期 */
      FLASH->ACR = FLASH_ACR_PRFTEN |
      FLASH_ACR_ICEN   |
      FLASH_ACR_DCEN   |
      FLASH_ACR_LATENCY_5WS;

/*----------------------------4-選擇系統時鐘來源-----------------------------------------*/
      RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
      while ( ( RCC_GetSYSCLKSource() ) != 0X08 )
      {
      }    
  }
  else{ /* HSI啟動識别,使用者在這裡添加糾錯代碼 */ }
}

// MCO1 PA8 GPIO 初始化
void MCO1_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  
  // MCO1 GPIO 配置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure); 
}

// MCO2 PC9 GPIO 初始化
void MCO2_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  
  // MCO2 GPIO 配置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}      
/**
  ******************************************************************************
  * @file    main.c
  * @author  Sumjess
  * @version V1.0
  * @date    2019-05-xx
  * @brief   MDK5.27
  ******************************************************************************
  * @attention
  *
  * 實驗平台   :STM32 F429 
  * 微信公衆号 :Tech雲
  *
  ******************************************************************************
  */
  
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_key.h" 

void Delay(__IO uint32_t nCount)   //簡單的延時函數
{
  for(; nCount != 0; nCount--);
}
int main(void)
{
    // 程式來到main函數之前,啟動檔案:statup_stm32f10x_hd.s已經調用
  // SystemInit()函數把系統時鐘初始化成180MHZ
  // SystemInit()在system_stm32f10x.c中定義
  // 如果使用者想修改系統時鐘,可自行編寫程式修改  
  // 重新設定系統時鐘,這時候可以選擇使用HSE還是HSI
  
  /* 把系統時鐘初始化為216M */
  //HSE_SetSysClk(25,432,2,9);      直接調用使用即可
  /* 把系統時鐘初始化為216M */
  HSI_SetSysClk(16,432,2,7);      直接調用使用即可
  LED_GPIO_Config();
  MCO1_GPIO_Config();
  // MCO1 輸出PLLCLK  
  RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_1);
  
  while (1)
  {
    LED1( ON );       // 亮 
    Delay(0xFFFFF);
    LED1( OFF );      // 滅
    Delay(0xFFFFF);
  }
}