STM32时钟 分析

STM32属于低功耗芯片,和51相比,51在使用外设如某个IO口的时候,只需要给指定的IO口高低电平,然而STM32在给指定的IO口电平之前,还需要配置时钟,这就使从51转过来的同学,配置起来比较麻烦,现在我来给大家分析下 STM3的时钟, 有错误的地方请大家多多指正。

下面是STM32的时钟树
STM32时钟 分析_第1张图片
从图中可以看到,时钟有四个来源:
1. HSI 高速的内部时钟 (8MHZ)
2. HSE 高速的外部时钟 (4~16MHZ ,一般接8M,为了后面的PLL(锁相环,用来倍频用的))
3. LSE 低速的外部时钟 (32.768kHZ)
4. LSI 低速的内部时钟 (40KHZ)

时钟经过PLL后,给SYSCLK后 给AHB经过倍频分频 给APB2,APB1在给各个外设,也有一些芯片内通过AHB给时钟。
从图中我们可以看到, SYSCLK有三个来源:
1. HSI 高速内部时钟
2. PLLCLK 锁相环时钟
3. HSE 高速外部时钟
SYSCLK 最是72MHZ。 APB2最大是72MHZ, APB1是36MHZ所以APB2相比而言是高速外设。
好了,时钟这里的理论部分分析完毕,我们接下来,具体是怎么来实现的。

单片机,说白了,就是在填写寄存器,特别是我们现在这样只是驱动外设就是在填写寄存器。
时钟控制的部分是RCC(Reset and clock control),RCC有如下寄存器
{
RCC_CR (时钟控制寄存器)
RCC_CFGR(时钟配置寄存器)
RCC_CIR(时钟中断寄存器)
RCC_APB2RSTR(APB2外设复位寄存器)
RCC_APB1RSTR(APB1外设复位寄存器)
RCC_AHBENR(AHB外设时钟使能寄存器)
RCC_APB2ENR(APB2外设时钟使能寄存器)
RCC_APB1ENR(APB1外设时钟使能寄存器)
RCC_BDCR(备份域控制寄存器)
RCC_CSR(控制/状态 寄存器)
}
开始看STM32的固件库是如何实现。

首先我们来看看程序是如何开始运行的。

int main(void)
   {
     led1_init();

        key_gpio_init();


    while(1)
  {
          if(key_scan(GPIOA,GPIO_Pin_0)==KEY_ON)   
            {
                  GPIOB->ODR ^=GPIO_Pin_0;
      }
  }         

打开调试 会看到,程序一开始并不是直接进入MAIN函数里面的,而是进入 system_stm32f10x.c(位于Libraries//CMSIS)这个文件里面的
void SystemInit(void ) 里 在这个函数里面就会把ST32的时钟初始化,但是例如我们呢要用GPIOA的PIN2 这个外设是挂载在APB2上的。所以在使用的时候还是要 开启APB2中的GPIOA的时钟。

现在开始分析 SystemInit(void) 主要是 在外部高速时钟和内部高速时钟之间切换,在系统上电之初,使用内部的时钟,等到外部的稳定后,使用外部的时钟作为系统的时基。

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 */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   

  /* 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;
  #ifdef STM32F10X_CL
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* Disable all interrupts and clear pending bits */
  RCC->CIR = 0x00FF0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* Disable all interrupts and clear pending bits */
  RCC->CIR = 0x009F0000;

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

#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

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

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 
}

RCC->CR |= (uint32_t)0x0000 0001;
内部8MHZ时钟启动,(目前没有使用外部的高速时钟).
RCC->CFGR &= (uint32_t) 0xf8ff 0000;
效果是: 微控制器时钟没有输出,PLL时钟直接作为USB的时钟,PLL16倍频输出,HSE (8M) 2分频后作为PLL的输入。PLCK2(APB2) 二分频后作为ADC时钟, HCLK(AHB2)不分频, HCLK(AHB1)低速不分频, AHB(SYSCLK)不分频,
系统时钟切换 HSI(高速内部时钟)作为系统时钟

RCC->CR &=(uint32_t) 0xfef6ffff; (1111 1110 1111 0110 )
效果是: PLL锁定,PLL关闭,时钟检测器关闭,外部高速晶体震荡器被旁路(没有被使用),外部高速晶体振荡器就绪,HSE振荡器关闭, 内部8M时钟就绪,内部8M开启

RCC->CR &= 0XFFFB FFFF ; ( 1111 1111 1111 1011 )
效果是: PLL锁定,PLL使能,如果外部8M就绪,启动监测,外部高速晶体 没有旁路,外部高速就绪, HSE振荡器开启

RCC->CFGR &= (uint32_t) 0xff80ffff ;( 1111 1111 1000 0000 )
效果是: PLL时钟2分频后输出, PLL时钟1.5倍分频后作为USB的时钟, PLL 2 倍频后输出, HSE不分频, HSI时钟2分频后作为PLL的输入时钟。 PLCK2 8分频后作为ADC时钟, HCLK(APB2)16分频, HCLK(APB1)16分频,SYSCLK 512分频,

之后,设置了一些安全时钟的,不去解释,可以自己去分析源码,把值填进相应的寄存器。

程序 接着进入了 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

 /* If none of the define above is enabled, the HSI is used as System clock source (default after reset) */ 
}

程序继而跳入 : SetSysClockTo72(); 可以从程序的名字了解到,把系统的时钟设置成了72MHZ。

/** * @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2 * and PCLK1 prescalers. * @note This function should be used only after reset. * @param None * @retval None */
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)
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    
  /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;

    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;

    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */

    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);

    /* Enable PLL2 */
     RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }


    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /* 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);
#endif /* STM32F10X_CL */

    /* 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 */
  }
}

分析代码:

RCC->CR |= (uint32_t) RCC_CR_HSEON ; RCC_CR_HSEON 是一个宏定义 0x00010000;
效果是: 把 HSE开启, 在这里,我们应该可以看到了,芯片使用时钟的变化了,从内部的高速时钟转换成了外部的高速时钟,

这个函数看起来很麻烦,其实里面都是些条件编译条件,在我们使用的 STM32F103VE 芯片时只是用了几句话,要是你有兴趣,可以分析一下, 跳出这个函数后, 我们芯片里面的时钟也基本已经设置完成了。
继续往下看 程序跳到了这里 : startup_stm32f10x_hd.s 注意这是一个 汇编文件,

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

LDR 是传送指令, 看格式: LDR {条件} 目的寄存器 <存储器地址>
BLX : 将下一个指令的地址复制到lr(R14 链接寄存器)

***LDR R0, =SystemInit
BLX R0
LDR R0, =__main***
把 SystemInit 放到R0 中, 然后指向 下一条指令, 把 main 放到 R0中, 程序到这一步,就开始进入我们写的主函数中。

你可能感兴趣的:(stm32)