STM32F10x芯片的时钟

1、介绍

            STM32F10x芯片的时钟控制主要包括以下几个方面知识:时钟源的选择(HSEHISPLL)、系统时钟频率的配置、总线(AHBAPB2APB1)时钟的配置、总线(AHBAPB2APB1)设备时钟的使能/除能、总线(AHBAPB2APB1)设备的复位

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总线时钟。注意:这三个时钟的设置可以在系统时钟、PLLHSEHIS启动之前设置,也可以在他们之后设置,但习惯在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))

       {             /* HSEPLL已经被禁止,但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——输出内部高速8MHzRC振荡器时钟

RCC_MCO_HSE——输出外部时钟信号

RCC_MCO_PLLCLK_Div2——输出PLL倍频后的二分频时钟

 

你可能感兴趣的:(stm32)