时钟
目录
一、时钟树
1.1.时钟和时钟树的概念
**时钟:**高低变化的方波信号
类似于人的心跳,片上外设的运行需要输入时钟信号
时钟信号的频率决定了片上外设的工作速度
时钟树:

1.2.分频器、锁相环和复用器
**分频器:**对频率做除法

**锁相环:**对频率做乘法

**复用器:**对频率做选择

1.3.树根

树根位于时钟树最低下的一部分
单片机中有两颗时钟树(类似于人体有两套血液循环系统)
每颗时钟树有两个时钟源(类似于人体有四颗心脏)
大树的频率需求比较高,一般为几十兆赫兹,一般称为高速树,高速树的时钟源称为高速时钟源
小树的频率需求比较低,一般为几十千赫兹,一般称为低速树,低速树的时钟源称为低速时钟源
|---|--------------------------|--------------------------|
| | 内 | 外 |
| 低 | LSI(Low Speed Internal) | LSE(Low Speed External) |
| 高 | HSI(High Speed Internal) | HSE(High Speed External) |
内部时钟源:
位于单片机内部
外部时钟源:
位于单片机外部
通过PC14和PC15引脚外接LSE晶振
通过PD0和PD1引脚外界HSE晶振

注:内部时钟源精度不高,所以需要外部时钟源
1.4.树干

1.4.1.SYSCLK来自HSI

精度低,SYSCLK = 8MHz
1.4.2.SYSCLK来自锁相环

灵活,SYSCLK可变
例如:8,12,16,20,36,72MHz
1.4.3.SYSCLK来自HSE

精度高,SYSCLK = HSE频率
1.5.树枝

**AHB(Advanced High Speed Bus):**高级高速总线
**APB1(Advanced Peripheral Bus 1):**高级外设总线1
**APB2(Advanced Peripheral Bus 2):**高级外设总线2

例1:
假设SYSCLK = 8MHz
要求HCLK = PCLK1 = PCLK2 = 8MHz
将三个分频器系数全都设置为1
例2:
假设SYSCLK = 72MHz
要求HCLK = 36MHz PCLK1 = 9MHz PCLK2 = 36MHz
AHB分频器系数为2
APB1分频器系数为4
APB2分频器系数为1
例3:
假设SYSCLK = Max
要求HCLK = PCLK1 = PCLK2 = Max
AHB分频器系数为1
APB1分频器系数为2
APB2分频器系数为1
二、时钟树的编程
2.1.时钟树的初始状态

SYSCLK的频率直接来自于HSI
AHB,APB1,APB2的分频系数都为1
Cortex-M3内核(CPU)的频率为8HMz
**示例:**使用for循环实现延迟500ms
CPU执行一条指令的时钟周期为:1 / 8*10^(6) = 0.125us
假设每个for循环消耗10个时钟周期:0.125 * 10 = 1.25us
延迟500ms需要的for循环次数为:0.5s / 1.25us = 400000
cpp
#include "stm32f10x.h"
int main(void)
{
/*启动GPIOC时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PC13引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
/*输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PA0引脚*/
GPIO_Init(GPIOC,&GPIO_InitStruct);
while(1)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
for(uint32_t i = 0;i < 400000;i++);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
for(uint32_t i = 0;i < 400000;i++);
}
}
2.2.标准库的启动代码
打开单片机的启动文件startup

找到reset代码

单片机复位后从ResetHandler开始执行

会将时钟树初始化为最高频率

可以用分号进行注释

cpp
#include "stm32f10x.h"
int main(void)
{
/*启动GPIOC时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PC13引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
/*输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PA0引脚*/
GPIO_Init(GPIOC,&GPIO_InitStruct);
while(1)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
for(uint32_t i = 0;i < 666666;i++);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
for(uint32_t i = 0;i < 666666;i++);
}
}
2.3.时钟树的编程接口
**RCC(Reset And Clock Controller):**复位和时钟控制器

cpp
void RCC_HSEConfig(uint32_t RCC_HSE);//HSE开关
void RCC_HSICmd(FounctionalState NewState);//HSI开关
void RCC_PLLConfig(uint32_t RCC_PLLSource,uint32_t RCC_PLLMul);//锁相环配置
void RCC_PLLCmd(FunctionalState NewState);//锁相环开关
void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);//SYSCLK配置
void RCC_HCLKConfig(uint32_T RCC_SYSCLK);//HCLK配置
void RCC_PCLK1Config(uint32_T RCC_HCLK);//PCLK1配置
void RCC_PCLK2Config(uint32_T RCC_HCLK);//PCLK2配置
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);//获取RCC状态
uint8_t RCC_GetSTSCLKSource(void);//获取SYSCLK来源
2.4.配置时钟树

