1. 程式人生 > >STM32F10X 時鐘相關程式碼及分析

STM32F10X 時鐘相關程式碼及分析

今天學習STM32F107VC時鐘相關暫存器以及控制,配合原始碼,資料手冊,寫了點學習筆記供以後參考。

很重要的示意圖:

時鐘樹


程式碼已加註釋,如下:

//引自: system_stm32f10x.c 檔案
void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  //先將HSION 內部高速時鐘使能,HSIRDY指示內部8MHz RC振盪器是否就緒
  RCC->CR |= (uint32_t)0x00000001; 

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL //如果不是網際網路型
  //RCC->CFGR &= 0xF8FF0000  意思是清除低16位(SW,HPRE,PPRE1,PPRE2,ADCPRE),第24~26位(MC0)
  RCC->CFGR &= (uint32_t)0xF8FF0000; 
#else 
  //STM32F107VC 是網際網路型晶片,只是將MC0的位數多加了一位,由之前的第24~26位變成第24~27位。MC0[2:0] -> MC0[3:0]
  //所以這裡清除第24~27位和低16位
  RCC->CFGR &= (uint32_t)0xF0FF0000; 
#endif /* STM32F10X_CL */   
  
  //這裡值得注意,按最初的想法是這裡可以合併起來做清除,而不用RCC->CR &= XXXX 然後又 RCC->CR &= XXXX,之所以這樣做的原因
  //是有些位必須在關閉關閉時鐘時才能寫入。如:PLLXTPRE PLLMUL PLLSRC USBPRE PLLON等
  /* Reset HSEON, CSSON and PLLON bits */
  //必須關閉HSEON後才能寫HSEBYP位,所以這裡將Reset HSEBYP bit單獨拿出來放在下面。
  RCC->CR &= (uint32_t)0xFEF6FFFF; 

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

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  //清除第16~22位 注意這幾個暫存器位很關鍵,必須將對應的時鐘關閉後才能配置
  //PLLSRC 選擇時鐘源作為PLL的時鐘,PLLXTPRE 當且僅當PLLSRC選擇HSE作為時鐘時有效,PLLMUL 為倍頻係數位,控制在2~16倍,但PLL的輸出頻率最大不能超過72M
  //USBPRE 預分頻後頻率為48MHz或24MHz

  RCC->CFGR &= (uint32_t)0xFF80FFFF; 

#ifndef STM32F10X_CL
  /* Disable all interrupts and clear pending bits  */
  //關閉所有時鐘中斷,並清除所有中斷標誌位 (CSSC PLL HSER HIS LSE LSI)
  RCC->CIR = 0x009F0000; 
#else //F107VC指向下面的程式碼
  /* Reset PLL2ON and PLL3ON bits */
  //其實就是關閉PLL2和PLL3
  RCC->CR &= (uint32_t)0xEBFFFFFF; 

  /* Disable all interrupts and clear pending bits  */
  //將CSSC PLL3 PLL2 HSE HIS LSE LSI的中斷標誌位都清除,這裡和C51有點不同,中斷標誌位暫存器分離了,
  //如: HSI_RDYF  HSIRDYC,前者是隻讀,由硬體置位;後者只寫,軟體置位HSIRDYC來控制清除HSI_RDYF位。
  RCC->CIR = 0x00FF0000; 

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000; //清空PREDIV PLL2MUL PLL3MUL等
#endif /* STM32F10X_CL */
    
  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();

}

//這個函式其實就是根據巨集的定義呼叫對應的時鐘設定函式,程式碼裡面定義了SYSCLK_FREQ_72MHz,所以預設是呼叫SetSysClockTo72()
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) */ 
}


