【ARM】---关于STM32F407启动后的系统时钟频率问题
玩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预取等待时间,否则会有问题!
由于主要目的是为了测试,因此代码着实有点粗鄙不堪。不够严谨,还望各位能够见谅,这里也只是希望能够给各位分享一下,上电之后系统主频的变化。从最底层的角度来帮助分析,这样才更加清楚明了,当你底层都会了,那么无论再来什么的库都不是问题。相反,你只是停留在库的层面,这个库你会了,下次换个库,你又得花时间整!
个人观点,不喜勿喷,希望能给予大家帮助。