一、理解RCC與時鍾樹
RCC 即Reset and Clock Control ,意思是復位和時鍾控制器,它負責單片機的復位以及時鍾的配置。
1.復位
STM32F10xxx支持三種復位形式,分別為系統復位、電源復位和備份區域復位。
(1)系統復位
當發生以下任一事件時,產生一個系統復位:
1. NRST引腳上的低電平(外部復位)
2. 窗口看門狗計數終止(WWDG復位)
3. 獨立看門狗計數終止(IWDG復位)
4. 軟件復位(SW復位)
5. 低功耗管理復位
除了時鍾控制器的RCC_CSR寄存器中的復位標志位和備份區域中的寄存器以外,系統
復位將復位所有寄存器至它們的復位狀態。
(2)電源復位
當以下事件中之一發生時,產生電源復位:
1. 上電/掉電復位(POR/PDR復位)
2. 從待機模式中返回
電源復位將復位除了備份區域外的所有寄存器。
(3)備份域復位
當以下事件中之一發生時,產生備份區域復位。
1. 軟件復位,備份區域復位可由設置備份域控制寄存器 (RCC_BDCR)中的BDRST位產生。
2. 在VDD和VBAT兩者掉電的前提下, VDD或VBAT上電將引發備份區域復位。
備份區域擁有兩個專門的復位,它們只影響備份區域。
2.時鍾
時鍾系統為硬件系統的各個模塊提供時鍾信號,就像人的脈搏心跳一樣不可或缺,而STM32的結構較為復雜,不同的硬件可能對時鍾信號有不同的要求,因此在系統中設置多個振盪器,分別提供時鍾信號,實際中經常從一個主振盪器開始,經過多次的倍頻、分頻、鎖相環等電路,生成每個模塊的時鍾信號。
下圖為單片機的時鍾樹,它描述了整個系統從振盪器到各個模塊的時鍾信號通路。
這個圖初看可能會覺得比較復雜,但只要抓住核心,也就容易了,其核心就是圖中標號4的部分,SYSCLK系統時鍾。
我們可以把時鍾樹以SYSCLK為界限分為左右兩部分,左邊是驅動SYSCLK所需的振盪器輸入、分頻、鎖相環倍頻等過程,而右邊是SYSCLK提供給總線以及各個外設的時鍾信號通路。
左邊:
有三種不同的時鍾源可被用來驅動系統時鍾(SYSCLK):HSI振盪器時鍾、HSE振盪器時鍾 和 PLL時鍾。
PLL時鍾是Phase Lock Loop時鍾即“鎖相環時鍾”的簡稱,它利用外部輸入的參考信號控制環路內部振盪信號的頻率和相位,故可以實現外部控制倍頻的效果。
PLL鎖相環時鍾被三種時鍾源其中之一驅動,分別是HSI/2 、HSE 和 HSE/2。
右邊:
由圖中5、6、7三個預分頻器控制HCLK、PCLK1和PCLK2的時鍾頻率,其中HCLK1最大只能達到36M,而HCLK2可以達到72M。
由HLK、PCLK1、PCLK2這三個總線時鍾,經過分頻和倍頻產生其他外設需要的時鍾頻率。
其他:
LSE、LSI用來產生RTC實時時鍾 和 IWDG獨立看門狗時鍾。
由PLLCLK/2、HSI、HSE、SYSCLK這四個其中之一,可以向單片機外部輸出MCO時鍾信號,對應單片機PA8引腳。
二、探究系統的初始化時鍾配置函數
在啟動文件“startup_stm32f10x_hd.s”中,有這樣一段匯編代碼,當系統復位時執行,效果是初始化系統的時鍾,並運行__main這段代碼,最終執行我們寫的main函數。
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
說一個題外話:
__main和main是完全兩個不同的函數,並且你無法找到__main代碼,因為這個是編譯器自動創建的。
查看MDK的文檔,會發現有這么一句說明:It is automatically created by the linker when it sees a definition of main(),意思是當編譯器發現定義了main函數,那么就會自動創建__main。
我們這里關注SystemInit這個函數,它初始化了系統的時鍾配置,對函數go to definition后發現該函數定義在“system_stm32f10x.c”中,其函數內容如下。
啟動文件中調用的的系統初始化函數
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
函數內部基本是對RCC寄存器的配置,前面大部分語句都是把RCC寄存器初始化成默認的復位狀態,最后又調用了SetSysClock()這個函數,函數內部內容如下。
SetSysClock()函數
/**
* @brief Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
* @param None
* @retval None
*/
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#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
函數內部判斷是否定義了SYSCLK_FREQ_72MHz,如果有定義過,則執行SetSysClockTo72();這個函數,而根據“system_stm32f10x.c”這一源文件的代碼,對stm32f10x的HD型設備,其默認定義了SYSCLK_FREQ_72MHz。
HD型設備默認定義
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
由於默認定義,會執行SetSysClockTo72()函數,函數內部如下。
SetSysClockTo72()函數
#elif defined SYSCLK_FREQ_72MHz
/**
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2
* and PCLK1 prescalers.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
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 != HSE_STARTUP_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;
#ifdef STM32F10X_CL
/* Configure PLLs ------------------------------------------------------*/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
/* Enable PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* Wait till PLL2 is ready */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}
/* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
#else
/* PLL configuration: PLLCLK = HSE * 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_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* 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 */
}
}
#endif
函數總體流程為:
- 使能HSE
- 等待HSE使能完畢
- 使能預取指
- 設置FLASH等待2個周期
- 配置HCLK
- 配置PCLK2
- 配置PCLK1
- 配置PLL鎖相環時鍾,包含兩個參數,分別是鎖相環的時鍾源和倍頻系數
- 使能PLL鎖相環
- 等待PLL鎖相環穩定
- 配置PLL鎖相環作為SYSCLK系統時鍾
- 等待PLL鎖相環成功配置為SYSCLK源
三、自己寫HSE配置系統時鍾函數
如果不使用系統自帶的RCC初始化函數,我們自己也可以根據庫函數仿照系統自帶的流程寫出一個配置函數,所需要的庫函數,在"stm32f10x_rcc.c"中有定義,在"stm32f10x_rcc.h"最下方有聲明,可以很方便地找到。
仿照系統自帶的配置流程,依據"stm32f10x_rcc.c"庫函數,寫出的HSE配置系統時鍾函數如下。
HSE配置系統時鍾函數
void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
{
ErrorStatus HSEStatus;
//把RCC寄存器復位成復位值
RCC_DeInit();
//使能HSE
RCC_HSEConfig(RCC_HSE_ON);
//等待HSE准備完畢
HSEStatus = RCC_WaitForHSEStartUp();
if(HSEStatus == SUCCESS)
{
//使能預取指緩沖
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
//設置FLASH等待2個周期
FLASH_SetLatency(FLASH_Latency_2);
//設置HCLK,即AHB的時鍾頻率為不分頻
RCC_HCLKConfig(RCC_SYSCLK_Div1);
//設置PCLK2時鍾頻率為不分頻
RCC_PCLK2Config(RCC_HCLK_Div1);
//設置PCLK1時鍾頻率為2分頻
RCC_PCLK1Config(RCC_HCLK_Div2);
//設置PLL鎖相環時鍾為HSE*x, x為函數的入口參數。
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
//使能PLL鎖相環
RCC_PLLCmd(ENABLE);
//等待PLL穩定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) ;
//配置PLL作為系統時鍾
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//等待PLL成功設置為SysClk源
while(RCC_GetSYSCLKSource() != 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 */
}
}