文章目录

  • 1. RCC 主要作用——时钟部分
  • 2. RCC 框图剖析—时钟部分
    • 2.1 系统时钟
      • ① HSE 高速外部时钟信号
      • ② PLL 时钟源
      • ③ PLL 时钟 PLLCLK
      • ④ 系统时钟 SYSCLK
      • ⑤ AHB 总线时钟 HCLK
      • ⑥ APB1 总线时钟 HCLK1
      • ⑦ APB2 总线时钟 HCLK2
      • 设置系统时钟库函数
    • 2.2 其他时钟
      • 2.2.1 USB 时钟
      • 2.2.2 Cortex 系统时钟
      • 2.2.3 ADC 时钟
      • 2.2.4 RTC 时钟、独立看门狗时钟
      • 2.2.5 MCO 时钟输出
  • 3. 配置系统时钟实验
    • 3.1 使用 HSE
    • 3.2 使用 HSI
    • 3.3 硬件设计
    • 3.4 软件设计
      • 3.4.1 编程要点
      • 3.4.2 代码分析
      • 3.4.3 代码整理
      • 3.4.3 注意事项

RCC : reset clock control 复位和时钟控制器。

1. RCC 主要作用——时钟部分

设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少) 、 设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子;控制 AHB、 APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。对于 SYSCLK、 HCLK、 PCLK2、 PCLK1 这四个时钟的配置一般是: PCLK2 = HCLK = SYSCLK = PLLCLK = 72M, PCLK1 = HCLK/2 = 36M。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。

2. RCC 框图剖析—时钟部分

时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容易 ,而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数: SetSysClockTo72(); 以这个函数的编写流程来讲解时钟树,这个函数也是我们用库的时候默认的系统时钟设置函数。该函数的功能是利用 HSE 把时钟设置为: PCLK2 = HCLK = SYSCLK = 72M, PCLK1 = HCLK/2 = 36M。下面我们就以这个代码的流程为主线, 来分析时钟树,对应的是图中的黄色部分,代码流程在时钟树中以数字的大小顺序标识。


图 16-1 STM32 时钟树

2.1 系统时钟

① HSE 高速外部时钟信号

HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4 - 16MHZ 不等。当使用有源晶振时,时钟从 OSC_IN 引脚进入, OSC_OUT 引脚悬空,当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT 进入,并且要配谐振电容。

HSE 最常使用的就是 8M 的无源晶振。当确定 PLL 时钟来源的时候,HSE 可以不分频或者 2 分频,这个由时钟配置寄存器 CFGR 的位 17: PLLXTPRE 设置,我们设置为 HSE 不分频。

② PLL 时钟源

PLL 时钟来源可以有两个,一个来自 HSE,另外一个是 HSI/2,具体用哪个由时钟配置寄存器 CFGR 的位 16: PLLSRC 设置。 HSI 是内部高速的时钟信号,频率为 8M,根据温度和环境的情况频率会有漂移,一般不作为 PLL 的时钟来源。这里我们选 HSE 作为 PLL 的时钟来源。

③ PLL 时钟 PLLCLK

通过设置 PLL 的倍频因子,可以对 PLL 的时钟来源进行倍频,倍频因子可以是: [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],具体设置成多少, 由时钟配置寄存器 CFGR 的位 21-18: PLLMUL[3:0] 设置。我们这里设置为 9 倍频,因为上一步我们设置 PLL 的时钟来源为 HSE = 8M,所以经过 PLL 倍频之后的 PLL 时钟: PLLCLK = 8M * 9 = 72M。 72M 是 ST 官方推荐的稳定运行时钟,如果你想超频的话,增大倍频因子即可,最高为 128M。我们这里设置 PLL 时钟: PLLCLK = 8M * 9 = 72M。

④ 系统时钟 SYSCLK

系统时钟来源可以是: HSI、 PLLCLK、 HSE,具体的时钟配置寄存器 CFGR 的位 1-0:SW[1:0]设置。我们这里设置系统时钟: SYSCLK = PLLCLK = 72M。

⑤ AHB 总线时钟 HCLK

系统时钟 SYSCLK 经过 AHB 预分频器分频之后得到时钟叫 APB 总线时钟,即 HCLK,分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器 CFGR 的位 7-4 : HPRE[3:0] 设置。片上大部分外设的时钟都是经过 HCLK 分频得到,至于 AHB 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB 的时钟即可。 我们这里设置为 1 分频,即 HCLK = SYSCLK = 72M。

⑥ APB1 总线时钟 HCLK1

