我们通过观察不同时钟频率下LED灯闪烁的快慢来判断时钟配置情况。
一般情况下,我们会使用HSE经过PLL倍频之后配置系统时钟。而HSE,我们经常使用8MHz的无源晶振。我们就以HSE配置系统时钟为例,按照时钟树构造流程来完成代码。下图4-1是STM32的时钟树。

图中的数字顺序便是配置流程顺序,代码也是按照这个流程写的。我们简要分析下各个步骤的相关配置。
1、如图4-2,这一步取决于第二步的PLL时钟来源PLLSRC,由时钟配置寄存器RCC_CFGR的位17:PLLXTPRE设置,“清0”为HSE不分频,“置1”为HSE 2分频。我们这里选择HSE不分频,即“清0”。代码如第二步所示,宏RCC_PLLSource_HSE_Div1即为HSE不分频作为PLL输入时钟。

我们需要先使能HSE,等待HSE稳定,并且在此之前我们还需要将RCC寄存器复位,代码如下。
#define RCC_HSE_ON ((uint32_t)0x00010000)
ErrorStatus HSEStatus;
// 把RCC寄存器复位
RCC_DeInit();
// 使能HSE
RCC_HSEConfig(RCC_HSE_ON);
// 返回HSE状态,等待HSE稳定
HSEStatus = RCC_WaitForHSEStartUp();
2、这一步即为PLL时钟源配置了。由CFGR寄存器的位16:PLLSRC设置,“清0”为HSI经二分频后作为PLL输入时钟,“置1”为HSE作为PLL输入时钟,如图4-3。

我们这里选择HSE作为PLL的时钟来源,即“置1”。
#define RCC_PLLSource_HSE_Div1 ((uint32_t)0x00010000)
#define RCC_PLLMul_9 ((uint32_t)0x001C0000)
// 配置锁相环(PLLCLK = HSE * 9 = 72MHz),必须在使能PLL前配置
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
这个函数在stm32f10x_rcc.c文件,用于配置PLL时钟源和倍频因子。
3、设置PLL的倍频因子,对PLL时钟来源进行倍频,具体为多少倍频,由CFGR寄存器的位21-18:PLLMUL[3:0]配置。例如我们可以设置为9倍频,前面我们提到HSE为8MHz的无源晶振,所以倍频之后,PLL时钟PLLCLK = 8MHz * 9 = 72MHz,这是ST官方推荐的稳定时钟频率。我们也可以选择增大倍频因子进行超频,其最高为128MHz。如图4-4为倍频因子描述。代码如上步所示,配置为RCC_PLLMul_9。

图4-4
4、配置系统时钟SYSCLK。SYSCLK来源可以是HSI、PLLCLK、HSE,这里我们配置为SYSCLK = PLLCLK = 72MHz,由CFGR寄存器的位1-0:SW[1:0]配置。如图4-5。

#define RCC_SYSCLKSource_PLLCLK ((uint32_t)0x00000002)
// 选择PLLCLK作为系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
5、AHB总线时钟HCLK。HCLK由SYSCLK经过AHB预分频器分频得到,由CFGR寄存器的位7-4:HPRE[3:0]配置。如图4-1的时钟树所示,大部分的片上外设时钟都由HCLK分频得到,这里我们设置为不分频,即:HCLK = SYSCLK = 72MHz。如下图4-6。

#define RCC_SYSCLK_Div1 ((uint32_t)0x00000000)
// 配置AHB总线时钟HCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
6、APB2总线时钟PCLK2。PCLK2由高速APB2预分频器得到,由CFGR寄存器的位13-11:PPRE2[2:0]配置。APB2总线为高速总线,片上的高速外设,如GPIO、USART1、SPI1等,都挂载到APB2总线上。所以我们这里设置不分频,即:PCLK2 = HCLK = 72MHz。如下图4-7。

#define RCC_HCLK_Div1 ((uint32_t)0x00000000)
// 配置APB2总线时钟PCLK2
RCC_PCLK2Config(RCC_HCLK_Div1);
7、APB1总线时钟PCLK1。PCLK1由低速APB1预分频器得到,由CFGR寄存器的位10-8:PPRE1[2:0]配置。PCLK1为低速总线时钟,最高为36MHz,片上的低速外设,如USART2/3/4/5、SPI2/3、I2C1/2等,都挂载到APB1总线上。我们设置其为2分频,即:PCLK1 = HCLK/2 = 36MHz。如下图4-8。

#define RCC_HCLK_Div2 ((uint32_t)0x00000400)
// 配置APB1总线时钟PCLK1
RCC_PCLK1Config(RCC_HCLK_Div2);
经过以上的简要分析后,我们大概清楚时钟的配置流程了,现在整理整理以上的代码,将其封装为函数。
/**
* @brief 配置HSE作为系统时钟来源
* @param RCC_PLLMul_x:PLL倍频因子,
* x可以是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
* @retval 无
*/
void HSE_SetSysClk(uint32_t RCC_PLLMul_x){
ErrorStatus HSEStatus;
// 把RCC寄存器复位
RCC_DeInit();
// 使能HSE
RCC_HSEConfig(RCC_HSE_ON);
// 返回HSE状态,等待HSE稳定
HSEStatus = RCC_WaitForHSEStartUp();
if(HSEStatus == SUCCESS){
// 使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// 2个等待周期
FLASH_SetLatency(FLASH_Latency_2);
// 配置AHB总线时钟HCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// 配置APB2总线时钟PCLK2,PCLK2 = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
// 配置APB1总线时钟PCLK1,PCLK1 = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
// 配置锁相环时钟(PLLCLK = HSE * 9 = 72MHz),必须在使能PLL前配置
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 选择PLLCLK作为系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}else{
/* 如果HSE启动失败,则可以在这里添加处理错误的代码 */
}
}
以上就算封装好函数了,可以在main函数中调用,以重新配置系统时钟。
int main(void){
// 倍频因子设置为9,则系统时钟配置为8MHZ * 9 = 72MHz
HSE_SetSysClk(RCC_PLLMul_9);
// 初始化LED灯
LED_GPIO_Config();
while(1){
GPIO_SetBits(GPIOB, GPIO_Pin_0); // 高电平关闭LED
Delay(0xFFFFF);
GPIO_ResetBits(GPIOB, GPIO_Pin_0); // 低电平开启LED
Delay(0xFFFFF);
}
}
我们可以通过改变倍频因子大小,来输出不同的时钟频率,如修改为RCC_PLLMul_16,则时钟频率输出为8MHZ * 16 = 128MHz。不同的时钟频率输出,灯闪烁的速度也不同,倍频因子越大,频率越高,灯闪烁越快;倍频因子越小,则实验效果反之。
另外,图4-1时钟树中的左下角有一个MCO输出,可以对外输出时钟频率,我们可以利用示波器监控MCO引脚的输出频率来检测系统时钟是否配置正确。