STM32開發筆記04—配置系統時間


架構圖

思考重點

  • 本文的目的是使用HSE外部晶振來配置系統時鐘
  • 參考手冊中的時鐘樹如何理解
  • 理解開發版初始化過程中對系統時鐘的操作
  • 如何自行變更系統時鐘

配置時鐘源

在開發版STM32F429,以HSE, HSI, PLL作為主要的系統時鐘的信號來源,並擁有LSI, LSE低速內外部信號時鐘源,兩者頻率分別為32, 32.768kHz

時鐘源相當於節拍器的功能,藉由穩定的信號源輸出,可以有效配置出單位時間內系統的運算次數。下面探討三個主要的系統時鐘來源的功能與特色:

  • HSE
    • 高速外部時鐘信號
    • 可由震盪器(oscillator)/晶振(crystal)提供信號源
    • 選擇震盪器為信號源需要接上引腳OSC_OUT, OSC_IN
    • 頻率: 4MHz~25MHz
  • HSI
    • 高速內部時鐘信號
    • 內部晶振提供信號源
    • 當HSE故障將會自動切成HSI直到HSE恢復正常
    • 頻率: 16MHz
  • PLL
    • 鎖向環
    • 主要目的是對時鐘信號源進行分頻以及倍頻處理,並將結果輸出給相關外設
    • 開發版具有兩個PLL,分別是主PLL和I2S專用PLLI2S
    • 官方建議最高運行頻率為180MHz
    • 主PLL
      • 用於PLLCLK系統時鐘
      • 用於USB OTG FS, RNG, SDIO時鐘(48MHz)
    • PLLI2S
      • 用於I2S的時鐘

時鐘樹

上圖為reference manual中對設置系統時鐘的描述,也就是著名的時鐘樹框圖,由於我們的目標是配製系統時間SYSCLK,因此可以把主線任務從上圖的紅框部分拆分成以下的流程圖,這樣對接下來的程式範例也好理解

從圖中我們可以發現配置系統時間過程中有兩大重點

  1. 選擇PLL時鐘源
  2. 選擇系統時鐘源

而系統時間通常是使用HSE, HSI, PLL三者之一,可以透過配置暫存器來選擇,一般來說系統時間會使用PLL倍頻之後的結果

PLL倍頻

由於外部時鐘來源的頻率不夠大,開發版需要透過PLL鎖向環,將輸入時鐘倍頻成適合的系統頻率。因此PLL的處理過程圍繞在將時鐘源切分成約1MHz後再進行放大,我們先來看看PLL系統時間的運算公式:

[(HSE/HSI)/分頻因子M] * 倍頻因子N / PLLCLK分頻因子P

操作流程

  1. 選擇時鐘輸入來源: HSE(25MHz)或HSI(16MHz)
  2. 設置分頻因子M: 時鐘源/M的結果務必在1~2MHz。M的範圍: 2~63
  3. 設置倍頻因子N : STM32F42xxx, STM32F43xxx系列頻率範圍: 192~432MHz
  4. 設置PLLCLK分頻因子P: P可以是2、4、6、8

舉例來說,我們選擇25MHz的外部震盪器時鐘源,透過將M配置成25,把V時鐘輸入結果配置成1MHz,然後把N設為360,將輸出結果放大成360MHz,最後設置P為2,輸出180MHz的PLL系統時鐘源

另外假如PLL時鐘來源選擇HSE,當HSE發生問題時,系統會自動將時鐘源切換成HSI

AHB外設時鐘HCLK

當我們選擇PLL作為系統時鐘來源後,首先會輸出到高速外設匯流排AHB,同樣的我們可以透過軟體控制暫存器的AHB分頻因子,分頻因子可以設置為1, 2, 4, 8, 16, 64, 128, 256, 512。幾乎所有周邊外設都使用AHB的系統時間頻率

APB2外設時鐘PCLK2

APB2匯流排時鐘經由AHB時鐘分頻得到,可以透過軟體操作暫存器的APB2分頻因子,分頻因子可以設置為1, 2, 4, 8, 16。需要注意APB2時鐘頻率不可以超過90MHz

