一、時鍾系統
-
概述
時鍾是單片機運行的基礎,時鍾信號推動單片機內各個部分執行相應的指令,時鍾系統就是CPU的脈搏,決定cpu速率。 STM32有多個時鍾來源的選擇,為什么 STM32 要有多個時鍾源呢?因為首先 STM32 本身非常復雜,外設非常的多,而使用任何外設都需要時鍾才能啟動,但是並不是所有外設都需要系統時鍾這么高的頻率,比如看門狗以及 RTC 只需要幾十 k 的時鍾即可。同一個電路,時鍾越快功耗越大,同時抗電磁干擾能力也會越弱,所以對於較為復雜的 MCU 一般都是采取多時鍾源的方法來解決這些問題。如圖所示:
-
STM32F10x時鍾系統圖
從圖中藍色部分可以看出STM32有5個時鍾源:HSI、HSE、LSE、LSI、PLL。
- HSI時鍾:高速內部時鍾,RC振盪器,頻率約為8MHz,精度不高。直接作為 8MHz 的系統時鍾或者用作 4MHz 的PLL時鍾輸入。
- HSE時鍾:高速外部時鍾,可接石英/陶瓷諧振器,或者接外部時鍾源,頻率范圍為3MHz~25MHz。(一般是8MHZ),外部振盪器可為系統提供非常精確的主時鍾。
- LSI時鍾:低速內部時鍾,RC振盪器,頻率為32kHz,提供低功耗時鍾。主要供獨立看門狗和自動喚醒單元使用。
- LSE時鍾:低速外部時鍾,接頻率為32.768kHz的石英晶體,它為實時時鍾或者其他定時功能提供一個低功耗且精確的時鍾源。
- PLL時鍾:產生倍頻的輸出
-
系統時鍾(SYSCLK)選擇
從時鍾系統圖中可以看出,系統復位后,HSI振盪器被選為系統時鍾。此時系統時鍾的來源有三種選擇,可以選擇HSI、PLL、HSE/2,而PLL又有兩種時鍾源可以選擇HSE、HSI/2。
注意:切換時鍾源時需要等待新的時鍾源就緒,否則系統時鍾源不會切換。在時空控制寄存器(RCC_CR)里的狀態指示可以看到已經准備好的時鍾已經被當前系統使用的時鍾。 -
時鍾安全系統(CSS)
從時鍾系統圖中可以看出,當HSE振盪器被直接或間接地作為系統時鍾時(直接指的是系統時鍾源為HSE/2,間接指的是HSE通過PLL產生的倍頻時鍾作為系統時鍾源),時鍾故障將導致系統時鍾自動切換到HSI振盪器,同時外部HSE振盪器被關閉。在時鍾失效時,如果HSE振盪器時鍾是作為PLL的輸入時鍾,PLL也將被關閉。
注意:一旦CSS被激活,並且HSE時鍾出現故障,CSS中斷就產生,並且NMI也自動產生。NMI將被不斷執行,直到CSS中斷掛起位被清除。因此,在NMI的處理程序中必須通過設置時鍾中斷寄存器(RCC_CIR)里的CSSC位來清除CSS中斷
# 如果需要72MHz的系統時鍾,我們可以選擇8MHz的外部時鍾(HSE),PLL的倍頻設置為9
SYSCLK = 8(HSE) * 9(PLL) = 72MHz
-
RTC時鍾(RTCCLK)
通過設置 備份域控制寄存器(RCC_BDCR)里的RTCSEL[1:0]位,RTCCLK時鍾源可以由HSE/128、LSE或LSI時鍾提供。 -
看門狗時鍾
如果獨立看門狗已經由硬件選項或軟件啟動,LSI振盪器將被強制在打開狀態,並且不能被關
閉。在LSI振盪器穩定后,時鍾供應給IWDG。 -
時鍾輸出
微控制器允許輸出時鍾信號到外部MCO引腳。通過MCO引腳輸出時鍾時,GPIO端口寄存器必須被配置為相應功能,時鍾信號有四種來源SYSCLK、HSI、HSE、PLL/2。
注意:在MCO上輸出的時鍾必須小於50MHz(這是I/O端口的最大速度),時鍾的選擇由時鍾配置寄存器(RCC_CFGR)中的MCO[3:0]位控制。
二、寄存器
-
時鍾控制寄存器(RCC_CR)
-
時鍾配置寄存器(RCC_CFGR)
-
時鍾中斷寄存器(RCC_CIR)
-
APB2 外設復位寄存器 (RCC_APB2RSTR)
-
APB1 外設復位寄存器 (RCC_APB1RSTR)
-
AHB外設時鍾使能寄存器 (RCC_AHBENR)
-
APB2 外設時鍾使能寄存器(RCC_APB2ENR)
-
APB1 外設時鍾使能寄存器(RCC_APB1ENR)
-
備份域控制寄存器 (RCC_BDCR)
-
控制/狀態寄存器 (RCC_CSR)
-
RCC寄存器地址映像
三、程序分析
從上面可以看出配置系統時鍾有10個寄存器,分別的作用就不用過多介紹了,文檔已經很詳細。接下來看STM32提供的庫函數是怎么配置的。
- RCC相關寄存器的結構體
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
#ifdef STM32F10X_CL
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
uint32_t RESERVED0;
__IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
} RCC_TypeDef;
-
系統時鍾的初始化函數
現在應知道,系統時鍾是CPU的心臟,所以在運行程序之前,首先需要進行時鍾的配置,也就是說在執行main函數之前已經執行了系統時鍾的配置。系統時鍾的初始函數SystemInit(),當然這個函數名是可以更改的,所以需要了解STM32運行時,是在哪里調用SystemInit()這個函數進行初始化的。
-
SystemInit()函數
void SystemInit (void)
{
/* 將RCC時鍾配置重置為默認重置狀態(用於調試目的) */
RCC->CR |= (uint32_t)0x00000001; // 打開HSION位(內部高速時鍾使能)
/* 復位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
RCC->CFGR &= (uint32_t)0xF8FF0000;
/* 復位 HSEON, CSSON 和 PLLON 位 */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* 復位 HSEBYP 位 */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* 復位 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位 */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
/* 禁用所有中斷並清除掛起位 */
RCC->CIR = 0x009F0000;
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl(); // 設置的是STM32F10X_HD,所以不執行此函數
#endif /* DATA_IN_ExtSRAM */
#endif
/* 配置系統時鍾頻率、HCLK、PCLK2和PCLK1預分頻器 */
/* 配置閃存延遲周期並啟用預取緩沖區 */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 內部SRAM中的向量表重定位. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 內部FLASH中的向量表重定位. */
#endif
}
- SetSysClock()函數
配置系統時鍾頻率、HCLK、PCLK2和PCLK1預分頻器。這里配置的系統時鍾頻率是72MHz
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並配置HCLK、PCLK2和PCLK1預分頻器。
/**
* @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 */
}
}
從這里看來系統時鍾的配置就比較簡單了,只需要花一些時間對一下相應的寄存器,希望感興趣的小伙伴自己按照上面的寄存器表格與程序進行對比一下,很容易就能看懂了。當然也可以跟着上面的思路自己寫一個SystemInit()函數,如果不考慮各種芯片的適配,那么程序相對就比較簡單,程序量也比較少。
參考文獻
STM32時鍾系統以及配置及源碼分析:https://blog.csdn.net/qq_36243942/article/details/83655339
【STM32】系統時鍾RCC詳解(超詳細,超全面):https://blog.csdn.net/as480133937/article/details/98845509
《STM32中文參考手冊》