
前言
近日,有群友困于STM32的時鐘系統(tǒng)。這里就詳細介紹一下關(guān)于內(nèi)外時鐘切換及時鐘超頻測試,希望對大家能有所幫助。

誠然,當(dāng)使用固件庫時,把外部晶振摘掉,系統(tǒng)確實會自動切換到內(nèi)部時鐘,但是只會以8M的默認值運行,顯然這是十分不可行的,8M的速度直接讓我們的STM32病入膏肓,今天的任務(wù)就是讓STM32失去外掛(晶振)時,依舊可以激情澎湃。
時鐘詳解這里不過多介紹,自己也沒有別人介紹的好,本文旨在解決現(xiàn)實問題。
此處插播廣告:群友問過這種問題,外部接8M晶振和16M晶振有啥區(qū)別?
以我微薄的經(jīng)驗來看,這兩個在用的時候差別不大,如果使用ST的固件庫(以STM32F103為例),使用8M的晶振會更方便,不用改任何代碼,時鐘就是72M的全速運行狀態(tài)。如果用16M晶振,則需要修改代碼:
在stm32f10x.h中修改宏定義HSE_VALUE ? ((uint32_t)8000000)為HSE_VALUE ? ((uint32_t)16000000)。

之后進入system_stm32f10x.c,將RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);改為RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSI_Div2| RCC_CFGR_PLLMULL9);此處是將輸入時鐘二分頻為8M,再進行9倍頻到72M,和使用了8M沒區(qū)別。
如果不進行該二分頻操作,時鐘還是有的,但是會以16M為基準(zhǔn)進行9倍頻到144M,此時單片機以超頻模式運行,也是可以運行的。但是,時鐘的精準(zhǔn)性不能得到保證。

系統(tǒng)的時鐘可以通過添加代碼在debug模式下顯示:
RCC_ClocksTypeDef ClockInfo;RCC_GetClocksFreq(&ClockInfo);

通過debug模式下觀察ClockInfo的值,便可知道此時系統(tǒng)時鐘速度:


這里提一下,在使用外部晶振的情況下,ST即使是超頻,依舊發(fā)揮穩(wěn)定,不得不夸一下ST的質(zhì)量。
此時我將我的開發(fā)板以8M的基準(zhǔn)倍頻16倍,得到128M的主頻,使用定時器定時10us,示波器測試無誤差。串口通信無誤。




以72M的主頻跑128依舊穩(wěn)定,贊一個,因為我的外部晶振只有8M最大只能倍頻到128,如果使用外部16M,不知繼續(xù)倍頻可以到多少。不過性能還是很好的。
預(yù)留測試GD32的效果:
寫本文時,將GD的GD32E230翻出來進行了同樣的測試,因為GD的倍頻器倍數(shù)較高,我已經(jīng)倍頻到144M(標(biāo)準(zhǔn)72M),測試定時器依舊穩(wěn)定。

廣告很長,請忍一下:
上半場結(jié)束,下半場繼續(xù):
此處歪解一下時鐘的問題,之前有群友很疑惑單片機的低功耗和時鐘的關(guān)系,疑惑高速的時鐘會不會增加MCU的功耗,為啥低功耗要降低時鐘速度。這里講解一下:
可以用用單位時間內(nèi)執(zhí)行的指令來看,高速時鐘在單位時間內(nèi)使系統(tǒng)跑了更多的指令,而低速時鐘單位時間內(nèi)跑的少,而單片機是直線結(jié)構(gòu),內(nèi)核是不會休息的,功耗就看執(zhí)行的指令多少。而單片機的低功耗就是降低時鐘,讓單片機跑慢點。就像人一樣,低功耗相當(dāng)于你不跑了,原地休息,但是你的心跳不會停止,你還是得消耗能量,即使再少還得消耗。
就像人一樣,時鐘就相當(dāng)于心跳,只要還活著就得消耗能量,你要想跑得快,心臟就得跳得快,跳得越快能量消耗越高,即使你去睡覺,心跳只要不停止,你還得消耗能量,如果心跳沒了,整個人就沒了,MCU也就宕機了。所以,在處理低功耗時最先解決的就是時鐘頻率,只有降低了時鐘的頻率,才能真正降低功耗。關(guān)于單片機進入低功耗和喚醒,以及降低整體運行功耗我看能不能在下文講解,近期剛好做了一個低功耗的項目,這里留懸念吧。
廣告結(jié)束,正文開始,不好意思,有點喧賓奪主了哈!
回到主題,為了解決時鐘切換的問題,才有了這個帖子,上文全屬歪樓,為最近開發(fā)時的經(jīng)驗總結(jié)。
我們在使用STM32103的固件庫時,時鐘配置在system_stm32f10x.c中,但是只是對外部晶振做了初始化,而對于內(nèi)部時鐘并沒有添加代碼,如果你的MCU沒有外部晶振,當(dāng)系統(tǒng)運行時是先啟動內(nèi)部時鐘,然后會檢測外部晶振,如果沒有檢測到晶振,系統(tǒng)便以內(nèi)部的8M繼續(xù)運行,這是不合理的。

