STM32時鐘系統設定程式原始碼深入分析

2022-06-07 21:03:33

一、分析程式的目的

最近我在移植實時系統是遇到了一些問題,所以決定深入瞭解系統時鐘的設定過程,當然想要學好stm32的小夥伴也有必要學習好時鐘系統的設定,所以我將學習的過程再次記錄,有寫得不好的地方,望小夥伴指出。

之前我已經記錄過一篇關於時鐘系統的文章,對程式中不瞭解的地方可以看我之前的筆記「STM32時鐘系統的設定暫存器和原始碼分析」。

這裡我用的晶片是STM32F103C8T6,用的庫函數是廠家提供的案例中提取出來的,這裡可能和其他型號的mcu有細微差別,但是原理都是一樣的。

二、程式執行的過程

當系統復位訊號發生的時候,程式將執行復位中斷函數,而在復位中斷函數中是先執行SystemInit函數後在執行__main函數,如下圖所示:

系統呼叫SystemInit函數後完成系統時鐘的設定,系統時鐘設定的過程如下所示:

從圖中可知,在系統時鐘設定的第三步有多個函數可以選擇,這裡可以根據自己的需求選擇相應的設定流程,只需要在stm32f10x.h檔案中定義相應的宏即可(預設設定為72MHz),如下圖所示:

在分析程式之前,需要了解一下相關暫存器的地址以及相應暫存器的作用,如下所示:

typedef struct
{
  __IO uint32_t CR;    // HSI、HSE、CSS、PLL等的使能和就緒標誌位
  __IO uint32_t CFGR;    // PLL等的時鐘源選擇,分頻係數設定
  __IO uint32_t CIR;    // 清除/使能時鐘就緒中斷
  __IO uint32_t APB2RSTR;    // APB2線上外設復位暫存器
  __IO uint32_t APB1RSTR;    // APB1線上外設復位暫存器
  __IO uint32_t AHBENR;    // DMA、SDIO等時鐘使能
  __IO uint32_t APB2ENR;    // APB2線上外設時鐘使能
  __IO uint32_t APB1ENR;    // APB1線上外設時鐘使能
  __IO uint32_t BDCR;    // 備用域控制暫存器
  __IO uint32_t CSR;    // 控制狀態暫存器
} RCC_TypeDef;

以上的暫存器都是相對RCC暫存器進行偏移的,如下圖所示:

通過查詢stm32f10x.h檔案中的定義可以知道暫存器RCC的地址,如下所示:
RCC = RCC_BASE = AHBPERIPH_BASE + 0x1000 = PERIPH_BASE(0x40000000) + 0x20000 = 0x40021000

三、SystemInit函數

程式如下所示:

/* 將RCC時鐘設定重置為預設重置狀態 */
void SystemInit (void)
{
  /* 開啟HSION位(內部高速時鐘使能) */
  RCC->CR |= (uint32_t)0x00000001;

  /* 復位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
  RCC->CFGR &= (uint32_t)0xF8FF0000;  

  /* 復位 HSEON, CSSON 和 PLLON 位  */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* 復位 HSEBYP 位 */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* 復位 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位 */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

  /* 禁用所有中斷並清除掛起位 */
  RCC->CIR = 0x00000000;
    
  /* 設定系統時脈頻率、HCLK、PCLK2和PCLK1預分頻器 */
  /* 設定快閃記憶體延遲週期並啟用預取緩衝區 */
  SetSysClock();

}

從上面的程式碼可以看出,和庫函數中的RCC_DeInit所執行的程式碼一下,所以在使用者程式中需要從新設定系統時鐘的話,不需要通過上面的程式碼將時鐘設定為預設狀態,只要呼叫RCC_DeInit函數即可。如下圖所示:

有不明白的地方只需要和相應的暫存器對應一下即可,相關的寄存說明請看「STM32時鐘系統的設定暫存器和原始碼分析」。

四、SetSysClock函數

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_20MHz
  SetSysClockTo20();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif

這是根據檔案中的宏定義選擇相應的系統時鐘設定函數,有需要更改的直接定義相應的宏即可,系統預設是的72MHz

