本章参考资料:《STM32F7xx参考手册》RCC章节。
学习本章时,配合《STM32F7xx参考手册》RCC章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。
RCC :reset clock control 复位和时钟控制器。本章我们主要讲解时钟部分,特别是要着重理解时钟树,理解了时钟树,F767的一切时钟的来龙去脉都会了如指掌。
15.1 RCC主要作用—时钟部分
设置系统时钟SYSCLK、设置AHB分频因子(决定HCLK等于多少)、设置APB2分频因子(决定PCLK2等于多少)、设置APB1分频因子(决定PCLK1等于多少)、设置各个外设的分频因子;控制AHB、APB2和APB1这三条总线时钟的开启、控制每个外设的时钟的开启。对于SYSCLK、HCLK、PCLK2、PCLK1这四个时钟的配置一般是:HCLK = SYSCLK=PLLCLK = 216 MHz,PCLK1=HCLK/2 = 108 MHz,PCLK1=HCLK/4 = 54 MHz。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。
15.2 RCC框图剖析—时钟树
时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容易,而且记忆还更深刻。这里以配置系统时钟函数SystemClock_Config () 的编写流程来讲解时钟树。该函数的功能是利用外部晶振HSE=25 MHz把时钟倍频设置为:HCLK = SYSCLK=PLLCLK = 216 MHz,PCLK1=HCLK/2 =108 MHz,PCLK1=HCLK/4 = 54 MHz下面我们就以这个代码的流程为主线,来分析时钟树,代码流程在时钟树中以数字的大小顺序标识。
图 15-1 STM32F767时钟树1
图 15-2 STM32F767时钟树2
15.2.1 系统时钟
1. ①HSE高速外部时钟信号
HSE是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从4-26 MHz不等。当使用有源晶振时,时钟从OSC_IN引脚进入,OSC_OUT引脚悬空为高阻态,当选用无源晶振时,时钟从OSC_IN进入,OSC_OUT输出,并且要配谐振电容。HSE我们使用25 MHz的无源晶振。如果我们使用HSE或者HSE经过PLL倍频之后的时钟作为系统时钟SYSCLK,当HSE故障时候,不仅HSE会被关闭,PLL也会被关闭,此时高速的内部时钟时钟信号HSI会作为备用的系统时钟,直到HSE恢复正常,HSI=16 MHz。
2. ②锁相环PLL
PLL的主要作用是对时钟进行倍频,然后把时钟输出到各个功能部件。PLL有三个,分别是主PLL、 PLLSAI、PLLI2S,他们均由HSE或者HSI提供时钟输入信号。
主PLL有两路的时钟输出,第一个输出时钟PLLCLK用于系统时钟,F767里面最高是216 MHz,第二个输出用于USB OTG FS的时钟(48 MHz)、RNG和SDMMC时钟(<=48 MHz)。PLLI2S用于生成精确时钟,给I2S、SAI、SPDIFRX提供时钟。PLLSAI为SAI、LCD_TFT提供时钟,同时也可以产生PLLSAI48CLK时钟用于USB OTG FS的时钟(48 MHz)、RNG和SDMMC时钟。
HSE或者HSI经过PLL时钟输入分频因子M(2~63)分频后,成为VCO的时钟输入,VCO的时钟必须在1~2 MHz之间,我们选择HSE=25 MHz作为PLL的时钟输入,M设置为25,那么VCO输入时钟就等于1 MHz。
VCO输入时钟经过VCO倍频因子N倍频之后,成为VCO时钟输出,VCO时钟必须在192~432 MHz之间。我们配置N为432,则VCO的输出时钟等于432 MHz。
VCO输出时钟之后有三个分频因子:PLLCLK分频因子p,USB OTG FS/RNG/SDMMC时钟分频因子Q,分频因子R(F769才有,F767没有)产生DSI时钟驱动屏幕。p可以取值2、4、6、8,我们配置为2,则得到PLLCLK=216 MHz。Q可以取值4~15,但是USB OTG FS必须使用48M,Q=VCO输出时钟432/48=9。有关PLL的配置有一个专门的RCC PLL配置寄存器RCC_PLLCFGR,具体描述看手册即可。
PLL的时钟配置经过,稍微整理下可由如下公式表达:
VCOCLK_IN = PLLCLK_IN / M = HSE / 25 = 1 MHz
VCOCLK_OUT = VCOCLK_IN * N = 1M * 432 = 432 MHz
PLLCLK_OUT=VCOCLK_OUT/P=432/2=216 MHz
USBCLK = VCOCLK_OUT/Q=432/9=48。
3. ③系统时钟SYSCLK
系统时钟来源可以是:HSI、PLLCLK、HSE,具体的由时钟配置寄存器RCC_CFGR的SW位配置。我们这里设置系统时钟:SYSCLK = PLLCLK = 216 MHz。如果系统时钟是由HSE经过PLL倍频之后的PLLCLK得到,当HSE出现故障的时候,系统时钟会切换为HSI=16M,直到HSE恢复正常为止。
4. ④AHB总线时钟HCLK
系统时钟SYSCLK经过AHB预分频器分频之后得到时钟叫APB总线时钟,即HCLK,分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器RCC_CFGR的HPRE位设置。片上大部分外设的时钟都是经过HCLK分频得到,至于AHB总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好APB的时钟即可。我们这里设置为1分频,即HCLK=SYSCLK=216 MHz。
5. ⑤APB1总线时钟HCLK1
APB1总线时钟PCLK1由HCLK经过低速APB预分频器得到,分频因子可以是: [1,2,4,8,16],具体由时钟配置寄存器RCC_CFGR的PPRE1位设置。HCLK1属于低速的总线时钟,最高为54 MHz,片上低速的外设就挂载到这条总线上,比如LPTIM、USART2/3/4/5/7/8、SPI2/3,I2C1/2等。至于APB1总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好APB1的时钟即可。我们这里设置为4分频,即PCLK1 = HCLK/4 = 54 MHz。
6. ⑥APB2总线时钟HCLK2
APB2总线时钟PCLK2由HCLK经过高速APB2预分频器得到,分频因子可以是: [1,2,4,8,16],具体由时钟配置寄存器RCC_CFGR的PPRE2位设置。HCLK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的GPIO、USART1/6、SPI1等。至于APB2总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好APB2的时钟即可。我们这里设置为2分频,即PCLK2 = HCLK /2= 108 MHz。
7. 设置系统时钟库函数
上面的6个步骤对应的设置系统时钟库函数如下,为了方便阅读,已经把跟767不相关的代码删掉,把英文注释翻译成了中文。该函数是直接填写相应的结构体,最后调用HAL_RCC_OscConfig函数和HAL_RCC_ClockConfig函数就可以初始化时钟,这里需要注意的是,由于在 PLL 使能后主 PLL 配置参数便不可更改,所以建议先对 PLL 进行配置,然后再使能(选择 HSI 或 HSE 振荡器作为 PLL 时钟源,并配置分频系数 M、 N、 P 和 Q)。
代码 10 设置系统时钟库函数
1 /** 2 * @brief System Clock 配置 3 * system Clock 配置如下 : 4 * System Clock source = PLL (HSE) 5 * SYSCLK(Hz) = 216000000 6 * HCLK(Hz) = 216000000 7 * AHB Prescaler = 1 8 * APB1 Prescaler = 4 9 * APB2 Prescaler = 2 10 * HSE Frequency(Hz) = 25000000 11 * PLL_M = 25 12 * PLL_N = 432 13 * PLL_P = 2 14 * PLL_Q = 9 15 * VDD(V) = 3.3 16 * Main regulator output voltage = Scale1 mode 17 * Flash Latency(WS) = 7 18 * @param 无 19 * @retval 无 20 */ 21 void SystemClock_Config(void) 22 { 23 RCC_ClkInitTypeDef RCC_ClkInitStruct; 24 RCC_OscInitTypeDef RCC_OscInitStruct; 25 HAL_StatusTypeDef ret = HAL_OK; 26 27 /* 28 使能HSE,配置HSE为PLL的时钟源,配置PLL的各种分频 29 因子M N P Q 30 * PLLCLK = HSE/M*N/P = 25M / 25 *432 / 2 = 216M 31 */ 32 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; 33 RCC_OscInitStruct.HSEState = RCC_HSE_ON; 34 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 35 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; 36 RCC_OscInitStruct.PLL.PLLM = 25; 37 RCC_OscInitStruct.PLL.PLLN = 432; 38 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; 39 RCC_OscInitStruct.PLL.PLLQ = 9; 40 41 ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); 42 if (ret != HAL_OK) { 43 while (1) { 44 ; 45 } 46 } 47 48 /* 激活 OverDrive 模式以达到216M频率 */ 49 ret = HAL_PWREx_EnableOverDrive(); 50 if (ret != HAL_OK) { 51 while (1) { 52 ; 53 } 54 } 55 56 /* 选择PLLCLK作为SYSCLK,并配置 HCLK, PCLK1 and PCLK2 57 的时钟分频因子 58 * SYSCLK = PLLCLK = 216M 59 * HCLK = SYSCLK / 1 = 216M 60 * PCLK2 = SYSCLK / 2 = 108M 61 * PCLK1 = SYSCLK / 4 = 54M 62 */ 63 RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | 64 RCC_CLOCKTYPE_HCLK | 65 RCC_CLOCKTYPE_PCLK1 | 66 RCC_CLOCKTYPE_PCLK2); 67 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 68 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 69 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; 70 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; 71 72 ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7); 73 if (ret != HAL_OK) { 74 while (1) { 75 ; 76 } 77 } 78 }
15.2.2 其他时钟
通过对系统时钟设置的讲解,整个时钟树我们已经把握的有六七成,剩下的时钟部分我们讲解几个重要的。
1. RTC时钟
图 15-3 STM32F767 RTC和IWDG时钟
RTCCLK 时钟源可以是 HSE 1 MHz( HSE 由一个可编程的预分频器分频)、 LSE 或者 LSI时钟。选择方式是编程 RCC 备份域控制寄存器 (RCC_BDCR) 中的 RTCSEL[1:0] 位和 RCC时钟配置寄存器 (RCC_CFGR) 中的 RTCPRE[4:0] 位。所做的选择只能通过复位备份域的方式修改。我们通常的做法是由LSE给RTC提供时钟,大小为32.768KHZ。LSE由外接的晶体谐振器产生,所配的谐振电容精度要求高,不然很容易不起震。
2. 独立看门狗时钟
独立看门狗时钟由内部的低速时钟LSI提供,大小为32KHZ。
3. I2S时钟
I2S时钟可由外部的时钟引脚I2S_CKIN输入,也可由专用的PLLI2SCLK提供,具体的由RCC 时钟配置寄存器 (RCC_CFGR)的I2SSCR位配置。我们在使用I2S外设驱动W8978的时候,使用的时钟是PLLI2SCLK,这样就可以省掉一个有源晶振。
4. PHY以太网时钟
F767要想实现以太网功能,除了有本身内置的MAC之外,还需要外接一个PHY芯片,常见的PHY芯片有DP83848和LAN8720,其中DP83848支持MII和RMII接口,LAN8720只支持RMII接口。秉火F767开发板用的是RMII接口,选择的PHY芯片是LAN8720。使用RMII接口的好处是使用的IO减少了一半,速度还是跟MII接口一样。当使用RMII接口时,PHY芯片只需输出一路时钟给MCU即可,如果是MII接口,PHY芯片则需要提供两路时钟给MCU。
5. USB PHY 时钟
F767的USB没有集成PHY,要想实现USB高速传输的话,必须外置USB PHY芯片,常用的芯片是USB3300。当外接USB PHY芯片时,PHY芯片需要给MCU提供一个时钟。
外扩USB3300会占用非常多的IO,跟SDRAM和RGB888的IO会复用的很厉害,鉴于USB高速传输用的比较少,秉火767就没有外扩这个芯片。
6. MCO时钟输出
图 15-4 STM32F767 MCO时钟
MCO是microcontroller clock output的缩写,是微控制器时钟输出引脚,主要作用是可以对外提供时钟,相当于一个有源晶振。F767中有两个MCO,由PA8/PC9复用所得。MCO1所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO1PRE[2:0] 和 MCO1[1:0]位选择。MCO2所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO2PRE[2:0] 和 MCO2位选择。有关MCO的IO、时钟选择和输出速率的具体信息如下表所示:
时钟输出 | IO | 时钟来源 | 最大输出速率 |
MCO1 | PA8 | HSI、LSE、HSE、PLLCLK | 108MHz |
MCO2 | PC9 | HSE、PLLCLK、SYSCLK、PLLI2SCLK | 108MHz |
15.3 配置系统时钟实验
15.3.1 使用HSE
一般情况下,我们都是使用HSE,然后HSE经过PLL倍频之后作为系统时钟。F767系统时钟最高为216 MHz,这个是官方推荐的最高的稳定时钟。
如果我们使用库函数编程,当程序来到main函数首先调用SystemClock_Config ()函数把系统时钟初始化成216 MHz,SystemClock_Config ()在文件:main.c中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件。
15.3.2 使用HSI
当HSE直接或者间接(HSE经过PLL倍频)的作为系统时钟的时候,如果HSE发生故障,不仅HSE会被关闭,连PLL也会被关闭,这个时候系统会自动切换HSI作为系统时钟,此时SYSCLK=HSI=16M,如果没有开启CSS和CSS中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。
如果开启了CSS功能的话,那么可以当HSE故障时,在CSS中断里面采取补救措施,使用HSI,重新设置系统频率为216 MHz,让系统恢复正常使用。但这只是权宜之计,并非万全之策,最好的方法还是要采取相应的补救措施并报警,然后修复HSE。临时使用HSI只是为了把损失降低到最小,毕竟HSI较于HSE精度还是要低点。
F103系列中,使用HSI最大只能把系统设置为64M,并不能跟使用HSE一样把系统时钟设置为72M,究其原因是HSI在进入PLL倍频的时候必须2分频,导致PLL倍频因子调到最大也只能到64M,而HSE进入PLL倍频的时候则不用2分频。
在F767中,无论是使用HSI还是HSE都可以把系统时钟设置为216 MHz,因为HSE或者HSI在进入PLL倍频的时候都会被分频为1M之后再倍频。
还有一种情况是,有些用户不想用HSE,想用HSI,但是又不知道怎么用HSI来设置系统时钟,因为调用库函数都是使用HSE,下面我们给出个使用HSI配置系统时钟例子,起个抛砖引玉的作用。
15.3.3 硬件设计
1、RCC
2、LED一个
RCC是单片机内部资源,不需要外部电路。通过LED闪烁的频率来直观的判断不同系统时钟频率对软件延时的效果。
15.3.4 软件设计
我们编写两个RCC驱动文件,bsp_clkconfig.h和bsp_clkconfig.c,用来存放RCC系统时钟配置函数。
1. 编程要点
1、开启HSE/HSI ,并等待 HSE/HSI 稳定
2、设置 AHB、APB2、APB1的预分频因子
3、设置PLL的时钟来源,设置VCO输入时钟 分频因子PLL_M,设置VCO输出时钟
倍频因子PLL_N,设置PLLCLK时钟分频因子PLL_P,设置OTG FS,SDIO,RNG
时钟分频因子 PLL_Q
4、开启PLL,并等待PLL稳定
5、把PLLCK切换为系统时钟SYSCLK
6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
2. 代码分析
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。
使用HSE配置系统时钟
代码 11 HSE作为系统时钟来源
1 /* 2 * 使用HSE时,设置系统时钟的步骤 3 * 1、开启HSE ,并等待 HSE 稳定 4 * 2、设置 AHB、APB2、APB1的预分频因子 5 * 3、设置PLL的时钟来源 6 * 设置VCO输入时钟 分频因子 m 7 * 设置VCO输出时钟 倍频因子 n 8 * 设置PLLCLK时钟分频因子 p 9 * 设置OTG FS,SDIO,RNG时钟分频因子 q 10 * 4、开启PLL,并等待PLL稳定 11 * 5、把PLLCK切换为系统时钟SYSCLK 12 * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟 13 */ 14 15 /* 16 * m: VCO输入时钟 分频因子,取值2~63 17 * n: VCO输出时钟 倍频因子,取值50~432 18 * p: PLLCLK时钟分频因子 ,取值2,4,6,8 19 * q: OTG FS,SDIO,RNG时钟分频因子,取值4~15 20 * 函数调用举例,使用HSE设置时钟 21 * SYSCLK=HCLK=216MHz,PCLK2=HCLK/2=108MHz,PCLK1=HCLK/4=54MHz 22 * HSE_SetSysClock(25, 432, 2, 9); 23 * 24 HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常 25 的做法 26 */ 27 void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q) 28 { 29 RCC_ClkInitTypeDef RCC_ClkInitStruct; 30 RCC_OscInitTypeDef RCC_OscInitStruct; 31 HAL_StatusTypeDef ret = HAL_OK; 32 33 /* 34 使能HSE,配置HSE为PLL的时钟源,配置PLL的各种分频因 35 子M N P Q 36 * PLLCLK = HSE/M*N/P = 25M / 25 *432 / 2 = 216M 37 */ 38 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; 39 RCC_OscInitStruct.HSEState = RCC_HSE_ON; 40 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 41 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; 42 RCC_OscInitStruct.PLL.PLLM = m; 43 RCC_OscInitStruct.PLL.PLLN = n; 44 RCC_OscInitStruct.PLL.PLLP = p; 45 RCC_OscInitStruct.PLL.PLLQ = q; 46 47 ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); 48 if (ret != HAL_OK) { 49 while (1) { 50 ; 51 } 52 } 53 54 /* 激活 OverDrive 模式以达到216M频率 */ 55 ret = HAL_PWREx_EnableOverDrive(); 56 if (ret != HAL_OK) { 57 while (1) { 58 ; 59 } 60 } 61 62 /* 选择PLLCLK作为SYSCLK,并配置 HCLK, PCLK1 and PCLK2 63 的时钟分频因子 64 * SYSCLK = PLLCLK = 216M 65 * HCLK = SYSCLK / 1 = 216M 66 * PCLK2 = SYSCLK / 2 = 108M 67 * PCLK1 = SYSCLK / 4 = 54M 68 */ 69 RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | 70 RCC_CLOCKTYPE_HCLK | 71 RCC_CLOCKTYPE_PCLK1 | 72 RCC_CLOCKTYPE_PCLK2); 73 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 74 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 75 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; 76 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; 77 78 ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7); 79 if (ret != HAL_OK) { 80 while (1) { 81 ; 82 } 83 } 84 }
这个函数采用库函数编写, 代码理解参考注释即可。函数有4个形参m、n、p、q,具体说明如下:
形参 | 形参说明 | 取值范围 |
m | VCO输入时钟 分频因子 | 2~63 |
n | VCO输出时钟 倍频因子 | 192~432 |
p | PLLCLK时钟分频因子 | 2/4/6/8 |
q | OTG FS,SDIO,RNG时钟分频因子 | 4~15 |
HSE我们使用25M,参数m我们一般也设置为25,所以我们需要修改系统时钟的时候只需要修改参数n和p即可,SYSCLK=PLLCLK=HSE/m*n/p。
函数调用举例:HSE_SetSysClock(25, 400, 2, 7) 把系统时钟设置为200 MHz。HSE_SetSysClock(25, 432, 2, 9)把系统时钟设置为216M。
使用HSI配置系统时钟
1 /* 2 * 使用HSI时,设置系统时钟的步骤 3 * 1、开启HSI ,并等待 HSI 稳定 4 * 2、设置 AHB、APB2、APB1的预分频因子 5 * 3、设置PLL的时钟来源 6 * 设置VCO输入时钟 分频因子 m 7 * 设置VCO输出时钟 倍频因子 n 8 * 设置SYSCLK时钟分频因子 p 9 * 设置OTG FS,SDIO,RNG时钟分频因子 q 10 * 4、开启PLL,并等待PLL稳定 11 * 5、把PLLCK切换为系统时钟SYSCLK 12 * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟 13 */ 14 15 /* 16 * m: VCO输入时钟 分频因子,取值2~63 17 * n: VCO输出时钟 倍频因子,取值50~432 18 * p: PLLCLK时钟分频因子 ,取值2,4,6,8 19 * q: OTG FS,SDIO,RNG时钟分频因子,取值4~15 20 * 函数调用举例,使用HSI设置时钟 21 * SYSCLK=HCLK=216MHz,PCLK2=HCLK/2=108M,PCLK1=HCLK/4=54MHz 22 * HSI_SetSysClock(16, 432, 2, 7); 23 24 */ 25 void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q) 26 { 27 RCC_ClkInitTypeDef RCC_ClkInitStruct; 28 RCC_OscInitTypeDef RCC_OscInitStruct; 29 HAL_StatusTypeDef ret = HAL_OK; 30 31 /* 32 使能HSE,配置HSE为PLL的时钟源,配置PLL的各种分频因 33 子M N P Q 34 * PLLCLK = HSE/M*N/P = 25M / 25 *432 / 2 = 216M 35 */ 36 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 37 RCC_OscInitStruct.HSIState = RCC_HSI_ON; 38 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 39 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; 40 RCC_OscInitStruct.PLL.PLLM = m; 41 RCC_OscInitStruct.PLL.PLLN = n; 42 RCC_OscInitStruct.PLL.PLLP = p; 43 RCC_OscInitStruct.PLL.PLLQ = q; 44 45 ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); 46 if (ret != HAL_OK) { 47 while (1) { 48 ; 49 } 50 } 51 52 /* 激活 OverDrive 模式以达到216M频率 */ 53 ret = HAL_PWREx_EnableOverDrive(); 54 if (ret != HAL_OK) { 55 while (1) { 56 ; 57 } 58 } 59 60 /* 选择PLLCLK作为SYSCLK,并配置 HCLK, PCLK1 and PCLK2 61 的时钟分频因子 62 * SYSCLK = PLLCLK = 216M 63 * HCLK = SYSCLK / 1 = 216M 64 * PCLK2 = SYSCLK / 2 = 108M 65 * PCLK1 = SYSCLK / 4 = 54M 66 */ 67 RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | 68 RCC_CLOCKTYPE_HCLK | 69 RCC_CLOCKTYPE_PCLK1 | 70 RCC_CLOCKTYPE_PCLK2); 71 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 72 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 73 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; 74 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; 75 76 ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7); 77 if (ret != HAL_OK) { 78 while (1) { 79 ; 80 } 81 } 82 }
这个函数采用库函数编写, 代码理解参考注释即可。函数有4个形参m、n、p、q,具体说明如下:
形参 | 形参说明 | 取值范围 |
m | VCO输入时钟 分频因子 | 2~63 |
n | VCO输出时钟 倍频因子 | 192~432 |
p | PLLCLK时钟分频因子 | 2/4/6/8 |
q | OTG FS,SDIO,RNG时钟分频因子 | 4~15 |
HSI为16M,参数m我们一般也设置为16,所以我们需要修改系统时钟的时候只需要修改参数n和p即可,SYSCLK=PLLCLK=HSI/m*n/p。
函数调用举例:HSI_SetSysClock(16, 400, 2, 7) 把系统时钟设置为200MHz,。HSI_SetSysClock(16, 432, 2, 9)把系统时钟设置为216MHz。
软件延时
1 void Delay(__IO uint32_t nCount) 2 { 3 for (; nCount != 0; nCount--); 4 }
软件延时函数,使用不同的系统时钟,延时时间不一样,可以通过LED闪烁的频率来判断。
MCO输出
在F767中,PA8/PC9可以复用为MCO1/2引脚,对外提供时钟输出,我们也可以用示波器监控该引脚的输出来判断我们的系统时钟是否设置正确。HAL库有现成的库函数HAL_RCC_MCOConfig,配置MCO,只需确定输出引脚,输出时钟源,以及分频系数就可以输出时钟使用非常方便。
主函数
在主函数中,可以调用HSE_SetSysClock()或者HSI_SetSysClock()这两个函数把系统时钟设置成各种常用的时钟,然后通过MCO引脚监控,或者通过LED闪烁的快慢体验不同的系统时钟对同一个软件延时函数的影响。
1 int main(void) 2 { 3 4 // 5 程序来到main函数之前,启动文件:statup_stm32f746xx. 6 s已经调用 7 // SystemInit()函数把系统时钟初始化成16MHZ 8 // SystemInit()在system_stm32f7xx.c中定义 9 // 如果用户想修改系统时钟,可自行编写程序修改 10 // 11 重新设置系统时钟,这时候可以选择使用HSE还是HSI 12 13 //系统时钟设置为216M,最高是250M 14 HSE_SetSysClock(25, 432, 2, 9); 15 16 // 使用HSI,配置系统时钟为216M 17 //HSI_SetSysClock(16, 50, 2, 9); 18 19 // LED 端口初始化 20 LED_GPIO_Config(); 21 22 // MCO1 输出PLLCLK 23 HAL_RCC_MCOConfig(RCC_MCO1,RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_2); 24 25 // MCO2 输出SYSCLK 26 HAL_RCC_MCOConfig(RCC_MCO2,RCC_MCO2SOURCE_SYSCLK, RCC_MCODIV_2); 27 28 while (1) { 29 LED1( ON ); // 亮 30 Delay(0x0FFFFF); 31 LED1( OFF ); // 灭 32 Delay(0x0FFFFF); 33 } 34 }
15.3.5 下载验证
15.3.6 下载验证
把编译好的程序下载到开发板,可以看到设置不同的系统时钟时,LED闪烁的快慢不一样。更精确的数据我们可以用示波器监控MCO引脚看到。