时钟控制RCC应用与详细解析

时钟控制RCC

为什么需要时钟系统?

时钟系统就像一个中枢神经,驱动着芯片上所有的器件在同一时间按照特定的工作频率进行工作。我认为时钟系统有如下功能:

① 时钟系统相当于一个“频率适配器”。我们生活中常用的是电压适配器,全球每个国家的工频电压不太一样,我们如果到这些国家旅行不可能带适配于特定国家的电源充电器吧,这太繁琐了,要实现“一器多用”,就要有适配器的概念。在STM32中,有许多器件,这些器件在工作时需要的频率不一样,例如:GPIO工作在性能状态时其工作频率是72MHz,但是如果把那么高的频率用在ADC采样(工作的最大频率为14MHz)上就直接完犊子了,我们知道ADC是“模拟量->数字量”,过高的频率会导致采样失真,就相当于“一个温度计刚接触到人体就直接观看体温数据”,这肯定不准确而且可能采样进来的是“高频噪声”;

② 时钟系统相当于一个“功耗调节器”。我们知道STM32有“正常工作模式”,“休眠模式”,“待机模式”这三种工作状态,每个工作模式的功耗不同,比如,我们在待机模式下要求的是续航时间并不是性能,而我们在正常模式下则考虑的是“STM32的工作性能”,因此频率的不同会导致功耗的不同;

为什么时钟系统需要多个时钟震荡源?

① 多个时钟源可以共同分担工作压力,弹性工作。就如同STM32使用了4个电压源一样,如果这些电压源都合为一个,那么我们的芯片工作一会就会发热,如果在性能最佳的时候可能芯片直接发热烧坏了。如果使用单一时钟源,那么时钟源出力越多它的工作性能就越难保证;

② 多个时钟源可以提高利用效率,降低对单个时钟源可靠性的依赖程度。单一时钟的话可能会导致性能过剩并且功耗过高,多个时钟的话可以平衡功耗和性能之间的平衡。

系统时钟的组成

系统共有四个时钟源,分别是“HSI (High-Speed Internal Clock Signal)”,”LSI (Low-Speed Internal Clock Signal”,”HSE (High-Speed External Clock Signal)”, ”LSE (Low-Speed External Clock Signal”。

 

 

 

HSI-内部高速时钟源

HSI时钟源的工作原理

HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。HSI 的RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。

HIS作为内部高速时钟,并没有HSE外接震荡信号那么可靠,因为芯片内部发热,而且晶体、陶瓷做的时钟源会发生温漂(由于温度升高导致的脉冲周期不稳定)。但是没有HIS还不行,当系统启动时,需要开启一些设备,就例如我们要把HSE当作system时钟的来源,但是由于一开始未通电导致没有通道可以传递HSE的震荡信号,因此HIS在这里可以起到“黑启动”的作用,由于有HIS,系统开始工作,等到HSE稳定后系统会从HSI切换至HSE,但是当HSE出现故障时,系统时钟来源又会从HSE转至HIS上(CSS的作用)。HIS就相当于一个“保底的时钟”,HIS虽然不准确,但是除了它其他外部时钟源出现故障时还真没东西可用。

HIS时钟源的位操作

① 位操作的含义:

系统复位时,工厂校准值被装载到时钟控制寄存器的HSICAL[7:0]位:

如果用户的应用基于不同的电压或环境温度,这将会影响RC振荡器的精度。可以通过时钟控制寄存器里的HSITRIM[4:0]位来调整HSI频率,就相当于如果我们测量得知“在80℃时,HIS实际振荡频率为7.6MHz比8MHz少40MHz”,此时我们要把HSITPIM中的数值由默认值(0x0010=16)改为“17=0x0011”:

 

时钟控制寄存器中的HSIRDY位用来指示HSI RC振荡器是否稳定。在时钟启动过程中,直到这一位被硬件置’1’,HIS的RC输出时钟才被释放。HIS的RC可由时钟控制寄存器中的HSION位来启动和关闭:

 

如果HSE晶体振荡器失效,HSI时钟会被作为备用时钟源,详细请参考时钟安全系统(CSS):

 

HIS时钟源的库函数

① 调整内部HSI的校准值的函数

函数原型:void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue)

函数功能:补偿由于温漂或其他原因带来的频率偏差

函数参数:

void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue)
{
  uint32_t tmpreg = 0;
  /* Check the parameters */
  assert_param(IS_RCC_CALIBRATION_VALUE(HSICalibrationValue));
  tmpreg = RCC->CR;
  /* Clear HSITRIM[4:0] bits */
  tmpreg &= CR_HSITRIM_Mask;
  /* Set the HSITRIM[4:0] bits according to HSICalibrationValue value */
  tmpreg |= (uint32_t)HSICalibrationValue << 3;
  /* Store the new value */
  RCC->CR = tmpreg;  // 将校正值移入CR寄存器的HSITRIM位
}

 