APB1 总线时钟 PCLK1 由 HCLK 经过低速 APB 预分频器得到,分频因子可以是: [1,2,4,8,16],具体的由时钟配置寄存器 CFGR 的位 10-8: PRRE1[2:0] 决定。HCLK1 属于低速的总线时钟,最高为 36M,片上低速的外设就挂载到这条总线上,比如 USART2/3/4/5、 SPI2/3, I2C1/2 等。至于 APB1 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB1 的时钟即可。我们这里设置为 2 分频,即 PCLK1 = HCLK/2 = 36M。

⑦ APB2 总线时钟 HCLK2

APB2 总线时钟 PCLK2 由 HCLK 经过高速 APB2 预分频器得到,分频因子可以是: [1,2,4,8,16],具体由时钟配置寄存器 CFGR 的位 13-11: PPRE2[2:0] 决定。 HCLK2 属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的 GPIO、 USART1、 SPI1 等。至于 APB2 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB2 的时钟即可。我们这里设置为 1 分频,即 PCLK2 = HCLK = 72M。

设置系统时钟库函数

上面的 7 个步骤对应的设置系统时钟库函数如下, 该函数截取自固件库文件 system_stm32f10x.c。 为了方便阅读,我已把互联型相关的代码删掉,把英文注释翻译成了中文,并把代码标上了序号,总共七个步骤。该函数是直接操作寄存器的,有关寄存器部分请参考数据手册的 RCC 的寄存器描述部分。

代码 16-1 设置系统时钟库函数

static void SetSysClockTo72(void)
{__IO uint32_t StartUpCounter = 0, HSEStatus = 0;// ① 使能 HSE,并等待 HSE 稳定RCC->CR |= ((uint32_t)RCC_CR_HSEON);// 等待 HSE 启动稳定,并做超时处理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;}// HSE 启动成功,则继续往下处理if (HSEStatus == (uint32_t)0x01){//-----------------------------------------------------------// 使能 FLASH 预存取缓冲区 */FLASH->ACR |= FLASH_ACR_PRFTBE;// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了// 0: 0 < SYSCLK <= 24M// 0: 0 < SYSCLK <= 24M// 2: 48< SYSCLK <= 72M */FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;//------------------------------------------------------------// ② 设置 AHB、 APB2、 APB1 预分频因子// HCLK = SYSCLKRCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;// PCLK2 = HCLKRCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;// PCLK1 = HCLK/2RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;// ③ 设置 PLL 时钟来源,设置 PLL 倍频因子, PLLCLK = HSE * 9 = 72 MHzRCC->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);// ④ 使能 PLLRCC->CR |= RCC_CR_PLLON;// ⑤ 等待 PLL 稳定while ((RCC->CR & RCC_CR_PLLRDY) == 0){}// ⑥ 选择 PLL 作为系统时钟来源RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;// ⑦ 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟while ((RCC->CFGR&(uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){}}else{// 如果 HSE 启动失败,用户可以在这里添加错误代码出来}
}

2.2 其他时钟

通过对系统时钟设置的讲解,整个时钟树我们已经把握的有六七成,剩下的时钟部分我们讲解几个重要的。

2.2.1 USB 时钟

USB 时钟是由 PLLCLK 经过 USB 预分频器得到,分频因子可以是: [1,1.5], 具体的由时钟配置寄存器 CFGR 的位 22: USBPRE 配置。 USB 的时钟最高是 48M,根据分频因子反推过来算 , PLLCLK 只能是 48M 或者是 72M。一般我们设置 PLLCLK = 72M,USBCLK = 48M。 USB 对时钟要求比较高,所以 PLLCLK 只能是由 HSE 倍频得到,不能使用 HSI 倍频。

2.2.2 Cortex 系统时钟

Cortex 系统时钟由 HCLK 8 分频得到,等于 9M, Cortex 系统时钟用来驱动内核的系统定时器 SysTick, SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。

2.2.3 ADC 时钟

ADC 时钟由 PCLK2 经过 ADC 预分频器得到,分频因子可以是 [2,4,6,8],具体的由时钟配置寄存器 CFGR 的位 15-14: ADCPRE[1:0] 决定。 很奇怪的是怎么没有 1 分频。 ADC 时钟最高只能是 14M,如果采样周期设置成最短的 1.5 个周期的话, ADC 的转换时间可以达到最短的 1us。如果真要达到最短的转换时间 1us 的话,那 ADC 的时钟就得是 14M,反推 PCLK2 的时钟只能是: 28M、 56M、 84M、 112M,鉴于 PCLK2 最高是 72M,所以只能取 28M 和 56M。

