什么是时钟?
时钟是具有周期性的脉冲信号,最常用的是占空比50%的方波(但不一定就是占空比为50%的方波)
时钟是单片机的脉搏,单片机在时钟信号的指引下,一步一步的运行着程序,时钟信号的频率影响着单片机运行程序的快慢。
如果你有学习过51单片机,那一定知道51单片机的时钟信号一般为12MHz和11.0592MHz,并且时钟频率一般是不可调配的,时钟系统相对单一,而对于STM32系列的单片机来说,系统的时钟周期是可以调控的,往往可以达到几十上百MHz,不同外设的时钟信号也是不同的,因此,STM32单片机的时钟系统就会变得错综复杂,各种外设的时钟彼此交织,形成我们常说的时钟树
因此搞懂时钟相关内容,对学习单片机至关重要。
时钟源
时钟源用来为环形脉冲发生器提供频率稳定且电平匹配的方波时钟脉冲信号。它通常由石英 晶体振荡器和与非门组成的正反馈振荡电路组成,其输出送至环形脉冲发生器。
简单来说;提供时钟信号的源头就是时钟源,NANO板的时钟源主要有以下几种:
时钟源名称 | 频率 | 材料 | 用途 |
---|---|---|---|
高速外部振荡器(HSE) | 4~26MHz | 晶体/陶瓷 | SYSCLK/RTC |
低速外部振荡器 (LSE) | 32.768KHz | 晶体/陶瓷 | RTC |
高速内部振荡器(HSI) | 16MHz | RC | SYSCLK |
低速内部振荡器(LSI) | 32KHz | RC | RTC/IWDG |
注意:内部振荡器使用的是单片机内部的RC振荡器,产生的频率容易受外界的环境干扰,而外部振荡器使用外接的晶振,其产生的频率稳定,几乎不受环境干扰,所以我们在使用的过程中更倾向于使用高速外部振荡器(HSE)。
时钟安全系统(CSS)
前面讲到在使用的过程中,我们更倾向于使用HSE作为系统时钟来源,但是天会下雨路会滑,难免有时会出现HSE出现故障的时候,导致程序无法正常运行,
CSS的作用就是在HSE出现故障时,自动切换为HSI振荡器提供系统时钟,以此保证程序的正常运行
具体原理为:当CSS使能时,如果HSE出现故障,则CSS会产生一个不可屏蔽中断,使得CPU讲时钟源切换为HSI
F411时钟树
有了上面的知识,我们大概对时钟信号如何产生有了一个基本的了解,不过也会产生一些疑问?
-
HSE提供的时钟信号只有4~26MHz,哪里来的几十上百的系统时钟呢?
-
系统时钟是如何调配各个外设的时钟的?时钟树又是这么形成的?
别急,接下来,我将一一为你解决这些疑问。首先,我们来看看F411的时钟树:
如果你是第一次看到这个时钟树,肯定会被它震惊到,但是不要紧,看我来慢慢给你分析。
首先我们来决定第一个问题: HSE提供的时钟信号只有4~26MHz,哪里来的几十上百的系统时钟呢?
具体流程如下:
外部晶振提供稳定的时钟信号,一方面可以直接给到系统时钟,这时系统时钟频率等于外部晶振频率,但是这样的话系统时钟只有4~26MHz。另一方面,可以作为PLL(锁相环)的输入,通过设置对应的分频/倍频系数,实现对外部时钟的倍频后再接入到系统时钟,作为系统时钟使用。这种方式可以得到的系统时钟可以达到几十MHz甚至上百MHz。也是我们最常用的一种方式。
接着我们来解决第二个问题:系统时钟是如何调配各个外设的时钟的?时钟树又是这么形成的?
STM32单片机上集成了各种各样的总线,每一根总线的时钟频率都是基于前一级总线频率,并且可以被对应的分频/倍频系数所控制,同时总线还可以分支形成不同的总线,形成的总线频率也可以不同。
此外,不同的总线上面挂在着各种各样的外设,在使用各种外设之前,必须使能外设所在的总线,并且应该使能外设所在总线的所有前级总线。
用一个形象的说法来说:各种各样的时钟源作为整棵时钟树的根系,系统时钟作为时钟树的主干,而不同的外设则作为时钟树的树干,而挂载在总线上的外设则时钟树的果实/叶子。
看到这里,你应该对STM32的系统时钟系统有了一个清晰的了解了。下面,我们来概况一些STM32的系统时钟系统简图:
F411系统时钟简图
这里大家自行理解,不做过多解释。
同时LSE和LSI通常用作IWDG(独立看门狗)和RTC(实时时钟)的时钟来源。
F411CubeMX时钟树
现在再看CubeMX的时钟树,是不是就一目了然啦。
下面是一些与时钟配置相关的函数:
时钟源、PLL:HAL_RCC_OscConfig()
系统时钟、总线:HAL_RCC_ClockConfig()
使能外设时钟:__HAL_RCC_PPP_CLK_ENABLE()
扩展外设时钟(PLLI2S/ I2S/ LTDC /RTC等):HAL_RCCEx_PeriphCLKConfig()
配置系统时钟
我们前面讲了这么多系统时钟的相关知识,那么到底要怎么去配置系统时钟呢?系统时钟的配置包含以下几步:
- 系统时钟配置步骤,sys_stm32_clock_init 函数
- 外设时钟使能和失能
1. 系统时钟配置步骤
接下来我们从工程源码分析一下:
- 配置HSE_VALUE(25000000Hz)
//配置HSE_VALUE,在stm32xxxx_hal_conf.h中可以找到
#if !defined (HSE_VALUE)
#define HSE_VALUE 25000000U /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
- 调用SystemInit() 函数
//调用SystemInit() 函数,具体我也不知道是干什么用的,
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location -------------------------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
}
- 选择时钟源,配置PLL,选择系统时钟源,配置总线分配器。
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/ //配置主内部稳压器输出电压
__HAL_RCC_PWR_CLK_ENABLE(); //电源时钟使能
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;//使用HSI作为时钟源
RCC_OscInitStruct.HSIState = RCC_HSI_ON; //打开HSI
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //开启PLL锁相环
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; //使用HSI作为PLL的时钟来源
RCC_OscInitStruct.PLL.PLLM = 16; //设置M分配系数
RCC_OscInitStruct.PLL.PLLN = 192; //设置N倍频系数
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;//设置P分频系数
RCC_OscInitStruct.PLL.PLLQ = 4; //设置Q分频系数
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
//要配置的时钟(SYSCLK/HCLK/PCLK1/PCLK2)
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; //设置系统时钟源
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; //设置AHB分频系数
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; //设置APB1分频系数
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; //设置APB2分频系数
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
}
- 配置外设扩展时钟
这个我看不懂,就不介绍了。
以上就是系统时钟配置步骤的基本步骤,过程上有那么亿丢丢的复杂,但是没关系,我们有万能的CubeMX,使用的时候,这些代码都是可以自动生成,不需要我们编写的。
2.外设使能与失能(这一步尤为重要,需要重点掌握)
当我们用到对应的外设时,需要使能对应的外设。
HAL库使能某个外设时钟的方法,如:
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 GPIOA 时钟
HAL库禁止某个外设时钟的方法,如:
__HAL_RCC_GPIOA_CLK_DISABLE(); /* 禁止 GPIOA 时钟 */