函数的使用:

RCC_AdjustHSICalibrationValue(17); // 相较于默认值增加1%的频率修正量,即+40KHz

 

② 使能HIS告诉内部时钟的函数

函数原型:void RCC_HSICmd(FunctionalState NewState)

函数参数:ENABLE/DISABLE

函数功能:使能或失能内部高速晶振(HSI)

函数使用:

  1. RCC_HSICmd(ENABLE); // 使能HSI时钟源

HSE-外部高速时钟源

HSE时钟源工作原理

高速外部时钟信号(HSE)由以下两种时钟源产生:

① HSE外部晶体/陶瓷谐振器(HSE晶振):

 

此时,相当于接了个无源RC振荡电路,如果外部晶振要起振必须要有“电源”的存在,此时OSC_OUT充当了这个直流电源,OSC_IN则可以接收到震荡信号,这个信号处理流程相当于:

 

② HSE用户外部时钟(HSE旁路):

 

此时,OSC_INT外接的是“时钟源”,与前面“所说的外部晶振”一词明显不同,晶振指的是“未经过标准化的震荡信号”,而时钟信号则是“由标准时钟源发出的标准的合格的震荡信号”。既然是有源电路发出的信号,那么就无需OSC_OUT引脚了, 此时OSC_OUT引脚对外呈高阻态

这里我们尤其要关注“旁路”一词,所谓HSE旁路模式,是指无需上面提到的使用外部震荡信号所需的内部配套整形电路(芯片内部时钟驱动组件),此时可以直接从外界导入标准的时钟信号,犹如“芯片内部的驱动组件被旁路了”。

这个时钟源信号不是随便取的,要符合以下规定:外部时钟信号(50%占空比的方波、正弦波或三角波)必须连到SOC_IN引脚。

③ HSE一般被选择为“外接无源振荡电路”的模式,其整形为合格时钟频率后的震荡频率为8MHz,在stm32f10x.h中由于定义:

#if !defined  HSE_VALUE
 #ifdef STM32F10X_CL   
  #define HSE_VALUE    ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
 #else 
  #define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
 #endif /* STM32F10X_CL */
#endif /* HSE_VALUE */

 

我们选择的如果是正点原子的战舰开发板(STM32F103ZET6)的话,是大容量高密度的芯片,因此HSE的频率为8MHz。

HSE时钟源的位操作

① HSE相关位的含义:

在时钟控制寄存器(RCC_CR)中的HSERDY位用来指示高速外部振荡器是否稳定。在启动时,

直到这一位被硬件置’1’,时钟才被释放出来。如果在时钟中断寄存器(RCC_CIR)中允许产生中断,将会产生相应中断。

HSE晶体可以通过设置时钟控制寄存器(RCC_CR)中的HSEON位被启动和关闭。

 

注意:当HSE关闭后,6个周期之后HSERDY位才会被置零,就相当于刚开启HSE时,需要等待一会,等其稳定震荡后在引入下一个通道,当关闭HSE时,还需要等待6个周期,HSE才会被真正关闭(就是关于HSE相应的位全部被置零)。

 

 

 