//肯定是執行這個巨集裡面的程式碼
#elif defined SYSCLK_FREQ_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; //等待HSE進入穩定狀態,就緒後會將HSERDY置1
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut)); //HSEStartUp_TimeOut 0x0500

  //執行到這裡有可能是超時,也有可能是HSE穩定了
  if ((RCC->CR & RCC_CR_HSERDY) != RESET) 
  {
    HSEStatus = (uint32_t)0x01; //這裡表明卻是穩定了
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  //注意只有在系統時鐘(SYSCLK)小於24MHz並且沒有開啟AHB的預分頻器(即HCLK必須等於
  //SYSHCLK)時,才能執行預取緩衝器的開啟和關閉操作,所以一般是在初始化時進行操作。
  if (HSEStatus == (uint32_t)0x01) //HSE 外部高速時鐘晶振已經開啟並且穩定
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE; //開啟 Flash 預取緩衝區

	//2等待週期,當 48MHz < SYSCLK ≤ 72MHz
    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

	//AHB橋的時鐘等於SYSCLK 72MHz
	//設定AHB預分頻,CFGR[7:4] -> HPRE[3:0] |= 0x00000000,SYSCLK不分頻
    /* HCLK = SYSCLK */
	RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; 
      
	//APB2橋的時鐘等於AHB時鐘 72MHz
	//設定PPRE2預分頻,CFGR[13:11] -> PPRE2[2:0] |= 0x00000000,HCLK不分頻
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; 
    
	//APB1橋的時鐘等於AHB時鐘的一半 36MHz,低速APB橋
	//設定PPRE1預分頻,CFGR[10:8] -> PPRE1[2:0] |= 0x00000400,使用HCLK二分頻
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; 

#ifdef STM32F10X_CL //STM32F107X 
    /* 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);
							  
	//從資料手冊的時鐘樹圖上可以看出,PLL2唯一的時鐘源就是PREDIV2預分頻後的HSE,這裡典型值取5分頻,PREDIV2 = 25 / 5 = 5MHz
	//PLL2通過PLL2MUL倍頻暫存器提高工作頻率,典型值為8倍頻,PLL2CLK = 40 MHz。
	//PLL的時鐘源可以由PREDIV1和HSI二分頻提供,這裡選用PREDIV1。PREDIV1自身的時鐘源又可以由HSE直接提供和PLL2提供,這裡選用PLL2提供。
	
	//設定PREDIV2、PREDIV1、PREDIV1SRC預分頻和PLL2MUL倍頻暫存器值
	//HSE頻率為25MHz,PREDIV2 5分頻後傳給PLL2的頻率為5MHz,然後PLL2MUL 選擇為8倍頻,PLL2的頻率為5 × 8 = 40MHz
	//使用PLL2為PREDIV1的時鐘源,並且PREDIV1 5分頻,PREDIV1的頻率為8MHz
    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; //開啟PLL2時鐘
    /* 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_PLLXTPRE_PREDIV1 0x00000000 不對輸入時鐘進行分頻
	//選擇PREDIV1為PLL的時鐘源,並且設定PLL倍頻為9倍頻,所以頻率為 72MHz
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
	//先清除PLLMUL 倍頻係數位,PLLSRC 輸入時鐘源位,PLLXTPRE HSE分頻位
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
										
	//使用HSE為PLL的輸入時鐘源,並且不對HSE分頻,另外PLLMUL倍頻係數選擇9倍頻,外部高速時鐘被限定為8MHz頻率									
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9); 
	//
#endif /* STM32F10X_CL */

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

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0) //等待PLL就緒
    {
    }
    
    /* Select PLL as system clock source */
	//將PLL配置為系統時鐘SYSCLK頻率
    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 */
	//檢查SWS位是不是是10,如果是就表明是PLL作為系統時鐘
    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 */    

    /* Go to infinite loop */
    while (1)
    {
    }
  }
}
#endif


總的來說,控制STM32F107 網際網路系列的時鐘可以分為下面幾步:

第一步、 開啟外部高速HSE時鐘,需要其等待其穩定 (因為後面PLL2要使用其作為時鐘源)

第二步、開啟Flash快閃記憶體預取緩衝區 (這個東西還沒有很瞭解)

第三步、設定AHB、APB1、APB2的預分頻暫存器和PLL相關暫存器 (主要是選定PLL2 、PLL時鐘源)

第四步、開啟PLL2和PLL並選用PLL作為系統時鐘源