五、SetSysClockTo72函數

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /*!< SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /*!< Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /*!< Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));

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

  if (HSEStatus == (uint32_t)0x01)
  {
    /*!< Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /*!< Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    
 
    /*!< HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /*!< PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /*!< PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
    
    /*!< PLLCLK = 8MHz * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9);

    /*!< Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

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

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

    /*!< Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  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 */    

    /*!< Go to infinite loop */
    while (1)
    {
    }
  }
}
  • 使能外部高速時鐘

    // #define  RCC_CR_HSEON                        ((uint32_t)0x00010000)
    
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
    do
    {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;  
    } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));
    
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
    {
        HSEStatus = (uint32_t)0x01;
    }
    else
    {
        HSEStatus = (uint32_t)0x00;
    } 
    

    從定義為檔案中可知RCC_CR_HSEON為0x00010000,也就是CR暫存器的第17位為1。HSEStartUp_TimeOut為0x0500表示HSE啟動超時,也就是說如下圖所示:

    注意:執行完上面程式後,接著判斷外部時鐘是否就緒,只要當外部時鐘就緒後才執行後面的流程,否成啟動失敗,程式將卡在while位置

  • FLASH處理

    FLASH->ACR |= FLASH_ACR_PRFTBE;
    
    /*!< Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    
    
    

    由於CPU的速度比flash的速度要快,所以這裡需要讓cpu等待兩個時鐘

  • 設定AHB、APB1、APB2預分頻的值

    // RCC_CFGR_HPRE_DIV1 = 0x00000000
    // RCC_CFGR_PPRE2_DIV1 = 0x00000000
    // RCC_CFGR_PPRE1_DIV2 = 0x00000400
    
    /*!< HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
    
    /*!< PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
      
    /*!< PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
    
    

    從註釋中可知AHB和APB2的預分頻為1,APB1的預分頻為2(因為PCLK1的最大頻率為36MHz)

  • 設定PLL的時鐘源和倍頻

    // RCC_CFGR_PLLSRC = 0x00010000
    // RCC_CFGR_PLLXTPRE = 0x00020000
    // RCC_CFGR_PLLMULL = 0x003C0000
    // RCC_CFGR_PLLMULL9 = 0x001C0000
    
    /*!< PLLCLK = 8MHz * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9);
    
    

    第一行程式碼的作用是將CFGR的[16:21]暫存器複製為0,第二行是將HSE設定為PLL的時鐘源,HSE分頻器不分頻,PLL倍頻係數設定為9

  • 使能PLL時鐘

    // RCC_CR_PLLON = 0x01000000
    // RCC_CR_PLLRDY = 0x02000000
    
    /*!< Enable PLL */
    RCC->CR |= RCC_CR_PLLON;
    
    /*!< Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    

    使能PLL時鐘,並等待PLL時鐘就緒

  • 設定PLL作為系統時鐘源

    // RCC_CFGR_SW = 0x00000003
    // RCC_CFGR_SW_PLL = 0x00000002
    // RCC_CFGR_SWS = 0x0000000C
    
    /*!< Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    
    
    /*!< Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
    

    設定PLL作為系統時鐘源,並判斷是否成功

注意: SetSysClockTo72函數的作用是設定HCLK為72MHz、PCLK1為36MHz、PCLK2為72MHz,如下圖所示:

六、時鐘設定系統的庫函數

標頭檔案是stm32f10x_rcc.h,原始檔是stm32f10x_rcc.c

  1. 時鐘使能設定

    // HSE時鐘使能
    void RCC_HSEConfig(uint32_t RCC_HSE);
    // HSI時鐘使能
    void RCC_HSICmd(FunctionalState NewState);
    // PLL時鐘使能
    void RCC_PLLCmd(FunctionalState NewState);
    // 啟用或禁用指定的RCC中斷
    void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState)
    // 使能LSI時鐘
    void RCC_LSICmd(FunctionalState NewState);
    // 使能RTC時鐘
    void RCC_RTCCLKCmd(FunctionalState NewState)
    // 使能AHB外圍時鐘
    void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState)
    // 使能高速APB(APB2)外圍時鐘
    void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
    // 使能低速APB(APB1)外圍時鐘
    void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
    // 使能時鐘安全系統
    void RCC_ClockSecuritySystemCmd(FunctionalState NewState)
    
    
  2. 時鐘相關設定

    // 設定PLL時鐘源,僅當PLL禁用時,才能使用此功能。
    void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul)
    // 設定系統時鐘(SYSCLK)。
    void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource)
    // 設定AHB時鐘(HCLK)
    void RCC_HCLKConfig(uint32_t RCC_SYSCLK)
    // 設定低速APB時鐘(PCLK1)
    void RCC_PCLK1Config(uint32_t RCC_HCLK)
    // 設定高速APB時鐘(PCLK2)
    void RCC_PCLK2Config(uint32_t RCC_HCLK)
    // 設定USB時鐘(USBCLK)
    void RCC_USBCLKConfig(uint32_t RCC_USBCLKSource)
    // 設定ADC時鐘(ADCCLK)
    void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
    // 設定外部低速振盪器(LSE)
    void RCC_LSEConfig(uint8_t RCC_LSE)
    // 設定RTC時鐘(RTCCLK)
    void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource)
    
    
  3. 其他時鐘設定

    // 調整內部高速振盪器(HSI)校準
    void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue)
    // 獲取時鐘源
    uint8_t RCC_GetSYSCLKSource(void)
    // 等待HSE時鐘啟動
    ErrorStatus RCC_WaitForHSEStartUp(void)
    // 獲取對應的時脈頻率
    void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks)
    // 強制復位高速APB(APB2)外圍裝置
    void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
    // 強制復位低速APB(APB1)外圍裝置
    void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
    // 強制重置備份域
    void RCC_BackupResetCmd(FunctionalState NewState)
    // 選擇要在MCO引腳上輸出的時鐘源
    void RCC_MCOConfig(uint8_t RCC_MCO)
    // 檢查是否設定了指定的RCC標誌
    FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG)
    // 清除RCC重置標誌
    void RCC_ClearFlag(void)
    // 檢查是否發生了指定的RCC中斷
    ITStatus RCC_GetITStatus(uint8_t RCC_IT)
    // 清除RCC中斷掛起位
    void RCC_ClearITPendingBit(uint8_t RCC_IT)
    
    

七、通過庫函數設定時鐘系統

void HSE_SetClk(uint32_t RCC_PLLMul_x)
{
    ErrorStatus HSEStaus;
    // 使能外部時鐘(HSE)
    RCC_HSEConfig(RCC_HSE_ON);
    HSEStaus = RCC_WaitForHSEStartUp();
    if ()
    {
        // 使能預取值
        未完成,稍後補上........

  



    }

}