MCU时钟系统
文章结构如下
-
1 时钟树
-
2 时钟配置
-
3 自定义系统时钟
时钟树
把 MCU 比作人体,那么时钟就相当于人的心脏,其重要不言而喻,它为单片机工作提供一个稳定的机器周期,从而使系统能够正常运行。
以 STM32F10x 系列为例,时钟树如下图所示
在 STM32 时钟系统中,有 5 个重要的时钟源,分别是 LSI, LSE, HSI, HSE, PLL。
按照时钟频率可分为高速时钟源和低速时钟源,HSI, HSE, PLL 是高速时钟源,LSI, LSE 是低速时钟源。
按照时钟来源可分为外部时钟源和内部时钟源,HSE, LSE 是外部时钟源,HSI, LSI, PLL 是内部时钟源。
(1)图标1 HSI 是内部高速时钟,RC振荡器,频率为8MHz,可作为系统时钟或PLL锁相环的输入
(2)图标2 HSE 是外部高速时钟,芯片的 23 和 24 引脚即为外部高速晶振管脚。可以外接一个频率范围为 4-16MHz 的时钟或者晶振,HSE 可以作为系统时钟和 PLL 锁相环输入,还可以经过 128 分频后输入给 RTC
(3)图标3 LSI 是内部低速时钟,RC 振荡器,频率大约为 40K,可工独立看门狗和 RTC 使用,并且独立看门狗只能使用 LSI 时钟
(4)图标4 LSE 是外部低速时钟,通常此管脚上外接一个 32.768KHz 的晶振
(5)图标5 PLL 是锁相环,用于倍频输出,因为开发板外部高速晶振也只有 8M,而此款芯片的最大时钟频率是 72M,就是通过锁相环来倍频的。图中 PLL 时钟输入源可选择为 HSI/2、HSE 或者 HSE/2,时钟源经过 2-16 倍频后输入给 PLLCLK,系统时钟可选择由 PLLCLK 提供
既然有时钟源,那必然也会有需要下游设备,那他们是怎么给其他外设和系统提供时钟的呢?
(A)MCO 是 STM32 的一个时钟输出 IO(PA8),可以选择一个时钟信号输出,可以选择 PLL 输出的 2 分频、HSI、HSE 或者系统时钟,用来给外部其他系统提供时钟源
(B)RTC 时钟,RTC 的时钟来源可以是内部低速的 LSI 时钟,外部低速 LSE 时钟,还可以是 HSE 128 分频后得到
(C)USB 时钟,串口需要一个频率为 48MHz 的时钟源,改时钟源只能从 PLL 输出端获取,可以选择 1.5 分频或者 1 分频,当需要使用 USB模块时,必须使能 PLL,并且 PLLCLK 时钟频率配置为 48MHz 或 72MHz
(D)SYSCLK 系统时钟,是绝大部分不见工作的时钟源,可由 HSI、HSE、PLLCLK 提供
(E)其他所有外设,从图中可以看出,所有外设的最终时钟来源都是 SYSCLK,SYSCLK 通过 AHB 分频器分频后送给各模块使用
APB1 上面链接的是低速外设,APB2 上面连接的是高速外设,大多数有关时钟输出部分都有一个使能控制,比如 AHB 总线、APB1 外设、APB2 外设、内核时钟等。当需要使用某个时钟的时候需要开启它的使能,否则将不工作。
时钟配置
时钟配置初始化
STM32 系统复位后首先进入 SystemInit() 函数进行时钟设置,然后再进入主函数,在system_stm32f10x.c 文件中可以找到 SystemInit(),函数的功能如下
使能 HSI 时钟,选择 HSI 作为系统时钟,设置完相关寄存器后换成 HSE作为系统时钟,接着调用 SetSysClock() 函数,设置系统时钟频率。
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
}
在 system_stm32f10x.c 文件的开头就有对此宏定义,系统默认的宏定义是 72MHz,如下:
#define SYSCLK_FREQ_72MHz 72000000
修改此宏定义的值可以改变系统时钟频率。
SetSysClockTo72() 此函数的功能是将系统时钟 SYSCLK 设置为 72M,AHB 总线时钟设置为 72M,APB2 总线时钟设置为72M,APB1 总线时钟设置为 36M,PLL 时钟设置为 72M
时钟使能配置函数
要使用一个外设,必须先使能它的时钟,可以通过库函数进行使能,固件库把时钟相关的寄存器的使能配置都封装好,放在 xxx_rcc.c 和 xxx_rcc.h 中,可以将这些函数功能分为一下几类:
- 外设时钟使能函数
- 时钟源和和倍频因子配置函数
- 外设复位函数
- 获取时钟源配置函数
外设时钟使能函数
包含外设时钟使能和时钟源使能
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
上述 3 个函数分别使能 STM32 的 3 条总线,STM32 的外设都是挂接在 AHB 和 APB 总线上,所以使用外设时,需要使能对应外设所挂接的总线时钟,比如 GPIO 外设挂接在 APB2 总线上,如果需要使用 GPIO 外设,就需要先调用 RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
,具体哪个外设挂接在哪个总线上,需要查找对应芯片的手册
void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FuncitonalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);
如果我们要使能 PLL 时钟,那么久需要调用 RCC_PLLCmd
函数,函数只有一个形参,ENABLE 表示使能,DISABLE 表示失能
时钟源和和倍频因子配置函数
用于选择相应的时钟源和配置时钟倍频因子,比如系统时钟,可以由 HSE、HSI、或则 PLLCLK 作为它的时钟源,具体选择哪个,就是通过时钟配置函数实现。如果设置 HSE 作为系统时钟源,那么可以调用如下函数:
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); //配置时钟源为 HSE
APB1 的时钟频率是 HCLK 的 2 分频,可以调用如下函数:
RCC_PCLK1Config(RCC_HCLK_Div2); // 设置低速 APB1 时钟
外设复位函数
用于外设复位
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
自定义系统时钟
通过修改锁相环中的倍系数值(2-16)可以改变系统的时钟频率,库函数中也有对时钟倍频因子配置的函数,如下:
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);
第一个参数是 PLL 时钟源选择,通常采用 HSE 作为 PLL 的时钟源,可以设置为 RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2
,第二个参数就是倍频因子值(RCC_PLLMul_2~RCC_PLLMul_16)
为了方便修改系统时钟,自定义一个系统时钟初始化函数
void RCC_HSE_Config(u32 div, u32 pllm)
{
RCC_DeInit(); // 将外设 RCC 寄存器重设为缺省值
RCC_HSEConfig(RCC_HSE_ON); // 设置外部高速晶振(HSE)
if(RCC_WaitForHSEStartUp() == SUCCESS) // 等待 HSE 起振
{
RCC_HCLKConfig(RCC_SYSCLK_Div1); // 设置 AHB 时钟(HCLK)
RCC_PCLK1Config(RCC_HCLK_Div2); // 设置低速 APB1 时钟(PCLK1)
RCC_PCLK2Config(RCC_HCLK_Div1); // 设置高速 APB2 时钟(PCLK2)
RCC_PLLConfig(div, pllm); // 设置 PLL 时钟源及倍频系数
RCC_PLLCmd(ENABLE); // 使能或者使能 PLL
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 检查指定的 RCC 标志位设置与否,PLL 就绪
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 设置系统时钟(SYSCLK)
while(RCC_GetSYSCLKSource()!=0x08); // 返回用作系统时钟的时钟源,0x08:PLL 作为系统时钟
}
}
div,pllm 两个参数分别对应锁相环的时钟源和倍频系数,只需要修改这两个参数即可修改系统时钟,无需修改函数内部程序。在未修改系统时钟时,系统初始化后的时钟时 72M,对应着此函数参数设置如下
RCC_HSE_Config(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
如果需要设置系统时钟位 36M,可以使用如下参数
RCC_HSE_Config(RCC_PLLSource_HSE_Div2, RCC_PLLMul_9);
可以通过一个 LED 指示灯闪烁速度来反映系统时钟修改后的效果,主函数代码如下
int main()
{
RCC_HSE_Config(RCC_PLLSource_HSE_DIV2, RCC_PLLMul_9); // 36M
LED_Init();
while(1)
{
GPIO_ResetBits(LED_PORT, GPIO_PIN_0); // 点亮 D1
delay(6000000);
GPIO_SetBits(LED_PORT, GPIO_Pin_0);
delay(6000000);
}
}