注意:当置位RCC_CIR寄存器当中的HSERDYF位时,HSE准备就绪时可以产生中断,中断信号随即传进NVIC中断优先级配置器中,置位HSERDYC位时,中断就绪位HSERDYF被清零。

② 应用示例(使用固件库函数):

ErrorStatus HSEStartUpStatus;

RCC_HSEConfig(RCC_HSE_OFF);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
// 由于该函数中已经轮询判断1280次“HSE是否开启”,轮询时间必定超过了6个周期,因此此时HSE已完全关闭
if(HSEStartUpStatus == ERROR)  
{  
    
}  
else  
{  

}

 

HSE时钟源的库函数

① HSE时钟源配置函数

函数原型:void RCC_HSEConfig(uint32_t RCC_HSE)

函数功能:设置HSE来源

void RCC_HSEConfig(uint32_t RCC_HSE)
{
  /* Check the parameters */
  assert_param(IS_RCC_HSE(RCC_HSE));
  /* Reset HSEON and HSEBYP bits before configuring the HSE ------------------*/
  /* Reset HSEON bit */
  RCC->CR &= CR_HSEON_Reset;
  /* Reset HSEBYP bit */
  RCC->CR &= CR_HSEBYP_Reset;
  /* Configure HSE (RCC_HSE_OFF is already covered by the code section above) */
  switch(RCC_HSE)
  {
    case RCC_HSE_ON:
      /* Set HSEON bit */
      RCC->CR |= CR_HSEON_Set;  // 引脚开启&内部整形电路正常工作
      break;
      
    case RCC_HSE_Bypass:
      /* Set HSEBYP and HSEON bits */
      RCC->CR |= CR_HSEBYP_Set | CR_HSEON_Set;  // 引脚开启&内部整形电路不工作
      break;
      
    default:
      break;
  }
}

 

函数参数:

 

注意:RCC_HSE_ON对应的是HSE 外接“外部晶体/陶瓷谐振器”。

② 判断HSE时钟源工作状态的函数

函数原型:ErrorStatus RCC_WaitForHSEStartUp(void)

函数功能:检测标志位,含超时检测功能

do
  {
    HSEStatus = RCC_GetFlagStatus(RCC_FLAG_HSERDY);
    StartUpCounter++;  
  } while((StartUpCounter != HSE_STARTUP_TIMEOUT) && (HSEStatus == RESET));

 

注意:HSE_STARTUP_TIMEOUT是在stm32f10x.h中预定义的“HSE启动超时时间”:

#define HSE_STARTUP_TIMEOUT   ((uint16_t)0x0500) /*!< Time out for HSE start up */

 

相当于在do-while函数中不断轮询1280次,如果HSE时钟仍未稳定,则

函数参数:

 

使用举例:

ErrorStatus HSEStartUpStatus;
  
RCC_HSEConfig(RCC_HSE_ON);  // 使用无源外部晶振起振
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // 等待HSE稳定震荡,并且接受标志位
 
if(HSEStartUpStatus == SUCCESS)  
{  
}  
else  
{  
}

 

PLL锁相环

PLL工作原理

PLL锁相环是用来倍频HSI和HSE的,时钟源可以选择HIS/2,HSE或HSE/2,倍频可选择为2~16倍,但其输出的频率最大不超过72MHz。

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
#else
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000 // 系统时钟频率
#endif

 

这里预定义了系统最高的系统频率。

注意:

① 必须在使能PLL之前完成PLL的配置(选择时钟源、预分频系数和倍频系数等),同时应该在它们的输入时钟稳定(就绪位)后才能使能。一旦使能了PLL,PLL倍频的时钟源的参数将不能再被改变;

② 当改变主PLL的输入时钟源时,必须在选中了新的时钟源(通过时钟配置寄存器(RCC_CFGR)的PLLSRC位)之后才能关闭原来的时钟源。