APB1外設時鐘PCLK1

APB1匯流排時鐘經由AHB時鐘分頻得到,可以透過軟體操作暫存器的APB1分頻因子,分頻因子可以設置為1, 2, 4, 8, 16。需要注意APB1時鐘頻率不可以超過45MHz

暫存器介紹

在接下來的系統時鐘配置環節主要涉及以下幾個暫存器,詳細可以參考reference manual

RCC_CR

  • PLLRDY: 主PLL鎖向環是否被開啟
  • PLLON: 開啟PLL鎖向環
  • CSSON: 開啟檢測外部震盪器是否穩定
  • HSEBYP: 當HSE發生問題時bypass給其他時鐘信號源
  • HSERDY: 由硬體判斷HSE是否被開啟
  • HSEON: 開啟HSE時鐘源
  • HSION: 開啟HSI時鐘源

RCC_PLLCFGR

  • PLLP: 配置分頻因子P
  • PLLN: 配置分頻因子N
  • PLLM: 配置分頻因子M

RCC_CFGR

  • PPRE2: 配置APB2分頻因子
  • PPRE1: 配置APB1分頻因子
  • HPRE: 配置AHB分頻因子
  • SWS: 等待硬體切換系統時鐘來源
  • SW: 選擇系統時鐘來源

SetSysClock()函式

關於開發版初始化的主要寫在函式SystemInit()當中,系統時鐘的配置當然也不例外。在SystemInit() 我們可以找到SetSysClock()函式,我們上一小節介紹的諸如PLL時鐘設定都在這個函式中完成,以下大致介紹SetSysClock():

  • 目的: 初始化系統時間
  • 主要操作:
    1. 開啟HSE
    2. 配置內部電壓調節器
    3. 配置AHB/APB1/APB2
    4. 配置PLL
    5. 開啟PLL
    6. 開啟Over-drive模式
    7. 配置Flash接口暫存器
    8. 將PLL配置成系統時間
static void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  /* 開啟HSE,操作RCC_CR的HSEON位*/
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

  /* 在指定時間內等待硬體將HSERDY置位成1 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  /* 查看HSE是否配置成功,或者只是因為timeout退出 */
  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01; // HSE配置成功,將HSEStatus設為1
  }
  else
  {
    HSEStatus = (uint32_t)0x00; // HSE配置失敗,將HSEStatus設為0
  }
  /* 若HSE配置成功,則可以開始系統時鐘的處理流程 */
  if (HSEStatus == (uint32_t)0x01)
  {
    /* 配置內部電壓調節器,以達到效率與功號之間的平衡 */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    /* AHB分頻因子配置成1 */
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    /* APB2分頻因子配置成2 */
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;

    /* APB1分頻因子配置成4*/
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;

    /* 配置RCC_PLLCFGR暫存器,我們主要關注M, N, P因子 */
    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-drive模式,使系統能夠運行更高頻率 */
    PWR->CR |= PWR_CR_ODEN;

    /* 等待Over-drive模式被成功開啟 */
    while((PWR->CSR & PWR_CSR_ODRDY) == 0)
    {
    }

    /* 將系統切換為Over-drive模式 */    
    PWR->CR |= PWR_CR_ODSWEN;

    /* 等待系統成功切換置Over-drive模式 */     
    while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
    {
    } 

    /* 配置Flash接口暫存器 */
    FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    /* 選擇PLL作為系統時鐘來源 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    /* 等待硬體切換系統時鐘來源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    {
    }
  }
  else
  { /* 使用者可以自行定義若HSE啟動失敗要做那些處理 */
  }
}

超頻處理

理解了SetSysClock()函式以後我們大致能掌握配置系統時間的操作,若我們想自行編寫一個改變PLL系統時鐘頻率的API,可以簡單地抓一下程式編寫重點:

  1. 初始化RCC時鐘配置成default狀態
  2. 開啟HSE
  3. 設置AHB/APB2/APB1分頻因子
  4. 配置分頻因子M, N, P
  5. 開啟PLL
  6. 配置PLL為系統時鐘來源

