STM32時鐘系統配置程序源碼深入分析
一、分析程序的目的
最近我在移植實時系統是遇到了一些問題,所以決定深入了解系統時鐘的配置過程,當然想要學好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
-
時鐘使能配置
// 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) -
時鐘相關配置
// 配置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) -
其他時鐘配置
// 調整內部高速振蕩器(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 ()
{
// 使能預取值
未完成,稍後補上........
}
}