2.2.4 RTC 时钟、独立看门狗时钟

RTC 时钟可由 HSE/128 分频得到,也可由低速外部时钟信号 LSE 提供,频率为 32.768KHZ,也可由低速内部时钟信号 HSI 提供,具体选用哪个时钟由备份域控制寄存器 BDCR 的位 9-8: RTCSEL[1:0] 配置。 独立看门狗的时钟由 LSI 提供,且只能是由 LSI 提供,LSI 是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取 40KHZ。

2.2.5 MCO 时钟输出

MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1 系列中 由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。 MCO 的时钟来源可以是: PLLCLK/2、 HSI、 HSE、 SYSCLK,具体选哪个由时钟配置寄存器 CFGR 的位 26-24: MCO[2:0] 决定。 除了对外提供时钟这个作用之外, 我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。

3. 配置系统时钟实验

3.1 使用 HSE

一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。通常的配置是: HSE = 8M, PLL 的倍频因子为: 9,系统时钟就设置成: SYSCLK = 8M * 9 = 72M。使用 HSE,系统时钟 SYSCLK 最高是 128M。我们使用的库函数就是这么干的, 当程序来到 main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用 SystemInit() 函数把系统时钟初始化成 72MHZ, SystemInit() 在库文件: system_stm32f10x.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。

3.2 使用 HSI

当 HSE 故障的时候, 如果 PLL 的时钟来源是 HSE, 那么当 HSE 故障的时候,不仅 HSE 不能使用,连 PLL 也会被关闭,这个时候系统会自动切换 HSI 作为系统时钟,此时 SYSCLK = HSI = 8M,如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施,使用 HSI ,并把系统时钟设置为更高的频率,最高是 64M,64M 的频率足够一般的外设使用,如: ADC 、 SPI、 I2C 等。但是这里就又有一个问题了,原来 SYSCLK = 72M,现在因为故障改成 64M,那么那些外设的时钟肯定被改变了,那么外设工作就会被打乱,那我们是不是在设置 HSI 时钟的时候,也重新调整外设总线的分频因子,即 AHB, APB2 和 APB1 的分频因子,使外设的时钟达到跟 HSE 没有故障之前一样。但是这个也不是最保障的办法,毕竟不能一直使用 HSI ,所以当 HSE 故障时还是要采取报警措施。

还有一种情况是,有些用户不想用 HSE ,想用 HSI ,但是又不知道怎么用 HSI 来设置系统时钟,因为调用库函数都是使用 HSE,下面我们给出个使用 HSI 配置系统时钟例子,起个抛砖引玉的作用。

3.3 硬件设计

① RCC
② LED 一个

RCC 是单片机内部资源,不需要外部电路。通过 LED 闪烁的频率来直观的判断不同系统时钟频率对软件延时的效果。

3.4 软件设计

我们编写两个 RCC 驱动文件, bsp_clkconfig.h 和 bsp_clkconfig.c,用来存放 RCC 系统时钟配置函数。

3.4.1 编程要点

编程要点对应着时钟树图中的序号。

① 开启 HSE/HSI , 并等待 HSE/HSI 稳定
② 设置 AHB、 APB2、 APB1 的预分频因子
③ 设置 PLL 的时钟来源,和 PLL 的倍频因子,设置各种频率主要就是在这里设置
④ 开启 PLL,并等待 PLL 稳定
⑤ 把 PLLCK 切换为系统时钟 SYSCLK
⑥ 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟

3.4.2 代码分析

使用 HSE 配置系统时钟

代码 16-2 HSE 作为系统时钟来源