還記得在介紹倍頻因子N的時候有提到STM32F42xxx, STM32F43xxx系列開發版可以將N設置到最大值432MHz嗎?實際上官方參考手冊雖建議最大頻率為180MHz,不過還是預留空間供超頻使用,因此我們希望透過自定義API將系統頻率重新設置成216MHz

  • 關鍵點:
    • 在編寫程式前可以先到標準庫中閱讀庫函數
    • 搭配參考手冊查閱暫存器設定
    • 參考SetSysClock()
  • 涉及庫函式:
  1. RCC_DeInit(void)
  2. RCC_HSEConfig(uint8_t RCC_HSE)
  3. RCC_WaitForHSEStartUp(void)
  4. RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t PLLM, uint32_t PLLN, uint32_t PLLP, uint32_t PLLQ)
  5. RCC_HCLKConfig(uint32_t RCC_SYSCLK)
  6. RCC_PCLK2Config(uint32_t RCC_HCLK)
  7. RCC_PCLK1Config(uint32_t RCC_HCLK)
  8. RCC_PLLCmd(FunctionalState NewState)
  9. RCC_GetFlagStatus(uint8_t RCC_FLAG)
  10. RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource)

程式碼範例

void My_Delay(__IO uint32_t count){
    for(;count > 0; count--);
}    

/**
 * vco range: 192~432Mhz
 * Re-set system clock by myself, the maximun frequency is 216Mhz
 * @param m Division factor
 * @param n Multiplication factor
 * @param p Division factor for main system clock
 * @param q Division factor for USB OTG FS, SDIO, etc.
 * @retval None
 */
void HSE_SetSysCLK(uint32_t m, uint32_t n, uint32_t p, uint32_t q){
        RCC_DeInit(); // 將RCC重置到預設模式
        RCC_HSEConfig(SET_ON); // 開啟HSE

        /* 等待HSE啟動成功 */
        while(!RCC_WaitForHSEStartUp()){
            My_Delay(TIMES);
            RCC_HSEConfig(SET_ON);
        }

        /* 配置PLL分倍頻因子*/
        RCC_PLLConfig(HSE,m,n,p,q);
        /* 配置外設時鐘分頻因子*/
        RCC_HCLKConfig(AHB_CFG); // AHB
        RCC_PCLK2Config(APB2_CFG); // APB2
        RCC_PCLK1Config(APB1_CFG); // APB1

        /* 啟動PLL */
        RCC_PLLCmd(ENABLE);
        /* 等待PLL啟動成功 */
        while(!RCC_GetFlagStatus(RCC_FLAG_PLLRDY)){}
        /* 配置PLL為系統時間 */
        RCC_SYSCLKConfig(PLLCLK);
}

然後我們只需要在main函式中初始化該API就可以調整系統時鐘

#include "bsp_rccclkconfig.h"
#include "bsp_led.h"

int main(void)
{  
  HSE_SetSysCLK(SYSCLK_M,SYSCLK_N,SYSCLK_P,SYSCLK_Q);
  App_Init();  
  /* Infinite loop */
  while (1)
  {
    App_Thread();
  }
}

相關的marco定義如下所示:

#ifndef __RCCCLKCONFIG_H_
#define __RCCCLKCONFIG_H_

#include "stm32f4xx.h"

#define RCC_TEST                 0
#define SET_ON                    1
#define TIMES                    100
#define HSE                        1
#define AHB_CFG                 1
#define APB2_CFG                4
#define APB1_CFG                5
#define PLLCLK                    2

#define PLLM                      25
#define PLLQ                      9
#define PLLN                     432
#define PLLP                      0

#define SYSCLK_M                   PLLM
#define SYSCLK_Q                PLLQ
#define SYSCLK_N                  PLLN
#define SYSCLK_P                PLLP

extern void HSE_SetSysCLK(uint32_t m, uint32_t n, uint32_t p, uint32_t q);

#endif /*__RCCCLKCONFIG_H_*/

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

%d 位部落客按了讚: