19集 STM32 時鐘

藍色方框為時鐘源:HSI RC、HSE OSC、PLL、LSE OSC、LSI RC
梯形紫色為選擇器:就像一個切換開關。
可以從兩步分析該時鐘圖:
1、從SYSCLK開始看,由誰可以提供時鐘給SYSCLK,逐一分析。
2、從RTCCLK、IWDGCLK開始分析,由誰可以提供時鐘給RTCCLK\IWDGCLK。
一、時鐘源:內(nèi)部時鐘的精度都比較差(《STM32中文參考手冊 V10》 ---- 57頁)
HSI RC: 高速內(nèi)部時鐘(約8M),當被用作PLL時鐘的輸入時,系統(tǒng)時鐘能得到的最大頻率為64MHz
HSE OSC: 高速外部時鐘,一般采用晶振(可選4~16M,一般選8M)
PLL: 鎖相環(huán)(2~16倍),用于倍頻時鐘,可選來源 HSI 二分頻、HSE、HSE二分頻。
PLL的設置必須在其激活前完成,一旦PLL被激活,這些參數(shù)都無法被改動,如果發(fā)現(xiàn)PLL的 設置無效,就可能在設置時PLL已經(jīng)完成激活,如果需要使用USB接口,PLL必須被設置為輸 出48MHz或者72MHz時鐘,用于提供48MHz的USBCLK時鐘。
LSE OSC: 低速外部時鐘,一般采用晶振(32.768KHz)用于提供RTC時鐘
LSI RC: 低速內(nèi)部時鐘(約40KHz),用于提供獨立看門口作為時鐘源
二、SYSCLK:(最重要的時鐘)
從上圖中可以看到,SYSCLK為最重要的時鐘,幾乎大部分的外設時鐘都是從SYSCLK得到時鐘信號。所以可以從SYSCLK開始分析。
SYSCLK可以選擇從HSI RC、PLL、HSE OSC取其中一個時鐘信號,而PLL則可以從HSI RC的二分頻、HSE OSC、HSE OSC 二分頻取得時鐘信號。
系統(tǒng)復位后,HSI為默認的系統(tǒng)時鐘來源。在做時鐘源切換時,目標時鐘源穩(wěn)定就緒后才會發(fā)生切換。
1、CSS 時鐘安全系統(tǒng):(當系統(tǒng)運行速度莫名其妙的下降、或者USB不工作,可以留意這里)
因STM32內(nèi)部時鐘是利用電容產(chǎn)生的時鐘,并不是太穩(wěn)定,所以一般項目中都會采用外部晶振通過HSE OSC提供穩(wěn)定的時鐘給系統(tǒng),當外部晶振出現(xiàn)問題即HSE失效時,CSS就會自動將時鐘源切換至HSI RC。
CSS可以通過軟件被激活,一旦其被激活,CSS將在HSE振蕩器啟動延遲后被使能,并在HSE時鐘關閉后關閉。
如果HSE時鐘發(fā)生故障,HSE振蕩器被自動關閉,時鐘失效事件將被送到高級定時器(TIM1和TIM8)的剎車輸入端,并產(chǎn)生時鐘安全中斷CSSI,允許軟件完成營救操作。此CSSI中斷連接到Cortex-M3的NMI中斷。
一旦CSS被激活,并且HSE時鐘出現(xiàn)故障,CSS中斷就產(chǎn)生,并且NMI也自動產(chǎn)生。NMI將不斷執(zhí)行,直到CSS中斷掛起位被清除。因此,在NMI的處理程序中必須通過設置時鐘中斷寄存器(RCC_CIR)里的CSSC位來清除CSS中斷。
如果HSE振蕩器被直接或間接地作為系統(tǒng)時鐘,時鐘故障將導致系統(tǒng)時鐘自動切換到HSI振蕩器,同時外部HSE振蕩器被關閉。在時鐘失效時,如果HSE振蕩器時鐘是用做系統(tǒng)時鐘的PLL的輸入時鐘,PLL也將關閉。(問題來了,那么USBCLK是不是也就失效了吧?)
2、每個時鐘都可以獨立開啟或關閉,這樣可以關閉不需要的時鐘從而降低功耗。
從下圖可以看出,每次需要使用某個外設時都要開啟相應的外設時鐘。