void HSE_SetSysClock(uint32_t pllmul)
{IO uint32_t StartUpCounter = 0, HSEStartUpStatus = 0;// 把 RCC 外设初始化成复位状态RCC_DeInit();// 使能 HSE,开启外部晶振, 野火 STM32F103 系列开发板用的是 8MRCC_HSEConfig(RCC_HSE_ON);// 等待 HSE 启动稳定HSEStartUpStatus = RCC_WaitForHSEStartUp();// 只有 HSE 稳定之后则继续往下执行if (HSEStartUpStatus == SUCCESS){
//-----------------------------------------------------------------//// 这两句是操作 FLASH 闪存用到的,如果不操作 FLASH,这两个注释掉也没影响// 使能 FLASH 预存取缓冲区FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了// 0: 0 < SYSCLK <= 24M// 1: 24< SYSCLK <= 48M// 2: 48< SYSCLK <= 72MFLASH_SetLatency(FLASH_Latency_2);
//-----------------------------------------------------------------//// AHB 预分频因子设置为 1 分频, HCLK = SYSCLKRCC_HCLKConfig(RCC_SYSCLK_Div1);// APB2 预分频因子设置为 1 分频, PCLK2 = HCLKRCC_PCLK2Config(RCC_HCLK_Div1);// APB1 预分频因子设置为 1 分频, PCLK1 = HCLK/2RCC_PCLK1Config(RCC_HCLK_Div2);//-----------------设置各种频率主要就是在这里设置-------------------//// 设置 PLL 时钟来源为 HSE,设置 PLL 倍频因子// PLLCLK = 8MHz * pllmulRCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);
//-------------------------------------------------------------//// 开启 PLLRCC_PLLCmd(ENABLE);// 等待 PLL 稳定while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}// 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLKRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟while (RCC_GetSYSCLKSource() != 0x08){}}else{// 如果 HSE 开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理// 当 HSE 开启失败或者故障的时候,单片机会自动把 HSI 设置为系统时钟,// HSI 是内部的高速时钟,8MHZwhile (1){}}
}

这个函数采用库函数编写, 函数有个形参 pllmul, pllmul 用来设置 PLL 的倍频因子,在调用的时候形参可以是: RCC_PLLMul_x , x:[2,3,…16],这些宏来源于库函数的定义,宏展开是一些 32 位的十六进制数,具体功能是配置了时钟配置寄存器 CFGR 的位 21-18 PLLMUL[3:0],预先定义好倍频因子,方便调用。

函数调用举例:HSE_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为: 8MHZ * 9 = 72MHZ。 HSE_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为: 8MHZ * 16 = 128MHZ 超频慎用。

使用 HSI 配置系统时钟

void HSI_SetSysClock(uint32_t pllmul)
{IO uint32_t HSIStartUpStatus = 0;// 把 RCC 外设初始化成复位状态RCC_DeInit();// 使能 HSIRCC_HSICmd(ENABLE);// 等待 HSI 就绪HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;// 只有 HSI 就绪之后则继续往下执行if (HSIStartUpStatus == RCC_CR_HSIRDY){//-------------------------------------------------------------//// 这两句是操作 FLASH 闪存用到的,如果不操作 FLASH,这两个注释掉也没影响// 使能 FLASH 预存取缓冲区FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了// 0: 0 < SYSCLK <= 24M// 1: 24< SYSCLK <= 48M// 2: 48< SYSCLK <= 72MFLASH_SetLatency(FLASH_Latency_2);//------------------------------------------------------------//// AHB 预分频因子设置为 1 分频, HCLK = SYSCLKRCC_HCLKConfig(RCC_SYSCLK_Div1);// APB2 预分频因子设置为 1 分频, PCLK2 = HCLKRCC_PCLK2Config(RCC_HCLK_Div1);// APB1 预分频因子设置为 1 分频, PCLK1 = HCLK/2RCC_PCLK1Config(RCC_HCLK_Div2);//-----------设置各种频率主要就是在这里设置-------------------//// 设置 PLL 时钟来源为 HSE,设置 PLL 倍频因子// PLLCLK = 4MHz * pllmulRCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);//-------------------------------------------------------//// 开启 PLLRCC_PLLCmd(ENABLE);// 等待 PLL 稳定while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){            }// 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLKRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟while (RCC_GetSYSCLKSource() != 0x08){            }        }else{// 如果 HSI 开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理// 当 HSE 开启失败或者故障的时候,单片机会自动把 HSI 设置为系统时钟,// HSI 是内部的高速时钟, 8MHZwhile (1){            }}
}

HSI 设置系统时钟函数跟 HSE 设置系统时钟函数在原理上是一样的,有一个区别的地方就是, HSI 必须 2 分频之后才能作为 PLL 的时钟来源,所以使用 HSI 时,最大的系统时钟 SYSCLK 只能是 HSI / 2 * 16 = 4 * 16 = 64MHZ。

函数调用举例: HSI_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为: 4MHZ * 9 = 36MHZ。

软件延时

void Delay(__IO uint32_t nCount)
{for (; nCount != 0; nCount--);
}

软件延时函数,使用不同的系统时钟,延时时间不一样,可以通过 LED 闪烁的频率来判断。

MCO 输出

在 STM32F103 系列中, PA8 可以复用为 MCO 引脚,对外提供时钟输出,我们也可以用示波器监控该引脚的输出来判断我们的系统时钟是否设置正确。

代码 16-3 MCO GPIO 初始化

/** 初始化 MCO 引脚 PA8* 在 F103 系列中 MCO 引脚只有一个,即 PA8,在 F4 系列中, MCO 引脚有两个
*/
void MCO_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;// 开启 GPIOA 的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 选择 GPIO8 引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;// 设置为复用功能推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 设置 IO 的翻转速率为 50M.GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 初始化 GPIOA8GPIO_Init(GPIOA, &GPIO_InitStructure);
}

代码 16-4 MCO 输出时钟选择

// 设置 MCO 引脚输出时钟,用示波器即可在 PA8 测量到输出的时钟信号,
// 我们可以把 PLLCLK/2 作为 MCO 引脚的时钟来检测系统时钟是否配置准确
// MCO 引脚输出可以是 HSE,HSI,PLLCLK/2,SYSCLK
// RCC_MCOConfig(RCC_MCO_HSE);
// RCC_MCOConfig(RCC_MCO_HSI);
// RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);
RCC_MCOConfig(RCC_MCO_SYSCLK);

我们初始化 MCO 引脚之后,可以直接调用库函数 RCC_MCOConfig() 来选择 MCO 时钟来源。

主函数

int main(void)
{// 程序来到 main 函数之前,启动文件: statup_stm32f10x_hd.s 已经调用// SystemInit()函数把系统时钟初始化成 72MHZ// SystemInit()在 system_stm32f10x.c 中定义// 如果用户想修改系统时钟,可自行编写程序修改// 重新设置系统时钟,这时候可以选择使用 HSE 还是 HSI// 使用 HSE 时, SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16],最高是 128MHSE_SetSysClock(RCC_PLLMul_9);// 使用 HSI 时, SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,...16],最高是 64MH//HSI_SetSysClock(RCC_PLLMul_16);// MCO 引脚初始化MCO_GPIO_Config();// 设置 MCO 引脚输出时钟,用示波器即可在 PA8 测量到输出的时钟信号,// 我们可以把 PLLCLK/2 作为 MCO 引脚的时钟来检测系统时钟是否配置准确// MCO 引脚输出可以是 HSE,HSI,PLLCLK/2,SYSCLK// RCC_MCOConfig(RCC_MCO_HSE);// RCC_MCOConfig(RCC_MCO_HSI);// RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);RCC_MCOConfig(RCC_MCO_SYSCLK);// LED 端口初始化LED_GPIO_Config();while (1){LED1( ON ); // 亮Delay(0x0FFFFF);LED1( OFF ); // 灭Delay(0x0FFFFF);}
}

在主函数中,可以调用 HSE_SetSysClock() 或者 HSI_SetSysClock() 这两个函数把系统时钟设置成各种常用的时钟,然后通过 MCO 引脚监控,或者通过 LED 闪烁的快慢体验不同的系统时钟对同一个软件延时函数的影响。

3.4.3 代码整理

bsp_led.h

#ifndef __LED_H
#define   __LED_H#include "stm32f10x.h"void LED_Init(void);
void LED0_Level(unsigned char Level);#endif /* __LED_H */

bsp_led.c

#include "bsp_led.h"/*******************************************************************************
* 函 数 名         : LED_Init
* 函数功能         : LED初始化函数
* 输    入         : 无
* 输    出         : 无
* 说 明          :无
*******************************************************************************/
void LED_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能PB端口时钟GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;            // LED0-->PB.5 端口配置GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;   // 推挽输出GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; // IO口速度为50MHzGPIO_Init(GPIOB,&GPIO_InitStructure);          // 根据设定参数初始化GPIOB.5GPIO_SetBits(GPIOB,GPIO_Pin_5);                   // PB.5 输出高
}/*******************************************************************************
* 函 数 名         : LED0_Level
* 函数功能         : LED0——IO电平输出
* 输    入         : Level: 1 —— 输出高;   0 —— 输出低
* 输    出         : 无
* 说 明          :无
*******************************************************************************/
void LED0_Level(unsigned char Level)
{if (Level == 1){// PB.5 输出高GPIO_SetBits(GPIOB,GPIO_Pin_5);}else if (Level == 0){// PB.5 输出低GPIO_ResetBits(GPIOB,GPIO_Pin_5);}
}

bsp_mcooutput.h

#ifndef __MCOOUTPUT_H
#define   __MCOOUTPUT_H#include "stm32f10x.h"void MCO_GPIO_Config(void);#endif /* __MCOOUTPUT */

bsp_mcooutput.c

#include "bsp_mcooutput.h"/*******************************************************************************
* 函 数 名         : MCO_GPIO_Config
* 函数功能         : 初始化MCO引脚PA8函数
* 输    入         : 无
* 输    出         : 无
* 说 明          :在F1系列中MCO引脚只有一个,即PA8,在F4系列中,MCO引脚会有两个
*******************************************************************************/
void MCO_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;// 开启GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 选择GPIO8引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//设置为复用功能推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//设置IO的翻转速率为50MGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 初始化GPIOA8GPIO_Init(GPIOA, &GPIO_InitStructure);
}

