01 什么是时钟
单片机如果要正常运行,时钟信号是必不可少的。作为CPU的脉搏,时钟的快慢决定了CPU的运行速率,执行指令的速度。一般时钟源会被分频器或倍频器分成多种频率的时钟,以满足系统的不同应用。
那么时钟信号是怎么产生的呢?
## 晶振
在石英晶体上按一定方位切下薄片,将薄片两端抛光并涂上导电的银层,再从银层上接出两个电极并封装起来的元件叫石英晶体谐振器,简称石英晶体。采用石英晶体的振荡器称为晶体振荡器,简称晶振。
晶体本身不能产生振荡信号,必须借助于外部的振荡器电路。另外振荡器件除了是晶振外,还可以是RC振荡电路,或者LC振荡电路。
晶体振荡器分为无源晶振和有源晶振两种类型,无源晶振英文叫做为crystal(晶体),而有源晶振则叫做oscillator(振荡器)。
### 无源晶振
无源晶振是有2个引脚的无极性元件,需要结合外部时钟电路组成一个振荡器才能产生振荡信号。而MCU可以使用无源晶振的原因,是因为其内部有集成构成振荡器的电路。
> 特点:无源晶振信号质量和精度较差,要匹配外部电容和滤波电阻,输出的波形一般都为正弦波。
### 有源晶振
有源晶振有4只引脚,是一个完整的振荡器,内部除了石英晶体外,还有晶体管和阻容元件等。
> 特点:有源晶振不需要MCU的内部时钟电路,信号稳定,质量较好,输出波形一般为方波。
## 时钟产生方式
### 外部时钟
大部分MCU的内部都会带有振荡电路,用来和外部的无源晶振产生时钟。也可以由外部有源晶振直接在MCU时钟输入脚输入外部时钟。
> 如APM32的时钟源,可以选择BYPASS Clock Source模式(HSECLK旁路)以匹配外部有源晶振或其他直接时钟输入源,而选择Crystal/Ceramic Resonator模式(HSECLK晶体)可以匹配外部无源晶振。
### 内部时钟
APM32 MCU内部也自带RC振荡电路,其内部时钟就是HSI和LSI都是由RC振荡器产生的。
> RC振荡器精度远低于晶振,且容易受到温度的影响。
02 APM32的时钟系统
下面主要以F407系列为例来讲解。
## APM32的时钟源
APM32单片机的外设非常之多,但我们在实际使用单片机时,一般情况下都只会用到有限的几个外设。
另外,在使用任何外设前都需要先启动时钟,但并不是所有的外设都需要主系统时钟那么高的频率,如果外设都用高速时钟,也势必造成浪费,徒增功耗。
所以会需要把时钟源分成高速或低速等多种时钟源来分频或倍频给相应内核和外设使用。下面列举常见的几种。
### MSI
多速率内部振荡器,可由软件配置产生不同频率的时钟。
### HSIAPM32内部高速内部时钟,是一个RC振荡器。
> 下表只列出各系列典型型号的时钟频率,不同型号,可能有多或少的多种频率的HSI。
### HSE高速外部时钟,可接石英/陶瓷谐振器,或者接外部的时钟源,不同系列型号支持不同的输入频率范围。
### LSI
APM32内部低速内部时钟,是一个RC振荡器。
> 下表只列出各系列典型型号的时钟频率,不同型号,可能有多或少的多种频率的LSI。
### LSE
低速外部时钟,不同系列型号支持不同的输入频率范围。一般接入32.768KHz的时钟。
## PLL(Phase Locked Loop)锁相环
锁相环是一种反馈控制电路,是非常好的同步技术,可以利用外部输入的参考信号控制环路内部振荡信号的频率和相位。作用是使得电路上的时钟和某一外部时钟的相位同步。
锁相环一般由鉴相器(PD,Phase Detector)、环路滤波器(LF,Loop Filter)和压控振荡器(VCO,Voltage Controlled Oscillator)三部分组成,有些还会有分频器(N,Counter)。
> 原理
1.当输出信号的频率与输入信号的频率相等时,输出电压与输入电压保持固定的相位差值,即输出电压与输入电压的相位被锁住,这就是锁相环名称的由来。
2.PLL是通过比较外部信号的相位和由压控晶振(VCXO)的相位来实现同步的,而在比较的过程中,锁相环电路会不断根据外部信号的相位来调整本地晶振的时钟相位,直到两个信号的相位同步。
### APM32F4的PLL
不同系列的MCU的PLL个数不同,而F4xx系列有两个PLL,分别是PLL1和PLL2。其中:
* PLL1由 HSE 或 HSI 振荡器提供时钟信号,并具有两个不同的输出时钟。
1.输出高速系统时钟(Max:168MHz)。
2.输出USB OTG FS的时钟(48MHz)、随机数发生器的时钟 (≤48 MHz) 和 SDIO 时钟 (≤48 MHz)。
* PLL2专用于生成精确时钟,从而在I2S接口实现高品质的音频性能。
#### 基本结构外部时钟源经PLL源选择开关后作为PLLIN输入到PLL中,经分频和倍频后分别输出到后端系统时钟选择开关、USB OTG FS时钟及I2S时钟。
#### 分倍频系数
#### PLL时钟的计算
PLLCLK = PLLIN * PLLA / (PLLB * PLLC)
PLL(USB OTG FS) = PLLIN * PLLA / (PLLB * PLLD)
PLL(I2SCLK) = PLLIN * PLL2A / (PLLB * PLL2C)
例如我们想配置系统高速时钟为168MHz,那么在保证VCO的两个限制条件
输入频率介于1MHz和2MHz之间;
输出频率介于192MHz和432MHz之间。
之后的配置如下:
> 注意
1.分频和倍频系数是有取值范围的;
2.VCO的输入和输出频率也有推荐取值,以便限制PLL抖动。
## 选择开关
APM32时钟系统中有多种选择开关,作为时钟输入或输出的开关器件。F4xx系列中分别有:
RTC时钟源选择开关;
SYSCLK系统时钟源选择开关;
PLL时钟源选择开关;
I2SCLK时钟源选择开关;
MCO1时钟输出选择开关;
MCO2时钟输出选择开关。
## 时钟分频器
时钟分频器对时钟进行分频以满足应用需求,除了存在于上述PLL中之外,整个时钟系统中到处都可见其身影。有外设的APB1、APB2分频器,RTC时钟源分频器,MCO时钟输出分频器等等。
## 总线架构
在讲系统时钟前,先简单看下M3/M4的总线架构简图(忽略DMA总线)。可以看到架构图中从内核引出三根总线,分别是:
I-BUS(Instruction Bus),指令总线将内核指令与FLASH存储器连接,用于CPU内核进行取指令操作;
D-BUS(Data Bus),数据总线与闪存存储器的数据接口相连接,用于CPU内核进行数据加载和调试访问(常量或者变量);
S-BUS(System Bus),系统总线将内核系统总线和总线矩阵相连接,在上面挂着AHB和APB总线,用于CPU内核进行访问位于外设或SRAM中数据。
## APM32系统时钟
系统时钟可以由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内核提供时钟信号。
### AHB1/2
APB(Advanced PeripheralBus)高级外设总线连接各种外设并和AHB桥接,APB1/2的时钟叫做PCLK1/2。
## 时钟信号输出
APM32可以通过选择开关和分频器把时钟信号输出到MCO引脚上,供外部使用。F4xx支持MCO1(PA8)和MCO2(PC9)两路时钟信号输出。
## 时钟安全系统
时钟安全系统CSS可通过软件激活。当激活后,时钟监测器将在 HSE 振荡器启动延迟后使能,并在此路振荡器停止时被关闭。如果 HSE 时钟发生故障,此路振荡器将自动禁止,并且一个时钟故障事件将发送到高级控制定时器TIM1和TIM8 的断路输入端,同时还将生成一个中断来向用户通知此故障(时钟安全系统中断,CSSI),以使 MCU 能够执行对应操作。
> CSSI 与 Cortex™-M4F NMI(不可屏蔽 中断)异常向量相链接。
03 时钟配置方法
system_apm32f4xx.c是整个系统的时钟配置文件。包括:
SystemInit()函数初始化FPU、Vector table、ext memory及系统时钟。
SystemCoreClock变量经过配置之后 SYSCLK 的值。
SystemClockConfig()函数在SystemInit()时钟配置中SystemClockConfig()会把系统时钟配置为168MHz。
默认SystemInit函数会先设置系统时钟为HSI(16000000Hz),而SystemClockConfig函数将系统时钟重设为168MHz。
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 RCM clock configuration to the default reset state */
/** Set HSIEN bit */
RCM->CTRL_B.HSIEN = BIT_SET;
/** Reset CFG register */
RCM->CFG = 0x00000000;
/** Reset HSEEN, CSSEN and PLL1EN bits */
RCM->CTRL &= (uint32_t)0xFEF6FFFF;
/** Reset PLL1CFG register */
RCM->PLL1CFG = 0x24003010;
/** Reset HSEBCFG bit */
RCM->CTRL &= (uint32_t)0xFFFBFFFF;
/** Disable all interrupts */
RCM->INT = 0x00000000;
#if defined(DATA_IN_ExtSRAM)
SystemInit_ExtSRAM();
#endif /** DATA_IN_ExtSRAM */
SystemClockConfig();
/** 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 = FMC_BASE | VECT_TAB_OFFSET; /** Vector Table Relocation in Internal FLASH */
#endif
}
要注意几个宏定义,宏定义的值要根据所设计板卡的外部晶振及需求配置来定义。
#define HSE_VALUE ((uint32_t)8000000)
#define HSI_VALUE ((uint32_t)16000000)
#define PLL_A 336
#define PLL_B 8
#define PLL_C 2
#define PLL_D 7
SystemClockConfig函数将系统时钟重设为168MHz。
static void SystemClockConfig(void)
{
__IO uint32_t i;
RCM->CTRL_B.HSEEN = BIT_SET;
for (i = 0; i < HSE_STARTUP_TIMEOUT; i++)
{
if (RCM->CTRL_B.HSERDY**)
{
break;
}
}
if (RCM->CTRL_B.HSERDY**)
{
/** Select regulator voltage output Scale 1 mode */
RCM->APB1CLKEN_B.PMUEN = BIT_SET;
PMU->CTRL_B.VOSSEL = BIT_SET;
/** HCLK = SYSCLK / 1*/
RCM->CFG_B.AHBPSC = 0x0000;
/** PCLK2 = HCLK / 2*/
RCM->CFG_B.APB2PSC = 0x04;
/** PCLK1 = HCLK / 4*/
RCM->CFG_B.APB1PSC = 0x05;
/** Configure the main PLL */
RCM->PLL1CFG = PLL_B | (PLL_A << 6) | (((PLL_C >> 1) -1) << 16) |(PLL_D << 24);
RCM->PLL1CFG_B.PLL1CLKS = 0x01;
/** Enable the main PLL */
RCM->CTRL_B.PLL1EN = BIT_SET;
/** Wait till the main PLL is ready */
while (RCM->CTRL_B.PLL1RDY** == 0)
{
}
/** Configure Flash prefetch, Instruction cache, Data cache and wait state */
FMC->ACCTRL = 0x05 | BIT8 | BIT9 | BIT10;
/** Select the main PLL as system clock source */
RCM->CFG_B.SCLKSEL = RESET;
RCM->CFG_B.SCLKSEL = 0x02;
/** Wait till the main PLL is used as system clock source */
while ((RCM->CFG_B.SCLKSWSTS) != 0x02)
{
}
}
else
{
/** If HSE fails to start-up, the application will have wrong clock configuration. */
}
}
PLL值的配置大家可以根据PLL章节的介绍来配置PLL相关寄存器,这里不再赘述。