【嵌入式系统】STM32时钟系统+时钟配置函数解析

【嵌入式系统】STM32时钟系统+时钟配置函数解析

1、时钟系统

时钟系统为整个硬件系统的各个模块提供时钟信号。时钟是整个数字电路的驱动之源,所有数字部件的运行都依赖时钟信号的输入才得以向前推进。
由于系统复杂性,各硬件模块可能对时钟信号有不同要求,因此在系统中应按需分别提供时钟信号。这些时钟信号或者来自不同振荡器,或者是从一个主振荡器开始,经过多次的倍频、分频、锁相环等电路而生成的独立时钟信号。不同时钟信号而非单一时钟的设计还有助于实现系统的低功耗:一些低速外设可以使用功耗更低的低速时钟,部分硬件没有使用时也可除能时钟位达到关闭的效果。

2、时钟树

在这里插入图片描述

图1 STM32时钟树

在这里插入图片描述

图2 STM32时钟树图例解析

在实际应用时,为避免因为外部时钟源失效造成MCU内部紊乱导致无可挽回的损失,引入CSS来监控外部时钟源,这是STM32作为一个可靠系统的必要条件。当外部时钟源出现故障失效时,CSS会主动切断外部时钟并开启内部时钟作为替代,同时产生一个时钟安全中断。此中断属于NMI,将不断执行直到CSS中断挂起位被清除。

3、时钟配置函数解析

①时钟配置常用寄存器组
用结构体复位与时钟控制**(RCC, Reset and Clock Control)**来表示。

typedef struct
{
  __IO uint32_t CR;               //HSI、HSE、CSS、PLL的使能和就绪标志位
  __IO uint32_t CFGR;            //PLL、USB等时钟源的选择和分频系数的设置
  __IO uint32_t CIR;              //清除/使能时钟就绪中断
  __IO uint32_t APB2RSTR;       //APB2上的外设复位寄存器
  __IO uint32_t APB1RSTR;       // APB1上的外设复位寄存器
  __IO uint32_t AHBENR;         //DMA、CRC等模块时钟使能
  __IO uint32_t APB2ENR;        //APB2外设时钟使能
  __IO uint32_t APB1ENR;        //APB1外设时钟使能
  __IO uint32_t BDCR;           //备份域控制寄存器
  __IO uint32_t CSR;             //控制状态寄存器
} RCC_TypeDef;

要使用某个模块或外设时,务必保证其驱动时钟被使能。

②系统初始化函数(战舰版)void SystemInit(void)
下面仅截取SystemInit()中与RCC配置相关,且采用STM32F10X_HD预编译头的代码

void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
  RCC->CFGR &= (uint32_t)0xF8FF0000;
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();
}

这个初始化函数对时钟系统的作用是:初始化RCC中的相关寄存器,并配置系统时钟。

对于RCC中寄存器的配置可以完全对照手册与注释进行解读,例如:
RCC->CFGR &= (uint32_t)0xF8FF0000
值得注意,位配置的 |= 运算一般是置位Set;&=运算一般是复位Reset。根据0xF8FF0000位向量知,此条指令是将CFGR中的[15:0]以及[26:24]清空。如图3所示为参考手册给出的CFGR位模式,清零位对应的是SW, HPRE, PPRE1, PPRE2, ADCPRE和MCO。
在这里插入图片描述

图3

下面考察系统时钟设置函数SetSysClock()

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();                           
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
}

系统时钟频率的设置是通过条件编译进行的,若宏定义了SYSCLK_FREQ_72MHz,则SetSysClock()中就只编译执行SetSysClockTo72()

为了研究更底层的封装,再考察SetSysClockTo72()

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET) HSEStatus = (uint32_t)0x01;
  else HSEStatus = (uint32_t)0x00;

  if (HSEStatus == (uint32_t)0x01)
  {
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

    /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  }
}

此函数首先使能了时钟源HSE

RCC->CR |= ((uint32_t)RCC_CR_HSEON);

其中#define RCC_CR_HSEON ((uint32_t)0x00010000)
#define RCC_CR_HSERDY ((uint32_t)0x00020000)
因此这条指令作用是将RCC->CR的Bit16置1,结合图参考手册中图4可以确认,这条指令确实使能了HSE请添加图片描述
请添加图片描述

图4

接下来很重要的一步操作是等待使能成功,例如上述执行使能HSE指令后,硬件开始根据指令执行置位操作,这里存在软硬件时间差 t ∆t ,在 t ∆t 内HSE仍然是除能状态,因此等待使能成功的目的就是起到对 t ∆t 的过渡作用,防止硬件误动作。

do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

这个while循环首先用HSEStatus获取HSE状态:若HSERDY=1即HSE已经使能,则HSEStatus=1;否则HSEStatus=0。

确认使能了HSE后,便开始配置一系列的时钟,这里将HCLK、PCLK2、PLLCLK配置为72MHz,将PCLK1配置为36MHz,例如:

RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

①通过&=进行复位。出于安全性考虑,置位操作中可能用到的位先清0,以防置位时产生进位引发错误。可以看到,这里将时钟源选择位、预分频位和倍频位清0,即此时PLL处于无时钟源、不分频、不倍频的状态。
②通过|=进行置位。RCC_CFGR_PLLSRC_HSE即选择HSE作为PLL时钟源,HSE一般接8MHz晶振;RCC_CFGR_PLLMULL9选择倍频系数为9,因此PLLCLK为8×9=72MHz的时钟。

至此,SystemInit()的原理讲解完毕。事实上,用户也可根据需要,自行选择相应寄存器来设计并封装初始化函数

猜你喜欢

转载自blog.csdn.net/FRIGIDWINTER/article/details/106800035