STM32时钟初始化函数SystemInit()详解

花了一天的时间,总算是了解了SystemInit()函数实现了哪些功能,初学STM32,,现记录如下(有理解错误的地方还请大侠指出):
使用的是3.5的库,用的是STM32F107VC,开发环境RVMDK4.23
我已经定义了STM32F10X_CL,SYSCLK_FREQ_72MHz
函数调用顺序:
startup_stm32f10x_cl.s(启动文件) → SystemInit() → SetSysClock () → SetSysClockTo72()
初始化时钟用到的RCC寄存器复位值:
RCC_CR = 0x0000 xx83; RCC_CFGR = 0x0000 0000;RCC_CIR = 0x0000 0000; RCC_CFGR2 = 0x0000 0000;
SystemInit()
在调用 SetSysClock()之前RCC寄存器的值如下(都是一些与运算,或运算,在此就不赘述了):
RCC->CR = 0x0000 0083; RCC->CIR = 0x00FF0000; RCC->CFGR2 = 0x00000000;至于这些寄存器都代表着什么意思,详见芯片资料RCC寄存器,该文重点不在此处;
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 //我的定义的是SYSCLK_FREQ_72MHz,所以调用SetSysClockTo72()
SetSysClockTo72();
#endif
}
SetSysClockTo72()函数如下:
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 */
}
}

1:AHB, APB1,APB2时钟确定
//HCLK = SYSCLK ,从下面的分析可以得出SYSCLK是使用PLLCLK时钟的,也就是72MHZ(至于72MHZ如何得来,请看下面分析)
//那么就是HCLK(AHB总线时钟)=PLLCLK = 72MHZ
//AHB总线时钟等于系统时钟SYSCLK,也就是 AHB时钟 = HCLK = SYSCLK = 72MHZ
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;

//PLCK2等于HCLK一分频, 所以PCLK2 = HCLK,HCLK = 72MHZ, 那么PLCK2(APB2总线时钟) = 72MHZ
//APB2总线时钟等于HCLK的一分频,也就是不分频;APB2 时钟 = HCLK = SYSCLK = 72MHZ
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;

//PCLK1 = HCLK / 2;PCLK1 等于HCLK时钟的二分频,那么PCLK1(APB1) = 72MHZ / 2 = 36MHZ    
//APB1总线时钟等于HCLK的二分频,也就是 APB1时钟= HCLK / 2 = 36MHZ
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;	

2:如何得出SYSCLK(系统时钟)为72MHZ(外部晶振25MHZ)
//记得参考英文芯片资料的时钟树P115页和RCC时钟寄存器进行理解
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);

RCC_CFGR2_PREDIV2_DIV5: PREDIV2 = 5; 5分频
也就是PREDIV2对输入的外部时钟 5分频,那么PLL2和PLL3没有倍频前是25 /5 = 5MHZ
RCC_CFGR2_PLL2MUL8 : PLL2MUL = 8; 8倍频
8倍频后,PLL2时钟 = 5 * 8 = 40MHZ; 因此 PLL2CLK = 40MHZ
RCC_CFGR2_PREDIV1SRC_PLL2 : RCC_CFGR2的第16位为1, 选择PLL2CLK 作为PREDIV1的时钟源
RCC_CFGR2_PREDIV1_DIV5:PREDIV1 = 5;PREDIV1对输入时钟5分频 PREDIV1CLK = PLL2CLK / 5 = 8MHZ

以上是对RCC_CFGR2进行的配置

RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);

RCC_CFGR_PLLXTPRE_PREDIV1 :操作的是RCC_CFGR的第17位PLLXTPRE,操作这一位和操作RCC_CFGR2寄存器的 位[3:0]中的最低位是相同的效果
RCC_CFGR_PLLSRC_PREDIV1 :选择PREDIV1输出作为PLL输入时钟;PREDIV1CLK = 8MHZ,所以输入给PLL倍频的 时钟源是8MHZ
RCC_CFGR_PLLMULL9 :PLLMUL = 9;PLL倍频系数为9,也就是对 PLLCLK = PREDIV1CLK * 8 = 72MHZ

以上是对RCC_CFGR进行的配置

RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; //选择PLLCLK作为系统时钟源


至此基本配置已经完成,配置的时钟如下所述:
SYSCLK(系统时钟) = 72MHZ
AHB总线时钟 = 72MHZ
APB1总线时钟 = 36MHZ
APB2总线时钟 = 72MHZ
PLL时钟 = 72MHZ
PLL2时钟 = 40MHZ