bsp_clkconfig.h

#ifndef __CLKCONFIG_H
#define   __CLKCONFIG_H#include "stm32f10x.h"
#include "stm32f10x_rcc.h"void HSE_SetSysClock(uint32_t pllmul);
void HSI_SetSysClock(uint32_t pllmul);#endif /* __CLKCONFIG_H */

bsp_clkconfig.c

#include "bsp_clkconfig.h"/** 使用HSE时,设置系统时钟的步骤* 1、开启HSE ,并等待 HSE 稳定* 2、设置 AHB、APB2、APB1的预分频因子* 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置* 4、开启PLL,并等待PLL稳定* 5、把PLLCK切换为系统时钟SYSCLK* 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟*//* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1* PCLK2 = HCLK = SYSCLK* PCLK1 = HCLK/2,最高只能是36M* 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]* 举例:User_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:8MHZ * 9 = 72MHZ*       User_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ,超频慎用** HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法*//* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1* PCLK2 = HCLK = SYSCLK* PCLK1 = HCLK/2,最高只能是36M* 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]* 举例:HSE_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:8MHZ * 9 = 72MHZ*       HSE_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ,超频慎用** HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法*/
void HSE_SetSysClock(uint32_t pllmul)
{__IO uint32_t StartUpCounter = 0, HSEStartUpStatus = 0;// 把RCC外设初始化成复位状态,这句是必须的RCC_DeInit();//使能HSE,开启外部晶振,野火开发板用的是8MRCC_HSEConfig(RCC_HSE_ON);// 等待 HSE 启动稳定HSEStartUpStatus = RCC_WaitForHSEStartUp();// 只有 HSE 稳定之后则继续往下执行if (HSEStartUpStatus == SUCCESS){//----------------------------------------------------------------------//// 使能FLASH 预存取缓冲区// FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了// 0:0 < SYSCLK <= 24M// 1:24< SYSCLK <= 48M// 2:48< SYSCLK <= 72MFLASH_SetLatency(FLASH_Latency_2);//----------------------------------------------------------------------//// AHB预分频因子设置为1分频,HCLK = SYSCLKRCC_HCLKConfig(RCC_SYSCLK_Div1); // APB2预分频因子设置为1分频,PCLK2 = HCLKRCC_PCLK2Config(RCC_HCLK_Div1); // APB1预分频因子设置为1分频,PCLK1 = HCLK/2RCC_PCLK1Config(RCC_HCLK_Div2);//-----------------设置各种频率主要就是在这里设置-------------------//// 设置PLL时钟来源为HSE,设置PLL倍频因子// PLLCLK = 8MHz * pllmulRCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);//------------------------------------------------------------------//// 开启PLLRCC_PLLCmd(ENABLE);// 等待 PLL稳定while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLKRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 读取时钟切换状态位,确保PLLCLK被选为系统时钟while (RCC_GetSYSCLKSource() != 0x08){}}else{ // 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,// HSI是内部的高速时钟,8MHZwhile (1){}}
}/** 使用HSI时,设置系统时钟的步骤* 1、开启HSI ,并等待 HSI 稳定* 2、设置 AHB、APB2、APB1的预分频因子* 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置* 4、开启PLL,并等待PLL稳定* 5、把PLLCK切换为系统时钟SYSCLK* 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟*//* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1* PCLK2 = HCLK = SYSCLK* PCLK1 = HCLK/2,最高只能是36M* 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]* 举例:HSI_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:4MHZ * 9 = 72MHZ*       HSI_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:4MHZ * 16 = 64MHZ** HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法* HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况* 如果HSI要作为PLL时钟的来源的话,必须二分频之后才可以,即HSI/2,而PLL倍频因子最大只能是16* 所以当使用HSI的时候,SYSCLK最大只能是4M*16=64M*/
void HSI_SetSysClock(uint32_t pllmul)
{__IO uint32_t HSIStartUpStatus = 0;// 把RCC外设初始化成复位状态,这句是必须的RCC_DeInit();//使能HSIRCC_HSICmd(ENABLE);// 等待 HSI 就绪HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;// 只有 HSI就绪之后则继续往下执行if (HSIStartUpStatus == RCC_CR_HSIRDY){//----------------------------------------------------------------------//// 使能FLASH 预存取缓冲区// FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了// 0:0 < SYSCLK <= 24M// 1:24< SYSCLK <= 48M// 2:48< SYSCLK <= 72MFLASH_SetLatency(FLASH_Latency_2);//----------------------------------------------------------------------//// AHB预分频因子设置为1分频,HCLK = SYSCLKRCC_HCLKConfig(RCC_SYSCLK_Div1); // APB2预分频因子设置为1分频,PCLK2 = HCLKRCC_PCLK2Config(RCC_HCLK_Div1); // APB1预分频因子设置为1分频,PCLK1 = HCLK/2RCC_PCLK1Config(RCC_HCLK_Div2);//-----------------设置各种频率主要就是在这里设置-------------------//// 设置PLL时钟来源为HSE,设置PLL倍频因子// PLLCLK = 4MHz * pllmulRCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);//------------------------------------------------------------------//// 开启PLLRCC_PLLCmd(ENABLE);// 等待 PLL稳定while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLKRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 读取时钟切换状态位,确保PLLCLK被选为系统时钟while (RCC_GetSYSCLKSource() != 0x08){}}else{ // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,// HSI是内部的高速时钟,8MHZwhile (1){}}
}

delay.h

#ifndef __DELAY_H
#define __DELAY_H#include "stm32f10x.h"void Delay(__IO uint32_t nCount);#endif

delay.c

#include "delay.h"void Delay(__IO uint32_t nCount)     //简单的延时函数
{for(; nCount != 0; nCount--);
}

main.c

/* * 配置MCO引脚:PA8 对外提供时钟,最高频率不能超过IO口的翻转频率50MHZ* MCO 时钟来源可以是:PLLCLK/2 ,HSI,HSE,SYSCLK*/
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_clkconfig.h"
#include "bsp_mcooutput.h"
#include "delay.h"/*** @brief  主函数* @param  无  * @retval 无*/
int main(void)
{    // 程序来到main函数之前,启动文件:statup_stm32f10x_hd.s已经调用// SystemInit()函数把系统时钟初始化成72MHZ// SystemInit()在system_stm32f10x.c中定义// 如果用户想修改系统时钟,可自行编写程序修改// 重新设置系统时钟,这时候可以选择使用HSE还是HSI// 使用HSE时,SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16],最高是128MHSE_SetSysClock(RCC_PLLMul_9);// 使用HSI时,SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,...16],最高是64MH// HSI_SetSysClock(RCC_PLLMul_16);// MCO 引脚初始化MCO_GPIO_Config();// 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号,// 我们可以把PLLCLK/2作为MCO引脚的时钟来检测系统时钟是否配置准确// MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK//RCC_MCOConfig(RCC_MCO_HSE);//RCC_MCOConfig(RCC_MCO_HSI);//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);RCC_MCOConfig(RCC_MCO_SYSCLK);            // LED 端口初始化LED_Init();while (1){LED0_Level(0);Delay(0x0FFFFF);LED0_Level(1);Delay(0x0FFFFF);       }
}

3.4.3 注意事项

① 使用到的固件库文件

#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_flash.h"

② FLASH_SetLatency(XXXX); 该函数必须要设置。

如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了。这个函数大多数情况下基本设置成 FLASH_SetLatency(FLASH_Latency_2);

如果没配置 FLASH_SetLatency(XXXX); 或者 配置成 FLASH_SetLatency(FLASH_Latency_0); 容易卡死在下面的位置。

// 读取时钟切换状态位,确保PLLCLK被选为系统时钟
while (RCC_GetSYSCLKSource() != 0x08)

FLASH_SetLatency(XXXX) 函数是对 FLASH->ACR 寄存器进行操作的。该寄存器在 《PM0075: STM32F10xxx Flash memory microcontrollers.PDF》 文件里面有描述。

摘抄自:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸道开发板》.pdf