PLL倍频器的位操作

当改变主PLL的输入时钟源时,必须在选中了新的时钟源(通过时钟配置寄存器(RCC_CFGR)的PLLSRC位)之后才能关闭原来的时钟源。如果使能了时钟中断寄存器(RCC_CIR),可以在PLL就绪时产生一个中断。

① 配置PLL的倍频系数:

 

 

 

注意:这里PLL的注意事项提及的“PLL一旦配置好就不可改变参数“!我们可以进行如下操作来改变PLL的倍频系数:

“开启HSI将其作为系统主时钟”->“等待HIS准备就绪后关闭PLL”->“改变PLL的参数”->“等待PLL准备就绪后关闭HSI”。

② 选择PLL的时钟源:

上面两个箭头代表着PLL倍频对象可以有两个“HSE分频之后的“和”HSI不分频“。我们常说”PLL的时钟来源可以是HSE/2或者HSE“,我们只需要配置如下位即可:

 

虽然HSE经过PREDIV1分频可以得到HSE/n(n=1,2,……,16)的时钟频率,但是这里我们在硬件上只是用了“不分频”和“2分频”两个分频模式。因此PLL倍频对象一共有三个“HSI”,”HSE/2”和“HSE”。

 

这一步我们可以使用多路复用选择器中的PLLSCR片选段来使能指定的时钟通道,把时钟信号引入PLL倍频器:

 

前面说了“PLL所连接的时钟源在工作时,其参数不可以被改变”,但是在这里我们可以不改变时钟源参数,直接把时钟源信号输入至PLL倍频器的通道失能了,不改变时钟源本身参数关闭通道总没事吧。但是关闭通道有前提:我们要关闭通道必须要有其他通道的时钟源准备就绪才可以。

如果我们意外关闭PLL的时钟源并且此时PLL提供给系统主时钟,那么此时系统将会发起自救运动(CSS):

 

使能CSS时钟安全系统:

 

在主系统时钟出现问题时CSS中断被触发:

 

 

 

以上两位就是我们固件库中常常提及的“中断标志位”和“清除中断位”。

③ 使能PLL倍频器

当我们配置完PLL属性后才可以使能PLL,有关使能PLL的位如下:

 

时钟信号传递的过程如下(以HSE为例):

⑴ HSE时钟ON,然后等待HSE高速外部时钟就绪位被置位

⑵ 使用PLLSRC位选择PLL锁相环的输入时钟源;

⑶ 配置PLLMUL倍频系数,然后配置PLL为ON状态;

等待PLL输出时钟就绪(PLLRDY位被置位),然后配置SW位使PLLCLK作为主系统时钟。

PLL倍频器的库函数

① PLL配置函数

函数原型:void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul)

函数参数:

 

 

 

 

 

注意:HSE默认为8MHz,因此我们只要x7就可以得到72MHz的主时钟频率。

② 使能/失能PLL函数

函数原型:void RCC_PLLCmd(FunctionalState NewState)

函数参数:ENABLE/DISABLE

注意:

 

如果PLL输出时钟被当作主时钟时失能,会触发CSS。

系统时钟配置

复位至正常工作时的时钟变化

 

系统时钟的位操作

 

 

 

相当于“只有SWS中被选中的系统时钟准备好时”,系统时钟切换才会成功进行。

一般主系统时钟切换有如下步骤(以PLLCLK(来源于HIS/2倍频产生)切换至HSE为例):

⑴ 使能HSE时钟,判断HSE准备就绪,同时保证PLLCLK正常运行;

⑵ 在判断HSE时钟源准备就绪之后,通过操作SW位来选择那个时钟信号作为系统时钟;

⑶ 判断相应时钟的SWS位是否置位,如果置位则切换时钟成功。

系统时钟的库函数

① 配置系统时钟

函数原型:void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource)

函数参数:

 

函数功能:

 

图示中的三个时钟通道,通过SW片选端选择充当系统主时钟的时钟源。