這里可以看到,如果外部啟動失敗,會進入這個else,但是這個else中并未添加任何代碼,所以只會用8M的內(nèi)鐘執(zhí)行,我們要做的就是在else中添加外部啟動失敗的代碼:
? ? /* 開啟HSI 即內(nèi)部晶振時鐘 */? ?? ???RCC->CR |= (uint32_t)0x00000001;? ?? ???/*選擇HSI為PLL的時鐘源HSI必須2分頻給PLL*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_PLLSRC_HSI_Div2;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???/*PLLCLK=8/2*13=52MHz? ?設(shè)置倍頻得到時鐘源PLL的頻率*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL12;? ?? ???/* PLL不分頻輸出??*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;? ?? ?? ?? ?? ???/* 使能 PLL時鐘 */? ?? ???RCC->CR |= RCC_CR_PLLON;? ?? ???/* 等待PLL時鐘就緒*/? ?? ???while((RCC->CR & RCC_CR_PLLRDY) == 0)? ?? ???{? ?? ???}? ?? ???/* 選擇PLL為系統(tǒng)時鐘的時鐘源 */? ?? ???RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;? ?? ?? ???/* 等到PLL成為系統(tǒng)時鐘的時鐘源*/? ?? ???while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)? ?? ???{????????}
該代碼填充后如果檢測到有外部時鐘,便以外部時鐘為基準(zhǔn)進行時鐘的倍頻處理,達到用戶想要的時鐘頻率,如果你的MCU沒有外部時鐘,則會執(zhí)行else內(nèi)部的代碼,將時鐘源切換到內(nèi)部時鐘并進行倍頻,如此便達到了自動檢測時鐘的目的。
問題:這是我根據(jù)STM32F031的時鐘切換代碼演變來的,但是這個只能用于主頻小于或等于48M時使用,如果倍頻因子超過12,也就是主頻超過48M是,就會出現(xiàn)硬件錯誤,直接卡死。當(dāng)需要更高的主頻時就需要如下配置。
在else里面最開頭添加:
? ? /* 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;?????????

問:如果我的MCU有晶振,但是我不想用外部,就想用內(nèi)部,如何處理呢?
答:打一頓就好了,有外部不用干啥用內(nèi)部呢?
上述純屬惡搞自己,被坑過……
因為內(nèi)部時鐘不準(zhǔn)!!!測試內(nèi)部時鐘在使用定時器時會有偏差,本人在此吃過虧。此問題在STM32F031和GD32E230中均有體現(xiàn)。但是USART和SPI通信是正常的,即使我用的2.5M波特率的USART和8M的SPI。
解決辦法,上述代碼不用動,添加如下代碼。

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);? ?? ???/*取消改行注釋并注釋上文,可默認啟動內(nèi)部時鐘*/? ?? ???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;? ? /* 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);? ? /*??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_PLLMULL16);? ? /* 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??{? ? /* 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;? ?? ?? ?? ?? ?? ???/* 開啟HSI 即內(nèi)部晶振時鐘 */? ?? ???RCC->CR |= (uint32_t)0x00000001;? ?? ???/*選擇HSI為PLL的時鐘源HSI必須2分頻給PLL*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_PLLSRC_HSI_Div2;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???/*PLLCLK=8/2*13=52MHz? ?設(shè)置倍頻得到時鐘源PLL的頻率*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL16;? ?? ???/* PLL不分頻輸出??*/? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;? ?? ?? ?? ?? ???/* 使能 PLL時鐘 */? ?? ???RCC->CR |= RCC_CR_PLLON;? ?? ???/* 等待PLL時鐘就緒*/? ?? ???while((RCC->CR & RCC_CR_PLLRDY) == 0)? ?? ???{? ?? ???}? ?? ???/* 選擇PLL為系統(tǒng)時鐘的時鐘源 */? ?? ???RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));? ?? ???RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;? ?? ?? ???/* 等到PLL成為系統(tǒng)時鐘的時鐘源*/? ?? ???while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)? ?? ???{? ?? ???}??}}
在STM32F030或者STM32F031中,同樣可以做類似操作:
static void SetSysClock(void){??__IO uint32_t StartUpCounter = 0, HSEStatus = 0;????/* SYSCLK, HCLK, PCLK configuration ----------------------------------------*/??/* Enable HSE */? ?RCC->CR |= ((uint32_t)RCC_CR_HSEON);? ?? ???//修改為內(nèi)部晶振? ?? ???//? ?? ???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 and set Flash Latency */? ? FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;? ? /* HCLK = SYSCLK */? ? RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;? ?? ?? ? /* PCLK = HCLK */? ? RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;? ? /* PLL configuration = HSE * 6 = 48 MHz */? ? RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));? ? RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL7);? ?? ?? ?? ?? ? /* 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)RCC_CFGR_SWS_PLL)? ? {? ? }??}??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 */? ?? ?? ?? ?? ?? ? // HSI 內(nèi)部時鐘做為PLL時鐘源并配置PLL 56M做為系統(tǒng)時鐘? ? /* Enable Prefetch Buffer and set Flash Latency */? ? FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;? ? /* HCLK = SYSCLK */? ? RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;? ? /* PCLK = HCLK */? ? RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;? ? // PLL configuration = (HSI/2) * 12 = 48 MHz? ? RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_14); // 8M/2 * 14 = 56M? ? /* 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_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // PLL 做系統(tǒng)時鐘? ? /* Wait till PLL is used as system clock source */? ? while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL)? ? {? ? }??}??}
在STM32F103中,使用內(nèi)部晶振,最大時鐘頻率也只能到64M,受倍頻因子的影響嘛,最大只能倍頻16倍。但在STM32F031中,標(biāo)準(zhǔn)使用內(nèi)部時鐘主頻只有48M,但是我們?nèi)匀豢梢岳^續(xù)倍頻,用內(nèi)部時鐘進行超頻達到64M。在我們的產(chǎn)品中就用過內(nèi)部超頻到56M,USART和SPI長時間無問題。
而GD32E230因為其高達32的倍頻因子,內(nèi)部時鐘可以倍頻到128M。

但是,這種幾分鐘內(nèi)沒有明顯發(fā)熱現(xiàn)象,不敢做長時間測試,現(xiàn)在MCU有點小貴。干費一個就心疼。
總之,無論ST還是國產(chǎn),其主頻更適合在規(guī)定的范圍內(nèi)運行,但是跑極限在短時間內(nèi)也沒有很大的問題。這些數(shù)據(jù)僅供參考。
至此單片機時鐘講解就結(jié)束了,沒有多少理論性的東西,主要是解決一些時鐘使用時的問題,自己也總是忘,留帖一篇作為自省。
本文中所有代碼都經(jīng)過本人測試,運行無任何問題,但是對于問題的闡述或者一些見解可能有錯誤,歡迎大佬們批評指正,一定接受各種批評,努力完善!
另外,如果有需要查看原圖和代碼的小伙伴,請點擊底部“閱讀原文”進行下載。