野火学习笔记(8) —— RCC —— 使用 HSE/HSI 配置时钟相关推荐

  1. STM32系统学习——RCC(使用HSE/HSI配置时钟)

    ** STM32系统学习--RCC(使用HSE/HSI配置时钟) ** RCC :reset clock control 复位和时钟控制器.主要讲解时钟部分,特别是要着重理解时钟树,理解了时钟树,ST ...

  2. STM32使用HSE/HSI配置时钟(六)

    STM32时钟树 系统时钟 ①HSE 高速外部时钟信号 HSE 是高速外部时钟信号,可以由有源晶振或者无源晶振提供,频率为4~16MHz.当使用有源晶振时,时钟从OSC_IN引脚进入,OSC_OUT引 ...

  3. RCC—使用 HSE/HIS 配置时钟

    RCC :reset clock control  复位和时钟控制器:特别是要着重理解时钟树,理解了时钟树,F429 的一切时钟的来龙去脉都会了如指掌. STM32F4系列有5个时钟源: LSIRC( ...

  4. STM32 FSMC学习笔记+补充(LCD的FSMC配置)

    STM32 FSMC学习笔记+补充(LCD的FSMC配置) STM32 FSMC学习笔记 STM32 FSMC的用法--LCD 转载于:https://www.cnblogs.com/LittleTi ...

  5. Windows x64内核学习笔记(一)—— 环境与配置

    Windows x64内核学习笔记(一)-- 环境与配置 前言 新特性 基础要求 实验环境 Guest Win10配置 问题解决 参考资料 前言 之前,跟着海哥学习了windows内核的一些机制,包括 ...

  6. python数据挖掘学习笔记】十三.WordCloud词云配置过程及词频分析

    #2018-03-28 09:59:40 March Wednesday the 13 week, the 087 day SZ SSMR 11,12因为涉及到数据库被我暂时放弃了 python数据挖 ...

  7. adg oracle,【学习笔记】Oracle ADG搭建与配置 windows平台11G ADG搭建与测试

    [学习笔记]Oracle ADG搭建与配置 windows平台11G ADG搭建与测试 时间:2016-10-26 20:09   来源:Oracle研究中心   作者:HTZ   点击: 次 天萃荷 ...

  8. CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出

    本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式实现CAS客户端登出及单点登出. 本文基于<CAS学习笔记三:SpringBoot自动/手动配置方式集成C ...

  9. oracle rac 环境配置文件,学习笔记:Oracle RAC spfile参数文件配置案例详解

    天萃荷净 rac中的spfile探讨,记录一下Oracle RAC搭建完成后关于spfile参数文件的配置案例,与更改RAC环境中参数文件的方法 今天朋友的的rac,因为被同事做数据库升级,分别在两个 ...

最新文章

  1. Word中大括号内公式如何左对齐
  2. 包含绑定变量的sql进行调优需注意一点
  3. vs2010 失效后的解决办法
  4. Python3 闭包函数及nonlocal
  5. mysql设置唯一键
  6. 优化案例(part4)--A novel consensus learning approach to incomplete multi-view clustering
  7. ANSYS CFX 脚本详细设置,实现循环计算
  8. testing framework
  9. 在ASP.NET页面中添加确认对话框的方法
  10. iphone查看html源码的app,使用扩展App在Safari上查看源代码
  11. java实现思维导图_Java并发(思维导图)
  12. AI口罩“督查官”诞生记
  13. Vue3.0快速上手-重要知识点罗列-系列二
  14. 很牛的几篇圈内爆料——影视圈
  15. 小白学习朴素贝叶斯——看即懂
  16. 避免项目延期,有效推进项目进度的4大关键方法
  17. oracle游标列转行,Oracle行转列和列转行
  18. 部署SNMP使网管与设备通信,配置关于TCP测试NQA的配置案例
  19. 用友U8调拨单、组装拆卸单、盘点单审核后自动审核对应的其他出入库单
  20. 成功的演讲需要些什么

热门文章

  1. 设备扩展(DEVICE_EXTENSION)
  2. 阿里云网盘内侧注册方法
  3. no accounts with itunes connect access问题排查解决
  4. 5OSPF的邻居和NBMA环境下的邻居
  5. 基于FPGA的数据采集、通讯和存储系统设计(即FPGA+RTL8211千兆以太网+SD卡存储+RTC+Uart+AD7606数模转换+电流放大采集等硬件设计及程序验证)
  6. 超微服务器安装操作系统,超微服务器bios设置
  7. 蘑菇街Android组件与插件化
  8. 抖音神曲《一百万个可能》:“在一瞬间,我们有一百万个可能”
  9. c语言入门经典+第5版+习题答案,C语言入门经典(第5版)
  10. 使用Node.js手撸一个建静态Web服务器,内部CV指南