② 返回作为系统时钟源的时钟源

函数原型:uint8_t RCC_GetSYSCLKSource(void)

函数参数:HSI:0x00;HSE:0x04;PLL:0x08

函数的实现:

 

函数通过与操作取出“时钟配置寄存器(RCC_CFGR)的[3:2]位”,SWS位是判断主系统时钟源来源的,SW是用于切换主系统时钟源的,一个读取状态(SWS)一个用于操作(SW)的,不要弄混了。

HSE当作系统时钟源

HSE可以被直接或者间接当作系统时钟源,有如下两种方式:

① HSE被间接作为系统时钟源

配置通道原理图:

 

配置代码如下:

void RCC_Configuration(void)
{
    RCC_DeInit(); // 使RCC回复默认设置
    RCC_HSEConfig(RCC_HSE_ON);//使能HSE时钟源(选择为外部无源晶振搭配内部整形电路作为时钟源产生的方式)
    while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);//等待HSE时钟源震荡信号稳定
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);//PLLCLK = HSEx9 = 8MHz x 9 = 72 MHz
    RCC_PLLCmd(ENABLE); //使能PLL倍频器(只有在配置完PLL属性后才可以使能PLL)
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出时钟信号稳定
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLLCLK作为系统时钟的时钟信号
    while(RCC_GetSYSCLKSource() != 0x08); //等待PLLCLK被选作SYSCLK时钟信号
}

 

② HSE直接被当做系统时钟源

配置通道原理图:

 

 

配置代码如下:

void RCC_Configuration(void)
{
    RCC_DeInit(); // 使RCC恢复默认设置
    RCC_HSEConfig(RCC_HSE_ON);//使能HSE时钟源(选择为外部无源晶振搭配内部整形电路作为时钟源产生的方式)
    while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);//等待HSE时钟源震荡信号稳定
    RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); //选择PLLCLK作为系统时钟的时钟信号
    while(RCC_GetSYSCLKSource() != 0x04); //等待HSE被选作SYSCLK时钟信号
}

 

内外设时钟配置

 

外设<->工作频率

① 工作频率为HCLK频率的内外设:

SDIO:读写SD卡,可选择HCLK或者HCLK/2;

FSMC:(Flexible Static Memory Controller,可变静态存储控制器) STM32 系列采用的一种新型的存储器扩展技术。 在 外部存储器 扩展方面具有独特的优势,可根据系统的应用需要,方便地进行不同类型大容量静态存储器的扩展;

Cortex系统时钟:HCLK/8作为系统时钟也就是systick时钟;

正点原子中的delay用的就是systick系统滴答时钟:

//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS         //如果需要支持OS.
 u32 reload;
#endif
 SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟  HCLK/8
 fac_us=SystemCoreClock/8000000;    //为系统时钟的1/8  
#if SYSTEM_SUPPORT_OS         //如果需要支持OS.
 reload=SystemCoreClock/8000000;    //每秒钟的计数次数 单位为K    
 reload*=1000000/delay_ostickspersec;  //根据delay_ostickspersec设定溢出时间
            //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右 
 fac_ms=1000/delay_ostickspersec;   //代表OS可以延时的最少单位    

 SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;    //开启SYSTICK中断
 SysTick->LOAD=reload;       //每1/delay_ostickspersec秒中断一次 
 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;    //开启SYSTICK    

#else
 fac_ms=(u16)fac_us*1000;     //非OS下,代表每个ms需要的systick时钟数   
#endif
}

 

② 工作频率为PCLK1频率的内外设:

