本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、什么是RCC时钟
RCC: reset clock control 复位和时钟控制器
二、关于RCC时钟相关代码
记得最开始没有配置固件库的时候,我们都需要写一个函数来避免编译器报错。
void SystemInit()
{
}
为什么呢,因为这个函数就是配置时钟相关的函数,我们就来看看这个函数相关的源代码
进去过后我们可以看到有一个设置系统时钟函数
在按F12我们就能看到时钟相关的函数了
可以看到这里的时钟有着不同的速度,代表不同的时钟。
三、时钟树讲解
在我们F103官方手册上我们可以看到一个时钟树
3.1 时钟
3.1.1 HSE时钟
HSE:High Speed External Clock sighal (高速的外部时钟)
控制:RCC_CR时钟控制寄存器的位16:HSEON
来源:无源晶振(4-16M),通常使用8M
对应框图:相关的寄存器
3.1.2 HSI时钟
HSE:High Speed Internal Clock sighal (高速的内部时钟)
控制:RCC_CR时钟控制寄存器的位0:HSEON
来源:芯片内部,大小位8M,当HSE故障的时候,系统时钟就会自动切换到HSI,直到HSE启动成功
对应框图:
相关的寄存器
3.1.3 RLLCLK时钟
RLLCLK:锁相环时钟
来源:HSI/2、HSE经过倍频所得
控制:GFGR:RLLXTPRE、RLLMUL
注意事项:PLL时钟源头使用HIS/2的时候,PLLMUL最大只能是16M,这个时候PLLCLK最大只能是64M,小于ST官方推荐的最大时钟72M
对应框图:
相关的详细原理可以去看火哥的视屏讲解。
3.1.4 SYSCLK时钟
SYSCLK:系统时钟
来源:HSI、HSE、RLLCLK
控制:GFGR:SW
注意事项:通常配置是SYSCLK = RLLCLK = 72M
对应框图:
3.1.5 HCLK时钟
HCLK: AHB高速总线时钟,速度最高为72M,为AHB总线的外设的提供时钟,
来源:系统时钟分频得到,一般设置HCLK = SYSCLK = 72M
控制:CFGR:HPRE
对应框图:
3.1.6 PCLK1时钟
PCLK1: APB1低速总线时钟,速度最高为36M,为AP1总线的外设的提供时钟,
来源:系统时钟分频得到,一般设置PCLK1 = HCLK/2 = 36M
控制:RCC_CFGR时钟配置寄存器的PPRE1位
对应框图:
3.1.7 PCLK2时钟
PCLK2: APB2高速总线时钟,速度最高为72M,为APB2总线的外设的提供时钟,
来源:系统时钟分频得到,一般设置PCLK2 = HCLK= 72M
控制:RCC_CFGR时钟配置寄存器的PPRE2位
对应框图:
3.1.7 RTC时钟
RTC: 为芯片内部的RTC外设提供时钟(独立看门狗时钟 real time)
来源:HSE_RTC、LSE、LSI
控制:RCC备份域控制寄存器RCC_BDCR;RECSEL位
对应框图:
3.1.8 MCO输出时钟
MCO: 微控制器时钟输出引脚,又PAB复用所得
来源:HSE、HSI、SYSCLK、PLCLK/2
控制:CRGR:MCO
对应框图:
到这里基本上所有时钟都说完了。可以去看看野火的视频,中级篇第一个视频。
四、使用HSE配置时钟系统来改变灯的闪烁
同样的,我们使用之前的固件库,复制一份,并在USER里面新建一个RCC文件夹,创建两个bsp_rccclkconfig.c 和 bsp_rccclkconfig.h文件,添加进来。
我们参考一下STM32官方的源代码
static void SetSysClockToHSE(void)
{__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
#if !defined STM32F10X_LD_VL && !defined STM32F10X_MD_VL && !defined STM32F10X_HD_VL
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 0 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
#ifndef STM32F10X_CL
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;
#else
if (HSE_VALUE <= 24000000)
{
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;
}
else
{
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_1;
}
#endif /* STM32F10X_CL */
#endif
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;
/* Select HSE as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_HSE;
/* Wait till HSE is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x04)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
我们自己配置bsp_rccclkconfig.c
这里面很多时钟相关的东西,所以我们需要打开stm32f10x_rcc.h这个库
我们可以直接跳到最后,这里有很多函数名,大概书写的这个函数的作用。
- 避免之前的时钟干扰,我们先将直线的时钟复位
//原型 void RCC_DeInit(void);
// 12 复位所有时钟
RCC_DeInit();
下面的就按照源代码的步骤进行调库处理 按照我源代码的顺序,我们第一步需要去使能HSE
- 使能HSE
//原型 void RCC_HSEConfig(uint32_t RCC_HSE);
// 1 使能HSE ,并等待HSE稳定。
RCC_HSEConfig(RCC_HSE_ON);
- // 2 等待HSE启动稳定,并做超时处理。判断启动成功和失败
//原型 ErrorStatus RCC_WaitForHSEStartUp(void);
ErrorStatus HSEStatus;
HSEStatus = RCC_WaitForHSEStartUp();
// 3 HSE 启动成功,则继续往下处理。
if(HSEStatus == SUCCESS)
{
//如果成功,则开始执行
}else
{
//如果失败则打印失败的消息
}
- 设定我们启动HSE成功 条件下。我们第一步就是使能FLASH预存缓冲
//原型 void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
- 设置SYSCLK 周期与闪存访问时间的比例,这里统一设置成 2,因为设置成 2 的时候,SYSCLK 低于 48M 也可以工作,如果设成 0 或者 1 的时候,如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了。
//原型 void FLASH_SetLatency(uint32_t FLASH_Latency);
FLASH_SetLatency(FLASH_Latency_2);
- 设置 AHB、APB2、APB1 预分频因子
//原型 void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
//原型 void RCC_PCLK1Config(uint32_t RCC_HCLK);
//原型 void RCC_PCLK2Config(uint32_t RCC_HCLK);
RCC_HCLKConfig(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
- 配置锁相环时钟
//原型 void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
/*设置锁相环时钟的来源和频率*/
- 使锁相环时钟使能
//原型 void RCC_PLLCmd(FunctionalState NewState);
RCC_PLLCmd(ENABLE);
- 等待系统稳定,并把锁相环时钟配置为系统时钟,判断读取状态
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
// 10 选择系统时钟来源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 11 读取时钟切换状态,确保PLLCLK被选为系统时钟
while(RCC_GetSYSCLKSource() != 0X08)
{
}
}
RCC_PLLMul_x 最高只能配置为16.
思路就是这样 ,,如果不知道函数返回值的意思,我们可以用F12进入到相关的函数解释里面,会有大概的解释我们看一下完整的代码 bsp_rccclkconfig.h
#ifndef _BSP_RCCCLKCONFIG_H
#define _BSP_RCCCLKCONFIG_H
#include "stm32f10x.h"
void HSE_SetSysClk(uint32_t RCC_PLLMul_x);
#endif /*_BSP_RCCCLKCONFIG_H*/
bsp_rccclkconfig.c
#include "bsp_rccclkconfig.h"
void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
{
//声明写在最前,不然会报错
ErrorStatus HSEStatus;
// 12 复位所有时钟
RCC_DeInit();
// 1 使能HSE ,并等待HSE稳定。
RCC_HSEConfig(RCC_HSE_ON);
// 2 等待HSE启动稳定,并做超时处理。
HSEStatus = RCC_WaitForHSEStartUp();
// 3 HSE 启动成功,则继续往下处理。
if(HSEStatus == SUCCESS)
{
// 4 使能 FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// 5 SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
FLASH_SetLatency(FLASH_Latency_2);
// 6 设置 AHB、APB2、APB1 预分频因子
RCC_HCLKConfig(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
// 7 配置PLLCLK
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
// 8 使能 PLL
RCC_PLLCmd(ENABLE);
// 9 等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
// 10 选择系统时钟来源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 11 读取时钟切换状态,确保PLLCLK被选为系统时钟
while(RCC_GetSYSCLKSource() != 0X08)
{
}
}
}else
{
//打印错误信息
}
}
我们还是采用之前的闪烁灯的代码 bsp_led.h
#ifndef _BSP_LED_H
#define _BSP_LED_H
#include "stm32f10x.h"
#define LED_GPIO_PORT GPIOB
#define LED_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED_GPIO_PIN GPIO_Pin_1
void LED_CONFIG(void);
#endif /*_BSP_LED_H*/
bsp_led.c
#include "bsp_led.h"
/*延时函数*/
void Delay(uint32_t count)
{
for(; count!= 0; count--);
}
void LED_CONFIG(void)
{
/*定义一个 GPIO_InitTypeDef 类型的结构体*/
GPIO_InitTypeDef LED_CONFIG;
/*打开APB2上 GPIOB的时钟*/
RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
/*配置GPIO口的输出*/
LED_CONFIG.GPIO_Mode = GPIO_Mode_Out_PP;
LED_CONFIG.GPIO_Pin = LED_GPIO_PIN;
LED_CONFIG.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_GPIO_PORT, &LED_CONFIG);
while(1)
{
/*配置低电平输入 亮灯*/
GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN);
Delay (0xFFFFF); //延时等待
/*配置高电平输入 灭灯*/
GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);
Delay (0xFFFFF); //延时等待
}
}
main.c
#include "stm32f10x.h" //默认是在当前文件夹
#include "bsp_led.h"
#include "bsp_rccclkconfig.h"
int main(void)
{
//通过修改返回值,来改变灯的闪烁频率
//默认配置72M,也就是 RCC_PLLMul_9 原来内部默认是8M 8*9 = 72M,我们这里可以改大改小
HSE_SetSysClk(RCC_PLLMul_16);
LED_CONFIG();
}
这样就可以看到我们配置不同的频率,我们灯在同一的延时下,延时的时长是不一样的。
五、使用 HSI 配置系统时钟
HSI 设置系统时钟函数跟 HSE 设置系统时钟函数在原理上是一样的,有一个区别的地方就是,HSI 必须 2 分频之后才能作为 PLL 的时钟来源,所以使用 HSI 时,最大的系统时钟 SYSCLK只能是 HSI/216=416=64MHZ。 函数调用举例:HSI_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:8 / 2 MHZ * 9 =36MHZ。 我们基本不需要修改什么,把HSE换成HSI就好了
bsp_rccclkconfig.h
#ifndef _BSP_RCCCLKCONFIG_H
#define _BSP_RCCCLKCONFIG_H
#include "stm32f10x.h"
void HSE_SetSysClk(uint32_t RCC_PLLMul_x);
void HSI_SetSysClk(uint32_t RCC_PLLMul_x);
#endif /*_BSP_RCCCLKCONFIG_H*/
bsp_rccclkconfig.c
void HSI_SetSysClk(uint32_t RCC_PLLMul_x)
{
//声明写在最前,不然会报错
ErrorStatus HSIStatus;
// 12 复位所有时钟
RCC_DeInit();
// 1 使能HSE ,并等待HSE稳定。
RCC_HSICmd(ENABLE);
// 2 等待HSE启动稳定,并做超时处理。
HSIStatus = RCC_WaitForHSEStartUp();
// 3 HSE 启动成功,则继续往下处理。
if(HSIStatus == SUCCESS)
{
// 4 使能 FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// 5 SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
FLASH_SetLatency(FLASH_Latency_2);
// 6 设置 AHB、APB2、APB1 预分频因子
RCC_HCLKConfig(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
// 7 配置PLLCLK
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
// 8 使能 PLL
RCC_PLLCmd(ENABLE);
// 9 等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
// 10 选择系统时钟来源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 11 读取时钟切换状态,确保PLLCLK被选为系统时钟
while(RCC_GetSYSCLKSource() != 0X08)
{
}
}
}else
{
//打印错误信息
}
}
这里就可以看到灯的闪烁频率明显要慢很多,因为HSI的频率只能是64M比较慢。不像HSE可以配置为128M。