时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU
的脉搏,决定CPU
速率,像人的心跳一样 只有有了心跳,人才能做其他的事情,而单片机有了时钟,才能够运行执行指令,才能够做其他的处理 (点灯,串口,ADC
),时钟的重要性不言而喻。
一、STM32F103
时钟介绍
STM32F103
本身十分复杂,外设非常多 但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费 并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU
都是采用多时钟源的方法来解决这些问题。所以便有了STM32F103
的时钟系统和时钟树。
1.1 系统时钟
系统时钟(SYSCLK
)有多种选择,图中左边的部分就是设置系统时钟使用那个时钟源;
HSI
振荡器时钟:HSI
是高速内部时钟,RC
振荡器,频率为8MHz
,精度不高;HSE
振荡器时钟:HSE
是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
;PLL
时钟;其时钟输入源可选择为HSI/2
、HSE
或者HSE/2
。倍频可选择为2~16
倍,但是其输出频率最大不得超过72MHz
;
系统时钟的右边,则是系统时钟通过AHB
预分频器,给相对应的外设设置相对应的时钟频率.
从左到右可以简单理解为 各个时钟源--->系统时钟来源的设置--->各个外设时钟的设置。
在我们使用的STM32F103F103
开发板中:
OSC32_IN
、OSC32_OUT
连接了32.768kHz
的晶振,用于给RTC
提供时钟信号;OSC_IN
、OSC_OUT
连接了8MHz
的晶振,作为系统时钟的来源。
Keil
编写程序是默认的时钟为72Mhz
,其实是这么来的:
- 外部晶振(
HSE
)提供的8MHz
通过PLLXTPRE
分频器后; - 进入
PLLSRC
选择开关; - 进而通过
PLLMUL
锁相环进行倍频(x9
)后,为系统提供72MHz
的系统时钟(SYSCLK
); - 之后是
AHB
预分频器对时钟信号进行分频,然后为低速外设提供时钟。
1.2 USB
时钟
STM32F103
中有一个全速功能的USB
模块,其串行接口引擎需要一个频率为48MHz
的时钟源。该时钟源只能从PLL
输出端获取(唯一的),可以选择为1.5
分频或者1
分频,也就是,当需要使用USB
模块时,PLL
必须使能,并且时钟频率配置为48MHz
或72MHz
。
1.3 时钟输出到外部
STM32F103
可以选择一个时钟信号输出到MCO
脚(PA8
)上,可以选择为PLL
输出的2
分频、HSI
、HSE
、或者系统时钟。可以把时钟信号输出供外部使用。
1.4 外设时钟
系统时钟通过AHB
分频器给外设提供时钟,AHB
分频器可选择1
、2
、4
、8
、16
、64
、128
、256
、512
分频。其中AHB
分频器输出的时钟送给如下模块使用:
SDIO
;FSMC
;- 内核总线:送给
AHB
总线、核心存、储器和DMA
使用的HCLK
时钟。; Tick
定时器:通过8分频后送给Cortex
的系统定时器时钟;- 直接送给
Cortex
的空闲运行时钟FCLK
; APB1
外设:送给APB1
分频器。APB1
分频器可选择1
、2
、4
、8
、16
分频;- 其输出一路供
APB1
外设使用(PCLK1
,最大频率36MHz
); - 另一路送给通用定时器使用。该倍频器可选择1或者2倍频,时钟输出供定时器
2-7
使用;
- 其输出一路供
APB2
外设:送给APB2
分频器。APB2
分频器可选择1
、2
、4
、8
、16
分频:- 其输出一路供
APB2
外设使用(PCLK2
,最大频率72MHz
); - 另一路送给高级定时器。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用;
- 另外
APB2
分频器还有一路输出供ADC
分频器使用,分频后送给ADC
模块使用。ADC
分频器可选择为2
、4
、6
、8
分频;
- 其输出一路供
- 经过2分频送至
SDIO
的AHB
。
需要注意的是,如果APB
预分频器分频系数是1
,则定时器时钟频率 (TIMxCLK)
为PCLKx
。否则,定时器时钟频率将为APB
域的频率的两倍:TIMxCLK = 2xPCLKx
。
1.4.1 APB1
和APB2
的对应外设
APB1
上面连接的是低速外设,包括电源接口、备份接口、CAN
、USB
、I2C1
、I2C2
、USART2
、USART3
、UART4
、UART5
、SPI2
、SP3
等;
而APB2
上面连接的是高速外设,包括UART1
、SPI1
、Timer1
、ADC1
、ADC2
、ADC3
、所有的普通I/O
口(PA-Pg
)、第二功能I/O
(AFIO
)口等。
二、时钟相关寄存器
2.1 时钟控制寄存器(RCC_CR
)
2.2 时钟配置寄存器(RCC_CFGR
)
2.3 时钟中断寄存器(RCC_CIR
)
2.4 APB2
外设复位寄存器(RCC_APB2RSTR)
)
2.5 APB1
外设复位寄存器(RCC_APB1RSTR)
)
2.6 AHB
外设使能寄存器(RCC_AHBENR
)
2.7 APB2
外设时钟使能寄存器(RCC_APB2ENR
)
2.8 APB1
外设时钟使能寄存器(RCC_APB1ENR
)
三、时钟配置源码
3.1 RCC_TypeDeff
RCC
寄存器结构RCC_TypeDeff
,在文件stm32f10x_map.h
中定义如下:
/*------------------------ Reset and Clock Control ---------------------------*/
typedef struct
{
vu32 CR; // 时钟控制寄存器 ;
vu32 CFGR; // 时钟配置寄存器 ;
vu32 CIR; // 时钟中断寄存器 ;
vu32 APB2RSTR; // APB2外设复位寄存器 ;
vu32 APB1RSTR; // APB1外设复位寄存器 ;
vu32 AHBENR; // AHB外设时钟使能寄存器 ;
vu32 APB2ENR; // APB2外设时钟使能寄存器 ;
vu32 APB1ENR; // APB1外设时钟使能寄存器 ;
vu32 BDCR; // 备份域控制寄存器 ;
vu32 CSR; // 控制/状态寄存器 ;
} RCC_TypeDef;
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#ifdef _RCC
#define RCC ((RCC_TypeDef *) RCC_BASE)
#endif /*_RCC */
在第二节中我们已经对RCC_TypeDef
结构体中定义的大部分结构体进行了详细的介绍,那么我们如何编码去初始化这些寄存器呢?
3.2 RCC
初始化
这里我们采用HSE
作为系统时钟输出,正常使用的时候也都是使用外部时钟。其初始化流程如下:
(1) APB1
外设复位,复位结束;
RCC_APB1RSTR
寄存器每一位写入1,然后再写入0;
(2) APB2
外设复位,复位结束;
RCC_APB2RSTR
寄存器每一位写入1,然后再写入0;
(3)AHB
开启SRAM
、闪存(睡眠模式时);
(4)APB1
外设时钟关闭;
RCC_APB1ENR
寄存器每一位写入0;
(5)APB2
外设时钟关闭;
RCC_APB2ENR
寄存器每一位写入0;
(6)复位MCO
、USBPRE
、PLLMUL
、PLLXTRRE
、PLLSRC
、ADCPRE
、PPRE2
、PPRE1
、HPRE
、SW
;PLLON
、CSSON
、HSEBYP
、HSEON
;
RCC_CR
寄存器相应位写入0;RCC_CFGR
寄存相应位写入0;
(7) 设置:
HSEON
使能:开启高速外部时钟信号,即设置RCC_CR
寄存器的位16
为1;- 等待
HSERDY
就绪:即等待RCC_CR
寄存器位17
置1; - 设置
APB1
、APB2
、AHB
分频系数、PLL
倍频系数;- 系统时钟为
72MHz
:PLL
倍频系数设置为9
,即RCC_CFGR
寄存器位[21:18]
设置为0111b
; AHB
预分频器设置为1分频:即RCC_CFGR
寄存器位[7:4]
设置为0xxxb
;APB1
预分频器由RCC_CFGR
寄存器位[10:8]
设置;如果设置为2分频,则时钟频率为36MHz
;APB2
预分频器由RCC_CFGR
寄存器位[13:11]
设置;如果设置为1分频,则时钟频率为72MHz
;
- 系统时钟为
- 设置
PLLSRC
:选择HSE
时钟作为PLL
输入时钟;- 设置
HSE
时钟作为PLL
输入时钟,即RCC_CFGR
寄存器位16
设置为1;
- 设置
PLL
使能:即设置RCC_CR
寄存器的位24
为1;- 等待
PLL
就绪:即等待RCC_CR
寄存器位25
置1; - 设置
SW
:系统时钟切换PLL
作为系统时钟,即设置RCC_CFGR
寄存器的位[1:0]
为10b
; - 等待
PLL
切换为系统时钟输入源:即等待RCC_CFGR
寄存器的位[3:2]
为10b
。
3.2.1 STM32_Clock_Init
/*****************************************************************************************************
*
* Description:系统时钟初始化
PLL :倍频系数 2~16
* APB1设置为2分频,APB2设置为1分频,AHB设置为1分频
* 设置PLLCLK作为系统时钟
*
****************************************************************************************************/
void STM32_Clock_Init(u8 PLL)
{
u8 temp=0;
RCC_Init(); //复位并配置向量表
RCC->CR |=0x00010000; //外部高速时钟使能HSEON:即外部晶振(4MHZ~16MHZ)
while(!(((RCC->CR>>17)&0x01)==0x01)); //等待外部时钟就绪
RCC->CFGR = 0x00000400; //APB1设置为2分频,APB2设置为1分频,AHB设置为1分频
PLL-=2; //抵消两个单元
RCC->CFGR|=PLL<<18; //设置PLL的值2~16
RCC->CFGR|=1<<16; //PLLSRC ON HSE时钟作为PLL输入时钟
FLASH->ACR |= 0x32; //FLASH两个延时周期
RCC->CR|=0x01000000; //PLLON
while(!(((RCC->CR>>25)&0x01)==0x01)); //等待PLL锁定
RCC->CFGR|=0x02; //PLL作为系统时钟
while(temp!=0x02) //等待PLL作为系统时钟设置成功
{
temp = RCC->CFGR>>2;
temp&=0x03;
}
}
我们只需要在 main
函数开始调用该函数,传入参数9,即可设置系统时钟为72MHz
。
STM32_Clock_Init(9); //系统时钟初始化
3.2.2 RCC_Init
/*****************************************************************************************************
*
* Description:复位外设,并关断所有中断,同时配置中断向量表
* APB1RST:APB1外设复位寄存器 0:无效 1:复位外设
APB1RST:APB2外设复位寄存器 0:无效 1:复位外设
AHBENR :AHB 外设时钟使能寄存器
CR :时钟控制寄存器
CFGR :时钟配置寄存器
ICR :中断标志寄存器
*
****************************************************************************************************/
void RCC_Init(void)
{
RCC->APB1RSTR = 0x00; //APB1外设初始化复位结束
RCC->APB2RSTR = 0x00; //APB2外设初始化复位结束
RCC->AHBENR = 0x14; //睡眠模式时闪存接口电路时钟开启,睡眠模式时SRAM时钟开启,DMA时钟关闭
RCC->APB2ENR = 0x00; //APB2总线上外设时钟关闭
RCC->APB1ENR = 0x00; //APB1总线上外设时钟关闭
RCC->CFGR &= 0xF8000000; //复位SW[3:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0],PLLSRC,PLLXTPRE,PLLMUL[3:0],USBPRE
RCC->CR &= 0xFEF2FFFF; //复位HSEON,CSSON,PLLON,HSEBYP
RCC->CIR = 0x00000000; //关闭所有中断
/********** 配置中断向量表 ***********************/
#ifdef VECT_TAB_RAM
NVIC_SetVectorTable(0x20000000,0x00);
#else
NVIC_SetVectorTable(0x08000000,0x00);
#endif
}
3.2.3 NVIC_SetVectorTable
/*****************************************************************************************************
*
* Description:设置向量表偏移地址
NVIC_VectorTable:基址
Offset: 偏移量
****************************************************************************************************/
void NVIC_SetVectorTable(u32 NVIC_VectorTable,u32 Offset)
{
SCB->VTOR = NVIC_VectorTable|(Offset&(u32)0x1FFFFF80); //设置NVIC的向量表偏移寄存器,用于标识向量表是在CODE区,还是在RAM区
}
四、源码下载
源码下载路径:stm32f103
。
参考文章
[1] STM32F103
时钟系统讲解
[2] Mini2440
裸机开发之系统时钟配置