STM32系统时钟配置
以正点原子战舰开发板为例
1. 查看时钟树结构
通过数据手册可以看到时钟树结构如下图:
根据右下角的图例可以发现这个芯片支持四类时钟,分别是高低速的内外部时钟,我们要配置的就是位于中间的SYSCLK,写着最大72MHz的那个东西,通过对他来进行各种分频后就可以为右边的各种片上内设和片上外设提供时钟了,或许会有疑问,不分频会怎么样?所有的外设都用系统时钟,理论上来说肯定是可以的,但是实际来说也没人这样干,会造成很大的资源浪费,因为有些外设根本用不到这么高频率的时钟,其实这也是右边的外设会挂载到APB1和APB2两条总线上的原因之一,如果全部外设都以这么高的频率跑,说不定芯片的温度就可以煎蛋了,所以我们要让花成花,让树成树,它左边的梯形符号表明了SYSCLK的时钟可以有三个来源,分别是:
- 高速内部时钟;
- 锁相环时钟;
- 高速外部时钟;
战舰开发板用的是高速外部时钟,所以我们后面配置的时候就不能选择HSI,通过硬件原理图可以看到告诉高速外部时钟信号是8Mhz,而且官方推荐使用的最高稳定时钟频率是72MHz,所以直接选择外部高速时钟作为信号输入也不行,所以我们需要选择锁相环时钟信号来作为我们的系统时钟源。
顺便提一下图中的低速时钟,RTC是实时时钟信号,就是用来以现实生活的时分秒的方式计时的,RTC的特殊之处是断电情况下仍可以独立运行,只要芯片的备用电源一直供电,RTC上的时间会一直走,所以就是个钟。可以通过128分频的外部高速时钟或者低速内部时钟和低速外部时钟作为时钟源,外部低速时钟一般都是32.768KHz,原因是2的15次方等于32768,所以用32768分频就可以产生精准的1Hz信号,这就是32.768KHz的来源。
2. 外部时钟的启动过程
从我们朴素的观念来思考这个问题,芯片上电以后开始执行代码,在刚开始执行代码的时候它肯定是不知道外部时钟是否存在的,没有人告诉他要使用外部时钟,所以在一开始的代码执行过程中使用的必然是内部时钟,所以选择外部时钟作为时钟源的启动过程分为下面三步:
- 上电后先使用内部时钟让芯片跑起来;
- 通过软件配置来选择外部时钟作为时钟源;
- 配置成功,选择外部时钟作为时钟源,否则用内部时钟作为时钟源。
3. 初始化过程
在配置前先简单浏览一遍和RCC有关的寄存器有哪些,以及各寄存器大致都是什么意思,这里就简单说一下,具体的可以去看数据手册:
- RCC_CR,时钟控制寄存器,表示内外部高速时钟的状态和打开关闭情况;
- RCC_CFGR,时钟配置寄存器,选择系统时钟源以及各个分频系数和倍频系数等,这个寄存器也涉及时钟输出和ADC时钟频率设置,跟本文无关就不讨论了;
- RCC_CIR,时钟中断寄存器,主要是操作系统进行任务调度使用,这里直接关闭就行了;
- RCC_APB2RSTR,APB2 外设复位寄存器,复位APB2总线上的外设;
- RCC_APB1RSTR,APB1 外设复位寄存器,复位APB1总线上的外设;
- RCC_AHBENR,AHB外设时钟使能寄存器,配置APB1总线上的外设时钟;
- RCC_APB2ENR,APB2 外设复位寄存器,配置APB2总线上的外设时钟;
- RCC_APB1ENR,APB1 外设复位寄存器,配置APB1总线上的外设时钟;
- RCC_BDCR,备份域控制寄存器,对低速时钟的一些配置;
- RCC_CSR,控制状态寄存器,记录系统的复位类型和低速时钟配置。
可以看出来和系统时钟配置有关的寄存器主要就是1, 2, 3,其他的虽然说也有用,但是没那么重要了
3.1 复位所有外设
RCC->APB1RSTR = 0x00000000; // 复位总线APB1上的外设
RCC->APB2RSTR = 0x00000000; // 复位总线APB2上的外设
复位的目的是为了让系统处于一个确定的状态,不至于被之前的配置所影响。
3.2 关闭外设时钟
RCC->AHBENR = 0x00000014; // 睡眠模式时闪存和SRAM时钟使能.其他关闭
RCC->APB2ENR = 0x00000000; // 关闭APB1和APB2的外设时钟
RCC->APB1ENR = 0x00000000;
在睡眠模式下,CPU仍然有可能需要访问闪存中的程序指令,所以闪存和SRAM的时钟需要使能,比如有可能会遇到需要外部中断唤醒的情况,其他的就和复位作用差不多,防止干扰配置。
3.3 配置默认时钟
这一步就是第2部分提到的,在配置外部时钟之前需要先让芯片跑起来,配置的核心逻辑就是使能高速内部时钟,并将其设置为系统时钟,使得系统有一个基本的时钟来进行后续的配置,同时对与外部时钟有关的内容进行初始化。
RCC->CR |= 0x00000001; // 使能内部高速时钟HSION
RCC->CFGR &= 0xF8FF0000; // 复位SW[1:0], SWS[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], ADCPRE[1:0], MCO[2:0]
RCC->CR &= 0xFEF6FFFF; // 复位HSEON, CSSON, PLLON
RCC->CR &= 0xFFFBFFFF; // 复位HSEBYP
RCC->CFGR &= 0xFF80FFFF; // 复位PLLSRC, PLLXTPRE, PLLMUL[3:0] 和 USBPRE/OTGFSPRE
RCC->CIR = 0x009F0000; // 关闭所有RCC中断并清除中断标志
0xF8FF0000的意思是这行代码只复位SW[1:0], SWS[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], ADCPRE[1:0], MCO[2:0],所以这些内容对应的位都置0,其余位保持1不变,该是什么样还是什么样;
接下来的两行看起来可以放一起写,但是数据手册说了,只有在只有在外部振荡器关闭的情况下,才能写入HSEBYP位,所以必须分两行来写;
倒数第二行的目的是初始化这些配置,从而等我们正儿八经配置外部时钟作为系统时钟源的时候不会被这些配置干扰;
最后关中断也是为了防止时钟中断对时钟配置产生影响。
4. 配置外部源为系统时钟
后续补充
作者:weixin_42932596