欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-16 17:45:05
...

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

1、时钟系统

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

2、时钟树

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

图1 STM32时钟树

【嵌入式系统】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。
【嵌入式系统】STM32时钟系统+时钟配置函数解析

图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【嵌入式系统】STM32时钟系统+时钟配置函数解析
【嵌入式系统】STM32时钟系统+时钟配置函数解析

图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()的原理讲解完毕。事实上,用户也可根据需要,自行选择相应寄存器来设计并封装初始化函数

相关标签: 嵌入式系统