本任务要求完成STM32F4开发板(板上连接的外部晶振频率为8 MHz)系统时钟的配置,具体配置要求如下:
● 系统时钟(SYSCLK)配置为168 MHz;
● 高性能总线时钟(HCLK)配置为168 MHz;
● 高速外设总线时钟(PCLK2)配置为84 MHz;
● 低速外设总线时钟(PCLK1)配置为42 MHz。
时钟(Clock)是微控制器的脉搏。在数字系统中,所有部件要正常工作都离不开时钟。时钟之于数字系统,如心跳之于人,其重要性不言而喻。
一般情况下,我们在使用标准外设库进行STM32开发时无须对系统时钟进行配置,原因是STM32开发板上提供的晶振频率基本上都与ST公司官方标准的晶振频率相同。但对于一些特殊的应用场景(如超低功耗),我们可能需要对MCU进行降频,即对系统时钟进行配置。
STM32F4标准外设库为用户提供了复位与时钟控制(Reset and Clock Control,RCC)库,用于系统时钟的配置。
本任务涉及的知识点有:
● STM32F4系列微控制器的时钟源;
● STM32F4系列微控制器中的一些重要的时钟信号概念;
● STM32F4标准外设库中与RCC库配置相关的内容。
与51系列单片机相比,STM32F4的时钟系统更加复杂。由于STM32F4的系统架构复杂,外设种类繁多,且每种外设所需的时钟频率不尽相同,因此系统有多个时钟源。STM32F4的时钟树图标明了各时钟源的信息,如图1-3-1所示。
图1-3-1中的标号
处分别标明了STM32F4的5个时钟源,它们的介绍如下。
(1)高速内部时钟
高速内部时钟(High Speed Internal,HSI)由STM32F4系列微控制器芯片内部的RC振荡器产生,其频率为16MHz,可作为SYSCLK或锁相环(PLL)的时钟源。它位于图1-3-1中标号
所示的位置。
(2)高速外部时钟
高速外部时钟(High Speed External,HSE)由石英振荡器或陶瓷振荡器产生,也可直接使用外部时钟源,其频率范围是4~26MHz,可作为SYSCLK或锁相环(PLL)的时钟源。它位于图1-3-1中标号
所示的位置。
图1-3-1 STM32F4的时钟树图
(3)低速内部时钟
低速内部时钟(Low Speed Internal,LSI)由STM32F4系列微控制器芯片内部的RC振荡器产生,其频率为32kHz,可供独立“看门狗”或实时时钟(RTC)使用。它位于图1-3-1中标号
所示的位置。
(4)低速外部时钟
低速外部时钟(Low Speed External,LSE)一般由频率为32.768kHz的石英振荡器产生,可供独立“看门狗”或实时时钟(RTC)使用。它位于图1-3-1中标号
所示的位置。
(5)锁相环倍频输出
锁相环(Phase Locked Loop,PLL)的主要作用是对其他输入时钟进行倍频,然后把时钟信号输出到各个功能部件。STM32F4系列微控制器有两个PLL,一个是主PLL,另一个是PLLI2S,它们位于图1-3-1中标号
所示的位置。
主PLL的输入可以是HSI或HSE,输出共有两路:一路输出PLLP提供的时钟信号PLLCLK,可作为系统时钟SYSCLK的时钟源,最高频率168MHz;另一路输出PLLQ,可用于生成48MHz的时钟信号PLL48CK,可供给USB OTG FS、随机数发生器和SDIO接口等使用。
PLLI2S用于生成精准时钟PLLI2SCLK,其可供给I2S总线接口以实现高品质音频输出。
图1-3-1中的标号A、B、C、D、E、F处分别标明了STM32F4中几个重要的时钟信号,它们的介绍如下。
(1)SYSCLK
系统时钟(SYSCLK)是STM32大部分器件的时钟源,它经AHB预分频器分频后分配到各个部件,如图1-3-1中标号“A”位置所示。SYSCLK的时钟源可以是HSI、HSE或PLLCLK,具体由时钟配置寄存器(RCC_CFGR)的SW位进行配置。
(2)HCLK
先进的高性能总线(Advanced High-Performance Bus,AHB)时钟(High-Performance Clock,HCLK)由AHB预分频器分频后得到,分频系数可以是1、2、4、8、16、64、128、256、512等,由RCC_CFGR的HPRE位进行配置,一般选择1分频。HCLK供Cortex-M4内核使用,它决定了STM32系列微控制器的运算速度和数据存取速度,一般也称为主频,如图1-3-1中标号“B”位置所示。
(3)SysTick
系统定时器(SysTick)是Cortex-M4内核的一个外设,它是一个24bit的向下递减的计数器,一般用于产生时基信号,如实时操作系统中的系统心跳信号就是由SysTick产生的。SysTick的时钟源一般设置为SYSCLK的1/8,如图1-3-1中标号“C”位置所示。
(4)FCLK
自由运行时钟(Free Running Clock,FCLK)独立于HCLK,它用于采样中断信号以及为调试模块计时。在处理器休眠期间,FCLK可确保采样到中断信号或跟踪到休眠事件,如图1-3-1中标号“D”位置所示。
(5)PCLK1和PCLK2
外设时钟(Peripheral Clock,PCLK)是APB上的时钟信号。STM32F4上有两条APB,分别是低速外设总线(APB1)和高速外设总线(APB2)。APB1上的时钟信号为PCLK1,APB2上的时钟信号为PCLK2。PCLK1和PCLK2由SYSCLK分别经APB1预分频器和APB2预分频器分频后产生,分频系数可以是1、2、4、8、16等。
PCLK1的最大频率为42MHz,它主要为挂载在APB1上的低速外设提供时钟,如USART2、TIM2和TIM3等。
PCLK2的最大频率为84MHz,它主要为挂载在APB2上的高速外设提供时钟,如USART1、TIM1和GPIO等,如图1-3-1中标号“E”位置所示。
(6)TIMxCLK
从图1-3-1中标号“F”位置可以看到,APB预分频器有一路输出接入定时器(Timer)的倍频器。PCLK信号经倍频后作为定时器时钟(TIMxCLK),倍频系数可选1或2,即TIMxCLK=PCLK×1(或2),更详细的配置内容见本书3.1.2节。
本任务要求将系统时钟(SYSCLK)配置为168MHz,而外部晶振(HSE)频率仅为8MHz,因此必须使用主PLL倍频来实现。主PLL的树图如图1-3-2所示,我们需要为主PLL配置以下参数:压控振荡器(VCO)输入时钟分频系数 M 、VCO输出时钟倍频系数 N 、PLLCLK时钟分频系数 P 和48MHz时钟输出的分频系数 Q 。
图1-3-2 主PLL的树图
根据《STM32F4xx中文参考手册》, M 的取值范围为2~63, N 的取值范围192~432, P 的取值为2、4、6、8中的一个, Q 的取值范围为2~15。
从图1-3-1可知,SYSCLK来源于PLLCLK。当HSE为8MHz时,如果要使PLLCLK输出为168MHz,则须配置 M 值为8, N 值为336, P 值为2。计算过程如下所示。
PLLCLK=(8MHz/ M )×( N / P )=1MHz×(336/2)=168MHz
同样地,配置 Q 值为7时,可使PLL48CK输出满足任务要求。计算过程如下所示。
PLL48CK=(8MHz/ M )×( N / Q )=1MHz×(336/7)=48MHz
STM32F4标准外设库的启动文件startup_stm32f40xx.s调用库函数SystemInit()进行系统时钟配置后,进入用户主程序main()函数。startup_stm32f40xx.s文件是用汇编语言实现的,其调用SystemInit库函数的具体实现如图1-3-3所示。
图1-3-3 启动文件调用SystemInit库函数
由于STM32F4标准外设库板载晶振的参数和主PLL的各项参数( M 、 N 、 P 、 Q )的默认配置可能与开发者所用的开发板不匹配,因此需要根据开发板硬件配置对标准外设库相关文件进行修改。需要修改的地方有以下几处。
一是开发板晶振频率的配置,此项配置位于stm32f4xx.h文件的第144行。ST公司官方开发板使用的晶振频率为25MHz,如果我们的开发板的板载晶振频率为8MHz,则需要将原有宏定义“#define HSE_VALUE((uint32_t)25000000)”修改为“#define HSE_VALUE((uint32_t)8000000)”。
二是主PLL各项参数( M 、 N 、 P 、 Q )的配置,它们分别位于system_stm32f4xx.c文件的第371、384、401和403行。如果板载晶振频率为8 MHz,则需要将第371行原有的宏定义“#define PLL_M 25”修改为“#define PLL_M 8”,即将VCO输入时钟分频系数 M 的值由25改为8。其他各项参数( N 、 P 、 Q )可根据项目实际需要进行配置。
本任务需要调用与RCC配置相关的库函数以完成系统时钟的配置,这些库函数都包含在stm32f4xx_rcc.c库文件中,因此我们应在工程中添加此文件,如图1-3-4所示。
图1-3-4 为工程添加stm32f4xx_rcc.c库文件
相关准备工作完成以后,我们就可以调用与RCC配置相关的函数进行系统时钟的配置。外部时钟HSE作为时钟源的配置步骤如下。
(1)复位RCC寄存器值为默认值
RCC_DeInit();
(2)开启HSE,并等待其工作稳定
RCC_HSEConfig(RCC_HSE_ON); HSEStartUpStatus = RCC_WaitForHSEStartUp();
(3)配置AHB、APB2、APB1的预分频系数
RCC_HCLKConfig(RCC_SYSCLK_Div1); // HCLK = SYSCLK / 1(1分频) RCC_PCLK2Config(RCC_HCLK_Div2); // PCLK2 = HCLK / 2 (2分频) RCC_PCLK1Config(RCC_HCLK_Div4); // PCLK1 = HCLK / 4 (4分频)
(4)配置主PLL的参数
RCC_PLLConfig(RCC_PLLSource_HSE, 8, 336, 2, 7);
上述代码片段配置 M =8, N =336, P =2, Q =7。
(5)开启PLL,并等待其工作稳定
RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
(6)选择PLLCLK作为SYSCLK的时钟源,并等待其稳定
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08);
复制一份任务1.2的工程,修改工程名为“RCC_Configuration”,在工程根目录下新建“SYSTEM”文件夹,并建立子文件夹“sys”,新建“sys.c”和“sys.h”两个文件,在“sys.c”文件中输入以下代码:
1 #include "sys.h" 2 3 /** 4 * @brief 使用外部晶振进行系统时钟的配置 5 * @param M: VCO输入时钟分频系数 6 * @param N: VCO输出时钟倍频系数 7 * @param P: PLLCLK时钟分频系数 8 * @param Q: 48MHz时钟输出的分频系数 9 * @retval None 10 */ 11 void HSE_RCC_Configuration(uint16_t M, uint16_t N, uint16_t P, uint16_t Q) 12 { 13 __IO uint32_t HSEStartUpStatus = 0; 14 15 RCC_DeInit();//将RCC寄存器重新设置为默认值 16 RCC_HSEConfig(RCC_HSE_ON);//打开HSE,并等待其工作稳定 17 HSEStartUpStatus = RCC_WaitForHSEStartUp(); 18 /* HSE启动成功 */ 19 if (HSEStartUpStatus == SUCCESS) 20 { 21 RCC->APB1ENR |= RCC_APB1ENR_PWREN; //调压器电压输出级别配置为1 22 PWR->CR |= PWR_CR_VOS; //工作时实现性能和功耗的平衡 23 24 RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK = SYSCLK / 1 25 RCC_PCLK2Config(RCC_HCLK_Div2); //PCLK2 = HCLK / 2 26 RCC_PCLK1Config(RCC_HCLK_Div4); //PCLK1 = HCLK / 4 27 /* 设置主PLL相关时钟分频系数 */ 28 RCC_PLLConfig(RCC_PLLSource_HSE, M, N, P, Q); 29 /* 开启PLL,并等待其工作稳定 */ 30 RCC_PLLCmd(ENABLE); 31 while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); 32 33 /* 配置FLASH预取值,指令缓存,数据缓存和等待状态 */ 34 FLASH->ACR = FLASH_ACR_PRFTEN 35 | FLASH_ACR_ICEN 36 | FLASH_ACR_DCEN 37 | FLASH_ACR_LATENCY_5WS; 38 /* 选择PLLCLK作为SYSCLK的时钟源,并等待其稳定 */ 39 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); 40 while (RCC_GetSYSCLKSource() != 0x08); 41 } 42 /* 如果HSE启动失败 */ 43 else 44 { 45 //TODO 出错处理 46 } 47 }
在“sys.h”文件中输入以下代码:
1 #ifndef __SYS_H 2 #define __SYS_H 3 #include "stm32f4xx.h" 4 5 void HSE_RCC_Configuration(uint16_t M, uint16_t N, uint16_t P, uint16_t Q); 6 #endif
在“main.c”文件中输入以下代码并调用自定义的系统时钟配置函数,编译、下载并观察程序运行情况。如果系统时钟配置成功,可以看到开发板上两个LED同亮同灭,并循环往复。
1 #include "sys.h" 2 3 void Delay(__IO uint32_t nCount); 4 5 int main(void) 6 { 7 /* 调用自定义的系统时钟配置函数 */ 8 HSE_RCC_Configuration(8,336,2,7); 9 10 GPIO_InitTypeDef GPIO_InitStructure; 11 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); 12 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 14 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 15 GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed; 16 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 17 GPIO_Init(GPIOF, &GPIO_InitStructure); 18 19 /* 无限循环 */ 20 while (1) 21 { 22 GPIO_SetBits(GPIOF, GPIO_Pin_9 | GPIO_Pin_10); 23 Delay(0x7FFFFF); 24 GPIO_ResetBits(GPIOF, GPIO_Pin_9 | GPIO_Pin_10); 25 Delay(0x7FFFFF); 26 } 27 } 28 29 /** 30 * @brief 软件延时函数 31 * @param None 32 * @retval None 33 */ 34 void Delay(__IO uint32_t nCount) 35 { 36 while(nCount--){} 37 }