STM32的时钟树
对于广大初次接触STM32的读者朋友(甚至是初次接触ARM器件的读者朋友)来说,在熟悉了开发环境的使用之后,往往“栽倒”在同一个问题上。这问题有个关键字叫:时钟树。
众所周知,微控制器(处理器)的运行必须要依赖周期性的时钟脉冲来驱动——往往由一个外部晶体振荡器提供时钟输入为始,最终转换为多个外部设备的周期性运作为末,这种时钟“能量”扩散流动的路径,犹如大树的养分通过主干流向各个分支,因此常称之为“时钟树”。在一些传统的低端8位单片机诸如51,AVR,PIC等单片机,其也具备自身的一个时钟树系统,但其中的绝大部分是不受用户控制的,亦即在单片机上电后,时钟树就固定在某种不可更改的状态(假设单片机处于正常工作的状态)。比如51单片机使用典型的12MHz晶振作为时钟源,则外设如IO口、定时器、串口等设备的驱动时钟速率便已经是固定的,用户无法将此时钟速率更改,除非更换晶振。
而STM32微控制器的时钟树则是可配置的,其时钟输入源与最终达到外设处的时钟速率不再有固定的关系,本文将来详细解析STM32微控制器的时钟树。图1是STM32微控制器的时钟树,表1是图中各个标号所表示的部件。
标号 图1标号释义
1 内部低速振荡器(LSI,40Khz)
2 外部低速振荡器(LSE,32.768Khz)
3 外部高速振荡器(HSE,3-25MHz)
4 内部高速振荡器(HIS,8MHz)
5 PLL输入选择位
6 RTC时钟选择位
7 PLL1分频数寄存器
8 PLL1倍频寄存器
9 系统时钟选择位
10 USB分频寄存器
11 AHB分频寄存器
12 APB1分频寄存器
13 AHB总线
14 APB1外设总线
15 APB2分频寄存器
16 APB2外设总线
17 ADC预分频寄存器
18 ADC外设
19 PLL2分频数寄存器
20 PLL2倍频寄存器
21 PLL时钟源选择寄存器
22 独立看门狗设备
23 RTC设备

STM32时钟初始化函数SystemInit()详解_第1张图片
图1 STM32的时钟树
在认识这颗时钟树之前,首先要明确“主干”和最终的“分支”。假设使用外部8MHz晶振作为STM32的时钟输入源(这也是最常见的一种做法),则这个8MHz便是“主干”,而“分支”很显然是最终的外部设备比如通用输入输出设备(GPIO)。这样可以轻易找出第一条时钟的“脉络”:
3——5——7——21——8——9——11——13
对此条时钟路径做如下解析:
对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;
对于5,通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);
对于7,设置外部晶振的分频数(假设1分频);
对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
对于8,设置PLL倍频数(假设9倍频);
对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);
对于11,设置AHB总线分频数(假设1分频);
对于13,时钟到达AHB总线;
在上一章节中所介绍的GPIO外设属于APB2设备,即GPIO的时钟来源于APB2总线,同样在图1中也可以寻获GPIO外设的时钟轨迹:
3——5——7——21——8——9——11——15——16
对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;
对于5, 通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);
对于7,设置外部晶振的分频数(假设1分频);
对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
对于8,设置PLL倍频数(假设9倍频);
对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);
对于11,设置AHB总线分频数(假设1分频);
对于15,设置APB2总线分频数(假设1分频);
对于16,时钟到达APB2总线;
现在来计算一下GPIO设备的最大驱动时钟速率(各个条件已在上述要点中假设):

  1. 由3所知晶振输入为8MHz,由5——21知PLL的时钟源为经过分频后的外部晶振时钟,并且此分频数为1分频,因此首先得出PLL的时钟源为:8MHz / 1 = 8MHz。
  2. 由8、9知PLL倍频数为9,且将PLL倍频后的时钟输出选择为系统时钟,则得出系统时钟为 8MHz * 9 = 72MHz。
  3. 时钟到达AHB预分频器,由11知时钟经过AHB预分频器之后的速率仍为72MHz。
  4. 时钟到达APB2预分频器,由15经过APB2预分频器后速率仍为72MHz。
  5. 时钟到达APB2总线外设。
    因此STM32的APB2总线外设,所能达到的最大速率为72MHz。依据以上方法读者可以搜寻出APB1总线外设时钟、RTC外设时钟、独立看门狗等外设时钟的来龙去脉。接下来从程序的角度分析时钟树的设置,程序清单如下:
    void RCC_Configuration(void)
    {
    ErrorStatus HSEStartUpStatus; (1)
    RCC_DeInit(); (2)
    RCC_HSEConfig(RCC_HSE_ON); (3)
    HSEStartUpStatus = RCC_WaitForHSEStartUp(); (4)
    if(HSEStartUpStatus == SUCCESS) (5)
    {
    RCC_HCLKConfig(RCC_SYSCLK_Div1); (6)
    RCC_PCLK2Config(RCC_HCLK_Div1); (7)
    RCC_PCLK1Config(RCC_HCLK_Div2); (8)
    FLASH_SetLatency(FLASH_Latency_2); (9)
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); (10)
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); (11)
    RCC_PLLCmd(ENABLE); (12)
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); (13)
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); (14)
    while(RCC_GetSYSCLKSource() != 0x08); (15)
    }
    }

你可能感兴趣的:(STM32)