STM32F10X 時鐘相關程式碼及分析
阿新 • • 發佈:2019-02-05
今天學習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作為系統時鐘源