1、介绍
STM32F10x芯片的时钟控制主要包括以下几个方面知识:时钟源的选择(HSE、HIS、PLL)、系统时钟频率的配置、总线(AHB、APB2、APB1)时钟的配置、总线(AHB、APB2、APB1)设备时钟的使能/除能、总线(AHB、APB2、APB1)设备的复位。
2、系统时钟框图
STM32F10x可以用三种不同的时钟源来驱动系统时钟(SYSCLK):HSI振荡器时钟;HSE外部时钟和PLL时钟。他们之间的关系如附件所示(时钟树)。
从时钟树中可以看出一下几点:
l 系统时钟的来源可以是HSI振荡器时钟、PLL时钟、或者HSE时钟;且系统总线时钟最大值为72MHz,AHB和APB2总线最大频率也是72MHz,APB1总线最大允许频率是36MHz;而Cortex-M3核的自由运行时钟是FCLK(来源于AHB总线);
l stm32f10x芯片总共有四个时钟源:HSE、LSE(外部时钟信号);HIS、LSI(内部时钟信号),芯片内的其他所有时钟都是通过如上四个时钟源分频得来;
l RTC的时钟来源可以是HSE外部时钟128分频之后的时钟、或者LSE外部时钟(32.768kHz)或者内部LSI振荡器时钟;
l IWDG的时钟来源必须是内部LSI振荡器时钟;
l MCO引脚的时钟输出源的来源有:PLL时钟的2分频、内部HIS时钟、外部HSE时钟以及系统时钟。
l PLL时钟的来源可以是HIS振荡器时钟或者HSE外部提供的时钟;
l USB外设是直接使用PLL输出时钟(如果使用USB外设,HSE和PLL时钟都必须使能,且系统时钟必须是48MHz或者72MHz);AHB总线的时钟输入源的是系统时钟;APB1和APB2的时钟来源是AHB;
l 始终安全系统(CSS)必须由HSE提供时钟源;若CSS激活且HSE时钟出现故障,则引发CSS中断,同时产生NMI(NMI中断是不可屏蔽的),NMI将被不断执行,知道CSS中断挂起位被清除;
l 定时器时钟要么等于总线时钟,要么等于总线时钟频率的两倍,这取决于总线分频系数的值是否为1;
l 当HIS被用于作为PLL时钟输入时,系统时钟能得到的最大频率是64MHz;
l Cortex-M3内核的自由运行时间是FCLK。
3、时钟寄存器描述
l 时钟控制寄存器:RCC_CR
l 时钟配置寄存器:RCC_CFGR
l 时钟中断寄存器:RCC_CIR
l APB2外设复位寄存器:RCC_APB2RSTR
l APB1外设复位寄存器:RCC_APB1RSTR
l AHB外设时钟使能寄存器:RCC_AHBENR
l APB2外设时钟使能寄存器:RCC_APB2ENR
l APB1外设时钟使能寄存器:RCC_APB1ENR
l 备份域控制寄存器:RCC_BDCR
l 控制/状态寄存器:RCC_CSR
4、时钟控制主要按照以下五步进行控制
l 系统复位后,HSI振荡器被选为系统时钟;
l 调用RCC_DeInit()函数将外设RCC寄存器重置为缺省值;
l 选择系统时钟:
? 若选择HSE做系统时钟:先调用RCC_HSEConfig()使能HSE,然后调用RCC_WaitForHSEStartUp()函数等待HSE起震,最后调用RCC_GetFlagStatus()函数获取HSE晶振状态,查看HIE晶振是否就绪;;
? 若选择HSI做系统时钟:首先调用RCC_AdjustHSICalibrationValue()函数调整内部高速晶振校准值(也可以不用,使用系统预留值),然后调用RCC_HSICmd()函数使能HSI,最后调用RCC_GetFlagStatus()函数获取HSI晶振状态,查看HIS晶振是否就绪;
? 若要使用PLL做系统时钟,如前面两步将HSE和HIS设定好之后,调用RCC_PLLConig()选择PLL时钟源并设定倍频系数,最后调用RCC_PLLCmd()使能PLL,最后调用RCC_GetFlagStatus()函数获取PLL晶振状态,查看PLL是否就绪;。
l 最后,在以上时钟配置就绪之后,调用RCC_SYSCLKConfig()函数选择系统时钟输入源:HSE/HIS/PLL。
至此,系统时钟设定完成,可以调用RCC_GetSYSCLKSource()函数来获取当前系统时钟是使用的哪个时钟(检测设置是否成功):0x010:HIS;x040:HSE;x08:PLL。
l 然后是总线时钟设置:设置AHB总线时钟:调用RCC_HCLKConfig()函数;设置APB1总线时钟:调用RCC_PCLK1Config()函数;设置APB2总线时钟:调用RCC_PCLK2Config()函数。其中AHB总线时钟来源于SYSCLK总线时钟,APB1和APB2总线时钟来源于AHB总线时钟。注意:这三个时钟的设置可以在系统时钟、PLL、HSE、HIS启动之前设置,也可以在他们之后设置,但习惯在PLL配置之前。
l 最后是根据应用需要配置各总线上的外围设备,启动/停用外围设备的函数有:RCC_AHBPeriphClockCmd();RCC_APB2PeriphClockCmd();RCC_APB1PeriphClockCmd();复位总线上的设备函数:RCC_APB2PeriphResetCmd();RCC_APB1PeriphResetCmd();具体可以查看RCC固件库。
注意:使能外设时钟的函数必须在调用外设初始化函数XXX_Init()函数之前,否则可能会导致对应外设初始化失败,编译器却不会因此报错。
5、时钟控制例子
void SetSysClockToHSE(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(SUCCESS == HSEStartUpStatus)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_0);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div1);
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);
while(0x04 != RCC_GetSYSCLKSource())
{
}
}
else
{
}
}
void SetSysClockTo20(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(SUCCESS == HSEStartUpStatus)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_0);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div1);
RCC_PLLConfig(RCC_PLLSource_HSE_Div2,RCC_PLLMul_5);
RCC_PLLCmd(ENABLE);
while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY))
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(0x08 != RCC_GetSYSCLKSource())
{
}
}
else
{
}
}
void SetSysClockTo36(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(SUCCESS == HSEStartUpStatus)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_1);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div1);
RCC_PLLConfig(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY))
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(0x08 != RCC_GetSYSCLKSource())
{
}
}
else
{
}
}
void SetSysClockTo48(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(SUCCESS == HSEStartUpStatus)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_1);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_6);
RCC_PLLCmd(ENABLE);
while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY))
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(0x08 != RCC_GetSYSCLKSource())
{
}
}
else
{
}
}
void SetSysClockTo72(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(SUCCESS == HSEStartUpStatus)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RESET == RCC_GetFlagStatus(RCC_FLAG_PLLRDY))
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(0x08 != RCC_GetSYSCLKSource())
{
}
}
else
{
}
}
6、系统时钟安全系统CSS
在实际应用中,经常出现由于晶体振荡器在运行中失去作用,造成微处理器的时钟源丢失,从而出现死机的现象,导致系统出错。为避免发声这种严重错误,STM32F10X系列芯片提供了一个时钟安全系统CCS机制。如下图:
时钟安全系统被激活后,时钟监控器将实时监控外部高速振荡器;如果HSE时钟发生故障,外部振荡器自动被关闭,产生时钟安全中断,此中断被连接到Cortex-M3的NVIC中断;与此同时CSS将内部RC振荡器(HSI)切换为STM32的系统时钟源。
注意:一旦CSS被激活,当HSE时钟出现故障产生CSS中断,同时自动产生NMI,NMI将不断执行,直到CSS中断挂起位被清除。因此在NMI的处理程序中,必须通过设置时钟中断寄存器RCC_CIR中的CSSC位(软件置1清除)来清除CSS中断。(其实RCC的其他各时钟源的就绪中断标志,也都需要通过软件置1来清除,只是CSS是NMI中断(不可屏蔽中断),其他中断需要设置相应位允许中断,并要在NVIC中打开RCC的中断通道)
7、系统时钟安全系统CSS应用
启动时钟安全系统CCS:
RCC_ClockSecuritySystemCmd(ENABLE);
编写NMI中断处理函数:
void NMI_Handler(void)
{
if(RESET != RCC_GetITStatus(RCC_IT_CSS))
{ /* HSE、PLL已经被禁止,但PLL设置未变 */
……/* 客户添加相应的系统保护代码处理 */
/* 下面添加HSE恢复后的预设代码 */
RCC_HSEConfig(RCC_HSE_ON);
RCC_ITConfig(RCC_IT_HSERDY,ENABLE);
RCC_ITConfig(RCC_IT_PLLRDY,ENABLE);
RCC_ClearITPendingBit(RCC_IT_CSS);
/* 至此一旦HSE时钟恢复,将发生HSERDY中断,在RCC中断处理程序中,可以将系统时钟设置到以前的状态 */
}
}
编写RCC中断处理函数:
void RCC_IRQHandler(void)
{
if(RESET != RCC_GetITStatus(RCC_IT_HSERDY))
{
/* 添加相应处理 */
RCC_ClearITPendingBit(RCC_IT_HSERDY);
}
if(RESET != RCC_GetITStatus(RCC_IT_PLLRDY))
{
/* 添加相应处理 */
RCC_ClearITPendingBit(RCC_IT_PLLRDY);
}
}
8、输出芯片内部时钟
STM32F10x芯片支持将内部时钟通过PA.8输出,但是必须注意GPIO输出管脚最大响应频率为50MHz,如果超过这个频率,输出的波形将会失真。应用实例如下:
首先配置端口PA.8
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
然后调用函数RCC_MCOConfig(RCC_MCO)选择要输出的内部时钟:RCC_MCO可以是:
RCC_MCO_NoClock——无时钟输出
RCC_MCO_SYSCLK——输出系统时钟
RCC_MCO_HSI——输出内部高速8MHz的RC振荡器时钟
RCC_MCO_HSE——输出外部时钟信号
RCC_MCO_PLLCLK_Div2——输出PLL倍频后的二分频时钟