最近我在移植實時系統是遇到了一些問題,所以決定深入瞭解系統時鐘的設定過程,當然想要學好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
程式如下所示:
/* 將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時鐘系統的設定暫存器和原始碼分析」。
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
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 ()
{
// 使能預取值
未完成,稍後補上........
}
}