赞
踩
1、系统时钟SYSCLK
在STM32F407中,除了一些特定的时钟(例如,USB OTG FS时钟,I2S时钟)外,系统所有外设的时钟均是通过SYSCLK来提供的。也就是说我们经常用到的外设时钟,都是通过SYSCLK分频得到的。下面是和SYSCLK相关的时钟树的图。
由图中可以看到,SYSCLK的来源主要有三个。
l HSI 振荡器时钟
l HSE 振荡器时钟
l 主 PLL (PLL) 时钟
下面分别来看下系统时钟SYSCLK的三个来源。
(1) 高速外部时钟HSE(4M~26M)
高速外部时钟是芯片内部的HSE振荡器产生的。高速外部信号(HSE)有两个时钟源
l HSE外部晶振,这种方式是通过在OSC_IN和OSC_OUT接入外部晶振实现的。在这种模式下,因为晶振起振需要一定的时间,因此可以通过RCC 时钟控制寄存器(RCC_CR) 中的HSERDY 标志指示高速外部振荡器是否稳定。
l HSE外部时钟,这种方式是指通过OSC_IN引脚输入一个外部时钟源,这个时钟源是外部产生的现成的时钟信号。在这种模式下,必须使用占空比大约为50%的外部时钟信号来驱动OSC_IN引脚。
(2) 高速内部时钟HSI
STM32F407内部自带一个16M的内部晶振,这个晶振产生的时钟信号可以直接用作系统时钟SYSCLK。也可以作为PLL的输入。内部晶振的优点是成本低,启动速度也比外部晶振要快,但是内部晶振的精度比外部晶振要差。可以通过RCC时钟控制寄存器(RCC_CR)中的HSIRDY 标志来判断HSI RC时钟信号是否稳定。
(3) 主PLL时钟
STM32F407具有两个PLL,用于产生不同的时钟信号。这里主要来讨论主PLL时钟。主PLL时钟的时钟源有两个信号,分别是上边提到的HIS信号和HSE信号。PLL通过把这两个信号倍频,分频等达到更高频率的时钟信号。一般来说,由于HIS和HSE的时钟频率较低,SYSCLK一般都不会选择HIS时钟和HSE时钟作为系统时钟,而主PLL产生的时钟一般会用作系统时钟。下面我们来看下主PLL的内部结构框图。
由图可以看出PLL的时钟源主要有两个:HSE时钟信号和HIS时钟信号。其中PLL_M,PLL_N,PLL_P,PLL_Q是一个分频器,而PLL_M是一个倍频器。假设图中经过PLL_N之后的时钟信号为PLL_VCO,PLL使用外部时钟信号HSE作为时钟源。那么有如下关系。
PLL_VCO = (HSE / PLL_M)* PLL_N
SYSCLK = PLL_VCO/ PLL_P
所以,如果外部晶振的频率已经确定了,那么确定PLL_M,PLL_N,PLL_P的系数就可以得到对应频率的系统时钟SYSCLK,其中PLL_M,PLL_N,PLL_P可以有多种组合,只要满足相应的条件即可。例如,假设外部晶振为8M,对STM32F407来说,系统时钟的最大频率为168M,那么久可以分别设置PLL_M = 8,PLL_N = 336,PLL_P = 2。来产生168M的时钟。即SYSCLK = ( 8 /8 ) * 336 / 2 = 168M。
通过配置主PLL,我们就得到了系统的主时钟SYSCLK,下一步我们就可以通过这个时钟来分频得到各个外设的时钟。
2、AHBx时钟
经过AHBx预分频器,可以得到AHBx外设时钟。一般来说AHBx分频器的值为1,也就是说AHBx的时钟和系统时钟相等。AHBx上边所挂的外设可以到RCC和AHBx外设相关的寄存器去看,在使能AHBx上的某个外设之前,必须使能该外设时钟。
3、APB1和APB2时钟
经过APB1预分频器和APB2预分频器可以得到APB1和APB2外设时钟。在STM32F407里边,APB1的最大时钟为42M,APB2的最大时钟为84M。同样可以在RCC的寄存器下边观测APB1和APB2上边所挂的外设。使用某个外设之前也要使能相应的时钟。
4、system_stm32f4xx.c
在stm32里边system_stm32f4xx.c是整个系统的时钟配置文件,上边三个部分的所有时钟配置都是在这个源文件中实现。ST官方有一个很强大的时钟配置工具stm32f4xx_Clock_Configuration_V1.0.0.xls,这个工具的界面是在excel里边实现的,功能很强大,通过图形化界面配置时钟的各个寄存器,配置完成之后可以由该工具自动生成system_stm32f4xx.c文件。该工具的界面如下所示。
通过这个图形化配置界面用户可以直接得到满足自己要求的时钟配置文件。下边来分析一下system_stm32f4xx.c文件的具体内容。
system_stm32f4xx.c 主要包含三个重要的东西:(1) SystemInit() 函数:这个函数主要用来配置时钟,这个函数可以用来设置上边讲到的时钟配置过程中的时钟源选择和时钟系数选择。经过这个函数以后系统的时钟就配置完成了。
(2) SystemCoreClock 变量:这个变量的值就是经过配置之后 SYSCLK 的值
(3) SystemCoreClockUpdate() 函数:这个函数用来更新 SystemCoreClock 变量,在时钟配置完成之后,需要调用这个函数来更新 SystemCoreClock 变量。
其中SystemInit()函数中,前边是RCC寄存器的初始化,SystemInit()函数会调用SetSysClock函数,这个函数是设置时钟的主要函数。下边主要来分析一下这个函数。这里选择主PLL作为系统时钟SYSCLK来源,选择HSE时钟作为PLL的时钟来源。SetSysClok()源码如下。
static void SetSysClock(void) { /******************************************************************************/ /* PLL (clocked by HSE) used as System clock source */ /******************************************************************************/ __IO uint32_t StartUpCounter = 0, HSEStatus = 0; RCC->CR |= ((uint32_t)RCC_CR_HSEON); //使能HSE时钟 do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); //等待HSE时钟信号稳定 if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; //表示HSE启动成功 } else { HSEStatus = (uint32_t)0x00; //表示HSE启动失败 } if (HSEStatus == (uint32_t)0x01) { /* Enable high performance mode, System frequency up to 168 MHz */ RCC->APB1ENR |= RCC_APB1ENR_PWREN; //使能电源接口时钟 PWR->CR |= PWR_CR_PMODE; //调压器输出电压级别选择 /* HCLK = SYSCLK / 1*/ RCC->CFGR |= RCC_CFGR_HPRE_DIV1; //设置AHBx预分频器的值,经过这个分频器设置得到 AHBx=SYSCLK / RCC_CFGR_HPRE_DIV1 /* PCLK2 = HCLK / 2*/ RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //设置APB2预分频器的值,经过这个分频器设置得到 APB2 = AHBx / RCC_CFGR_PPRE2_DIV2 /* PCLK1 = HCLK / 4*/ RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; //设置APB1预分频器的值,经过这个分频器设置得到 APB1 = AHBx / RCC_CFGR_PPRE1_DIV4 /* Configure the main PLL */ RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | //设置主PLL的时钟来源,这里设置为HSE. 设置主PLL分频器的值,也就是PLL_M,PLL_N,PLL_P,PLL_Q. (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24); /* Enable the main PLL */ RCC->CR |= RCC_CR_PLLON; //启动主PLL /* Wait till the main PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) //等待主PLL时钟就绪 { } /* Configure Flash prefetch, Instruction cache, Data cache and wait state */ FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS; /* Select the main PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //选择PLL作为系统时钟SYSCLK来源 RCC->CFGR |= RCC_CFGR_SW_PLL; /* Wait till the main PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL); //等待PLL成为主时钟来源 { } } 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 */ } }经过以上配置,系统的时钟就配置完成了。然后用户可以调用SystemCoreClockUpdate()函数来更新SystemCoreClock变量,SystemCoreClockUpdate()函数实际上就是根据HSE的值以及上边配置过程中设置的系数重新计算出来一个SystemCoreClock的值,相当于读取上边配置好寄存器里边的值,再把系统时钟计算一遍。但是在更新之前,必须正确设置HSE_VALUE这个宏定义的值,一般在stm32f4xx.h文件中会默认为25M,用户需要根据自己的需要来修改这个值,我这边晶振是8M。所以HSE_VALUE得值修改为8M。否则会导致计算出来的SystemCoreClock不正确。
5、系统时钟输出功能
STM32F407允许用户把时钟分频后通过MCO1引脚和MCO2引脚输出,用户可以将时钟用作他用或者用来测量时钟信号。
(1) MCO1
用户可以通过配置分频器的值(1-5)向MCO1引脚输出四个不同的时钟源:HSI时钟,LSE时钟,HSE时钟,PLL时钟
(2) MCO2
用户可以通过配置分频器的值(1-5)向MCO2引脚输出四个不同的时钟源:HSE时钟,PLL时钟,系统时钟SYSCLK,PLLI2S时钟。
ST官方给的固件库里边的工程模板就是将时钟进行输出的。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。