(參考STM32中文參考手冊 V10 56頁)
三、RTCCLK(RTC時鐘)
RTCCLK可以選擇從HSE OSC的128分頻、LSE OSC、LSI RC中取時鐘信號。
這里沒有CSS,那如果采用HSE的時鐘信號而其又失效,豈不是RTC也就失效了?
四、IWDGCLK(看門狗時鐘)
看門口時鐘只能從 LSI RC中取得時鐘源。
五、MCO 輸出(PA8引腳)
STM32還可以對外輸出時鐘,時鐘源可以從SYSCLK、HSI、HSE、PLLCLK的二分頻取得信號。
六、重要的時鐘
SYSCLK系統(tǒng)時鐘
AHB總線時鐘(高速)
APB1總線時鐘(低速):速度最高36MHz 連接低速外設
APB2總線時鐘(高速):速度最高72MHz 連接高速外設
PLL時鐘:用于倍頻
七、寄存器相關(具體參閱 《STM32中文參考手冊 V10》 60~75頁)
typedef struct
{
__IO uint32_t CR; // HSI、HSE、CSS、PLL等的使能和就緒標志位
__IO uint32_t CFGR; // PLL等的時鐘源選擇、分頻系數(shù)設定
__IO uint32_t CIR; // 清除、使能時鐘就緒中斷
__IO uint32_t APB2RSTR; // APB2線上所有外設復位寄存器
__IO uint32_t APB1RSTR; // APB1線上所有外設復位寄存器
__IO uint32_t AHBENR; // DMA、SDIO等時鐘使能
__IO uint32_t APB2ENR; // APB2線上所有外設時鐘使能
__IO uint32_t APB1ENR; // APB1線上所有外設時鐘使能
__IO uint32_t BDCR; // 備份域控制寄存器
__IO uint32_t CSR; // 控制狀態(tài)寄存器
} RCC_TypeDef;
0x4002 1000 - 0x4002 13FF 復位和時鐘控制(RCC)
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC ((RCC_TypeDef *) RCC_BASE)
八、庫函數(shù)類型(system_stm32f10x.c -- SystemInit() 函數(shù)中通過直接配置寄存器配置,并未采用庫函數(shù))
1、時鐘使能配置:
RCC_LSEConfig() 、RCC_HSEConfig()、
RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() …
2、時鐘源相關配置:
RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、
RCC_RTCCLKConfig() …
3、分頻系數(shù)選擇配置:
RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()…
4、外設時鐘使能:
RCC_APB1PeriphClockCmd(): //APB1線上外設時鐘使能
RCC_APB2PeriphClockCmd(); //APB2線上外設時鐘使能
RCC_AHBPeriphClockCmd(); //AHB線上外設時鐘使能
5. 其他外設時鐘配置:
RCC_ADCCLKConfig (); RCC_RTCCLKConfig();
6、狀態(tài)參數(shù)獲取參數(shù):
RCC_GetClocksFreq();
RCC_GetSYSCLKSource();
RCC_GetFlagStatus()
7、RCC中斷相關函數(shù) :
RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()…
20集 SystemInit()
SystemInit()函數(shù)在 startup_stm32f10x_hd.s 文件里面先于main函數(shù)調(diào)用。
也就是說我們在寫mian函數(shù)時,不必去調(diào)用SystemInit()函數(shù)。
一、代碼分析 (需要參考 《STM32中文參考手冊 V10.pdf》、《STM32F10xxx閃存編程參考手冊.pdf》)
void SystemInit (void)
{
// 開啟HSI內(nèi)部8MHz高速時鐘 1
RCC->CR |= (uint32_t)0x00000001;
// 設置HSI做為系統(tǒng)時鐘 1:0
// SYSCLK不分頻 7:4
// APB1\APB2 不分頻 13:8
// ADC預分頻 PCLK2 2分頻后作為ADC時鐘 15:14
// MCO引腳不輸出時鐘 26:24
RCC->CFGR &= (uint32_t)0xF8FF0000;
// HSE振蕩器關閉 16
// CSS時鐘監(jiān)視關閉 19
// PLL關閉 24
RCC->CR &= (uint32_t)0xFEF6FFFF;
// 設置外部4-16MHz振蕩器沒有旁路 18
RCC->CR &= (uint32_t)0xFFFBFFFF;
// 設置HSI振蕩器時鐘經(jīng)2分頻后作為PLL輸入時鐘 16
// 設置HSE不分頻 17
// 設置PLL 2倍頻輸出 21:18
// 設置USB預分頻 PLL時鐘1.5倍分頻作為USB時鐘 22
RCC->CFGR &= (uint32_t)0xFF80FFFF;
// 設置LSI、LSE、HSI、HSE、PLL就緒中斷關閉 12:8
// 清除LSI、LSE、HSI、HSE、PLL就緒中斷標志位 20:16
// 清楚CSSF時鐘安全系統(tǒng)中斷標志位 23
RCC->CIR = 0x009F0000;
/* 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
}
#define SYSCLK_FREQ_72MHz 72000000
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(); // <--- 該函數(shù)被調(diào)用
#endif
}
#define RCC_CR_HSEON ((uint32_t)0x00010000)
#define RCC_CR_HSERDY ((uint32_t)0x00020000)
#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)
#define FLASH_ACR_PRFTBE ((uint8_t)0x10)
#define FLASH_ACR_LATENCY ((uint8_t)0x03)
#define FLASH_ACR_LATENCY_2 ((uint8_t)0x02)
#define RCC_CFGR_HPRE_DIV1 ((uint32_t)0x00000000)
#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000)
#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0x00000400)
#define RCC_CFGR_PLLSRC ((uint32_t)0x00010000)
#define RCC_CFGR_PLLXTPRE ((uint32_t)0x00020000)
#define RCC_CFGR_PLLMULL ((uint32_t)0x003C0000)
#define RCC_CFGR_PLLSRC_HSE ((uint32_t)0x00010000)
#define RCC_CFGR_PLLMULL9 ((uint32_t)0x001C0000)
#define RCC_CR_PLLON ((uint32_t)0x01000000)
#define RCC_CR_PLLRDY ((uint32_t)0x02000000)
#define RCC_CFGR_SW ((uint32_t)0x00000003)
#define RCC_CFGR_SW_PLL ((uint32_t)0x00000002)
#define RCC_CFGR_SWS ((uint32_t)0x0000000C)
typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
/* 要設置 72MHz 需要使用HSE外部時鐘 */
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
// 開啟HSE時鐘 16
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
// 在一定的時間內(nèi)等待 HSE 時鐘穩(wěn)定
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; // HSE時鐘就緒
}
else
{
HSEStatus = (uint32_t)0x00; // HSE時鐘未就緒
}
// 如果HSE時鐘就緒
if (HSEStatus == (uint32_t)0x01)
{
// 啟用預取緩沖區(qū) 4
FLASH->ACR |= FLASH_ACR_PRFTBE;
// 48MHz < SYSCLK <= 72MHz 設置兩個等待周期 2:0
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/*** SYSCLK -> HCLK -> PLCK1\PCLK2 */
/* HCLK = SYSCLK */
// 設置AHB分頻系數(shù)為 1 即不分頻,等于SYSCLK的頻率 即72MHz
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
// 設置APB2分頻系數(shù)為 1 即不分頻,等于HCLK的頻率 即72MHz
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK/2 */
// 設置APB1分頻系數(shù)為 2 即二分頻,等于HCLK/2的頻率 即36MHz
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
// 這里主要是清零操作 21:16
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
// 設置HSE作為PLL輸入時鐘 16
// 設置PLL 9 倍頻輸出 (HSE為8M晶振輸入:SYSCLK = 8MHz * 9 = 72MHz)
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
/* Enable PLL */
// 開啟PLL
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
// 等待PLL就緒標志位就緒 25
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
// 清零操作
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
// 設置PLL為SYSCLK時鐘源
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 // HSE未就緒
{ /* 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():
設置SYSCLK、APB1、APB2不分頻,MCO不輸出時鐘
SYSCLK由PLL提供時鐘、PLL則由HSI經(jīng)2分頻后提供時鐘
設置USB時鐘為PLL輸出時鐘的1.5倍
關閉所有時鐘源的中斷、清除所有時鐘源的中斷標志位
SetSysClockTo72():
該函數(shù)首先是開啟HSE時鐘,然后在一定的時間等待其穩(wěn)定。
穩(wěn)定之后,設置FLASH開啟預取緩沖區(qū)、設置兩個等待周期。
接著設置AHB、APB2、APB1分配系數(shù)來配置HCLK、PCLK2、PCLK1的時鐘頻率。
再設置HSE作為PLL時鐘源,并設置PLL為9倍頻,再開啟PLL,等待PLL就緒。
三、注意
SystemCoreClock 在實際寫代碼中可以通過這個變量來獲得當前所設置的系統(tǒng)時鐘頻率值
system_stm32f10x.h:
extern uint32_t SystemCoreClock;
system_stm32f10x.c:
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
#ifdef SYSCLK_FREQ_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_HSE;
#elif defined SYSCLK_FREQ_24MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz;
#elif defined SYSCLK_FREQ_36MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_36MHz;
#elif defined SYSCLK_FREQ_48MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz;
#elif defined SYSCLK_FREQ_56MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz;
#elif defined SYSCLK_FREQ_72MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;
#else /*!< HSI Selected as System Clock source */
uint32_t SystemCoreClock = HSI_VALUE;
#endif