#define RCC_APB1Periph_TIM2              ((uint32_t)0x00000001)
#define RCC_APB1Periph_TIM3              ((uint32_t)0x00000002)
#define RCC_APB1Periph_TIM4              ((uint32_t)0x00000004)
#define RCC_APB1Periph_TIM5              ((uint32_t)0x00000008)
#define RCC_APB1Periph_TIM6              ((uint32_t)0x00000010)
#define RCC_APB1Periph_TIM7              ((uint32_t)0x00000020)
#define RCC_APB1Periph_TIM12             ((uint32_t)0x00000040)
#define RCC_APB1Periph_TIM13             ((uint32_t)0x00000080)
#define RCC_APB1Periph_TIM14             ((uint32_t)0x00000100)
#define RCC_APB1Periph_WWDG              ((uint32_t)0x00000800)
#define RCC_APB1Periph_SPI2              ((uint32_t)0x00004000)
#define RCC_APB1Periph_SPI3              ((uint32_t)0x00008000)
#define RCC_APB1Periph_USART2            ((uint32_t)0x00020000)
#define RCC_APB1Periph_USART3            ((uint32_t)0x00040000)
#define RCC_APB1Periph_UART4             ((uint32_t)0x00080000)
#define RCC_APB1Periph_UART5             ((uint32_t)0x00100000)
#define RCC_APB1Periph_I2C1              ((uint32_t)0x00200000)
#define RCC_APB1Periph_I2C2              ((uint32_t)0x00400000)
#define RCC_APB1Periph_USB               ((uint32_t)0x00800000)
#define RCC_APB1Periph_CAN1              ((uint32_t)0x02000000)
#define RCC_APB1Periph_CAN2              ((uint32_t)0x04000000)
#define RCC_APB1Periph_BKP               ((uint32_t)0x08000000)
#define RCC_APB1Periph_PWR               ((uint32_t)0x10000000)
#define RCC_APB1Periph_DAC               ((uint32_t)0x20000000)
#define RCC_APB1Periph_CEC               ((uint32_t)0x40000000)

 

③ 工作频率为PCLK2频率的内外设:

#define RCC_APB2Periph_AFIO              ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA             ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB             ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC             ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD             ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE             ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF             ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG             ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1              ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2              ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1              ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1              ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8              ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1            ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3              ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15             ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16             ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17             ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9              ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10             ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11             ((uint32_t)0x00200000)

 

外设时钟的库函数

① 对系统时钟进行直接分频得到HCLK

函数原型:void RCC_HCLKConfig(uint32_t RCC_SYSCLK)

函数参数:

 

② 对HCLK进行分频得到APB1

函数原型:void RCC_PCLK1Config(uint32_t RCC_HCLK)

函数参数:

 

③ 对HCLK分频得到APB2

函数原型:void RCC_PCLK2Config(uint32_t RCC_HCLK)

函数参数:

 

④ 时钟准备就绪中断使能

函数原型:void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState)

函数参数:

 

⑤ 对PLLCLK进行分频可以得到USB的时钟

 

函数原型:void RCC_USBCLKConfig(uint32_t RCC_USBCLKSource)

函数参数:

 

⑥ PLCK2分频得到ADC时钟

 

函数原型:void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)

函数参数:

 

注意:ADC最大工作频率为14MHz。

时钟配置实例

如果我们想要将TIM2的时钟配置为72MHz,我们应该执行如下程序:

 

注意“当APB1预分频系数不为1时TIMxCLK(2,3,……,7)=2xPCLK1”。

我们前面通过HSE经过PLL倍频之后间接的作为SYSCLK系统主时钟,这里不再赘叙。

由于APB1上面挂的是低速外设,因此最大频率为36MHz(APB2为高速外设总线最高频率为72MHz),而且这个关系存在的原因就是“使得TIMxCLK(2,3,……,7)最高可达到72MHz”。程序如下:

void RCC_Configuration(void)
{
    RCC_DeInit(); // 使RCC恢复默认设置
    RCC_HSEConfig(RCC_HSE_ON);//使能HSE时钟源(选择为外部无源晶振搭配内部整形电路作为时钟源产生的方式)
    while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);//等待HSE时钟源震荡信号稳定
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);//PLLCLK = HSEx9 = 8MHz x 9 = 72 MHz
    RCC_PLLCmd(ENABLE); //使能PLL倍频器(只有在配置完PLL属性后才可以使能PLL)
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出时钟信号稳定
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLLCLK作为系统时钟的时钟信号
    while(RCC_GetSYSCLKSource() != 0x08); //等待PLLCLK被选作SYSCLK时钟信号
    
    RCC_HCLKConfig(RCC_SYSCLK_Div2); // 保证APB1上的最大频率不超过36MHz
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 使能APB1上的内外设TIM2
}

 

