玩STM32的時間也比較久了,最早的一直玩的是STD標准庫的103系列,但是ST公司也是“與時俱進”,舍棄了當年的標准庫,轉而推廣HAL庫,反正無論怎么樣把,對於STM32的使用也僅僅停留在使用階段,底層涉入不神,我一直覺得真正的大牛們,都是趴在最底層不願意起來的那一群,唉……底層難啊。近來由於課設要求,重新撿起來F407的板子,繼續ST進階之路。長時間不玩,對於STM32陌生了好多,這次玩板子,看的更深了點,花了一下午,終於解決了一個問題:關於板子上電以后系統時鍾頻率的設置問題。
以前最先用103的STD庫做開發,后來搞過一段時間寄存器開發,一直都認為STM32的主流時鍾頻率比如72M,168M等是在啟動文件里,main()函數之前SystemInit函數中設置,后來才發現目前的代碼都沒有對他進行設置。本人喜好寄存器版的簡單粗暴,不需要添加過多的文件,直接建個工程,弄個main函數就開始跑程序了,那么問題就來了,這時候的系統時鍾頻率到底是多少呢,SystemInit沒有對他設置,那么到底是在哪里設置了他呢,不然程序怎么能跑起來?帶着這些問題,搞了一下午,也算是了解了!
OK,。下面,以我自己的STM32F407的板子為例進行測試說明,HSE晶振為8M,HSI為16M
下面是:啟動文件里面的原版代碼,粘來是方便大家看下,查找源碼可以發現,SystemInit的代碼的第一行是開啟HSI時鍾源,其他的基本都是復位操作,還有一些內存SRAM的設置。我們建立工程之后,直接在main中寫代碼,程序是可以運行的,那么這時候我的407板子的時鍾是多少M呢?
1 //啟動代碼中的函數 2 Reset_Handler PROC 3 EXPORT Reset_Handler [WEAK] 4 IMPORT SystemInit 5 IMPORT __main 6 7 LDR R0, =SystemInit 8 BLX R0 9 LDR R0, =__main 10 BX R0 11 ENDP
我分了幾個部分對這個進行測試,
一、確保程序可以運行,
1 int main(void) 2 { 3 u32 fre1; 4 5 fre1 = 2; 6 }
建立完工程,編譯無錯誤之后,寫入上面的代碼,編譯通過后,load到板子里,然后開啟debug,調試,通過watch窗口,查看到fre1變量的值會變成2,如下圖,說明程序可以運行,fre2,fre3是測試程序時添加的變量,這里不用在意。
二、OK,測試程序可以跑,那么接下來開始思考,這時系統的主頻是多少,我采用了,HAL庫提供的一個函數HAL_RCC_GetSysClockFreq(),通過它可以得到系統主頻的值。代碼也是非常簡單,
1 int main(void) 2 { 3 u32 fre1; 4 fre1 = 2; 5 fre1 = HAL_RCC_GetSysClockFreq(); 6 }
將上面的程序load到板子后,繼續開啟debug模式,查看系統主頻,結果如下,可以看到fre1最后的值是0x00F42400,通過計算器換算之后發現該值其實是16M,那也就是說此時的系統時鍾頻率是16M,這就讓我比較奇怪了,怎么會是16M!
為了查看為什么此時系統時鍾的值是16M,我查看了以下此時RCC中關於時鍾配置的寄存器的值,如下圖
先來說CFGR寄存器,因為要查看的是系統時鍾,那么就要搞清楚目前系統時鍾的來源是誰,看最低兩位的SW1和SW0,手冊上是這樣描述的!
由上圖可以看到,該兩位都是0,根據手冊,此時的系統時鍾來源是HSI,就沒有HSE和PLL啥事,查閱407板載資料,HSI的時鍾源是16MHZ,至此我明白了,原來此時的系統時鍾,是由系統時鍾HSI時鍾源提供的,那么到底是不是呢,為了驗證我的想法,我繼續查看了CR寄存器,此時,他的值如下,可以看到此時HSION是被置位的,也就是此時開啟了,HSI的時鍾,那應該就沒啥錯了!
不過還有一個疑問,就是到底是誰開啟了HSI時鍾呢,又是在哪段代碼里開啟了呢,這可以說是一個不算問題的問題,但是筆者還真的試圖去源碼當中找過,也真的是太不認真了,后來才發現這個CR寄存器的復位值,就直接將HSION位置1了。這句是直接從手冊中截圖來的,也就是每一次的復位,系統硬件都會把HSION位置1,表明此時開啟了HSI時鍾源!
而且剛剛還忽略了一個嚴重的問題,在SystemInit()函數中,第一條代碼,就是開啟HSI時鍾,我嘗試將該行代碼注釋掉,發現並沒有任何影響,看來確實是由硬件置位的。看到這里,問題基本被解決了一大半,此時系統的主時鍾基本是有寄存器初始復位值所決定,原來搞了這么久都竟然沒發現這個問題,唉,實在是太不應該,如果有像筆者一樣的菜鳥的話,也在此給各位提個醒。
三、問題基本被搞清楚了,那么我就想,既然現在是HSI時鍾源,那么晚我想將系統時鍾變為其他的頻率,可以嗎?答案是當然,我沒有直接去設置168M,而是用了簡單的三行代碼,將時鍾源選擇改為了HSE,大家看過時鍾樹圖的都應該清楚,系統時鍾的來源可以是HSE,也可以是PLL,而如果要設為高速時鍾的話,肯定就需要PLL,不過我的目的僅僅是測試下修改時鍾,因此也就不那么麻煩了,我添加了幾行代碼,如下:
1 int main(void) 2 { 3 u32 fre1; 4 fre1 = 2; 5 fre1 = HAL_RCC_GetSysClockFreq(); 6 7 /*選擇時鍾源為HSE*/ 8 RCC->CR |= 0X1<<16; //開啟HSE時鍾 9 while((RCC->CR & 0X1<<16) == 1); //等待HSERDY就緒 10 11 RCC->CFGR |= 0X1; //時鍾源選擇為HSE 12 13 fre1 = HAL_RCC_GetSysClockFreq(); 14 }
根據以上的代碼,大家應該能猜到fre1的值的變化情況把 ,第一次為2,第二次是16M,那么第三次…………,沒錯就應該是8M了,結果如下:0x007a1200,經過換算,沒錯確實是8M,一切成功。
四、設置時鍾主頻為168M
玩過407的人應該都知道,407推薦主頻為168M比較好,那么我們就在這里設置下他的系統主頻,168M是一個高速時鍾,那么我的時鍾源肯定要不能直接使用HSE了,而應該使用PLL(其實PLL的源是HSE),而系統主頻的源是PLL,這三者之間的關系大家要搞清楚。在這里我們先不做大的改動,直接將PLL時鍾源開啟,然后將系統時鍾的源選擇為PLL,測試一下時鍾頻率是多少,代碼如下:
1 int main(void) 2 { 3 u32 fre1 = 0; 4 5 /*選擇PLL的時鍾源是HSE*/ 6 RCC->PLLCFGR |= 0X1<<22; //必需在開啟PLL和HSE之前設置 7 8 /*開啟HSE*/ 9 RCC->CR |= 0X1<<16; 10 while((RCC->CR & 0X1<<16) == 1); 11 12 /*開啟PLL*/ 13 RCC->CR |= 0X1<<24; 14 while((RCC->CR & 0X1<<24) == 1);//等待PLLRDY准備就緒 15 16 /*選擇PLL為系統的時鍾源*/ 17 RCC->CFGR |= 0X2; 18 19 FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS; //設置FALSH預取等待時間 20 21 fre1 = HAL_RCC_GetSysClockFreq(); 22 }
使用PLL的源得到的系統時鍾經過測試為48M,結果如下圖:
明明沒有設置過RCC_PLLCFGR的值,但是竟然還是得到了48M的時鍾,原因是什么呢,跟前面一樣,同樣這里RCC_PLLCFGR的寄存器有個RESET值,有心的朋友可以對照着手冊查看一下,該寄存器的RESET值最終能夠得到pll_m = 16,pll_p = 2,pll_n = 192,計算之后同樣能夠的得到這個48M的時鍾頻率,那么如果你想要設置主頻時鍾為168M,那么在筆者的代碼基礎上修改一下PLL_CFGR寄存器的值,應該就可以解決所有的問題了,着重提醒一下,當你的時鍾設置為168M后,必須要設置FLASH預取等待時間,否則會有問題!
由於主要目的是為了測試,因此代碼着實有點粗鄙不堪。不夠嚴謹,還望各位能夠見諒,這里也只是希望能夠給各位分享一下,上電之后系統主頻的變化。從最底層的角度來幫助分析,這樣才更加清楚明了,當你底層都會了,那么無論再來什么的庫都不是問題。相反,你只是停留在庫的層面,這個庫你會了,下次換個庫,你又得花時間整!
個人觀點,不喜勿噴,希望能給予大家幫助。