一、分析程式的目的
最近我在移植實時系統是遇到了一些問題,所以決定深入瞭解系統時鐘的配置過程,當然想要學好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 ()
{
// 使能預取值
未完成,稍後補上........
}
}