注意:

我们常常提及如下3个函数:

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

 

这三个函数都是用于开启相应总线上的内外设时钟的,对应如下逻辑图:

 

只有与门的两个操作量都为1,则输出才为1。

常见问题

为什么LSI时钟振荡频率是32.768KHz?

 

32.768KHZ的时钟晶振产生的振荡信号经过石英钟内部分频器进行15次分频后得到1HZ秒信号,即秒针每秒钟走一下,石英钟内部分频器只能进行15次分频,要是换成别的频率的晶振,15次分频后就不是1HZ的秒信号,时钟就不准了。32.768K=32768=2的15次方,数据转换比较方便、精确。

直接接一个有源的晶振(温度对有源晶振的影响较无源晶振小)很多时候大家会用到 32.768K 的时钟晶体来做时钟,而不是用单片机的晶体分频后来做时钟,这个原因很多人想不明白,其实这个跟晶体的稳定度有关,频率越高的晶体,Q 值一般难以做高,频率稳定度不高,32.768K的晶体稳定度等各方面都不错,形成了一个工业标准, 比较容易做高。

无源晶振如何起振?

无源晶振的起振的原理类似于“RC振荡电路”,在RC振荡电路中我们只需要在二端口处加一个直流电压,我们就可以在电容两端检测到振荡波型,但是这个振荡波型不可以直接用于时钟系统,必须要有“类似于施密特触发器这种起到整定波形的器件”将无源电路输出的电压波形进一步整形一下才可以被时钟系统所使用。

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当涉及到STM32的串口接收数据包解析程序时,以下是一个简单的示例代码,可以帮助你开始: ```c #include "stm32f4xx.h" #include <stdio.h> #define BUFFER_SIZE 64 // 数据包缓冲区大小 #define START_BYTE 0x7E // 数据包起始字节 #define END_BYTE 0x7F // 数据包结束字节 uint8_t buffer[BUFFER_SIZE]; uint8_t packet[BUFFER_SIZE]; uint8_t packetIndex = 0; uint8_t packetLength = 0; uint8_t receivingPacket = 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART2); if (receivingPacket) { if (data == END_BYTE) { // 数据包接收完成 memcpy(packet, buffer, packetLength); packetIndex = 0; packetLength = 0; receivingPacket = 0; // 在这里对接收到的数据包进行处理 // TODO: 添加你的数据包处理代码 } else { // 继续接收数据包 buffer[packetIndex++] = data; packetLength++; if (packetIndex >= BUFFER_SIZE) { // 数据包过长,清空缓冲区 packetIndex = 0; packetLength = 0; receivingPacket = 0; } } } else { if (data == START_BYTE) { // 开始接收数据包 receivingPacket = 1; } } } } int main(void) { // 初始化相关的硬件和外设 USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能串口时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 配置串口引脚 // TODO: 根据你的具体硬件配置进行修改 GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); // 配置串口参数 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); // 使能串口接收中断 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 配置串口中断优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能串口 USART_Cmd(USART2, ENABLE); while (1) { // 主程序逻辑,可以添加其他代码 } } ``` 这段代码实现了STM32的串口接收数据包解析功能。当接收到起始字节(0x7E)时,开始接收数据包,直到接收到结束字节(0x7F),然后对接收到的数据包进行处理。你可以在注释的`TODO`部分添加你的数据包处理代码。 请注意,这只是一个简单的示例程序,实际应用中可能需要根据你的具体需求进行适当的修改和优化。另外,具体的硬件配置(如引脚连接)可能需要根据你的硬件平台进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值