STM32系统时钟及配置方法
从时钟、晶振到STM32时钟系统各个模块的详解及其配置方法。
什么是时钟
单片机如果要正常运行,时钟信号是必不可少的。作为CPU的脉搏,时钟的快慢决定了CPU的运行速率,执行指令的速度。一般时钟源会被分频器或倍频器分成多种频率的时钟,以满足系统的不同应用。
那么时钟信号是怎么产生的呢?
晶振
在石英晶体上按一定方位切下薄片,将薄片两端抛光并涂上导电的银层,再从银层上接出两个电极并封装起来的元件叫石英晶体谐振器,简称石英晶体。采用石英晶体的振荡器称为晶体振荡器,简称晶振。
晶体本身不能产生振荡信号,必须借助于外部的振荡器电路。另外振荡器件除了是晶振外,还可以是RC振荡电路,或者LC振荡电路。
晶体振荡器分为无源晶振和有源晶振两种类型,无源晶振英文叫做为crystal(晶体),而有源晶振则叫做oscillator(振荡器)。
无源晶振
无源晶振是有2个引脚的无极性元件,需要结合外部时钟电路组成一个振荡器才能产生振荡信号。而MCU可以使用无源晶振的原因,是因为其内部有集成构成振荡器的电路。
特点:无源晶振信号质量和精度较差,要匹配外部电容和滤波电阻,输出的波形一般都为正弦波。
可以将无源晶振外壳用焊锡接到晶振附近的回流地,以减少对外界的影响。
有源晶振
有源晶振有4只引脚,是一个完整的振荡器,内部除了石英晶体外,还有晶体管和阻容元件等。
特点:有源晶振不需要MCU的内部时钟电路,信号稳定,质量较好,输出波形一般为方波。
时钟产生方式
外部时钟
大部分MCU的内部都会带有振荡电路,用来和外部的无源晶振产生时钟。也可以由外部有源晶振直接在MCU时钟输入脚输入外部时钟。
如STM32的时钟源,可以选择BYPASS Clock Source模式以匹配外部有源晶振或其他直接时钟输入源,而选择Crystal/Ceramic Resonator模式可以匹配外部无源晶振。
内部时钟
STM32 MCU内部自带RC振荡电路,其内部时钟就是RC振荡器产生的。
RC振荡器精度远低于晶振,且容易受到温度的影响。
02 STM32的时钟系统
下面主要以F407系列为例来讲解。
STM32的时钟源
STM32单片机的外设非常之多但我们在实际使用单片机时,一般情况下都只会用到有限的几个外设。
另外,在使用任何外设前都需要先启动时钟,但并不是所有的外设都需要主系统时钟那么高的频率,如果外设都用高速时钟,也势必造成浪费,徒增功耗。
所以会需要把时钟源分成高速或低速等多种时钟源来分频或倍频给相应内核和外设使用。下面列举常见的几种。
MSI
多速率内部振荡器,可由软件配置产生不同频率的时钟。
序号 | 系列 | 典型频率(MHz) |
---|---|---|
1 | L0 | 2.097 |
HSI
STM32内部高速内部时钟,是一个RC振荡器。
下表只列出各系列典型型号的时钟频率,不同型号,可能有多或少的多种频率的HSI。
序号 | 系列 | 典型频率(MHz) |
---|---|---|
1 | L0 | 16 |
2 | F0 | 8(HSI)/14(HSI14)/48(HSI48 低端的F03x系列没有) |
3 | F1 | 8 |
4 | F4 | 16 |
5 | H7 | 64 |
HSE
高速外部时钟,可接石英/陶瓷谐振器,或者接外部的时钟源,不同系列型号支持不同的输入频率范围。
LSI
STM32内部低速内部时钟,是一个RC振荡器。
下表只列出各系列典型型号的时钟频率,不同型号,可能有多或少的多种频率的LSI。
序号 | 系列 | 典型频率(KHz) |
---|---|---|
1 | L0 | 37 |
2 | F0 | 40 |
3 | F1 | 40 |
4 | F4 | 32 |
5 | H7 | 32 |
LSE
低速外部时钟,不同系列型号支持不同的输入频率范围。一般接入32.768KHz的时钟。
CSI
STM32内部低功耗内部振荡器。
序号 | 系列 | 典型频率(MHz) |
---|---|---|
1 | H7 | 4 |
PLL(Phase Locked Loop)锁相环
锁相环是一种反馈控制电路,是非常好的同步技术,可以利用外部输入的参考信号控制环路内部振荡信号的频率和相位。作用是使得电路上的时钟和某一外部时钟的相位同步。
锁相环一般由鉴相器(PD,Phase Detector)、环路滤波器(LF,Loop Filter)和压控振荡器(VCO,Voltage Controlled Oscillator)三部分组成,有些还会有分频器(N,Counter)。
原理
1.当输出信号的频率与输入信号的频率相等时,输出电压与输入电压保持固定的相位差值,即输出电压与输入电压的相位被锁住,这就是锁相环名称的由来。
2.PLL是通过比较外部信号的相位和由压控晶振(VCXO)的相位来实现同步的,而在比较的过程中,锁相环电路会不断根据外部信号的相位来调整本地晶振的时钟相位,直到两个信号的相位同步。
STM32的PLL
不同系列的MCU的PLL个数不同,而F4xx系列有两个PLL,分别是Main PLL和PLLI2S。其中:
- Main PLL由 HSE 或 HSI 振荡器提供时钟信号,并具有两个不同的输出时钟。
- 输出高速系统时钟(Max:168MHz)。
- 输出USB OTG FS的时钟(48MHz)、随机数发生器的时钟 (≤48 MHz) 和 SDIO 时钟 (≤48 MHz)。
- PLLI2S专用于生成精确时钟,从而在I2S接口实现高品质的音频性能。
基本结构
外部时钟源经PLL源选择开关后作为PLLIN输入到PLL中,经分频和倍频后分别输出到后端系统时钟选择开关、USB OTG FS时钟及I2S时钟。
分倍频系数
序号 | 系数名 | 描述 |
---|---|---|
1 | PLLQ | 主 PLL(Main PLL) 分频系数,适用于 USB OTG FS、SDIO 和随机数发生器时钟。 |
2 | PLLP | 主 PLL(Main PLL) 分频系数,适用于主系统时钟。 |
3 | PLLN | 主 PLL(Main PLL) 倍频系数。 |
4 | PLLM | 适用于主 PLL(Main PLL)和音频 PLL (PLLI2S) 输入时钟的分频系数。 |
5 | PLLI2SR | I2S 时钟的 PLLI2S 分频系数。 |
PLL时钟的计算
- PLLCLK = PLLIN * PLLN / (PLLM * PLLP)
- PLL(USB OTG FS) = PLLIN * PLLN / (PLLM * PLLQ)
- PLL(I2SCLK) = PLLIN * PLLN / (PLLM * PLLSR)
例如我们想配置系统高速时钟为168MHz,那么在保证VCO的两个限制条件
- 输入频率介于1MHz和2MHz之间;
- 输出频率介于192MHz和432MHz之间。
之后的配置如下:
注意
1.分频和倍频系数是有取值范围的;
2.VCO的输入和输出频率也有推荐取值,以便限制PLL抖动。
选择开关
STM32时钟系统中有多种选择开关,作为时钟输入或输出的开关器件。F4xx系列中分别有:
- RTC时钟源选择开关;
- SYSCLK系统时钟源选择开关;
- PLL时钟源选择开关;
- I2SCLK时钟源选择开关;
- MCO1时钟输出选择开关;
- MCO2时钟输出选择开关。
时钟分频器
时钟分频器对时钟进行分频以满足应用需求,除了存在于上述PLL中之外,整个时钟系统中到处都可见其身影。有外设的APB1、APB2分频器,RTC时钟源分频器,MCO时钟输出分频器等等。
总线架构
在讲系统时钟前,先简单看下M3/M4的总线架构简图(忽略DMA总线)。可以看到架构图中从内核引出三根总线,分别是:
- I-CODE,指令总线将内核指令与FLASH存储器连接,用于CPU内核进行取指令操作;
- D-CODE,数据总线与闪存存储器的数据接口相连接,用于CPU内核进行数据加载和调试访问(常量或者变量);
- System,系统总线将内核系统总线和总线矩阵相连接,在上面挂着AHB和APB总线,用于CPU内核进行访问位于外设或SRAM中数据。
系统时钟
系统时钟可以由HSI、HSE或PLLCLK来提供。另外,除了以下时钟外,所有外设时钟都是由系统时钟(SYSCLK)提供的:
- PLL 输出 (PLL48CLK) 的 USB OTG FS 时钟 (48 MHz)、基于模拟技术的随
机数发生器 (RNG) 时钟 (≤48 MHz) 和 SDIO 时钟 (≤48 MHz)。 - I2S 时钟
- 由外部 PHY 提供的 USB OTG HS (60 MHz) 时钟
- 由外部 PHY 提供的以太网 MAC 时钟
AHB
AHB(Advanced High performanceBus)高级高性能总线连接APB与总线矩阵,桥接APB1和APB2,主要用于高性能模块或外设的应用,AHB的时钟叫做HCLK。
FCLK
FCLK(Free Running Clock)自由运行时钟,为CPU内核提供时钟信号。
APB1/2
APB(Advanced PeripheralBus)高级外设总线连接各种外设并和AHB桥接,APB1/2的时钟叫做PCLK1/2。
时钟信号输出
STM32可以通过选择开关和分频器把时钟信号输出到MCO引脚上,供外部使用。F4xx支持MCO1(PA8)和MCO2(PC9)两路时钟信号输出。
引脚 | 时钟信号 | |||
---|---|---|---|---|
MCO1 | LSE | HSE | HSI | PLLCLK |
MCO2 | SYSCLK | PLLI2SCLK | HSE | PLLCLK |
时钟安全系统
时钟安全系统CSS可通过软件激活。当激活后,时钟监测器将在 HSE 振荡器启动延迟后使能,并在此路振荡器停止时被关闭。
如果 HSE 时钟发生故障,此路振荡器将自动禁止,并且一个时钟故障事件将发送到高级控制定时器TIM1和TIM8 的断路输入端,同时还将生成一个中断来向用户通知此故障(时钟安全系统中断,CSSI),以使 MCU 能够执行对应操作。
CSSI 与 Cortex™-M4F NMI(不可屏蔽 中断)异常向量相链接。
03 时钟配置方法
system_stm32f4xx.c是整个系统的时钟配置文件。包括:
- SystemInit()函数
初始化FPU、Vector table、ext memory及系统时钟。 - SystemCoreClock变量
经过配置之后 SYSCLK 的值。 - SystemCoreClockUpdate()函数
在时钟配置完成之后,需要调用这个函数来更新 SystemCoreClock 变量。
默认SystemInit函数会设置系统时钟为HSI(16000000Hz)。
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
要注意两个宏定义,宏定义的HSE_VALUE值要根据所设计板卡的外部晶振来定义。
#define HSE_VALUE ((uint32_t)25000000)
#define HSI_VALUE ((uint32_t)16000000)
SystemCoreClockUpdate()函数根据RCC相关寄存器值来更新SystemCoreClock 。
void SystemCoreClockUpdate(void)
{
uint32_t tmp = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2;
/* Get SYSCLK source -------------------------------------------------------*/
tmp = RCC->CFGR & RCC_CFGR_SWS;
switch (tmp)
{
case 0x00: /* HSI used as system clock source */
SystemCoreClock = HSI_VALUE;
break;
case 0x04: /* HSE used as system clock source */
SystemCoreClock = HSE_VALUE;
break;
case 0x08: /* PLL used as system clock source */
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N
SYSCLK = PLL_VCO / PLL_P
*/
pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22;
pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM;
if (pllsource != 0)
{
/* HSE used as PLL clock source */
pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
}
else
{
/* HSI used as PLL clock source */
pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
}
pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2;
SystemCoreClock = pllvco/pllp;
break;
default:
SystemCoreClock = HSI_VALUE;
break;
}
/* Compute HCLK frequency --------------------------------------------------*/
/* Get HCLK prescaler */
tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];
/* HCLK frequency */
SystemCoreClock >>= tmp;
}
另外可以使用PLL配置来将SYSCLK设置为168MHz高速时钟,大家参考上述PLL章节来配置PLL相关寄存器,这里不再赘述。
参考文献
-
[Cortex-M4权威指南]
-
[AN4488]
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)