- 开启HSE时钟
- 配置锁相环参数,启动锁相环
- 配置分频器的分频系数
- 选择SYSCLK的来源
2.4.1.启动HSE
cpp
//HSE开关
void RCC_HSEConfig(uint32_t RCC_HSE);
解析:
**参数:**开关HSE
- RCC_HSE_ON:开
- RCC_HSE_OFF:关
cpp
//获取RCC的状态
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
解析:
**参数:**RCC_FLAG_HSE
**返回值:**SET(就绪)RESET(未就绪)
cpp
//#1:开启HSE
RCC_HSEConfig(RCC_HSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
2.4.2.配置锁相环
cpp
//配置锁相环的参数
void RCC_PLLConfig(uint32_t RCC_PLLSource,uint32_t RCC_PLLMul);
解析:
**参数1:**选择锁相环输入来源
- RCC_PLLSource_HSE_Div1:HSE
- RCC_PLLSource_HSE_Div2:HSE / 2
- RCC_PLLSource_HSI_Div2:HSI / 2
**参数2:**选择锁相环分频系数
- RCC_PLLMul_2 ~ RCC_PLLMul_16
cpp
//控制锁相环开关
void RCC_PLLCmd(FunctionalStatus NewState);
解析:
**参数:**ENABLE(开)DISABLE(关)
cpp
//获取RCC的状态
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
解析:
**参数:**RCC_FLAG_PLLRDY
**返回值:**SET(就绪)RESET(未就绪)
cpp
//#2:配置并启动锁相环
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
2.4.3.配置分频器
cpp
//配置AHB分频器的分频系数
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
//配置APB1分频器的分频系数
void RCC_PCLK1Config(uint32_t RCC_HCLK);
//配置APB2分频器的分频系数
void RCC_PCLK2Config(uint32_t RCC_HCLK);
解析:
参数:
- RCC_SYSCLK_Div1 ~ RCC_SYSCLK_Div51
- RCC_HCLK_Div1 ~ RCC_HCLK_Div16
- RCC_HCLK_Div1 ~ RCC_HCLK_Div16
cpp
//#3:配置AHB APB1 APB2的分频系数
/*配置AHB分频器的分频系数*/
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/*配置APB1分频器的分频系数*/
RCC_PCLK1Config(RCC_HCLK_Div2);
/*配置APB2分频器的分频系数*/
RCC_PCLK2Config(RCC_HCLK_Div2);
2.4.4.配置SYSCLK来源
cpp
//设置SYSCLK的来源
void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
解析:
**参数:**SYSCLK来源
- RCC_SYSCLKSource_HSI:HSI
- RCC_SYSCLKSource_HSE:HSE
- RCC_SYSCLKSource_PLLCLK:锁相环
cpp
//获取SYSCLK的来源
uint8_t RCC_GetSYSCLKSource(void);
解析:
返回值:
- 0x00:HSI
- 0x04:HSE
- 0x08:锁相环
cpp
//#4:切换SYSCLK的来源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
2.5.配置Flash指令预取

单片机运行时,CPU从Flash中读取代码执行
但是从Flash模块读取代码的速度远底于CPU执行程序速度
所以要在CPU与Flash之间增加一个缓冲区
Flash将程序提前放在缓冲区中,加快速度(指令预取)

cpp
//开启Flash指令预取
FLASH_PrefetchBufferCmd(ENABLE);
**注:**需要在SYSCLK <= 8MHz时进行

cpp
//设置Flash访问延迟
FLASH_SetLatency(FLASH_Latency_2);
总代码:
cpp
#include "stm32f10x.h"
void App_SystemClock_Init(void);
int main(void)
{
App_SystemClock_Init();
/*启动GPIOC时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PC13引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
/*输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PA0引脚*/
GPIO_Init(GPIOC,&GPIO_InitStruct);
while(1)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
for(uint32_t i = 0;i < 666666;i++);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
for(uint32_t i = 0;i < 666666;i++);
}
}
void App_SystemClock_Init(void)
{
//开启Flash指令预取
FLASH_PrefetchBufferCmd(ENABLE);
//设置Flash访问延迟
FLASH_SetLatency(FLASH_Latency_2);
//#1:开启HSE
RCC_HSEConfig(RCC_HSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
//#2:配置并启动锁相环
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//#3:配置AHB APB1 APB2的分频系数
/*配置AHB分频器的分频系数*/
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/*配置APB1分频器的分频系数*/
RCC_PCLK1Config(RCC_HCLK_Div2);
/*配置APB2分频器的分频系数*/
RCC_PCLK2Config(RCC_HCLK_Div2);
//#4:切换SYSCLK的来源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}
2.6.片上外设开关和复位
cpp
//开关AHB总线上的片上外设的时钟
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph,FunctionalStatus NewState);
//开关APB2总线上的片上外设的时钟
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,FunctionalStatus NewState);
//开关APB1总线上的片上外设的时钟
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph,FunctionalStatus NewState);
//复位APB2总线上的片上外设
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph,FunctionalStatus NewState);
//复位APB1总线上的片上外设
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph,FunctionalStatus NewState);
**示例1:**开启GPIOA时钟
cpp
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
**示例2:**开启USART2时钟
cpp
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
**示例3:**开启DMA1时钟
cpp
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
**示例4:**复位I2C1时钟
cpp
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);